Merge branch '5.0'

* 5.0:
  [Debug] fix ClassNotFoundFatalErrorHandler
  [FrameworkBundle] Document the router.cache_class_prefix parameter removal
  [Routing] Fix using a custom matcher & generator dumper class
  [Notifier] Add more specific types at documentation level when php engine can't
  [Serializer] Fix cache in MetadataAwareNameConverter
  [Dotenv] Fixed infinite loop with missing quote followed by quoted value
  [HttpClient] Added missing sprintf
  [TwigBridge] button_widget now has its title attr translated even if its label = null or false
  [PhpUnitBridge] When using phpenv + phpenv-composer plugin, composer executable is wrapped into a bash script
  [Messenger] Added check if json_encode succeeded
  [Messenger] Added check if json_encode succeeded
  [FrameworkBundle][ContainerLintCommand] Only skip .errored. services
  [HttpClient] fix exception in case of PSR17 discovery failure
  [DependencyInjection] Handle ServiceClosureArgument for callable in container linting
  fix processing chain adapter based cache pool
  [HttpKernel] release lock explicitly
  [Security] Prevent canceled remember-me cookie from being accepted
  [FrameworkBundle][TranslationUpdateCommand] Do not output positive feedback on stderr
  [Security\Guard] Fix missing typehints
  do not render preferred choices as selected
This commit is contained in:
Nicolas Grekas 2020-01-08 18:33:57 +01:00
commit 8acfd3a9d2
35 changed files with 225 additions and 48 deletions

View File

@ -192,15 +192,15 @@ HttpKernel
* The `Kernel::getRootDir()` and the `kernel.root_dir` parameter have been deprecated * The `Kernel::getRootDir()` and the `kernel.root_dir` parameter have been deprecated
* The `KernelInterface::getName()` and the `kernel.name` parameter have been deprecated * The `KernelInterface::getName()` and the `kernel.name` parameter have been deprecated
* Deprecated the first and second constructor argument of `ConfigDataCollector` * Deprecated the first and second constructor argument of `ConfigDataCollector`
* Deprecated `ConfigDataCollector::getApplicationName()` * Deprecated `ConfigDataCollector::getApplicationName()`
* Deprecated `ConfigDataCollector::getApplicationVersion()` * Deprecated `ConfigDataCollector::getApplicationVersion()`
Messenger Messenger
--------- ---------
* The `MiddlewareInterface::handle()` and `SenderInterface::send()` methods must now return an `Envelope` instance. * The `MiddlewareInterface::handle()` and `SenderInterface::send()` methods must now return an `Envelope` instance.
* The return value of handlers isn't forwarded anymore by middleware and buses. * The return value of handlers isn't forwarded anymore by middleware and buses.
If you used to return a value, e.g in query bus handlers, you can either: If you used to return a value, e.g in query bus handlers, you can either:
- get the result from the `HandledStamp` in the envelope returned by the bus. - get the result from the `HandledStamp` in the envelope returned by the bus.
- use the `HandleTrait` to leverage a message bus, expecting a single, synchronous message handling and returning its result. - use the `HandleTrait` to leverage a message bus, expecting a single, synchronous message handling and returning its result.
@ -214,15 +214,15 @@ Messenger
$query->setResult($yourResult); $query->setResult($yourResult);
``` ```
* The `EnvelopeAwareInterface` was removed and the `MiddlewareInterface::handle()` method now requires an `Envelope` object * The `EnvelopeAwareInterface` was removed and the `MiddlewareInterface::handle()` method now requires an `Envelope` object
as first argument. When using built-in middleware with the provided `MessageBus`, you will not have to do anything. as first argument. When using built-in middleware with the provided `MessageBus`, you will not have to do anything.
If you use your own `MessageBusInterface` implementation, you must wrap the message in an `Envelope` before passing it to middleware. If you use your own `MessageBusInterface` implementation, you must wrap the message in an `Envelope` before passing it to middleware.
If you created your own middleware, you must change the signature to always expect an `Envelope`. If you created your own middleware, you must change the signature to always expect an `Envelope`.
* The `MiddlewareInterface::handle()` second argument (`callable $next`) has changed in favor of a `StackInterface` instance. * The `MiddlewareInterface::handle()` second argument (`callable $next`) has changed in favor of a `StackInterface` instance.
When using built-in middleware with the provided `MessageBus`, you will not have to do anything. When using built-in middleware with the provided `MessageBus`, you will not have to do anything.
If you use your own `MessageBusInterface` implementation, you can use the `StackMiddleware` implementation. If you use your own `MessageBusInterface` implementation, you can use the `StackMiddleware` implementation.
If you created your own middleware, you must change the signature to always expect an `StackInterface` instance If you created your own middleware, you must change the signature to always expect an `StackInterface` instance
and call `$stack->next()->handle($envelope, $stack)` instead of `$next` to call the next middleware: and call `$stack->next()->handle($envelope, $stack)` instead of `$next` to call the next middleware:
Before: Before:
```php ```php
public function handle($message, callable $next): Envelope public function handle($message, callable $next): Envelope
@ -230,7 +230,7 @@ Messenger
// do something before // do something before
$message = $next($message); $message = $next($message);
// do something after // do something after
return $message; return $message;
} }
``` ```
@ -242,7 +242,7 @@ Messenger
// do something before // do something before
$envelope = $stack->next()->handle($envelope, $stack); $envelope = $stack->next()->handle($envelope, $stack);
// do something after // do something after
return $envelope; return $envelope;
} }
``` ```
@ -251,7 +251,7 @@ Messenger
respectively `ReceivedStamp`, `ValidationStamp`, `SerializerStamp` and moved to the `Stamp` namespace. respectively `ReceivedStamp`, `ValidationStamp`, `SerializerStamp` and moved to the `Stamp` namespace.
* `AllowNoHandlerMiddleware` has been removed in favor of a new constructor argument on `HandleMessageMiddleware` * `AllowNoHandlerMiddleware` has been removed in favor of a new constructor argument on `HandleMessageMiddleware`
* The `ConsumeMessagesCommand` class now takes an instance of `Psr\Container\ContainerInterface` * The `ConsumeMessagesCommand` class now takes an instance of `Psr\Container\ContainerInterface`
as first constructor argument, i.e a message bus locator. The CLI command now expects a mandatory as first constructor argument, i.e a message bus locator. The CLI command now expects a mandatory
`--bus` option value if there is more than one bus in the locator. `--bus` option value if there is more than one bus in the locator.
* `MessageSubscriberInterface::getHandledMessages()` return value has changed. The value of an array item * `MessageSubscriberInterface::getHandledMessages()` return value has changed. The value of an array item
needs to be an associative array or the method name. needs to be an associative array or the method name.

