diff --git a/CHANGELOG-4.3.md b/CHANGELOG-4.3.md index a045a58269..26e086b34e 100644 --- a/CHANGELOG-4.3.md +++ b/CHANGELOG-4.3.md @@ -7,6 +7,51 @@ in 4.3 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v4.3.0...v4.3.1 +* 4.3.0-BETA2 (2019-05-22) + + * bug #31569 [HttpClient] Only use CURLMOPT_MAX_HOST_CONNECTIONS & CURL_VERSION_HTTP2 if defined (GawainLynch) + * bug #31566 [Security] fixed a fatal error when upgrading from 4.2 (fabpot) + * bug #31219 [HttpClient] Allow arrays as query parameters (sleepyboy) + * bug #31482 [Messenger][DoctrineBridge] Throws UnrecoverableMessageHandlingException when passed invalid entity manager name (Koc) + * feature #31471 [Messenger] Add "non sendable" stamps (weaverryan) + * bug #31545 [Messenger] Fix redis Connection::get() should be non blocking by default (chalasr) + * bug #31537 [Workflow] use method marking store (noniagriconomie) + * bug #31551 [ProxyManager] isProxyCandidate() does not take into account interfaces (andrerom) + * bug #31542 [HttpClient] add missing argument check (nicolas-grekas) + * bug #31544 [Messenger] Fix undefined index on read timeout (chalasr) + * bug #31335 [Doctrine] Respect parent class contract in ContainerAwareEventManager (Koc) + * bug #31421 [Routing][AnnotationClassLoader] fix utf-8 encoding in default route name (przemyslaw-bogusz) + * bug #31493 [EventDispatcher] Removed "callable" type hint from WrappedListener constructor (wskorodecki) + * bug #31502 [WebProfilerBundle][Form] The form data collector return serialized data (Simperfit) + * bug #31510 Use the current working dir as default first arg in 'link' binary (lyrixx) + * bug #31524 [HttpFoundation] prevent deprecation when filesize matches error code (xabbuh) + * bug #31535 [Debug] Wrap call to require_once in a try/catch (lyrixx) + * feature #31030 [Serializer] Deprecate calling createChildContext without the format parameter (dbu) + * bug #31459 Fix the interface incompatibility of EventDispatchers (keulinho) + * bug #31463 [DI] default to service id - *not* FQCN - when building tagged locators (nicolas-grekas) + * feature #31294 [Form] Add intl/choice_translation_locale option to TimezoneType (ro0NL) + * feature #31452 [FrameworkBundle] Add cache configuration for PropertyInfo (alanpoulain) + * feature #31486 [Doctrine][PropertyInfo] Detect if the ID is writeable (dunglas) + * bug #31481 [Validator] Autovalidation: skip readonly props (dunglas) + * bug #31480 Update dependencies in the main component (DavidPrevot) + * bug #31477 [PropertyAccess] Add missing property to PropertyAccessor (vudaltsov) + * bug #31479 [Cache] fix saving unrelated keys in recursive callback calls (nicolas-grekas) + * bug #30930 [FrameworkBundle] Fixed issue when a parameter contains a '' (lyrixx) + * bug #31438 [Serializer] Fix denormalization of object with variadic constructor typed argument (ajgarlag) + * bug #31445 [Messenger] Making cache rebuild correctly when message subscribers change (weaverryan) + * bug #31442 [Validator] Fix finding translator parent definition in compiler pass (deguif) + * bug #31475 [HttpFoundation] Allow set 'None' on samesite cookie flag (markitosgv) + * feature #31454 [Messenger] remove send_and_handle which can be achieved with SyncTransport (Tobion) + * bug #31425 [Messenger] On failure retry, make message appear received from original sender (weaverryan) + * bug #31472 [Messenger] Fix routable message bus default bus (weaverryan) + * bug #31465 [Serializer] Fix BC break: DEPTH_KEY_PATTERN must be public (dunglas) + * bug #31458 [TwigBundle] Fix Mailer integration in Twig (fabpot) + * bug #31456 Remove deprecated usage of some Twig features (fabpot) + * bug #31207 [Routing] Fixed unexpected 404 NoConfigurationException (yceruto) + * bug #31261 [Console] Commands with an alias should not be recognized as ambiguous when using register (Simperfit) + * bug #31371 [DI] Removes number of elements information in debug mode (jschaedl) + * bug #31418 [FrameworkBundle] clarify the possible class/interface of the cache (xabbuh) + * 4.3.0-BETA1 (2019-05-09) * feature #31249 [Translator] Set sources when extracting strings from php files (Stadly) diff --git a/src/Symfony/Bridge/Doctrine/Messenger/DoctrineCloseConnectionMiddleware.php b/src/Symfony/Bridge/Doctrine/Messenger/DoctrineCloseConnectionMiddleware.php index 6520ac0a35..0996859221 100644 --- a/src/Symfony/Bridge/Doctrine/Messenger/DoctrineCloseConnectionMiddleware.php +++ b/src/Symfony/Bridge/Doctrine/Messenger/DoctrineCloseConnectionMiddleware.php @@ -12,8 +12,8 @@ namespace Symfony\Bridge\Doctrine\Messenger; use Doctrine\Common\Persistence\ManagerRegistry; -use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException; use Symfony\Component\Messenger\Middleware\MiddlewareInterface; use Symfony\Component\Messenger\Middleware\StackInterface; @@ -40,10 +40,10 @@ class DoctrineCloseConnectionMiddleware implements MiddlewareInterface */ public function handle(Envelope $envelope, StackInterface $stack): Envelope { - $entityManager = $this->managerRegistry->getManager($this->entityManagerName); - - if (!$entityManager instanceof EntityManagerInterface) { - throw new \InvalidArgumentException(sprintf('The ObjectManager with name "%s" must be an instance of EntityManagerInterface', $this->entityManagerName)); + try { + $entityManager = $this->managerRegistry->getManager($this->entityManagerName); + } catch (\InvalidArgumentException $e) { + throw new UnrecoverableMessageHandlingException($e->getMessage(), 0, $e); } try { diff --git a/src/Symfony/Bridge/Doctrine/Messenger/DoctrinePingConnectionMiddleware.php b/src/Symfony/Bridge/Doctrine/Messenger/DoctrinePingConnectionMiddleware.php index 021d7a8392..ca9f65d9de 100644 --- a/src/Symfony/Bridge/Doctrine/Messenger/DoctrinePingConnectionMiddleware.php +++ b/src/Symfony/Bridge/Doctrine/Messenger/DoctrinePingConnectionMiddleware.php @@ -12,8 +12,8 @@ namespace Symfony\Bridge\Doctrine\Messenger; use Doctrine\Common\Persistence\ManagerRegistry; -use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException; use Symfony\Component\Messenger\Middleware\MiddlewareInterface; use Symfony\Component\Messenger\Middleware\StackInterface; @@ -40,10 +40,10 @@ class DoctrinePingConnectionMiddleware implements MiddlewareInterface */ public function handle(Envelope $envelope, StackInterface $stack): Envelope { - $entityManager = $this->managerRegistry->getManager($this->entityManagerName); - - if (!$entityManager instanceof EntityManagerInterface) { - throw new \InvalidArgumentException(sprintf('The ObjectManager with name "%s" must be an instance of EntityManagerInterface', $this->entityManagerName)); + try { + $entityManager = $this->managerRegistry->getManager($this->entityManagerName); + } catch (\InvalidArgumentException $e) { + throw new UnrecoverableMessageHandlingException($e->getMessage(), 0, $e); } $connection = $entityManager->getConnection(); diff --git a/src/Symfony/Bridge/Doctrine/Messenger/DoctrineTransactionMiddleware.php b/src/Symfony/Bridge/Doctrine/Messenger/DoctrineTransactionMiddleware.php index efab98d54f..0c207567ca 100644 --- a/src/Symfony/Bridge/Doctrine/Messenger/DoctrineTransactionMiddleware.php +++ b/src/Symfony/Bridge/Doctrine/Messenger/DoctrineTransactionMiddleware.php @@ -12,8 +12,8 @@ namespace Symfony\Bridge\Doctrine\Messenger; use Doctrine\Common\Persistence\ManagerRegistry; -use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException; use Symfony\Component\Messenger\Middleware\MiddlewareInterface; use Symfony\Component\Messenger\Middleware\StackInterface; @@ -40,10 +40,10 @@ class DoctrineTransactionMiddleware implements MiddlewareInterface */ public function handle(Envelope $envelope, StackInterface $stack): Envelope { - $entityManager = $this->managerRegistry->getManager($this->entityManagerName); - - if (!$entityManager instanceof EntityManagerInterface) { - throw new \InvalidArgumentException(sprintf('The ObjectManager with name "%s" must be an instance of EntityManagerInterface', $this->entityManagerName)); + try { + $entityManager = $this->managerRegistry->getManager($this->entityManagerName); + } catch (\InvalidArgumentException $e) { + throw new UnrecoverableMessageHandlingException($e->getMessage(), 0, $e); } $entityManager->getConnection()->beginTransaction(); diff --git a/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrineCloseConnectionMiddlewareTest.php b/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrineCloseConnectionMiddlewareTest.php index 3036b42593..df5414e3cc 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrineCloseConnectionMiddlewareTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrineCloseConnectionMiddlewareTest.php @@ -16,6 +16,7 @@ use Doctrine\DBAL\Connection; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bridge\Doctrine\Messenger\DoctrineCloseConnectionMiddleware; use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException; use Symfony\Component\Messenger\Test\Middleware\MiddlewareTestCase; class DoctrineCloseConnectionMiddlewareTest extends MiddlewareTestCase @@ -50,4 +51,19 @@ class DoctrineCloseConnectionMiddlewareTest extends MiddlewareTestCase $this->middleware->handle(new Envelope(new \stdClass()), $this->getStackMock()); } + + public function testInvalidEntityManagerThrowsException() + { + $managerRegistry = $this->createMock(ManagerRegistry::class); + $managerRegistry + ->method('getManager') + ->with('unknown_manager') + ->will($this->throwException(new \InvalidArgumentException())); + + $middleware = new DoctrineCloseConnectionMiddleware($managerRegistry, 'unknown_manager'); + + $this->expectException(UnrecoverableMessageHandlingException::class); + + $middleware->handle(new Envelope(new \stdClass()), $this->getStackMock(false)); + } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrinePingConnectionMiddlewareTest.php b/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrinePingConnectionMiddlewareTest.php index cc15625227..ae71d0d168 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrinePingConnectionMiddlewareTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrinePingConnectionMiddlewareTest.php @@ -16,6 +16,7 @@ use Doctrine\DBAL\Connection; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bridge\Doctrine\Messenger\DoctrinePingConnectionMiddleware; use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException; use Symfony\Component\Messenger\Test\Middleware\MiddlewareTestCase; class DoctrinePingConnectionMiddlewareTest extends MiddlewareTestCase @@ -71,4 +72,19 @@ class DoctrinePingConnectionMiddlewareTest extends MiddlewareTestCase $this->middleware->handle(new Envelope(new \stdClass()), $this->getStackMock()); } + + public function testInvalidEntityManagerThrowsException() + { + $managerRegistry = $this->createMock(ManagerRegistry::class); + $managerRegistry + ->method('getManager') + ->with('unknown_manager') + ->will($this->throwException(new \InvalidArgumentException())); + + $middleware = new DoctrinePingConnectionMiddleware($managerRegistry, 'unknown_manager'); + + $this->expectException(UnrecoverableMessageHandlingException::class); + + $middleware->handle(new Envelope(new \stdClass()), $this->getStackMock(false)); + } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrineTransactionMiddlewareTest.php b/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrineTransactionMiddlewareTest.php index 5927a993d1..04fb86140e 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrineTransactionMiddlewareTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrineTransactionMiddlewareTest.php @@ -16,6 +16,7 @@ use Doctrine\DBAL\Connection; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bridge\Doctrine\Messenger\DoctrineTransactionMiddleware; use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException; use Symfony\Component\Messenger\Test\Middleware\MiddlewareTestCase; class DoctrineTransactionMiddlewareTest extends MiddlewareTestCase @@ -67,4 +68,19 @@ class DoctrineTransactionMiddlewareTest extends MiddlewareTestCase $this->middleware->handle(new Envelope(new \stdClass()), $this->getThrowingStackMock()); } + + public function testInvalidEntityManagerThrowsException() + { + $managerRegistry = $this->createMock(ManagerRegistry::class); + $managerRegistry + ->method('getManager') + ->with('unknown_manager') + ->will($this->throwException(new \InvalidArgumentException())); + + $middleware = new DoctrineTransactionMiddleware($managerRegistry, 'unknown_manager'); + + $this->expectException(UnrecoverableMessageHandlingException::class); + + $middleware->handle(new Envelope(new \stdClass()), $this->getStackMock(false)); + } } diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index c3e0be594c..ddedaeba32 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -29,11 +29,11 @@ "symfony/dependency-injection": "~3.4|~4.0", "symfony/form": "~4.3", "symfony/http-kernel": "~3.4|~4.0", - "symfony/messenger": "~4.2", + "symfony/messenger": "~4.3", "symfony/property-access": "~3.4|~4.0", "symfony/property-info": "~3.4|~4.0", "symfony/proxy-manager-bridge": "~3.4|~4.0", - "symfony/security": "~3.4|~4.0", + "symfony/security-core": "~3.4|~4.0", "symfony/expression-language": "~3.4|~4.0", "symfony/validator": "~3.4|~4.0", "symfony/translation": "~3.4|~4.0", @@ -49,7 +49,7 @@ "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", "symfony/dependency-injection": "<3.4", "symfony/form": "<4.3", - "symfony/messenger": "<4.2" + "symfony/messenger": "<4.3" }, "suggest": { "symfony/form": "", diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index 201d8ea906..dedbfa64d5 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -34,8 +34,9 @@ "symfony/templating": "~3.4|~4.0", "symfony/translation": "~4.2", "symfony/yaml": "~3.4|~4.0", - "symfony/security": "~3.4|~4.0", "symfony/security-acl": "~2.8|~3.0", + "symfony/security-csrf": "~3.4|~4.0", + "symfony/security-http": "~3.4|~4.0", "symfony/stopwatch": "~3.4|~4.0", "symfony/console": "~3.4|~4.0", "symfony/var-dumper": "~3.4|~4.0", @@ -59,7 +60,9 @@ "symfony/templating": "For using the TwigEngine", "symfony/translation": "For using the TranslationExtension", "symfony/yaml": "For using the YamlExtension", - "symfony/security": "For using the SecurityExtension", + "symfony/security-core": "For using the SecurityExtension", + "symfony/security-csrf": "For using the CsrfExtension", + "symfony/security-http": "For using the LogoutUrlExtension", "symfony/stopwatch": "For using the StopwatchExtension", "symfony/var-dumper": "For using the DumpExtension", "symfony/expression-language": "For using the ExpressionExtension", diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index f49e337a39..a1d03e7280 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -117,6 +117,7 @@ use Symfony\Component\Workflow\WorkflowInterface; use Symfony\Component\Yaml\Command\LintCommand as BaseYamlLintCommand; use Symfony\Component\Yaml\Yaml; use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Cache\TagAwareCacheInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\Service\ResetInterface; use Symfony\Contracts\Service\ServiceSubscriberInterface; @@ -737,7 +738,7 @@ class FrameworkExtension extends Extension } if (!class_exists(Security::class)) { - throw new LogicException('Cannot guard workflows as the Security component is not installed. Try running "composer require symfony/security".'); + throw new LogicException('Cannot guard workflows as the Security component is not installed. Try running "composer require symfony/security-core".'); } $guard = new Definition(Workflow\EventListener\GuardListener::class); @@ -1819,13 +1820,9 @@ class FrameworkExtension extends Extension $pool['adapter'] = '.'.$pool['adapter'].'.inner'; } $definition = new ChildDefinition($pool['adapter']); - if (!\in_array($name, ['cache.app', 'cache.system'], true)) { - $container->registerAliasForArgument($name, CacheInterface::class); - $container->registerAliasForArgument($name, CacheItemPoolInterface::class); - } if ($pool['tags']) { - if ($config['pools'][$pool['tags']]['tags'] ?? false) { + if (true !== $pool['tags'] && ($config['pools'][$pool['tags']]['tags'] ?? false)) { $pool['tags'] = '.'.$pool['tags'].'.inner'; } $container->register($name, TagAwareAdapter::class) @@ -1837,7 +1834,21 @@ class FrameworkExtension extends Extension $pool['name'] = $name; $pool['public'] = false; $name = '.'.$name.'.inner'; + + if (!\in_array($pool['name'], ['cache.app', 'cache.system'], true)) { + $container->registerAliasForArgument($pool['name'], TagAwareCacheInterface::class); + $container->registerAliasForArgument($name, CacheInterface::class, $pool['name']); + $container->registerAliasForArgument($name, CacheItemPoolInterface::class, $pool['name']); + } + } elseif (!\in_array($name, ['cache.app', 'cache.system'], true)) { + $container->register('.'.$name.'.taggable', TagAwareAdapter::class) + ->addArgument(new Reference($name)) + ; + $container->registerAliasForArgument('.'.$name.'.taggable', TagAwareCacheInterface::class, $name); + $container->registerAliasForArgument($name, CacheInterface::class); + $container->registerAliasForArgument($name, CacheItemPoolInterface::class); } + $definition->setPublic($pool['public']); unset($pool['adapter'], $pool['public'], $pool['tags']); diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 7f35fc762d..521da7a681 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -38,7 +38,6 @@ "symfony/css-selector": "~3.4|~4.0", "symfony/dom-crawler": "^4.3", "symfony/polyfill-intl-icu": "~1.0", - "symfony/security": "~3.4|~4.0", "symfony/form": "^4.3", "symfony/expression-language": "~3.4|~4.0", "symfony/http-client": "^4.3", @@ -46,8 +45,8 @@ "symfony/messenger": "^4.3", "symfony/mime": "^4.3", "symfony/process": "~3.4|~4.0", - "symfony/security-core": "~3.4|~4.0", "symfony/security-csrf": "~3.4|~4.0", + "symfony/security-http": "~3.4|~4.0", "symfony/serializer": "^4.3", "symfony/stopwatch": "~3.4|~4.0", "symfony/translation": "~4.2", diff --git a/src/Symfony/Component/Cache/Adapter/AbstractTagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/AbstractTagAwareAdapter.php index 1f922d0fbd..2eb5ce8c6e 100644 --- a/src/Symfony/Component/Cache/Adapter/AbstractTagAwareAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/AbstractTagAwareAdapter.php @@ -253,7 +253,8 @@ abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagA } } catch (\Exception $e) { } - CacheItem::log($this->logger, 'Failed to delete key "{key}"', ['key' => $key, 'exception' => $e]); + $message = 'Failed to delete key "{key}"'.($e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e]); $ok = false; } diff --git a/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php index d4ee186789..4e09387213 100644 --- a/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php @@ -198,7 +198,7 @@ class RedisTagAwareAdapter extends AbstractTagAwareAdapter $info = $host->info('Server'); $info = isset($info['Server']) ? $info['Server'] : $info; if (version_compare($info['redis_version'], '3.2', '<')) { - CacheItem::log($this->logger, 'Redis server needs to be version 3.2 or higher, your Redis server was detected as {version}', ['version' => $info['redis_version']]); + CacheItem::log($this->logger, 'Redis server needs to be version 3.2 or higher, your Redis server was detected as '.$info['redis_version']); return $this->redisServerSupportSPOP = false; } diff --git a/src/Symfony/Component/Cache/Simple/AbstractCache.php b/src/Symfony/Component/Cache/Simple/AbstractCache.php index 29fbd7719e..312a7dbfd0 100644 --- a/src/Symfony/Component/Cache/Simple/AbstractCache.php +++ b/src/Symfony/Component/Cache/Simple/AbstractCache.php @@ -56,7 +56,7 @@ abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterfac return $value; } } catch (\Exception $e) { - CacheItem::log($this->logger, 'Failed to fetch key "{key}"', ['key' => $key, 'exception' => $e]); + CacheItem::log($this->logger, 'Failed to fetch key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e]); } return $default; @@ -90,7 +90,7 @@ abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterfac try { $values = $this->doFetch($ids); } catch (\Exception $e) { - CacheItem::log($this->logger, 'Failed to fetch requested values', ['keys' => $keys, 'exception' => $e]); + CacheItem::log($this->logger, 'Failed to fetch values: '.$e->getMessage(), ['keys' => $keys, 'exception' => $e]); $values = []; } $ids = array_combine($ids, $keys); @@ -129,7 +129,8 @@ abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterfac foreach (\is_array($e) ? $e : array_keys($valuesById) as $id) { $keys[] = substr($id, \strlen($this->namespace)); } - CacheItem::log($this->logger, 'Failed to save values', ['keys' => $keys, 'exception' => $e instanceof \Exception ? $e : null]); + $message = 'Failed to save values'.($e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['keys' => $keys, 'exception' => $e instanceof \Exception ? $e : null]); return false; } @@ -175,7 +176,7 @@ abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterfac yield $key => $value; } } catch (\Exception $e) { - CacheItem::log($this->logger, 'Failed to fetch requested values', ['keys' => array_values($keys), 'exception' => $e]); + CacheItem::log($this->logger, 'Failed to fetch values: '.$e->getMessage(), ['keys' => array_values($keys), 'exception' => $e]); } foreach ($keys as $key) { diff --git a/src/Symfony/Component/Cache/Traits/AbstractAdapterTrait.php b/src/Symfony/Component/Cache/Traits/AbstractAdapterTrait.php index f1d97abf2d..eb464c319e 100644 --- a/src/Symfony/Component/Cache/Traits/AbstractAdapterTrait.php +++ b/src/Symfony/Component/Cache/Traits/AbstractAdapterTrait.php @@ -52,7 +52,7 @@ trait AbstractAdapterTrait $isHit = true; } } catch (\Exception $e) { - CacheItem::log($this->logger, 'Failed to fetch key "{key}"', ['key' => $key, 'exception' => $e]); + CacheItem::log($this->logger, 'Failed to fetch key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e]); } return $f($key, $value, $isHit); @@ -74,7 +74,7 @@ trait AbstractAdapterTrait try { $items = $this->doFetch($ids); } catch (\Exception $e) { - CacheItem::log($this->logger, 'Failed to fetch requested items', ['keys' => $keys, 'exception' => $e]); + CacheItem::log($this->logger, 'Failed to fetch items: '.$e->getMessage(), ['keys' => $keys, 'exception' => $e]); $items = []; } $ids = array_combine($ids, $keys); @@ -129,7 +129,7 @@ trait AbstractAdapterTrait yield $key => $f($key, $value, true); } } catch (\Exception $e) { - CacheItem::log($this->logger, 'Failed to fetch requested items', ['keys' => array_values($keys), 'exception' => $e]); + CacheItem::log($this->logger, 'Failed to fetch items: '.$e->getMessage(), ['keys' => array_values($keys), 'exception' => $e]); } foreach ($keys as $key) { diff --git a/src/Symfony/Component/Cache/Traits/AbstractTrait.php b/src/Symfony/Component/Cache/Traits/AbstractTrait.php index 2553ea75cb..1d6c189c81 100644 --- a/src/Symfony/Component/Cache/Traits/AbstractTrait.php +++ b/src/Symfony/Component/Cache/Traits/AbstractTrait.php @@ -94,7 +94,7 @@ trait AbstractTrait try { return $this->doHave($id); } catch (\Exception $e) { - CacheItem::log($this->logger, 'Failed to check if key "{key}" is cached', ['key' => $key, 'exception' => $e]); + CacheItem::log($this->logger, 'Failed to check if key "{key}" is cached: '.$e->getMessage(), ['key' => $key, 'exception' => $e]); return false; } @@ -122,7 +122,7 @@ trait AbstractTrait try { return $this->doClear($this->namespace) || $cleared; } catch (\Exception $e) { - CacheItem::log($this->logger, 'Failed to clear the cache', ['exception' => $e]); + CacheItem::log($this->logger, 'Failed to clear the cache: '.$e->getMessage(), ['exception' => $e]); return false; } @@ -166,7 +166,8 @@ trait AbstractTrait } } catch (\Exception $e) { } - CacheItem::log($this->logger, 'Failed to delete key "{key}"', ['key' => $key, 'exception' => $e]); + $message = 'Failed to delete key "{key}"'.($e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e]); $ok = false; } diff --git a/src/Symfony/Component/Cache/Traits/ArrayTrait.php b/src/Symfony/Component/Cache/Traits/ArrayTrait.php index 182c74159e..8ae9cad59b 100644 --- a/src/Symfony/Component/Cache/Traits/ArrayTrait.php +++ b/src/Symfony/Component/Cache/Traits/ArrayTrait.php @@ -128,8 +128,8 @@ trait ArrayTrait $serialized = serialize($value); } catch (\Exception $e) { $type = \is_object($value) ? \get_class($value) : \gettype($value); - $message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.'); - CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]); + $message = sprintf('Failed to save key "{key}" of type %s: %s', $type, $e->getMessage()); + CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e]); return; } @@ -151,7 +151,7 @@ trait ArrayTrait try { $value = unserialize($value); } catch (\Exception $e) { - CacheItem::log($this->logger, 'Failed to unserialize key "{key}"', ['key' => $key, 'exception' => $e]); + CacheItem::log($this->logger, 'Failed to unserialize key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e]); $value = false; } if (false === $value) { diff --git a/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php b/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php index 3991c0165f..e047639081 100644 --- a/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php +++ b/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php @@ -41,7 +41,7 @@ class WrappedListener public function __construct($listener, ?string $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null) { $this->listener = $listener; - $this->optimizedListener = $listener instanceof \Closure ? $listener : \Closure::fromCallable($listener); + $this->optimizedListener = $listener instanceof \Closure ? $listener : (\is_callable($listener) ? \Closure::fromCallable($listener) : null); $this->stopwatch = $stopwatch; $this->dispatcher = $dispatcher; $this->called = false; @@ -123,7 +123,7 @@ class WrappedListener $e = $this->stopwatch->start($this->name, 'event_listener'); - ($this->optimizedListener)($event, $eventName, $dispatcher); + ($this->optimizedListener ?? $this->listener)($event, $eventName, $dispatcher); if ($e->isStarted()) { $e->stop(); diff --git a/src/Symfony/Component/EventDispatcher/Tests/Debug/WrappedListenerTest.php b/src/Symfony/Component/EventDispatcher/Tests/Debug/WrappedListenerTest.php index 56792fe33a..75e9012073 100644 --- a/src/Symfony/Component/EventDispatcher/Tests/Debug/WrappedListenerTest.php +++ b/src/Symfony/Component/EventDispatcher/Tests/Debug/WrappedListenerTest.php @@ -21,7 +21,7 @@ class WrappedListenerTest extends TestCase /** * @dataProvider provideListenersToDescribe */ - public function testListenerDescription(callable $listener, $expected) + public function testListenerDescription($listener, $expected) { $wrappedListener = new WrappedListener($listener, null, $this->getMockBuilder(Stopwatch::class)->getMock(), $this->getMockBuilder(EventDispatcherInterface::class)->getMock()); @@ -34,6 +34,7 @@ class WrappedListenerTest extends TestCase [new FooListener(), 'Symfony\Component\EventDispatcher\Tests\Debug\FooListener::__invoke'], [[new FooListener(), 'listen'], 'Symfony\Component\EventDispatcher\Tests\Debug\FooListener::listen'], [['Symfony\Component\EventDispatcher\Tests\Debug\FooListener', 'listenStatic'], 'Symfony\Component\EventDispatcher\Tests\Debug\FooListener::listenStatic'], + [['Symfony\Component\EventDispatcher\Tests\Debug\FooListener', 'invalidMethod'], 'Symfony\Component\EventDispatcher\Tests\Debug\FooListener::invalidMethod'], ['var_dump', 'var_dump'], [function () {}, 'closure'], [\Closure::fromCallable([new FooListener(), 'listen']), 'Symfony\Component\EventDispatcher\Tests\Debug\FooListener::listen'], diff --git a/src/Symfony/Component/Filesystem/Filesystem.php b/src/Symfony/Component/Filesystem/Filesystem.php index 9240a215c6..224fbc5a34 100644 --- a/src/Symfony/Component/Filesystem/Filesystem.php +++ b/src/Symfony/Component/Filesystem/Filesystem.php @@ -572,7 +572,7 @@ class Filesystem $targetDirInfo = new \SplFileInfo($targetDir); foreach ($iterator as $file) { - if ($file->getPathName() === $targetDir || $file->getRealPath() === $targetDir || 0 === strpos($file->getRealPath(), $targetDirInfo->getRealPath())) { + if ($file->getPathname() === $targetDir || $file->getRealPath() === $targetDir || 0 === strpos($file->getRealPath(), $targetDirInfo->getRealPath())) { continue; } diff --git a/src/Symfony/Component/HttpClient/CurlHttpClient.php b/src/Symfony/Component/HttpClient/CurlHttpClient.php index e28ade68dc..fd8ecbaeed 100644 --- a/src/Symfony/Component/HttpClient/CurlHttpClient.php +++ b/src/Symfony/Component/HttpClient/CurlHttpClient.php @@ -70,7 +70,9 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface if (\defined('CURLPIPE_MULTIPLEX')) { curl_multi_setopt($this->multi->handle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX); } - curl_multi_setopt($this->multi->handle, CURLMOPT_MAX_HOST_CONNECTIONS, 0 < $maxHostConnections ? $maxHostConnections : PHP_INT_MAX); + if (\defined('CURLMOPT_MAX_HOST_CONNECTIONS')) { + curl_multi_setopt($this->multi->handle, CURLMOPT_MAX_HOST_CONNECTIONS, 0 < $maxHostConnections ? $maxHostConnections : PHP_INT_MAX); + } // Skip configuring HTTP/2 push when it's unsupported or buggy, see https://bugs.php.net/bug.php?id=77535 if (0 >= $maxPendingPushes || \PHP_VERSION_ID < 70217 || (\PHP_VERSION_ID >= 70300 && \PHP_VERSION_ID < 70304)) { @@ -188,7 +190,7 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface $curlopts[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0; } elseif (1.1 === (float) $options['http_version'] || 'https:' !== $scheme) { $curlopts[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1; - } elseif (CURL_VERSION_HTTP2 & curl_version()['features']) { + } elseif (\defined('CURL_VERSION_HTTP2') && CURL_VERSION_HTTP2 & curl_version()['features']) { $curlopts[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0; } diff --git a/src/Symfony/Component/HttpClient/Exception/HttpExceptionTrait.php b/src/Symfony/Component/HttpClient/Exception/HttpExceptionTrait.php index 3278af9be0..76e650934b 100644 --- a/src/Symfony/Component/HttpClient/Exception/HttpExceptionTrait.php +++ b/src/Symfony/Component/HttpClient/Exception/HttpExceptionTrait.php @@ -27,7 +27,7 @@ trait HttpExceptionTrait $this->response = $response; $code = $response->getInfo('http_code'); $url = $response->getInfo('url'); - $message = sprintf('HTTP %d returned for URL "%s".', $code, $url); + $message = sprintf('HTTP %d returned for "%s".', $code, $url); $httpCodeFound = false; $isJson = false; @@ -37,7 +37,7 @@ trait HttpExceptionTrait break; } - $message = sprintf('%s returned for URL "%s".', $h, $url); + $message = sprintf('%s returned for "%s".', $h, $url); $httpCodeFound = true; } diff --git a/src/Symfony/Component/HttpClient/HttpClientTrait.php b/src/Symfony/Component/HttpClient/HttpClientTrait.php index abf7d86c80..0023e3bbb0 100644 --- a/src/Symfony/Component/HttpClient/HttpClientTrait.php +++ b/src/Symfony/Component/HttpClient/HttpClientTrait.php @@ -481,17 +481,20 @@ trait HttpClientTrait } } - foreach ($queryArray as $k => $v) { - if (is_scalar($v)) { - $queryArray[$k] = rawurlencode($k).'='.rawurlencode($v); - } elseif (null === $v) { - unset($queryArray[$k]); - - if ($replace) { + if ($replace) { + foreach ($queryArray as $k => $v) { + if (null === $v) { unset($query[$k]); } - } else { - throw new InvalidArgumentException(sprintf('Unsupported value for query parameter "%s": scalar or null expected, %s given.', $k, \gettype($v))); + } + } + + $queryString = http_build_query($queryArray, '', '&', PHP_QUERY_RFC3986); + $queryArray = []; + + if ($queryString) { + foreach (explode('&', $queryString) as $v) { + $queryArray[rawurldecode(explode('=', $v, 2)[0])] = $v; } } diff --git a/src/Symfony/Component/HttpClient/Response/CurlResponse.php b/src/Symfony/Component/HttpClient/Response/CurlResponse.php index 2a4cd5546a..a54ab6dc20 100644 --- a/src/Symfony/Component/HttpClient/Response/CurlResponse.php +++ b/src/Symfony/Component/HttpClient/Response/CurlResponse.php @@ -245,7 +245,7 @@ final class CurlResponse implements ResponseInterface while ($info = curl_multi_info_read($multi->handle)) { $multi->handlesActivity[(int) $info['handle']][] = null; - $multi->handlesActivity[(int) $info['handle']][] = \in_array($info['result'], [\CURLE_OK, \CURLE_TOO_MANY_REDIRECTS], true) || (\CURLE_WRITE_ERROR === $info['result'] && 'destruct' === @curl_getinfo($info['handle'], CURLINFO_PRIVATE)) ? null : new TransportException(curl_error($info['handle'])); + $multi->handlesActivity[(int) $info['handle']][] = \in_array($info['result'], [\CURLE_OK, \CURLE_TOO_MANY_REDIRECTS], true) || (\CURLE_WRITE_ERROR === $info['result'] && 'destruct' === @curl_getinfo($info['handle'], CURLINFO_PRIVATE)) ? null : new TransportException(sprintf('%s for"%s".', curl_strerror($info['result']), curl_getinfo($info['handle'], CURLINFO_EFFECTIVE_URL))); } } finally { self::$performing = false; diff --git a/src/Symfony/Component/HttpClient/Response/MockResponse.php b/src/Symfony/Component/HttpClient/Response/MockResponse.php index df38c7e5bb..04b33a143a 100644 --- a/src/Symfony/Component/HttpClient/Response/MockResponse.php +++ b/src/Symfony/Component/HttpClient/Response/MockResponse.php @@ -25,7 +25,9 @@ use Symfony\Contracts\HttpClient\ResponseInterface; */ class MockResponse implements ResponseInterface { - use ResponseTrait; + use ResponseTrait { + doDestruct as public __destruct; + } private $body; private $requestOptions = []; @@ -162,8 +164,8 @@ class MockResponse implements ResponseInterface $offset = 0; $chunk[1]->getStatusCode(); $response->headers = $chunk[1]->getHeaders(false); - $multi->handlesActivity[$id][] = new FirstChunk(); self::readResponse($response, $chunk[0], $chunk[1], $offset); + $multi->handlesActivity[$id][] = new FirstChunk(); } catch (\Throwable $e) { $multi->handlesActivity[$id][] = null; $multi->handlesActivity[$id][] = $e; diff --git a/src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php index 0f75d1c359..630c37b063 100644 --- a/src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php +++ b/src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php @@ -61,14 +61,14 @@ class CurlHttpClientTest extends HttpClientTestCase $js->getHeaders(); $expected = [ - 'Request: GET https://http2-push.io/', - 'Queueing pushed response: https://http2-push.io/css/style.css', - 'Queueing pushed response: https://http2-push.io/js/http2-push.js', - 'Response: 200 https://http2-push.io/', - 'Connecting request to pushed response: GET https://http2-push.io/css/style.css', - 'Connecting request to pushed response: GET https://http2-push.io/js/http2-push.js', - 'Response: 200 https://http2-push.io/css/style.css', - 'Response: 200 https://http2-push.io/js/http2-push.js', + 'Request: "GET https://http2-push.io/"', + 'Queueing pushed response: "https://http2-push.io/css/style.css"', + 'Queueing pushed response: "https://http2-push.io/js/http2-push.js"', + 'Response: "200 https://http2-push.io/"', + 'Connecting request to pushed response: "GET https://http2-push.io/css/style.css"', + 'Connecting request to pushed response: "GET https://http2-push.io/js/http2-push.js"', + 'Response: "200 https://http2-push.io/css/style.css"', + 'Response: "200 https://http2-push.io/js/http2-push.js"', ]; $this->assertSame($expected, $logger->logs); } diff --git a/src/Symfony/Component/HttpClient/Tests/HttpClientTraitTest.php b/src/Symfony/Component/HttpClient/Tests/HttpClientTraitTest.php index 5a55ec424e..056181e30e 100644 --- a/src/Symfony/Component/HttpClient/Tests/HttpClientTraitTest.php +++ b/src/Symfony/Component/HttpClient/Tests/HttpClientTraitTest.php @@ -141,6 +141,10 @@ class HttpClientTraitTest extends TestCase yield [[null, null, 'bar', '?a=1&c=c', null], 'bar?a=a&b=b', ['b' => null, 'c' => 'c', 'a' => 1]]; yield [[null, null, 'bar', '?a=b+c&b=b', null], 'bar?a=b+c', ['b' => 'b']]; yield [[null, null, 'bar', '?a=b%2B%20c', null], 'bar?a=b+c', ['a' => 'b+ c']]; + yield [[null, null, 'bar', '?a%5Bb%5D=c', null], 'bar', ['a' => ['b' => 'c']]]; + yield [[null, null, 'bar', '?a%5Bb%5Bc%5D=d', null], 'bar?a[b[c]=d', []]; + yield [[null, null, 'bar', '?a%5Bb%5D%5Bc%5D=dd', null], 'bar?a[b][c]=d&e[f]=g', ['a' => ['b' => ['c' => 'dd']], 'e[f]' => null]]; + yield [[null, null, 'bar', '?a=b&a%5Bb%20c%5D=d&e%3Df=%E2%9C%93', null], 'bar?a=b', ['a' => ['b c' => 'd'], 'e=f' => '✓']]; } /** diff --git a/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php index e719428c81..710d86a258 100644 --- a/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php +++ b/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php @@ -115,7 +115,7 @@ class MockHttpClientTest extends HttpClientTestCase case 'testResolve': $responses[] = new MockResponse($body, ['response_headers' => $headers]); $responses[] = new MockResponse($body, ['response_headers' => $headers]); - $responses[] = $client->request('GET', 'http://symfony.com:8057/'); + $responses[] = new MockResponse((function () { throw new \Exception('Fake connection timeout'); yield ''; })(), ['response_headers' => $headers]); break; case 'testTimeoutOnStream': diff --git a/src/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeExtensionGuesser.php b/src/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeExtensionGuesser.php index 55cf7d3f18..651be070e1 100644 --- a/src/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeExtensionGuesser.php +++ b/src/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeExtensionGuesser.php @@ -645,6 +645,7 @@ class MimeTypeExtensionGuesser implements ExtensionGuesserInterface 'audio/x-aiff' => 'aif', 'audio/x-caf' => 'caf', 'audio/x-flac' => 'flac', + 'audio/x-hx-aac-adts' => 'aac', 'audio/x-matroska' => 'mka', 'audio/x-mpegurl' => 'm3u', 'audio/x-ms-wax' => 'wax', diff --git a/src/Symfony/Component/Ldap/Tests/Adapter/ExtLdap/EntryManagerTest.php b/src/Symfony/Component/Ldap/Tests/Adapter/ExtLdap/EntryManagerTest.php index 0617762ed6..758ae78e49 100644 --- a/src/Symfony/Component/Ldap/Tests/Adapter/ExtLdap/EntryManagerTest.php +++ b/src/Symfony/Component/Ldap/Tests/Adapter/ExtLdap/EntryManagerTest.php @@ -17,6 +17,22 @@ use Symfony\Component\Ldap\Entry; class EntryManagerTest extends TestCase { + /** + * @expectedException \Symfony\Component\Ldap\Exception\LdapException + * @expectedExceptionMessage Entry "$$$$$$" malformed, could not parse RDN. + */ + public function testMove() + { + $connection = $this->createMock(Connection::class); + $connection + ->expects($this->once()) + ->method('isBound')->willReturn(true); + + $entry = new Entry('$$$$$$'); + $entryManager = new EntryManager($connection); + $entryManager->move($entry, 'a'); + } + /** * @expectedException \Symfony\Component\Ldap\Exception\NotBoundException * @expectedExceptionMessage Query execution is not possible without binding the connection first. diff --git a/src/Symfony/Component/Messenger/CHANGELOG.md b/src/Symfony/Component/Messenger/CHANGELOG.md index 08a3520324..49d04feb1f 100644 --- a/src/Symfony/Component/Messenger/CHANGELOG.md +++ b/src/Symfony/Component/Messenger/CHANGELOG.md @@ -4,6 +4,9 @@ CHANGELOG 4.3.0 ----- + * Added `NonSendableStampInterface` that a stamp can implement if + it should not be sent to a transport. Transport serializers + must now check for these stamps and not encode them. * [BC BREAK] `SendersLocatorInterface` has an additional method: `getSenderByAlias()`. * Removed argument `?bool &$handle = false` from `SendersLocatorInterface::getSenders` diff --git a/src/Symfony/Component/Messenger/Envelope.php b/src/Symfony/Component/Messenger/Envelope.php index 0be3355e2e..825ebf79e4 100644 --- a/src/Symfony/Component/Messenger/Envelope.php +++ b/src/Symfony/Component/Messenger/Envelope.php @@ -80,6 +80,22 @@ final class Envelope return $cloned; } + /** + * Removes all stamps that implement the given type. + */ + public function withoutStampsOfType(string $type): self + { + $cloned = clone $this; + + foreach ($cloned->stamps as $class => $stamps) { + if ($class === $type || \is_subclass_of($class, $type)) { + unset($cloned->stamps[$class]); + } + } + + return $cloned; + } + public function last(string $stampFqcn): ?StampInterface { return isset($this->stamps[$stampFqcn]) ? end($this->stamps[$stampFqcn]) : null; diff --git a/src/Symfony/Component/Messenger/Stamp/NonSendableStampInterface.php b/src/Symfony/Component/Messenger/Stamp/NonSendableStampInterface.php new file mode 100644 index 0000000000..ca8c31078e --- /dev/null +++ b/src/Symfony/Component/Messenger/Stamp/NonSendableStampInterface.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Messenger\Stamp; + +/** + * A stamp that should not be included with the Envelope if sent to a transport. + * + * @author Ryan Weaver + * + * @experimental in 4.3 + */ +interface NonSendableStampInterface extends StampInterface +{ +} diff --git a/src/Symfony/Component/Messenger/Tests/EnvelopeTest.php b/src/Symfony/Component/Messenger/Tests/EnvelopeTest.php index 417d3dedc0..eb99c0a3b0 100644 --- a/src/Symfony/Component/Messenger/Tests/EnvelopeTest.php +++ b/src/Symfony/Component/Messenger/Tests/EnvelopeTest.php @@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Stamp\DelayStamp; use Symfony\Component\Messenger\Stamp\ReceivedStamp; +use Symfony\Component\Messenger\Stamp\StampInterface; use Symfony\Component\Messenger\Stamp\ValidationStamp; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; @@ -50,6 +51,30 @@ class EnvelopeTest extends TestCase $this->assertCount(1, $envelope->all(DelayStamp::class)); } + public function testWithoutStampsOfType() + { + $envelope = new Envelope(new DummyMessage('dummy'), [ + new ReceivedStamp('transport1'), + new DelayStamp(5000), + new DummyExtendsDelayStamp(5000), + new DummyImplementsFooBarStampInterface(), + ]); + + $envelope2 = $envelope->withoutStampsOfType(DummyNothingImplementsMeStampInterface::class); + $this->assertEquals($envelope, $envelope2); + + $envelope3 = $envelope2->withoutStampsOfType(ReceivedStamp::class); + $this->assertEmpty($envelope3->all(ReceivedStamp::class)); + + $envelope4 = $envelope3->withoutStampsOfType(DelayStamp::class); + $this->assertEmpty($envelope4->all(DelayStamp::class)); + $this->assertEmpty($envelope4->all(DummyExtendsDelayStamp::class)); + + $envelope5 = $envelope4->withoutStampsOfType(DummyFooBarStampInterface::class); + $this->assertEmpty($envelope5->all(DummyImplementsFooBarStampInterface::class)); + $this->assertEmpty($envelope5->all()); + } + public function testLast() { $receivedStamp = new ReceivedStamp('transport'); @@ -92,3 +117,16 @@ class EnvelopeTest extends TestCase $this->assertCount(1, $envelope->all(ReceivedStamp::class)); } } + +class DummyExtendsDelayStamp extends DelayStamp +{ +} +interface DummyFooBarStampInterface extends StampInterface +{ +} +interface DummyNothingImplementsMeStampInterface extends StampInterface +{ +} +class DummyImplementsFooBarStampInterface implements DummyFooBarStampInterface +{ +} diff --git a/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/ConnectionTest.php b/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/ConnectionTest.php index c2654d326d..1a602f41f2 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/ConnectionTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/ConnectionTest.php @@ -46,9 +46,9 @@ class ConnectionTest extends TestCase 'host' => 'localhost', 'port' => 6379, ], [ - 'blocking_timeout' => 30, + 'serializer' => 2, ]), - Connection::fromDsn('redis://localhost/queue/group1/consumer1', ['blocking_timeout' => 30]) + Connection::fromDsn('redis://localhost/queue/group1/consumer1', ['serializer' => 2]) ); } @@ -59,9 +59,9 @@ class ConnectionTest extends TestCase 'host' => 'localhost', 'port' => 6379, ], [ - 'blocking_timeout' => 30, + 'serializer' => 2, ]), - Connection::fromDsn('redis://localhost/queue/group1/consumer1?blocking_timeout=30') + Connection::fromDsn('redis://localhost/queue/group1/consumer1?serializer=2') ); } @@ -134,16 +134,20 @@ class ConnectionTest extends TestCase $redis->del('messenger-rejectthenget'); } - public function testBlockingTimeout() + public function testGetNonBlocking() { $redis = new \Redis(); - $connection = Connection::fromDsn('redis://localhost/messenger-blockingtimeout', ['blocking_timeout' => 1], $redis); + + $connection = Connection::fromDsn('redis://localhost/messenger-getnonblocking', [], $redis); try { $connection->setup(); } catch (TransportException $e) { } - $this->assertNull($connection->get()); - $redis->del('messenger-blockingtimeout'); + $this->assertNull($connection->get()); // no message, should return null immediately + $connection->add('1', []); + $this->assertNotEmpty($message = $connection->get()); + $connection->reject($message['id']); + $redis->del('messenger-getnonblocking'); } } diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Serialization/PhpSerializerTest.php b/src/Symfony/Component/Messenger/Tests/Transport/Serialization/PhpSerializerTest.php index 6e508365e5..c146d2619d 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/Serialization/PhpSerializerTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/Serialization/PhpSerializerTest.php @@ -14,6 +14,7 @@ namespace Symfony\Component\Messenger\Tests\Transport\Serialization; use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Exception\MessageDecodingFailedException; +use Symfony\Component\Messenger\Stamp\NonSendableStampInterface; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer; @@ -63,4 +64,20 @@ class PhpSerializerTest extends TestCase 'body' => 'O:13:"ReceivedSt0mp":0:{}', ]); } + + public function testEncodedSkipsNonEncodeableStamps() + { + $serializer = new PhpSerializer(); + + $envelope = new Envelope(new DummyMessage('Hello'), [ + new DummyPhpSerializerNonSendableStamp(), + ]); + + $encoded = $serializer->encode($envelope); + $this->assertNotContains('DummyPhpSerializerNonSendableStamp', $encoded['body']); + } +} + +class DummyPhpSerializerNonSendableStamp implements NonSendableStampInterface +{ } diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Serialization/SerializerTest.php b/src/Symfony/Component/Messenger/Tests/Transport/Serialization/SerializerTest.php index 16e2c66a71..f43ad2efcb 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/Serialization/SerializerTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/Serialization/SerializerTest.php @@ -14,6 +14,7 @@ namespace Symfony\Component\Messenger\Tests\Transport\Serialization; use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Exception\MessageDecodingFailedException; +use Symfony\Component\Messenger\Stamp\NonSendableStampInterface; use Symfony\Component\Messenger\Stamp\SerializerStamp; use Symfony\Component\Messenger\Stamp\ValidationStamp; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; @@ -193,4 +194,19 @@ class SerializerTest extends TestCase 'headers' => ['type' => 'NonExistentClass'], ]); } + + public function testEncodedSkipsNonEncodeableStamps() + { + $serializer = new Serializer(); + + $envelope = new Envelope(new DummyMessage('Hello'), [ + new DummySymfonySerializerNonSendableStamp(), + ]); + + $encoded = $serializer->encode($envelope); + $this->assertNotContains('DummySymfonySerializerNonSendableStamp', print_r($encoded['headers'], true)); + } +} +class DummySymfonySerializerNonSendableStamp implements NonSendableStampInterface +{ } diff --git a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpReceivedStamp.php b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpReceivedStamp.php index 78ccb08add..65c0173907 100644 --- a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpReceivedStamp.php +++ b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpReceivedStamp.php @@ -11,14 +11,14 @@ namespace Symfony\Component\Messenger\Transport\AmqpExt; -use Symfony\Component\Messenger\Stamp\StampInterface; +use Symfony\Component\Messenger\Stamp\NonSendableStampInterface; /** * Stamp applied when a message is received from Amqp. * * @experimental in 4.3 */ -class AmqpReceivedStamp implements StampInterface +class AmqpReceivedStamp implements NonSendableStampInterface { private $amqpEnvelope; private $queueName; diff --git a/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineReceivedStamp.php b/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineReceivedStamp.php index f11217a74a..536ecacd4c 100644 --- a/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineReceivedStamp.php +++ b/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineReceivedStamp.php @@ -11,14 +11,14 @@ namespace Symfony\Component\Messenger\Transport\Doctrine; -use Symfony\Component\Messenger\Stamp\StampInterface; +use Symfony\Component\Messenger\Stamp\NonSendableStampInterface; /** * @author Vincent Touzet * * @experimental in 4.3 */ -class DoctrineReceivedStamp implements StampInterface +class DoctrineReceivedStamp implements NonSendableStampInterface { private $id; diff --git a/src/Symfony/Component/Messenger/Transport/RedisExt/Connection.php b/src/Symfony/Component/Messenger/Transport/RedisExt/Connection.php index 566e3d6c62..b190f3105d 100644 --- a/src/Symfony/Component/Messenger/Transport/RedisExt/Connection.php +++ b/src/Symfony/Component/Messenger/Transport/RedisExt/Connection.php @@ -31,7 +31,6 @@ class Connection private $stream; private $group; private $consumer; - private $blockingTimeout; private $couldHavePendingMessages = true; public function __construct(array $configuration, array $connectionCredentials = [], array $redisOptions = [], \Redis $redis = null) @@ -42,7 +41,6 @@ class Connection $this->stream = $configuration['stream'] ?? '' ?: 'messages'; $this->group = $configuration['group'] ?? '' ?: 'symfony'; $this->consumer = $configuration['consumer'] ?? '' ?: 'consumer'; - $this->blockingTimeout = $redisOptions['blocking_timeout'] ?? null; } public static function fromDsn(string $dsn, array $redisOptions = [], \Redis $redis = null): self @@ -83,8 +81,7 @@ class Connection $this->group, $this->consumer, [$this->stream => $messageId], - 1, - $this->blockingTimeout + 1 ); } catch (\RedisException $e) { } @@ -142,7 +139,7 @@ class Connection } } - public function add(string $body, array $headers) + public function add(string $body, array $headers): void { $e = null; try { diff --git a/src/Symfony/Component/Messenger/Transport/RedisExt/RedisReceivedStamp.php b/src/Symfony/Component/Messenger/Transport/RedisExt/RedisReceivedStamp.php index c0b6ad37bd..2f6b5c2484 100644 --- a/src/Symfony/Component/Messenger/Transport/RedisExt/RedisReceivedStamp.php +++ b/src/Symfony/Component/Messenger/Transport/RedisExt/RedisReceivedStamp.php @@ -11,14 +11,14 @@ namespace Symfony\Component\Messenger\Transport\RedisExt; -use Symfony\Component\Messenger\Stamp\StampInterface; +use Symfony\Component\Messenger\Stamp\NonSendableStampInterface; /** * @author Alexander Schranz * * @experimental in 4.3 */ -class RedisReceivedStamp implements StampInterface +class RedisReceivedStamp implements NonSendableStampInterface { private $id; diff --git a/src/Symfony/Component/Messenger/Transport/Serialization/PhpSerializer.php b/src/Symfony/Component/Messenger/Transport/Serialization/PhpSerializer.php index a916bf795c..b65c0fcf63 100644 --- a/src/Symfony/Component/Messenger/Transport/Serialization/PhpSerializer.php +++ b/src/Symfony/Component/Messenger/Transport/Serialization/PhpSerializer.php @@ -13,6 +13,7 @@ namespace Symfony\Component\Messenger\Transport\Serialization; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Exception\MessageDecodingFailedException; +use Symfony\Component\Messenger\Stamp\NonSendableStampInterface; /** * @author Ryan Weaver @@ -40,6 +41,8 @@ class PhpSerializer implements SerializerInterface */ public function encode(Envelope $envelope): array { + $envelope = $envelope->withoutStampsOfType(NonSendableStampInterface::class); + $body = addslashes(serialize($envelope)); return [ diff --git a/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php b/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php index 371e3ce14f..798b9a5526 100644 --- a/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php +++ b/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php @@ -14,6 +14,7 @@ namespace Symfony\Component\Messenger\Transport\Serialization; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Exception\LogicException; use Symfony\Component\Messenger\Exception\MessageDecodingFailedException; +use Symfony\Component\Messenger\Stamp\NonSendableStampInterface; use Symfony\Component\Messenger\Stamp\SerializerStamp; use Symfony\Component\Messenger\Stamp\StampInterface; use Symfony\Component\Serializer\Encoder\JsonEncoder; @@ -47,7 +48,7 @@ class Serializer implements SerializerInterface public static function create(): self { if (!class_exists(SymfonySerializer::class)) { - throw new LogicException(sprintf('The default Messenger Serializer requires Symfony\'s Serializer component. Try running "composer require symfony/serializer".')); + throw new LogicException(sprintf('The "%s" class requires Symfony\'s Serializer component. Try running "composer require symfony/serializer" or use "%s" instead.', __CLASS__, PhpSerializer::class)); } $encoders = [new XmlEncoder(), new JsonEncoder()]; @@ -98,6 +99,8 @@ class Serializer implements SerializerInterface $context = $serializerStamp->getContext() + $context; } + $envelope = $envelope->withoutStampsOfType(NonSendableStampInterface::class); + $headers = ['type' => \get_class($envelope->getMessage())] + $this->encodeStamps($envelope); return [ diff --git a/src/Symfony/Component/Messenger/Transport/Serialization/SerializerInterface.php b/src/Symfony/Component/Messenger/Transport/Serialization/SerializerInterface.php index f534659f50..01a4abc16e 100644 --- a/src/Symfony/Component/Messenger/Transport/Serialization/SerializerInterface.php +++ b/src/Symfony/Component/Messenger/Transport/Serialization/SerializerInterface.php @@ -39,6 +39,9 @@ interface SerializerInterface * Encodes an envelope content (message & stamps) to a common format understandable by transports. * The encoded array should only contain scalars and arrays. * + * Stamps that implement NonSendableStampInterface should + * not be encoded. + * * The most common keys of the encoded array are: * - `body` (string) - the message body * - `headers` (string) - a key/value pair of headers diff --git a/src/Symfony/Component/Mime/MimeTypes.php b/src/Symfony/Component/Mime/MimeTypes.php index eefe1124f6..eea75fa2df 100644 --- a/src/Symfony/Component/Mime/MimeTypes.php +++ b/src/Symfony/Component/Mime/MimeTypes.php @@ -1157,6 +1157,7 @@ final class MimeTypes implements MimeTypesInterface 'audio/x-flac' => ['flac'], 'audio/x-flac+ogg' => ['oga', 'ogg'], 'audio/x-gsm' => ['gsm'], + 'audio/x-hx-aac-adts' => ['aac', 'adts', 'ass'], 'audio/x-imelody' => ['imy', 'ime'], 'audio/x-iriver-pla' => ['pla'], 'audio/x-it' => ['it'], @@ -1631,7 +1632,7 @@ final class MimeTypes implements MimeTypesInterface 'a78' => ['application/x-atari-7800-rom'], 'aa' => ['audio/vnd.audible', 'audio/vnd.audible.aax', 'audio/x-pn-audibleaudio'], 'aab' => ['application/x-authorware-bin'], - 'aac' => ['audio/aac', 'audio/x-aac'], + 'aac' => ['audio/aac', 'audio/x-aac', 'audio/x-hx-aac-adts'], 'aam' => ['application/x-authorware-map'], 'aas' => ['application/x-authorware-seg'], 'aax' => ['audio/vnd.audible', 'audio/vnd.audible.aax', 'audio/x-pn-audibleaudio'], @@ -1648,7 +1649,7 @@ final class MimeTypes implements MimeTypesInterface 'adf' => ['application/x-amiga-disk-format'], 'adp' => ['audio/adpcm'], 'ads' => ['text/x-adasrc'], - 'adts' => ['audio/aac', 'audio/x-aac'], + 'adts' => ['audio/aac', 'audio/x-aac', 'audio/x-hx-aac-adts'], 'aep' => ['application/vnd.audiograph'], 'afm' => ['application/x-font-afm', 'application/x-font-type1'], 'afp' => ['application/vnd.ibm.modcap'], @@ -1687,7 +1688,7 @@ final class MimeTypes implements MimeTypesInterface 'asm' => ['text/x-asm'], 'aso' => ['application/vnd.accpac.simply.aso'], 'asp' => ['application/x-asp'], - 'ass' => ['audio/aac', 'audio/x-aac', 'text/x-ssa'], + 'ass' => ['audio/aac', 'audio/x-aac', 'audio/x-hx-aac-adts', 'text/x-ssa'], 'asx' => ['application/x-ms-asx', 'audio/x-ms-asx', 'video/x-ms-asf', 'video/x-ms-wax', 'video/x-ms-wmx', 'video/x-ms-wvx'], 'atc' => ['application/vnd.acucorp'], 'atom' => ['application/atom+xml'], diff --git a/src/Symfony/Component/Security/Core/Authentication/Token/AbstractToken.php b/src/Symfony/Component/Security/Core/Authentication/Token/AbstractToken.php index f8f05a1ed8..b30d136821 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Token/AbstractToken.php +++ b/src/Symfony/Component/Security/Core/Authentication/Token/AbstractToken.php @@ -199,7 +199,15 @@ abstract class AbstractToken implements TokenInterface */ public function __unserialize(array $data): void { - [$this->user, $this->authenticated, $this->roles, $this->attributes, $this->roleNames] = $data; + [$this->user, $this->authenticated, $this->roles, $this->attributes] = $data; + + // migration path to 4.3+ + if (null === $this->roleNames = $data[4] ?? null) { + $this->roleNames = []; + foreach ($this->roles as $role) { + $this->roleNames[] = (string) $role; + } + } } /** diff --git a/src/Symfony/Component/Translation/DataCollectorTranslator.php b/src/Symfony/Component/Translation/DataCollectorTranslator.php index 68200a7f45..0284b77e9b 100644 --- a/src/Symfony/Component/Translation/DataCollectorTranslator.php +++ b/src/Symfony/Component/Translation/DataCollectorTranslator.php @@ -68,10 +68,10 @@ class DataCollectorTranslator implements LegacyTranslatorInterface, TranslatorIn { if ($this->translator instanceof TranslatorInterface) { $trans = $this->translator->trans($id, ['%count%' => $number] + $parameters, $domain, $locale); + } else { + $trans = $this->translator->transChoice($id, $number, $parameters, $domain, $locale); } - $trans = $this->translator->transChoice($id, $number, $parameters, $domain, $locale); - $this->collectMessage($locale, $domain, $id, $trans, ['%count%' => $number] + $parameters); return $trans;