View File

@ -243,6 +243,7 @@ FrameworkBundle
* Removed `routing.loader.service`. * Removed `routing.loader.service`.
* Added support for PHPUnit 8. A `void` return-type was added to the `KernelTestCase::tearDown()` and `WebTestCase::tearDown()` method. * Added support for PHPUnit 8. A `void` return-type was added to the `KernelTestCase::tearDown()` and `WebTestCase::tearDown()` method.
* Removed the `lock.store.flock`, `lock.store.semaphore`, `lock.store.memcached.abstract` and `lock.store.redis.abstract` services. * Removed the `lock.store.flock`, `lock.store.semaphore`, `lock.store.memcached.abstract` and `lock.store.redis.abstract` services.
* Removed the `router.cache_class_prefix` parameter.
HttpClient HttpClient
---------- ----------

View File

@ -106,7 +106,7 @@ $COMPOSER = file_exists($COMPOSER = $oldPwd.'/composer.phar')
|| ($COMPOSER = rtrim('\\' === DIRECTORY_SEPARATOR ? preg_replace('/[\r\n].*/', '', `where.exe composer.phar`) : `which composer.phar 2> /dev/null`)) || ($COMPOSER = rtrim('\\' === DIRECTORY_SEPARATOR ? preg_replace('/[\r\n].*/', '', `where.exe composer.phar`) : `which composer.phar 2> /dev/null`))
|| ($COMPOSER = rtrim('\\' === DIRECTORY_SEPARATOR ? preg_replace('/[\r\n].*/', '', `where.exe composer`) : `which composer 2> /dev/null`)) || ($COMPOSER = rtrim('\\' === DIRECTORY_SEPARATOR ? preg_replace('/[\r\n].*/', '', `where.exe composer`) : `which composer 2> /dev/null`))
|| file_exists($COMPOSER = rtrim('\\' === DIRECTORY_SEPARATOR ? `git rev-parse --show-toplevel 2> NUL` : `git rev-parse --show-toplevel 2> /dev/null`).DIRECTORY_SEPARATOR.'composer.phar') || file_exists($COMPOSER = rtrim('\\' === DIRECTORY_SEPARATOR ? `git rev-parse --show-toplevel 2> NUL` : `git rev-parse --show-toplevel 2> /dev/null`).DIRECTORY_SEPARATOR.'composer.phar')
? $PHP.' '.escapeshellarg($COMPOSER) ? (file_get_contents($COMPOSER, null, 0, 18) === '#!/usr/bin/env php' ? $PHP : '').' '.escapeshellarg($COMPOSER) // detect shell wrappers by looking at the shebang
: 'composer'; : 'composer';
$SYMFONY_PHPUNIT_REMOVE = $getEnvVar('SYMFONY_PHPUNIT_REMOVE', 'phpspec/prophecy'.($PHPUNIT_VERSION < 6.0 ? ' symfony/yaml': '')); $SYMFONY_PHPUNIT_REMOVE = $getEnvVar('SYMFONY_PHPUNIT_REMOVE', 'phpspec/prophecy'.($PHPUNIT_VERSION < 6.0 ? ' symfony/yaml': ''));

View File

@ -65,12 +65,14 @@
{%- endif -%} {%- endif -%}
{%- if preferred_choices|length > 0 -%} {%- if preferred_choices|length > 0 -%}
{% set options = preferred_choices %} {% set options = preferred_choices %}
{% set render_preferred_choices = true %}
{{- block('choice_widget_options') -}} {{- block('choice_widget_options') -}}
{%- if choices|length > 0 and separator is not none -%} {%- if choices|length > 0 and separator is not none -%}
<option disabled="disabled">{{ separator }}</option> <option disabled="disabled">{{ separator }}</option>
{%- endif -%} {%- endif -%}
{%- endif -%} {%- endif -%}
{%- set options = choices -%} {%- set options = choices -%}
{%- set render_preferred_choices = false -%}
{{- block('choice_widget_options') -}} {{- block('choice_widget_options') -}}
</select> </select>
{%- endblock choice_widget_collapsed -%} {%- endblock choice_widget_collapsed -%}
@ -83,7 +85,7 @@
{{- block('choice_widget_options') -}} {{- block('choice_widget_options') -}}
</optgroup> </optgroup>
{%- else -%} {%- else -%}
<option value="{{ choice.value }}"{% if choice.attr %}{% with { attr: choice.attr } %}{{ block('attributes') }}{% endwith %}{% endif %}{% if choice is selectedchoice(value) %} selected="selected"{% endif %}>{{ choice_translation_domain is same as(false) ? choice.label : choice.label|trans({}, choice_translation_domain) }}</option> <option value="{{ choice.value }}"{% if choice.attr %}{% with { attr: choice.attr } %}{{ block('attributes') }}{% endwith %}{% endif %}{% if not render_preferred_choices|default(false) and choice is selectedchoice(value) %} selected="selected"{% endif %}>{{ choice_translation_domain is same as(false) ? choice.label : choice.label|trans({}, choice_translation_domain) }}</option>
{%- endif -%} {%- endif -%}
{% endfor %} {% endfor %}
{%- endblock choice_widget_options -%} {%- endblock choice_widget_options -%}
@ -226,13 +228,11 @@
'%name%': name, '%name%': name,
'%id%': id, '%id%': id,
}) %} }) %}
{%- elseif label is same as(false) -%} {%- elseif label is not same as(false) -%}
{% set translation_domain = false %}
{%- else -%}
{% set label = name|humanize %} {% set label = name|humanize %}
{%- endif -%} {%- endif -%}
{%- endif -%} {%- endif -%}
<button type="{{ type|default('button') }}" {{ block('button_attributes') }}>{{ translation_domain is same as(false) ? label : label|trans(label_translation_parameters, translation_domain) }}</button> <button type="{{ type|default('button') }}" {{ block('button_attributes') }}>{{ translation_domain is same as(false) or label is same as(false) ? label : label|trans(label_translation_parameters, translation_domain) }}</button>
{%- endblock button_widget -%} {%- endblock button_widget -%}
{%- block submit_widget -%} {%- block submit_widget -%}

View File

@ -160,12 +160,14 @@
{%- endif %} {%- endif %}
{%- if preferred_choices|length > 0 -%} {%- if preferred_choices|length > 0 -%}
{% set options = preferred_choices %} {% set options = preferred_choices %}
{% set render_preferred_choices = true %}
{{- block('choice_widget_options') -}} {{- block('choice_widget_options') -}}
{% if choices|length > 0 and separator is not none -%} {% if choices|length > 0 and separator is not none -%}
<option disabled="disabled">{{ separator }}</option> <option disabled="disabled">{{ separator }}</option>
{%- endif %} {%- endif %}
{%- endif -%} {%- endif -%}
{% set options = choices -%} {% set options = choices -%}
{%- set render_preferred_choices = false -%}
{{- block('choice_widget_options') -}} {{- block('choice_widget_options') -}}
</select> </select>
{%- endblock choice_widget_collapsed %} {%- endblock choice_widget_collapsed %}

View File

@ -546,6 +546,31 @@ abstract class AbstractBootstrap3LayoutTest extends AbstractLayoutTest
); );
} }
public function testSingleChoiceWithSelectedPreferred()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [
'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'],
'preferred_choices' => ['&a'],
'multiple' => false,
'expanded' => false,
]);
$this->assertWidgetMatchesXpath($form->createView(), ['separator' => '-- sep --', 'attr' => ['class' => 'my&class']],
'/select
[@name="name"]
[@class="my&class form-control"]
[not(@required)]
[
./option[@value="&a"][not(@selected)][.="[trans]Choice&A[/trans]"]
/following-sibling::option[@disabled="disabled"][not(@selected)][.="-- sep --"]
/following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"]
/following-sibling::option[@value="&b"][.="[trans]Choice&B[/trans]"]
]
[count(./option)=4]
'
);
}
public function testSingleChoiceWithPreferredAndNoSeparator() public function testSingleChoiceWithPreferredAndNoSeparator()
{ {
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [

View File

@ -29,6 +29,7 @@ CHANGELOG
* Service route loaders must be tagged with `routing.route_loader`. * Service route loaders must be tagged with `routing.route_loader`.
* Added `slugger` service and `SluggerInterface` alias * Added `slugger` service and `SluggerInterface` alias
* Removed the `lock.store.flock`, `lock.store.semaphore`, `lock.store.memcached.abstract` and `lock.store.redis.abstract` services. * Removed the `lock.store.flock`, `lock.store.semaphore`, `lock.store.memcached.abstract` and `lock.store.redis.abstract` services.
* Removed the `router.cache_class_prefix` parameter.
4.4.0 4.4.0
----- -----

View File

@ -102,11 +102,12 @@ final class ContainerLintCommand extends Command
$refl->setAccessible(true); $refl->setAccessible(true);
$refl->setValue($parameterBag, true); $refl->setValue($parameterBag, true);
$passConfig = $container->getCompilerPassConfig(); $skippedIds = [];
$passConfig->setRemovingPasses([]); foreach ($container->getServiceIds() as $serviceId) {
$passConfig->setAfterRemovingPasses([]); if (0 === strpos($serviceId, '.errored.')) {
$skippedIds[$serviceId] = true;
$skippedIds = $kernelContainer->getRemovedIds(); }
}
} }
$container->setParameter('container.build_hash', 'lint_container'); $container->setParameter('container.build_hash', 'lint_container');

View File

@ -175,12 +175,12 @@ EOF
} }
} }
$errorIo->title('Translation Messages Extractor and Dumper'); $io->title('Translation Messages Extractor and Dumper');
$errorIo->comment(sprintf('Generating "<info>%s</info>" translation files for "<info>%s</info>"', $input->getArgument('locale'), $currentName)); $io->comment(sprintf('Generating "<info>%s</info>" translation files for "<info>%s</info>"', $input->getArgument('locale'), $currentName));
// load any messages from templates // load any messages from templates
$extractedCatalogue = new MessageCatalogue($input->getArgument('locale')); $extractedCatalogue = new MessageCatalogue($input->getArgument('locale'));
$errorIo->comment('Parsing templates...'); $io->comment('Parsing templates...');
$this->extractor->setPrefix($input->getOption('prefix')); $this->extractor->setPrefix($input->getOption('prefix'));
foreach ($viewsPaths as $path) { foreach ($viewsPaths as $path) {
if (is_dir($path) || is_file($path)) { if (is_dir($path) || is_file($path)) {
@ -190,7 +190,7 @@ EOF
// load any existing messages from the translation files // load any existing messages from the translation files
$currentCatalogue = new MessageCatalogue($input->getArgument('locale')); $currentCatalogue = new MessageCatalogue($input->getArgument('locale'));
$errorIo->comment('Loading translation files...'); $io->comment('Loading translation files...');
foreach ($transPaths as $path) { foreach ($transPaths as $path) {
if (is_dir($path)) { if (is_dir($path)) {
$this->reader->read($path, $currentCatalogue); $this->reader->read($path, $currentCatalogue);
@ -258,7 +258,7 @@ EOF
} }
if ('xlf' === $input->getOption('output-format')) { if ('xlf' === $input->getOption('output-format')) {
$errorIo->comment(sprintf('Xliff output version is <info>%s</info>', $input->getOption('xliff-version'))); $io->comment(sprintf('Xliff output version is <info>%s</info>', $input->getOption('xliff-version')));
} }
$resultMessage = sprintf('%d message%s successfully extracted', $extractedMessagesCount, $extractedMessagesCount > 1 ? 's were' : ' was'); $resultMessage = sprintf('%d message%s successfully extracted', $extractedMessagesCount, $extractedMessagesCount > 1 ? 's were' : ' was');
@ -270,7 +270,7 @@ EOF
// save the files // save the files
if (true === $input->getOption('force')) { if (true === $input->getOption('force')) {
$errorIo->comment('Writing files...'); $io->comment('Writing files...');
$bundleTransPath = false; $bundleTransPath = false;
foreach ($transPaths as $path) { foreach ($transPaths as $path) {
@ -290,7 +290,7 @@ EOF
} }
} }
$errorIo->success($resultMessage.'.'); $io->success($resultMessage.'.');
return 0; return 0;
} }

View File

@ -33,7 +33,7 @@ class ClearRememberMeTest extends AbstractWebTestCase
$this->assertNotNull($cookieJar->get('REMEMBERME')); $this->assertNotNull($cookieJar->get('REMEMBERME'));
$client->request('GET', '/foo'); $client->request('GET', '/foo');
$this->assertSame(200, $client->getResponse()->getStatusCode()); $this->assertRedirect($client->getResponse(), '/login');
$this->assertNull($cookieJar->get('REMEMBERME')); $this->assertNull($cookieJar->get('REMEMBERME'));
} }
} }

View File

@ -103,7 +103,12 @@ class CachePoolPass implements CompilerPassInterface
if (ChainAdapter::class === $class) { if (ChainAdapter::class === $class) {
$adapters = []; $adapters = [];
foreach ($adapter->getArgument(0) as $provider => $adapter) { foreach ($adapter->getArgument(0) as $provider => $adapter) {
$chainedPool = $adapter = new ChildDefinition($adapter); if ($adapter instanceof ChildDefinition) {
$chainedPool = $adapter;
} else {
$chainedPool = $adapter = new ChildDefinition($adapter);
}
$chainedTags = [\is_int($provider) ? [] : ['provider' => $provider]]; $chainedTags = [\is_int($provider) ? [] : ['provider' => $provider]];
$chainedClass = ''; $chainedClass = '';

View File

@ -12,7 +12,9 @@
namespace Symfony\Component\Cache\Tests\DependencyInjection; namespace Symfony\Component\Cache\Tests\DependencyInjection;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Component\Cache\Adapter\ApcuAdapter;
use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\ChainAdapter;
use Symfony\Component\Cache\Adapter\RedisAdapter; use Symfony\Component\Cache\Adapter\RedisAdapter;
use Symfony\Component\Cache\DependencyInjection\CachePoolPass; use Symfony\Component\Cache\DependencyInjection\CachePoolPass;
use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ChildDefinition;
@ -174,4 +176,42 @@ class CachePoolPassTest extends TestCase
$this->cachePoolPass->process($container); $this->cachePoolPass->process($container);
} }
public function testChainAdapterPool()
{
$container = new ContainerBuilder();
$container->setParameter('kernel.container_class', 'app');
$container->setParameter('kernel.project_dir', 'foo');
$container->register('cache.adapter.array', ArrayAdapter::class)
->addTag('cache.pool');
$container->register('cache.adapter.apcu', ApcuAdapter::class)
->setArguments([null, 0, null])
->addTag('cache.pool');
$container->register('cache.chain', ChainAdapter::class)
->addArgument(['cache.adapter.array', 'cache.adapter.apcu'])
->addTag('cache.pool');
$container->setDefinition('cache.app', new ChildDefinition('cache.chain'))
->addTag('cache.pool');
$container->setDefinition('doctrine.result_cache_pool', new ChildDefinition('cache.app'))
->addTag('cache.pool');
$this->cachePoolPass->process($container);
$appCachePool = $container->getDefinition('cache.app');
$this->assertInstanceOf(ChildDefinition::class, $appCachePool);
$this->assertSame('cache.chain', $appCachePool->getParent());
$chainCachePool = $container->getDefinition('cache.chain');
$this->assertNotInstanceOf(ChildDefinition::class, $chainCachePool);
$this->assertCount(2, $chainCachePool->getArgument(0));
$this->assertInstanceOf(ChildDefinition::class, $chainCachePool->getArgument(0)[0]);
$this->assertSame('cache.adapter.array', $chainCachePool->getArgument(0)[0]->getParent());
$this->assertInstanceOf(ChildDefinition::class, $chainCachePool->getArgument(0)[1]);
$this->assertSame('cache.adapter.apcu', $chainCachePool->getArgument(0)[1]->getParent());
$doctrineCachePool = $container->getDefinition('doctrine.result_cache_pool');
$this->assertInstanceOf(ChildDefinition::class, $doctrineCachePool);
$this->assertSame('cache.app', $doctrineCachePool->getParent());
}
} }

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\DependencyInjection\Compiler; namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Container; use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\EnvNotFoundException; use Symfony\Component\DependencyInjection\Exception\EnvNotFoundException;
@ -219,6 +220,10 @@ final class CheckTypeDeclarationsPass extends AbstractRecursivePass
return; return;
} }
if (\in_array($type, ['callable', 'Closure'], true) && $value instanceof ServiceClosureArgument) {
return;
}
if ('iterable' === $type && (\is_array($value) || $value instanceof \Traversable || $value instanceof IteratorArgument)) { if ('iterable' === $type && (\is_array($value) || $value instanceof \Traversable || $value instanceof IteratorArgument)) {
return; return;
} }

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Compiler;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Compiler\CheckTypeDeclarationsPass; use Symfony\Component\DependencyInjection\Compiler\CheckTypeDeclarationsPass;
use Symfony\Component\DependencyInjection\Compiler\ResolveParameterPlaceHoldersPass; use Symfony\Component\DependencyInjection\Compiler\ResolveParameterPlaceHoldersPass;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
@ -697,4 +698,28 @@ class CheckTypeDeclarationsPassTest extends TestCase
$this->addToAssertionCount(1); $this->addToAssertionCount(1);
} }
public function testProcessSuccessWhenPassingServiceClosureArgumentToCallable()
{
$container = new ContainerBuilder();
$container->register('bar', BarMethodCall::class)
->addMethodCall('setCallable', [new ServiceClosureArgument(new Reference('foo'))]);
(new CheckTypeDeclarationsPass(true))->process($container);
$this->addToAssertionCount(1);
}
public function testProcessSuccessWhenPassingServiceClosureArgumentToClosure()
{
$container = new ContainerBuilder();
$container->register('bar', BarMethodCall::class)
->addMethodCall('setClosure', [new ServiceClosureArgument(new Reference('foo'))]);
(new CheckTypeDeclarationsPass(true))->process($container);
$this->addToAssertionCount(1);
}
} }

View File

@ -40,4 +40,8 @@ class BarMethodCall
public function setCallable(callable $callable): void public function setCallable(callable $callable): void
{ {
} }
public function setClosure(\Closure $closure): void
{
}
} }

View File

@ -272,7 +272,10 @@ final class Dotenv
$this->cursor += 1 + $len; $this->cursor += 1 + $len;
} elseif ('"' === $this->data[$this->cursor]) { } elseif ('"' === $this->data[$this->cursor]) {
$value = ''; $value = '';
++$this->cursor;
if (++$this->cursor === $this->end) {
throw $this->createFormatException('Missing quote to end the value');
}
while ('"' !== $this->data[$this->cursor] || ('\\' === $this->data[$this->cursor - 1] && '\\' !== $this->data[$this->cursor - 2])) { while ('"' !== $this->data[$this->cursor] || ('\\' === $this->data[$this->cursor - 1] && '\\' !== $this->data[$this->cursor - 2])) {
$value .= $this->data[$this->cursor]; $value .= $this->data[$this->cursor];

View File

@ -40,6 +40,7 @@ class DotenvTest extends TestCase
['FOO', "Missing = in the environment variable declaration in \".env\" at line 1.\n...FOO...\n ^ line 1 offset 3"], ['FOO', "Missing = in the environment variable declaration in \".env\" at line 1.\n...FOO...\n ^ line 1 offset 3"],
['FOO="foo', "Missing quote to end the value in \".env\" at line 1.\n...FOO=\"foo...\n ^ line 1 offset 8"], ['FOO="foo', "Missing quote to end the value in \".env\" at line 1.\n...FOO=\"foo...\n ^ line 1 offset 8"],
['FOO=\'foo', "Missing quote to end the value in \".env\" at line 1.\n...FOO='foo...\n ^ line 1 offset 8"], ['FOO=\'foo', "Missing quote to end the value in \".env\" at line 1.\n...FOO='foo...\n ^ line 1 offset 8"],
["FOO=\"foo\nBAR=\"bar\"", "Missing quote to end the value in \".env\" at line 1.\n...FOO=\"foo\\nBAR=\"bar\"...\n ^ line 1 offset 18"],
['FOO=\'foo'."\n", "Missing quote to end the value in \".env\" at line 1.\n...FOO='foo\\n...\n ^ line 1 offset 9"], ['FOO=\'foo'."\n", "Missing quote to end the value in \".env\" at line 1.\n...FOO='foo\\n...\n ^ line 1 offset 9"],
['export FOO', "Unable to unset an environment variable in \".env\" at line 1.\n...export FOO...\n ^ line 1 offset 10"], ['export FOO', "Unable to unset an environment variable in \".env\" at line 1.\n...export FOO...\n ^ line 1 offset 10"],
['FOO=${FOO', "Unclosed braces on variable expansion in \".env\" at line 1.\n...FOO=\${FOO...\n ^ line 1 offset 9"], ['FOO=${FOO', "Unclosed braces on variable expansion in \".env\" at line 1.\n...FOO=\${FOO...\n ^ line 1 offset 9"],

View File

@ -30,6 +30,10 @@ class ClassNotFoundErrorEnhancerTest extends TestCase
// get class loaders wrapped by DebugClassLoader // get class loaders wrapped by DebugClassLoader
if ($function[0] instanceof DebugClassLoader) { if ($function[0] instanceof DebugClassLoader) {
$function = $function[0]->getClassLoader(); $function = $function[0]->getClassLoader();
if (!\is_array($function)) {
continue;
}
} }
if ($function[0] instanceof ComposerClassLoader) { if ($function[0] instanceof ComposerClassLoader) {

View File

@ -16,6 +16,7 @@ use Http\Client\Exception\NetworkException;
use Http\Client\Exception\RequestException; use Http\Client\Exception\RequestException;
use Http\Client\HttpAsyncClient; use Http\Client\HttpAsyncClient;
use Http\Client\HttpClient as HttplugInterface; use Http\Client\HttpClient as HttplugInterface;
use Http\Discovery\Exception\NotFoundException;
use Http\Discovery\Psr17FactoryDiscovery; use Http\Discovery\Psr17FactoryDiscovery;
use Http\Message\RequestFactory; use Http\Message\RequestFactory;
use Http\Message\StreamFactory; use Http\Message\StreamFactory;
@ -75,9 +76,13 @@ final class HttplugClient implements HttplugInterface, HttpAsyncClient, RequestF
throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\HttplugClient" as no PSR-17 factories have been provided. Try running "composer require nyholm/psr7".'); throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\HttplugClient" as no PSR-17 factories have been provided. Try running "composer require nyholm/psr7".');
} }
$psr17Factory = class_exists(Psr17Factory::class, false) ? new Psr17Factory() : null; try {
$this->responseFactory = $this->responseFactory ?? $psr17Factory ?? Psr17FactoryDiscovery::findResponseFactory(); $psr17Factory = class_exists(Psr17Factory::class, false) ? new Psr17Factory() : null;
$this->streamFactory = $this->streamFactory ?? $psr17Factory ?? Psr17FactoryDiscovery::findStreamFactory(); $this->responseFactory = $this->responseFactory ?? $psr17Factory ?? Psr17FactoryDiscovery::findResponseFactory();
$this->streamFactory = $this->streamFactory ?? $psr17Factory ?? Psr17FactoryDiscovery::findStreamFactory();
} catch (NotFoundException $e) {
throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\HttplugClient" as no PSR-17 factories have been found. Try running "composer require nyholm/psr7".', 0, $e);
}
} }
$this->waitLoop = new HttplugWaitLoop($this->client, $this->promisePool, $this->responseFactory, $this->streamFactory); $this->waitLoop = new HttplugWaitLoop($this->client, $this->promisePool, $this->responseFactory, $this->streamFactory);

View File

@ -11,6 +11,7 @@
namespace Symfony\Component\HttpClient; namespace Symfony\Component\HttpClient;
use Http\Discovery\Exception\NotFoundException;
use Http\Discovery\Psr17FactoryDiscovery; use Http\Discovery\Psr17FactoryDiscovery;
use Nyholm\Psr7\Factory\Psr17Factory; use Nyholm\Psr7\Factory\Psr17Factory;
use Nyholm\Psr7\Request; use Nyholm\Psr7\Request;
@ -68,9 +69,13 @@ final class Psr18Client implements ClientInterface, RequestFactoryInterface, Str
throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\Psr18Client" as no PSR-17 factories have been provided. Try running "composer require nyholm/psr7".'); throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\Psr18Client" as no PSR-17 factories have been provided. Try running "composer require nyholm/psr7".');
} }
$psr17Factory = class_exists(Psr17Factory::class, false) ? new Psr17Factory() : null; try {
$this->responseFactory = $this->responseFactory ?? $psr17Factory ?? Psr17FactoryDiscovery::findResponseFactory(); $psr17Factory = class_exists(Psr17Factory::class, false) ? new Psr17Factory() : null;
$this->streamFactory = $this->streamFactory ?? $psr17Factory ?? Psr17FactoryDiscovery::findStreamFactory(); $this->responseFactory = $this->responseFactory ?? $psr17Factory ?? Psr17FactoryDiscovery::findResponseFactory();
$this->streamFactory = $this->streamFactory ?? $psr17Factory ?? Psr17FactoryDiscovery::findStreamFactory();
} catch (NotFoundException $e) {
throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\HttplugClient" as no PSR-17 factories have been found. Try running "composer require nyholm/psr7".', 0, $e);
}
} }
/** /**

View File

@ -353,7 +353,7 @@ trait ResponseTrait
} }
if ('' !== $chunk && null !== $response->content && \strlen($chunk) !== fwrite($response->content, $chunk)) { if ('' !== $chunk && null !== $response->content && \strlen($chunk) !== fwrite($response->content, $chunk)) {
$multi->handlesActivity[$j] = [null, new TransportException('Failed writing %d bytes to the response buffer.', \strlen($chunk))]; $multi->handlesActivity[$j] = [null, new TransportException(sprintf('Failed writing %d bytes to the response buffer.', \strlen($chunk)))];
continue; continue;
} }

View File

@ -475,7 +475,7 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
} }
} }
public function __destruct() public function release()
{ {
flock($this->lock, LOCK_UN); flock($this->lock, LOCK_UN);
fclose($this->lock); fclose($this->lock);
@ -553,7 +553,7 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
} }
$this->dumpContainer($cache, $container, $class, $this->getContainerBaseClass()); $this->dumpContainer($cache, $container, $class, $this->getContainerBaseClass());
unset($cache); $cache->release();
$this->container = require $cachePath; $this->container = require $cachePath;
$this->container->set('kernel', $this); $this->container->set('kernel', $this);

View File

@ -186,6 +186,18 @@ class ConnectionTest extends TestCase
$redis->del('messenger-getnonblocking'); $redis->del('messenger-getnonblocking');
} }
public function testJsonError()
{
$redis = new \Redis();
$connection = Connection::fromDsn('redis://localhost/json-error', [], $redis);
try {
$connection->add("\xB1\x31", []);
} catch (TransportException $e) {
}
$this->assertSame('Malformed UTF-8 characters, possibly incorrectly encoded', $e->getMessage());
}
public function testMaxEntries() public function testMaxEntries()
{ {
$redis = $this->getMockBuilder(\Redis::class)->disableOriginalConstructor()->getMock(); $redis = $this->getMockBuilder(\Redis::class)->disableOriginalConstructor()->getMock();

View File

@ -248,6 +248,10 @@ class Connection
'uniqid' => uniqid('', true), 'uniqid' => uniqid('', true),
]); ]);
if (false === $message) {
throw new TransportException(json_last_error_msg());
}
$score = (int) ($this->getCurrentTimeInMilliseconds() + $delayInMs); $score = (int) ($this->getCurrentTimeInMilliseconds() + $delayInMs);
$added = $this->connection->zadd($this->queue, ['NX'], $score, $message); $added = $this->connection->zadd($this->queue, ['NX'], $score, $message);
} else { } else {
@ -256,6 +260,10 @@ class Connection
'headers' => $headers, 'headers' => $headers,
]); ]);
if (false === $message) {
throw new TransportException(json_last_error_msg());
}
if ($this->maxEntries) { if ($this->maxEntries) {
$added = $this->connection->xadd($this->stream, '*', ['message' => $message], $this->maxEntries, true); $added = $this->connection->xadd($this->stream, '*', ['message' => $message], $this->maxEntries, true);
} else { } else {

View File

@ -23,6 +23,9 @@ use Symfony\Component\Notifier\Transport\TransportInterface;
*/ */
final class NexmoTransportFactory extends AbstractTransportFactory final class NexmoTransportFactory extends AbstractTransportFactory
{ {
/**
* @return NexmoTransport
*/
public function create(Dsn $dsn): TransportInterface public function create(Dsn $dsn): TransportInterface
{ {
$scheme = $dsn->getScheme(); $scheme = $dsn->getScheme();

View File

@ -23,6 +23,9 @@ use Symfony\Component\Notifier\Transport\TransportInterface;
*/ */
final class SlackTransportFactory extends AbstractTransportFactory final class SlackTransportFactory extends AbstractTransportFactory
{ {
/**
* @return SlackTransport
*/
public function create(Dsn $dsn): TransportInterface public function create(Dsn $dsn): TransportInterface
{ {
$scheme = $dsn->getScheme(); $scheme = $dsn->getScheme();

View File

@ -24,6 +24,9 @@ use Symfony\Component\Notifier\Transport\TransportInterface;
*/ */
final class TelegramTransportFactory extends AbstractTransportFactory final class TelegramTransportFactory extends AbstractTransportFactory
{ {
/**
* @return TelegramTransport
*/
public function create(Dsn $dsn): TransportInterface public function create(Dsn $dsn): TransportInterface
{ {
$scheme = $dsn->getScheme(); $scheme = $dsn->getScheme();

View File

@ -23,6 +23,9 @@ use Symfony\Component\Notifier\Transport\TransportInterface;
*/ */
final class TwilioTransportFactory extends AbstractTransportFactory final class TwilioTransportFactory extends AbstractTransportFactory
{ {
/**
* @return TwilioTransport
*/
public function create(Dsn $dsn): TransportInterface public function create(Dsn $dsn): TransportInterface
{ {
$scheme = $dsn->getScheme(); $scheme = $dsn->getScheme();

View File

@ -58,7 +58,7 @@ final class SmsMessage implements MessageInterface
return $this->phone; return $this->phone;
} }
public function getRecipientId(): ?string public function getRecipientId(): string
{ {
return $this->phone; return $this->phone;
} }

View File

@ -20,6 +20,9 @@ use Symfony\Component\Notifier\Exception\UnsupportedSchemeException;
*/ */
final class NullTransportFactory extends AbstractTransportFactory final class NullTransportFactory extends AbstractTransportFactory
{ {
/**
* @return NullTransport
*/
public function create(Dsn $dsn): TransportInterface public function create(Dsn $dsn): TransportInterface
{ {
if ('null' === $dsn->getScheme()) { if ('null' === $dsn->getScheme()) {

View File

@ -45,7 +45,7 @@ class GuardAuthenticationListener extends AbstractListener
* @param string $providerKey The provider (i.e. firewall) key * @param string $providerKey The provider (i.e. firewall) key
* @param iterable|AuthenticatorInterface[] $guardAuthenticators The authenticators, with keys that match what's passed to GuardAuthenticationProvider * @param iterable|AuthenticatorInterface[] $guardAuthenticators The authenticators, with keys that match what's passed to GuardAuthenticationProvider
*/ */
public function __construct(GuardAuthenticatorHandler $guardHandler, AuthenticationManagerInterface $authenticationManager, string $providerKey, $guardAuthenticators, LoggerInterface $logger = null) public function __construct(GuardAuthenticatorHandler $guardHandler, AuthenticationManagerInterface $authenticationManager, string $providerKey, iterable $guardAuthenticators, LoggerInterface $logger = null)
{ {
if (empty($providerKey)) { if (empty($providerKey)) {
throw new \InvalidArgumentException('$providerKey must not be empty.'); throw new \InvalidArgumentException('$providerKey must not be empty.');

View File

@ -48,7 +48,7 @@ class GuardAuthenticationProvider implements AuthenticationProviderInterface
* @param iterable|AuthenticatorInterface[] $guardAuthenticators The authenticators, with keys that match what's passed to GuardAuthenticationListener * @param iterable|AuthenticatorInterface[] $guardAuthenticators The authenticators, with keys that match what's passed to GuardAuthenticationListener
* @param string $providerKey The provider (i.e. firewall) key * @param string $providerKey The provider (i.e. firewall) key
*/ */
public function __construct($guardAuthenticators, UserProviderInterface $userProvider, string $providerKey, UserCheckerInterface $userChecker, UserPasswordEncoderInterface $passwordEncoder = null) public function __construct(iterable $guardAuthenticators, UserProviderInterface $userProvider, string $providerKey, UserCheckerInterface $userChecker, UserPasswordEncoderInterface $passwordEncoder = null)
{ {
$this->guardAuthenticators = $guardAuthenticators; $this->guardAuthenticators = $guardAuthenticators;
$this->userProvider = $userProvider; $this->userProvider = $userProvider;

View File

@ -97,6 +97,10 @@ abstract class AbstractRememberMeServices implements RememberMeServicesInterface
*/ */
final public function autoLogin(Request $request): ?TokenInterface final public function autoLogin(Request $request): ?TokenInterface
{ {
if (($cookie = $request->attributes->get(self::COOKIE_ATTR_NAME)) && null === $cookie->getValue()) {
return null;
}
if (null === $cookie = $request->cookies->get($this->options['name'])) { if (null === $cookie = $request->cookies->get($this->options['name'])) {
return null; return null;
} }

View File

@ -39,6 +39,17 @@ class AbstractRememberMeServicesTest extends TestCase
$this->assertNull($service->autoLogin(new Request())); $this->assertNull($service->autoLogin(new Request()));
} }
public function testAutoLoginReturnsNullAfterLoginFail()
{
$service = $this->getService(null, ['name' => 'foo', 'path' => null, 'domain' => null]);
$request = new Request();
$request->cookies->set('foo', 'foo');
$service->loginFail($request);
$this->assertNull($service->autoLogin($request));
}
public function testAutoLoginThrowsExceptionWhenImplementationDoesNotReturnUserInterface() public function testAutoLoginThrowsExceptionWhenImplementationDoesNotReturnUserInterface()
{ {
$this->expectException('RuntimeException'); $this->expectException('RuntimeException');

View File

@ -47,7 +47,7 @@ final class MetadataAwareNameConverter implements AdvancedNameConverterInterface
return $this->normalizeFallback($propertyName, $class, $format, $context); return $this->normalizeFallback($propertyName, $class, $format, $context);
} }
if (!isset(self::$normalizeCache[$class][$propertyName])) { if (!\array_key_exists($class, self::$normalizeCache) || !\array_key_exists($propertyName, self::$normalizeCache[$class])) {
self::$normalizeCache[$class][$propertyName] = $this->getCacheValueForNormalization($propertyName, $class); self::$normalizeCache[$class][$propertyName] = $this->getCacheValueForNormalization($propertyName, $class);
} }
@ -64,7 +64,7 @@ final class MetadataAwareNameConverter implements AdvancedNameConverterInterface
} }
$cacheKey = $this->getCacheKey($class, $context); $cacheKey = $this->getCacheKey($class, $context);
if (!isset(self::$denormalizeCache[$cacheKey][$propertyName])) { if (!\array_key_exists($cacheKey, self::$denormalizeCache) || !\array_key_exists($propertyName, self::$denormalizeCache[$cacheKey])) {
self::$denormalizeCache[$cacheKey][$propertyName] = $this->getCacheValueForDenormalization($propertyName, $class, $context); self::$denormalizeCache[$cacheKey][$propertyName] = $this->getCacheValueForDenormalization($propertyName, $class, $context);
} }
@ -78,7 +78,7 @@ final class MetadataAwareNameConverter implements AdvancedNameConverterInterface
} }
$attributesMetadata = $this->metadataFactory->getMetadataFor($class)->getAttributesMetadata(); $attributesMetadata = $this->metadataFactory->getMetadataFor($class)->getAttributesMetadata();
if (!isset($attributesMetadata[$propertyName])) { if (!\array_key_exists($propertyName, $attributesMetadata)) {
return null; return null;
} }
@ -93,7 +93,7 @@ final class MetadataAwareNameConverter implements AdvancedNameConverterInterface
private function getCacheValueForDenormalization(string $propertyName, string $class, array $context): ?string private function getCacheValueForDenormalization(string $propertyName, string $class, array $context): ?string
{ {
$cacheKey = $this->getCacheKey($class, $context); $cacheKey = $this->getCacheKey($class, $context);
if (!isset(self::$attributesMetadataCache[$cacheKey])) { if (!\array_key_exists($cacheKey, self::$attributesMetadataCache)) {
self::$attributesMetadataCache[$cacheKey] = $this->getCacheValueForAttributesMetadata($class, $context); self::$attributesMetadataCache[$cacheKey] = $this->getCacheValueForAttributesMetadata($class, $context);
} }