From 3e209c3cbadd82579493aba9d2a7756f8a6bff35 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 2 Mar 2022 17:12:46 +0100 Subject: [PATCH 01/91] [HttpKernel] Fix advertizing deprecations for *TestSessionListener --- UPGRADE-5.4.md | 2 +- src/Symfony/Component/HttpKernel/CHANGELOG.md | 2 +- .../EventListener/AbstractTestSessionListener.php | 8 +++----- .../HttpKernel/EventListener/TestSessionListener.php | 9 +++------ 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/UPGRADE-5.4.md b/UPGRADE-5.4.md index 4d0a01e129216..97f221ae6f9a8 100644 --- a/UPGRADE-5.4.md +++ b/UPGRADE-5.4.md @@ -43,7 +43,7 @@ FrameworkBundle HttpKernel ---------- - * Deprecate `AbstractTestSessionListener::getSession` inject a session in the request instead + * Deprecate `AbstractTestSessionListener` and `TestSessionListener`, use `AbstractSessionListener` and `SessionListener` instead HttpFoundation -------------- diff --git a/src/Symfony/Component/HttpKernel/CHANGELOG.md b/src/Symfony/Component/HttpKernel/CHANGELOG.md index 129201e3c3733..d0dc2076c286e 100644 --- a/src/Symfony/Component/HttpKernel/CHANGELOG.md +++ b/src/Symfony/Component/HttpKernel/CHANGELOG.md @@ -5,7 +5,7 @@ CHANGELOG --- * Add the ability to enable the profiler using a request query parameter, body parameter or attribute - * Deprecate `AbstractTestSessionListener::getSession` inject a session in the request instead + * Deprecate `AbstractTestSessionListener` and `TestSessionListener`, use `AbstractSessionListener` and `SessionListener` instead * Deprecate the `fileLinkFormat` parameter of `DebugHandlersListener` * Add support for configuring log level, and status code by exception class * Allow ignoring "kernel.reset" methods that don't exist with "on_invalid" attribute diff --git a/src/Symfony/Component/HttpKernel/EventListener/AbstractTestSessionListener.php b/src/Symfony/Component/HttpKernel/EventListener/AbstractTestSessionListener.php index 157d50a199394..838c2944b4e6b 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/AbstractTestSessionListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/AbstractTestSessionListener.php @@ -19,6 +19,8 @@ use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; +trigger_deprecation('symfony/http-kernel', '5.4', '"%s" is deprecated use "%s" instead.', AbstractTestSessionListener::class, AbstractSessionListener::class); + /** * TestSessionListener. * @@ -29,7 +31,7 @@ * * @internal * - * @deprecated the TestSessionListener use the default SessionListener instead + * @deprecated since Symfony 5.4, use AbstractSessionListener instead */ abstract class AbstractTestSessionListener implements EventSubscriberInterface { @@ -39,8 +41,6 @@ abstract class AbstractTestSessionListener implements EventSubscriberInterface public function __construct(array $sessionOptions = []) { $this->sessionOptions = $sessionOptions; - - trigger_deprecation('symfony/http-kernel', '5.4', 'The %s is deprecated use the %s instead.', __CLASS__, AbstractSessionListener::class); } public function onKernelRequest(RequestEvent $event) @@ -114,8 +114,6 @@ public static function getSubscribedEvents(): array /** * Gets the session object. * - * @deprecated since Symfony 5.4, will be removed in 6.0. - * * @return SessionInterface|null */ abstract protected function getSession(); diff --git a/src/Symfony/Component/HttpKernel/EventListener/TestSessionListener.php b/src/Symfony/Component/HttpKernel/EventListener/TestSessionListener.php index c5308269c4c05..45fa312be7478 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/TestSessionListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/TestSessionListener.php @@ -14,6 +14,8 @@ use Psr\Container\ContainerInterface; use Symfony\Component\HttpFoundation\Session\SessionInterface; +trigger_deprecation('symfony/http-kernel', '5.4', '"%s" is deprecated, use "%s" instead.', TestSessionListener::class, SessionListener::class); + /** * Sets the session in the request. * @@ -21,7 +23,7 @@ * * @final * - * @deprecated the TestSessionListener use the default SessionListener instead + * @deprecated since Symfony 5.4, use SessionListener instead */ class TestSessionListener extends AbstractTestSessionListener { @@ -33,13 +35,8 @@ public function __construct(ContainerInterface $container, array $sessionOptions parent::__construct($sessionOptions); } - /** - * @deprecated since Symfony 5.4, will be removed in 6.0. - */ protected function getSession(): ?SessionInterface { - trigger_deprecation('symfony/http-kernel', '5.4', '"%s" is deprecated and will be removed in 6.0, inject a session in the request instead.', __METHOD__); - if ($this->container->has('session')) { return $this->container->get('session'); } From 309998b94d117b0c330ea2dbf09c270f01ca5917 Mon Sep 17 00:00:00 2001 From: Gabrielle Langer Date: Fri, 4 Mar 2022 18:34:47 +0100 Subject: [PATCH 02/91] [FrameworkBundle] Fix compiler passes processing a container twice when it's loaded from the debug dump --- .../FrameworkBundle/Command/BuildDebugContainerTrait.php | 4 ++++ .../Bundle/FrameworkBundle/Command/ContainerLintCommand.php | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/BuildDebugContainerTrait.php b/src/Symfony/Bundle/FrameworkBundle/Command/BuildDebugContainerTrait.php index 440c4762a95e9..785027dbc8d4e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/BuildDebugContainerTrait.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/BuildDebugContainerTrait.php @@ -53,6 +53,10 @@ protected function getContainerBuilder(KernelInterface $kernel): ContainerBuilde (new XmlFileLoader($container = new ContainerBuilder(), new FileLocator()))->load($kernel->getContainer()->getParameter('debug.container.dump')); $locatorPass = new ServiceLocatorTagPass(); $locatorPass->process($container); + + $container->getCompilerPassConfig()->setBeforeOptimizationPasses([]); + $container->getCompilerPassConfig()->setOptimizationPasses([]); + $container->getCompilerPassConfig()->setBeforeRemovingPasses([]); } return $this->containerBuilder = $container; diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerLintCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerLintCommand.php index 2df5b72559c64..337f1f4203af2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerLintCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerLintCommand.php @@ -118,6 +118,10 @@ private function getContainerBuilder(): ContainerBuilder $skippedIds[$serviceId] = true; } } + + $container->getCompilerPassConfig()->setBeforeOptimizationPasses([]); + $container->getCompilerPassConfig()->setOptimizationPasses([]); + $container->getCompilerPassConfig()->setBeforeRemovingPasses([]); } $container->setParameter('container.build_hash', 'lint_container'); From a0bce33815df3ed01280fb7ab551c57c071a39c7 Mon Sep 17 00:00:00 2001 From: Eric COURTIAL Date: Sat, 5 Mar 2022 16:39:30 +0100 Subject: [PATCH 03/91] Remove blocking test for adding support for placeholders in EmumNode in 6.1 --- .../Compiler/ValidateEnvPlaceholdersPassTest.php | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ValidateEnvPlaceholdersPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ValidateEnvPlaceholdersPassTest.php index dd88c71227c52..3258328993650 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ValidateEnvPlaceholdersPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ValidateEnvPlaceholdersPassTest.php @@ -156,19 +156,6 @@ public function testConcatenatedEnvInConfig() $this->assertSame(['scalar_node' => $expected], $container->resolveEnvPlaceholders($ext->getConfig())); } - public function testEnvIsIncompatibleWithEnumNode() - { - $this->expectException(InvalidConfigurationException::class); - $this->expectExceptionMessage('A dynamic value is not compatible with a "Symfony\Component\Config\Definition\EnumNode" node type at path "env_extension.enum_node".'); - $container = new ContainerBuilder(); - $container->registerExtension(new EnvExtension()); - $container->prependExtensionConfig('env_extension', [ - 'enum_node' => '%env(FOO)%', - ]); - - $this->doProcess($container); - } - public function testEnvIsIncompatibleWithArrayNode() { $this->expectException(InvalidConfigurationException::class); From a16383d692c098a444c5b4c20cc73d03fe4d4b38 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 5 Mar 2022 22:04:43 +0100 Subject: [PATCH 04/91] Update CHANGELOG for 4.4.39 --- CHANGELOG-4.4.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG-4.4.md b/CHANGELOG-4.4.md index af2b1a47f2ce5..29cc4c6552b90 100644 --- a/CHANGELOG-4.4.md +++ b/CHANGELOG-4.4.md @@ -7,6 +7,15 @@ in 4.4 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.4.0...v4.4.1 +* 4.4.39 (2022-03-05) + + * bug #45631 [HttpFoundation] Fix PHP 8.1 deprecation in `Response::isNotModified` (HypeMC) + * bug #45610 [HttpKernel] Guard against bad profile data (nicolas-grekas) + * bug #45532 Fix deprecations on PHP 8.2 (nicolas-grekas) + * bug #45595 [FrameworkBundle] Fix resetting container between tests (nicolas-grekas) + * bug #45585 [HttpClient] fix checking for unset property on PHP <= 7.1.4 (nicolas-grekas) + * bug #45583 [WebProfilerBundle] Fixes HTML syntax regression introduced by #44570 (xavismeh) + * 4.4.38 (2022-02-28) * bug #44570 [WebProfilerBundle] add nonces to profiler (garak) From f7b5636d15dcb7f22dfb19522dd9195d725d9d17 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 5 Mar 2022 22:04:53 +0100 Subject: [PATCH 05/91] Update CONTRIBUTORS for 4.4.39 --- CONTRIBUTORS.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index c8eb4764f6c53..e2781d32f4a30 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -78,8 +78,8 @@ The Symfony Connect username in parenthesis allows to get more information - Henrik Bjørnskov (henrikbjorn) - Antoine M (amakdessi) - Miha Vrhovnik - - Diego Saint Esteben (dii3g0) - Mathieu Piot (mpiot) + - Diego Saint Esteben (dii3g0) - Konstantin Kudryashov (everzet) - Vladimir Reznichenko (kalessil) - Bilal Amarni (bamarni) @@ -110,13 +110,13 @@ The Symfony Connect username in parenthesis allows to get more information - Luis Cordova (cordoval) - Daniel Holmes (dholmes) - Sebastiaan Stok (sstok) + - Alexandre Daubois (alexandre-daubois) - HypeMC (hypemc) - Toni Uebernickel (havvg) - Bart van den Burg (burgov) - Jordan Alliot (jalliot) - John Wards (johnwards) - Tomas Norkūnas (norkunas) - - Alexandre Daubois (alexandre-daubois) - Julien Falque (julienfalque) - Baptiste Clavié (talus) - Massimiliano Arione (garak) @@ -2723,6 +2723,7 @@ The Symfony Connect username in parenthesis allows to get more information - Cyrille Jouineau (tuxosaurus) - Vladimir Chernyshev (volch) - Wim Godden (wimg) + - Xav` (xavismeh) - Yorkie Chadwick (yorkie76) - Maxime Aknin (3m1x4m) - Geordie From c82721799f46dc7222775a56c72c2ff53d7bb03e Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 5 Mar 2022 22:04:55 +0100 Subject: [PATCH 06/91] Update VERSION for 4.4.39 --- src/Symfony/Component/HttpKernel/Kernel.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 5676a967ac8d8..e4903bfff60aa 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -76,12 +76,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private static $freshCache = []; - public const VERSION = '4.4.39-DEV'; + public const VERSION = '4.4.39'; public const VERSION_ID = 40439; public const MAJOR_VERSION = 4; public const MINOR_VERSION = 4; public const RELEASE_VERSION = 39; - public const EXTRA_VERSION = 'DEV'; + public const EXTRA_VERSION = ''; public const END_OF_MAINTENANCE = '11/2022'; public const END_OF_LIFE = '11/2023'; From c3ba272a9d7a125f55b147f88387907a3017d4bc Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 5 Mar 2022 22:14:05 +0100 Subject: [PATCH 07/91] Bump Symfony version to 4.4.40 --- src/Symfony/Component/HttpKernel/Kernel.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index e4903bfff60aa..233c80f10b55d 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -76,12 +76,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private static $freshCache = []; - public const VERSION = '4.4.39'; - public const VERSION_ID = 40439; + public const VERSION = '4.4.40-DEV'; + public const VERSION_ID = 40440; public const MAJOR_VERSION = 4; public const MINOR_VERSION = 4; - public const RELEASE_VERSION = 39; - public const EXTRA_VERSION = ''; + public const RELEASE_VERSION = 40; + public const EXTRA_VERSION = 'DEV'; public const END_OF_MAINTENANCE = '11/2022'; public const END_OF_LIFE = '11/2023'; From e073bfc63c64c097673dacb93ee4b178de901e11 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 5 Mar 2022 22:14:47 +0100 Subject: [PATCH 08/91] Update CHANGELOG for 5.4.6 --- CHANGELOG-5.4.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG-5.4.md b/CHANGELOG-5.4.md index 7658ece3a6a90..0d0bcc40bafd3 100644 --- a/CHANGELOG-5.4.md +++ b/CHANGELOG-5.4.md @@ -7,6 +7,18 @@ in 5.4 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/v5.4.0...v5.4.1 +* 5.4.6 (2022-03-05) + + * bug #45619 [redis-messenger] remove undefined array key warnings (PhilETaylor) + * bug #45637 [Cache] do not pass DBAL connections to PDO adapters (xabbuh) + * bug #45631 [HttpFoundation] Fix PHP 8.1 deprecation in `Response::isNotModified` (HypeMC) + * bug #45610 [HttpKernel] Guard against bad profile data (nicolas-grekas) + * bug #45532 Fix deprecations on PHP 8.2 (nicolas-grekas) + * bug #45595 [FrameworkBundle] Fix resetting container between tests (nicolas-grekas) + * bug #45590 [Console] Revert StringInput bc break from #45088 (bobthecow) + * bug #45585 [HttpClient] fix checking for unset property on PHP <= 7.1.4 (nicolas-grekas) + * bug #45583 [WebProfilerBundle] Fixes HTML syntax regression introduced by #44570 (xavismeh) + * 5.4.5 (2022-02-28) * bug #45351 [WebProfilerBundle] Log section minor fixes (missing "notice" filter, log priority, accessibility) (Amunak) From 11bb129b3d387d354aa0a7b2eed912c54f1b1d70 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 5 Mar 2022 22:14:51 +0100 Subject: [PATCH 09/91] Update VERSION for 5.4.6 --- src/Symfony/Component/HttpKernel/Kernel.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 528eff079af96..0c87dbd502065 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -78,12 +78,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl */ private static $freshCache = []; - public const VERSION = '5.4.6-DEV'; + public const VERSION = '5.4.6'; public const VERSION_ID = 50406; public const MAJOR_VERSION = 5; public const MINOR_VERSION = 4; public const RELEASE_VERSION = 6; - public const EXTRA_VERSION = 'DEV'; + public const EXTRA_VERSION = ''; public const END_OF_MAINTENANCE = '11/2024'; public const END_OF_LIFE = '11/2025'; From ca27974f5138120a43a874aa5f21a808ee8ce91c Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 5 Mar 2022 22:18:53 +0100 Subject: [PATCH 10/91] Bump Symfony version to 5.4.7 --- src/Symfony/Component/HttpKernel/Kernel.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 0c87dbd502065..53e4b784bbdde 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -78,12 +78,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl */ private static $freshCache = []; - public const VERSION = '5.4.6'; - public const VERSION_ID = 50406; + public const VERSION = '5.4.7-DEV'; + public const VERSION_ID = 50407; public const MAJOR_VERSION = 5; public const MINOR_VERSION = 4; - public const RELEASE_VERSION = 6; - public const EXTRA_VERSION = ''; + public const RELEASE_VERSION = 7; + public const EXTRA_VERSION = 'DEV'; public const END_OF_MAINTENANCE = '11/2024'; public const END_OF_LIFE = '11/2025'; From a14ccba8d21f22a12928c5de4b3f3b1bf202baf6 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 5 Mar 2022 22:24:56 +0100 Subject: [PATCH 11/91] Bump Symfony version to 6.0.7 --- src/Symfony/Component/HttpKernel/Kernel.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 7fbb334c8fde2..cee5a4d5e74ff 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -78,12 +78,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl */ private static array $freshCache = []; - public const VERSION = '6.0.6'; - public const VERSION_ID = 60006; + public const VERSION = '6.0.7-DEV'; + public const VERSION_ID = 60007; public const MAJOR_VERSION = 6; public const MINOR_VERSION = 0; - public const RELEASE_VERSION = 6; - public const EXTRA_VERSION = ''; + public const RELEASE_VERSION = 7; + public const EXTRA_VERSION = 'DEV'; public const END_OF_MAINTENANCE = '01/2023'; public const END_OF_LIFE = '01/2023'; From a3730d7bdf729b8dc1b223fb42632b070c535a18 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sun, 6 Mar 2022 12:25:32 +0100 Subject: [PATCH 12/91] Fix tests --- src/Symfony/Bridge/PhpUnit/Tests/expectdeprecationfail.phpt | 2 +- src/Symfony/Bridge/PhpUnit/Tests/expectrisky.phpt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Bridge/PhpUnit/Tests/expectdeprecationfail.phpt b/src/Symfony/Bridge/PhpUnit/Tests/expectdeprecationfail.phpt index 9f9bf8c17508e..f968cd188a0a7 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/expectdeprecationfail.phpt +++ b/src/Symfony/Bridge/PhpUnit/Tests/expectdeprecationfail.phpt @@ -6,7 +6,7 @@ $test = realpath(__DIR__.'/FailTests/ExpectDeprecationTraitTestFail.php'); passthru('php '.getenv('SYMFONY_SIMPLE_PHPUNIT_BIN_DIR').'/simple-phpunit.php --colors=never '.$test); ?> --EXPECTF-- -PHPUnit %s by Sebastian Bergmann and contributors. +PHPUnit %s %ATesting Symfony\Bridge\PhpUnit\Tests\FailTests\ExpectDeprecationTraitTestFail FF 2 / 2 (100%) diff --git a/src/Symfony/Bridge/PhpUnit/Tests/expectrisky.phpt b/src/Symfony/Bridge/PhpUnit/Tests/expectrisky.phpt index 608c56488979f..91e0830553950 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/expectrisky.phpt +++ b/src/Symfony/Bridge/PhpUnit/Tests/expectrisky.phpt @@ -8,7 +8,7 @@ $test = realpath(__DIR__.'/FailTests/NoAssertionsTestRisky.php'); passthru('php '.getenv('SYMFONY_SIMPLE_PHPUNIT_BIN_DIR').'/simple-phpunit.php --fail-on-risky --colors=never '.$test); ?> --EXPECTF-- -PHPUnit %s by Sebastian Bergmann and contributors. +PHPUnit %s %ATesting Symfony\Bridge\PhpUnit\Tests\FailTests\NoAssertionsTestRisky R. 2 / 2 (100%) From c4d5b048540b8756e791344a2ac9146dd93f5144 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sun, 6 Mar 2022 20:58:04 +0100 Subject: [PATCH 13/91] reflect Cache component version dependent default value in test --- .../Tests/DependencyInjection/ConfigurationTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index 0b6ccbf3afab3..18556bfc38f66 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Configuration; use Symfony\Bundle\FullStack; +use Symfony\Component\Cache\Adapter\DoctrineAdapter; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\Config\Definition\Processor; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -505,7 +506,7 @@ protected static function getBundleDefaultConfig() 'default_redis_provider' => 'redis://localhost', 'default_memcached_provider' => 'memcached://localhost', 'default_doctrine_dbal_provider' => 'database_connection', - 'default_pdo_provider' => ContainerBuilder::willBeAvailable('doctrine/dbal', Connection::class, ['symfony/framework-bundle']) ? 'database_connection' : null, + 'default_pdo_provider' => ContainerBuilder::willBeAvailable('doctrine/dbal', Connection::class, ['symfony/framework-bundle']) && class_exists(DoctrineAdapter::class) ? 'database_connection' : null, 'prefix_seed' => '_%kernel.project_dir%.%kernel.container_class%', ], 'workflows' => [ From 448cc3a60bb894be8cf03c8d0f3ff38c529c85aa Mon Sep 17 00:00:00 2001 From: Kevin Bond Date: Sun, 27 Feb 2022 09:06:52 -0500 Subject: [PATCH 14/91] [HttpKernel] fix using Target with controller args --- .../RegisterControllerArgumentLocatorsPass.php | 2 +- .../RegisterControllerArgumentLocatorsPassTest.php | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php index b1c24d5b3962b..8fd1f553e0030 100644 --- a/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php @@ -192,7 +192,7 @@ public function process(ContainerBuilder $container) $args[$p->name] = new Reference($erroredId, ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE); } else { $target = ltrim($target, '\\'); - $args[$p->name] = $type ? new TypedReference($target, $type, $invalidBehavior, $p->name) : new Reference($target, $invalidBehavior); + $args[$p->name] = $type ? new TypedReference($target, $type, $invalidBehavior, Target::parseName($p)) : new Reference($target, $invalidBehavior); } } // register the maps as a per-method service-locators diff --git a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php index b3a750e953398..1e3d25d440f5c 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php @@ -428,6 +428,9 @@ public function testBindWithTarget() $container = new ContainerBuilder(); $resolver = $container->register('argument_resolver.service')->addArgument([]); + $container->register(ControllerDummy::class, 'bar'); + $container->register(ControllerDummy::class.' $imageStorage', 'baz'); + $container->register('foo', WithTarget::class) ->setBindings(['string $someApiKey' => new Reference('the_api_key')]) ->addTag('controller.service_arguments'); @@ -437,7 +440,11 @@ public function testBindWithTarget() $locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0); $locator = $container->getDefinition((string) $locator['foo::fooAction']->getValues()[0]); - $expected = ['apiKey' => new ServiceClosureArgument(new Reference('the_api_key'))]; + $expected = [ + 'apiKey' => new ServiceClosureArgument(new Reference('the_api_key')), + 'service1' => new ServiceClosureArgument(new TypedReference(ControllerDummy::class, ControllerDummy::class, ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE, 'imageStorage')), + 'service2' => new ServiceClosureArgument(new TypedReference(ControllerDummy::class, ControllerDummy::class, ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE, 'service2')), + ]; $this->assertEquals($expected, $locator->getArgument(0)); } } @@ -513,7 +520,10 @@ class WithTarget { public function fooAction( #[Target('some.api.key')] - string $apiKey + string $apiKey, + #[Target('image.storage')] + ControllerDummy $service1, + ControllerDummy $service2 ) { } } From 698cc970fd3dcf5b92633c527d6b9f1d36e53a3d Mon Sep 17 00:00:00 2001 From: RTUnreal <22859658+RTUnreal@users.noreply.github.com> Date: Mon, 7 Mar 2022 14:29:34 +0100 Subject: [PATCH 15/91] Center icons vertically in trace list Make icons centered in trace list and not on the bottom trace-line-header div --- .../Component/ErrorHandler/Resources/assets/css/exception.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/ErrorHandler/Resources/assets/css/exception.css b/src/Symfony/Component/ErrorHandler/Resources/assets/css/exception.css index 952c66d2fc936..3f629a4104fef 100644 --- a/src/Symfony/Component/ErrorHandler/Resources/assets/css/exception.css +++ b/src/Symfony/Component/ErrorHandler/Resources/assets/css/exception.css @@ -184,7 +184,7 @@ header .container { display: flex; justify-content: space-between; } .trace-line + .trace-line { border-top: var(--border); } .trace-line:hover { background: var(--base-1); } .trace-line a { color: var(--base-6); } -.trace-line .icon { opacity: .4; position: absolute; left: 10px; top: 11px; } +.trace-line .icon { opacity: .4; position: absolute; left: 10px; } .trace-line .icon svg { height: 16px; width: 16px; } .trace-line-header { padding-left: 36px; padding-right: 10px; } From 0d99a547ad107f43ac9c84687420bf933e062e38 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Mon, 7 Mar 2022 19:38:28 +0100 Subject: [PATCH 16/91] [Asset] Update the error message when assets are not built --- .../Asset/VersionStrategy/JsonManifestVersionStrategy.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Asset/VersionStrategy/JsonManifestVersionStrategy.php b/src/Symfony/Component/Asset/VersionStrategy/JsonManifestVersionStrategy.php index 72688a4926444..998c636e6134a 100644 --- a/src/Symfony/Component/Asset/VersionStrategy/JsonManifestVersionStrategy.php +++ b/src/Symfony/Component/Asset/VersionStrategy/JsonManifestVersionStrategy.php @@ -54,7 +54,7 @@ private function getManifestPath(string $path): ?string { if (null === $this->manifestData) { if (!file_exists($this->manifestPath)) { - throw new \RuntimeException(sprintf('Asset manifest file "%s" does not exist.', $this->manifestPath)); + throw new \RuntimeException(sprintf('Asset manifest file "%s" does not exist. Did you forget to build the assets with npm or yarn?', $this->manifestPath)); } $this->manifestData = json_decode(file_get_contents($this->manifestPath), true); From 608b1ddf6c74a4a300a8c06e8a34af78a2bdb019 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 8 Mar 2022 12:26:23 +0100 Subject: [PATCH 17/91] [FrameworkBundle] Ensure container is reset between tests --- src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php index a65a17b4c53e2..1ec6548be2738 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpKernel\KernelInterface; +use Symfony\Contracts\Service\ResetInterface; /** * KernelTestCase is the base class for tests needing a Kernel. @@ -131,8 +132,13 @@ protected static function ensureKernelShutdown() { if (null !== static::$kernel) { static::$kernel->boot(); + $container = static::$kernel->getContainer(); static::$kernel->shutdown(); static::$booted = false; + + if ($container instanceof ResetInterface) { + $container->reset(); + } } static::$container = null; From a39f4f31e2dd1b8c26c5e1785263817e5f234ae3 Mon Sep 17 00:00:00 2001 From: Hugo Alliaume Date: Tue, 8 Mar 2022 16:09:30 +0100 Subject: [PATCH 18/91] [Runtime] Fix passing $debug parameter to `ErrorHandler` --- src/Symfony/Component/Runtime/Internal/SymfonyErrorHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Runtime/Internal/SymfonyErrorHandler.php b/src/Symfony/Component/Runtime/Internal/SymfonyErrorHandler.php index bf6e1cfe331a1..40c125a91e333 100644 --- a/src/Symfony/Component/Runtime/Internal/SymfonyErrorHandler.php +++ b/src/Symfony/Component/Runtime/Internal/SymfonyErrorHandler.php @@ -29,7 +29,7 @@ public static function register(bool $debug): void if (class_exists(ErrorHandler::class)) { DebugClassLoader::enable(); restore_error_handler(); - ErrorHandler::register(new ErrorHandler(new BufferingLogger(), true)); + ErrorHandler::register(new ErrorHandler(new BufferingLogger(), $debug)); } } } From 37ca066c6fb3aac241ccfb12b2531675798528d7 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 8 Mar 2022 09:52:50 +0100 Subject: [PATCH 19/91] Stand with Ukraine --- src/Symfony/Bundle/FrameworkBundle/Console/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php index dddde43dda4a1..02c38c2a2c1bc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php @@ -148,7 +148,7 @@ public function all($namespace = null) */ public function getLongVersion() { - return parent::getLongVersion().sprintf(' (env: %s, debug: %s)', $this->kernel->getEnvironment(), $this->kernel->isDebug() ? 'true' : 'false'); + return parent::getLongVersion().sprintf(' (env: %s, debug: %s) #StandWithUkraine https://sf.to/ukraine', $this->kernel->getEnvironment(), $this->kernel->isDebug() ? 'true' : 'false'); } public function add(Command $command) From 0558ea37191d01108f411f50d38a7bc6a9356291 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 8 Mar 2022 16:15:48 +0100 Subject: [PATCH 20/91] Fix colors for 4.4 --- src/Symfony/Bundle/FrameworkBundle/Console/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php index 02c38c2a2c1bc..20fb6e05f73de 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php @@ -148,7 +148,7 @@ public function all($namespace = null) */ public function getLongVersion() { - return parent::getLongVersion().sprintf(' (env: %s, debug: %s) #StandWithUkraine https://sf.to/ukraine', $this->kernel->getEnvironment(), $this->kernel->isDebug() ? 'true' : 'false'); + return parent::getLongVersion().sprintf(' (env: %s, debug: %s) #StandWithUkraine https://sf.to/ukraine', $this->kernel->getEnvironment(), $this->kernel->isDebug() ? 'true' : 'false'); } public function add(Command $command) From b30675a253e9cdda17842d0d3b530024f576b97a Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 8 Mar 2022 16:49:36 +0100 Subject: [PATCH 21/91] Revert "Fix colors for 4.4" This reverts commit 0558ea37191d01108f411f50d38a7bc6a9356291. --- src/Symfony/Bundle/FrameworkBundle/Console/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php index 0ce26c3cab701..55510b9d594e4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php @@ -147,7 +147,7 @@ public function all(string $namespace = null) */ public function getLongVersion() { - return parent::getLongVersion().sprintf(' (env: %s, debug: %s) #StandWithUkraine https://sf.to/ukraine', $this->kernel->getEnvironment(), $this->kernel->isDebug() ? 'true' : 'false'); + return parent::getLongVersion().sprintf(' (env: %s, debug: %s) #StandWithUkraine https://sf.to/ukraine', $this->kernel->getEnvironment(), $this->kernel->isDebug() ? 'true' : 'false'); } public function add(Command $command) From 947152b2ff849df9d7510654ae5b99beb3f9685e Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Tue, 8 Mar 2022 18:16:11 +0100 Subject: [PATCH 22/91] [Process] Don't return executable directories in PhpExecutableFinder --- .../Component/Process/PhpExecutableFinder.php | 10 +++++-- .../Process/Tests/PhpExecutableFinderTest.php | 30 +++++++++++++++---- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/Symfony/Component/Process/PhpExecutableFinder.php b/src/Symfony/Component/Process/PhpExecutableFinder.php index 3d5eabd997cbe..92e0262ad7c95 100644 --- a/src/Symfony/Component/Process/PhpExecutableFinder.php +++ b/src/Symfony/Component/Process/PhpExecutableFinder.php @@ -47,6 +47,10 @@ public function find($includeArgs = true) } } + if (@is_dir($php)) { + return false; + } + return $php; } @@ -59,7 +63,7 @@ public function find($includeArgs = true) } if ($php = getenv('PHP_PATH')) { - if (!@is_executable($php)) { + if (!@is_executable($php) || @is_dir($php)) { return false; } @@ -67,12 +71,12 @@ public function find($includeArgs = true) } if ($php = getenv('PHP_PEAR_PHP_BIN')) { - if (@is_executable($php)) { + if (@is_executable($php) && !@is_dir($php)) { return $php; } } - if (@is_executable($php = \PHP_BINDIR.('\\' === \DIRECTORY_SEPARATOR ? '\\php.exe' : '/php'))) { + if (@is_executable($php = \PHP_BINDIR.('\\' === \DIRECTORY_SEPARATOR ? '\\php.exe' : '/php')) && !@is_dir($php)) { return $php; } diff --git a/src/Symfony/Component/Process/Tests/PhpExecutableFinderTest.php b/src/Symfony/Component/Process/Tests/PhpExecutableFinderTest.php index cf3ffb55efb78..0ef4cf092b3ba 100644 --- a/src/Symfony/Component/Process/Tests/PhpExecutableFinderTest.php +++ b/src/Symfony/Component/Process/Tests/PhpExecutableFinderTest.php @@ -50,12 +50,32 @@ public function testFindArguments() public function testNotExitsBinaryFile() { $f = new PhpExecutableFinder(); - $phpBinaryEnv = \PHP_BINARY; - putenv('PHP_BINARY=/usr/local/php/bin/php-invalid'); - $this->assertFalse($f->find(), '::find() returns false because of not exist file'); - $this->assertFalse($f->find(false), '::find(false) returns false because of not exist file'); + $originalPhpBinary = getenv('PHP_BINARY'); - putenv('PHP_BINARY='.$phpBinaryEnv); + try { + putenv('PHP_BINARY=/usr/local/php/bin/php-invalid'); + + $this->assertFalse($f->find(), '::find() returns false because of not exist file'); + $this->assertFalse($f->find(false), '::find(false) returns false because of not exist file'); + } finally { + putenv('PHP_BINARY='.$originalPhpBinary); + } + } + + public function testFindWithExecutableDirectory() + { + $originalPhpBinary = getenv('PHP_BINARY'); + + try { + $executableDirectoryPath = sys_get_temp_dir().'/PhpExecutableFinderTest_testFindWithExecutableDirectory'; + @mkdir($executableDirectoryPath); + $this->assertTrue(is_executable($executableDirectoryPath)); + putenv('PHP_BINARY='.$executableDirectoryPath); + + $this->assertFalse((new PhpExecutableFinder())->find()); + } finally { + putenv('PHP_BINARY='.$originalPhpBinary); + } } } From 9e5305e68aaf022ba24a7da10318716335205a46 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 8 Mar 2022 19:37:06 +0100 Subject: [PATCH 23/91] [HttpClient] Fix reading proxy settings from dotenv when curl is used --- .../Component/HttpClient/CurlHttpClient.php | 15 +++++++++++++-- .../HttpClient/Test/HttpClientTestCase.php | 10 ++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpClient/CurlHttpClient.php b/src/Symfony/Component/HttpClient/CurlHttpClient.php index e77eb86c4c452..660ba61ee9045 100644 --- a/src/Symfony/Component/HttpClient/CurlHttpClient.php +++ b/src/Symfony/Component/HttpClient/CurlHttpClient.php @@ -92,6 +92,10 @@ public function request(string $method, string $url, array $options = []): Respo $scheme = $url['scheme']; $authority = $url['authority']; $host = parse_url($authority, \PHP_URL_HOST); + $proxy = $options['proxy'] + ?? ('https:' === $url['scheme'] ? $_SERVER['https_proxy'] ?? $_SERVER['HTTPS_PROXY'] ?? null : null) + // Ignore HTTP_PROXY except on the CLI to work around httpoxy set of vulnerabilities + ?? $_SERVER['http_proxy'] ?? (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) ? $_SERVER['HTTP_PROXY'] ?? null : null) ?? $_SERVER['all_proxy'] ?? $_SERVER['ALL_PROXY'] ?? null; $url = implode('', $url); if (!isset($options['normalized_headers']['user-agent'])) { @@ -107,7 +111,7 @@ public function request(string $method, string $url, array $options = []): Respo \CURLOPT_MAXREDIRS => 0 < $options['max_redirects'] ? $options['max_redirects'] : 0, \CURLOPT_COOKIEFILE => '', // Keep track of cookies during redirects \CURLOPT_TIMEOUT => 0, - \CURLOPT_PROXY => $options['proxy'], + \CURLOPT_PROXY => $proxy, \CURLOPT_NOPROXY => $options['no_proxy'] ?? $_SERVER['no_proxy'] ?? $_SERVER['NO_PROXY'] ?? '', \CURLOPT_SSL_VERIFYPEER => $options['verify_peer'], \CURLOPT_SSL_VERIFYHOST => $options['verify_host'] ? 2 : 0, @@ -404,8 +408,15 @@ private static function createRedirectResolver(array $options, string $host): \C } $url = self::parseUrl(curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL)); + $url = self::resolveUrl($location, $url); - return implode('', self::resolveUrl($location, $url)); + curl_setopt($ch, \CURLOPT_PROXY, $options['proxy'] + ?? ('https:' === $url['scheme'] ? $_SERVER['https_proxy'] ?? $_SERVER['HTTPS_PROXY'] ?? null : null) + // Ignore HTTP_PROXY except on the CLI to work around httpoxy set of vulnerabilities + ?? $_SERVER['http_proxy'] ?? (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) ? $_SERVER['HTTP_PROXY'] ?? null : null) ?? $_SERVER['all_proxy'] ?? $_SERVER['ALL_PROXY'] ?? null + ); + + return implode('', $url); }; } } diff --git a/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php b/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php index 7ebf055d75701..ddc5324492c86 100644 --- a/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php +++ b/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php @@ -929,6 +929,16 @@ public function testProxy() $body = $response->toArray(); $this->assertSame('Basic Zm9vOmI9YXI=', $body['HTTP_PROXY_AUTHORIZATION']); + + $_SERVER['http_proxy'] = 'http://localhost:8057'; + try { + $response = $client->request('GET', 'http://localhost:8057/'); + $body = $response->toArray(); + $this->assertSame('localhost:8057', $body['HTTP_HOST']); + $this->assertMatchesRegularExpression('#^http://(localhost|127\.0\.0\.1):8057/$#', $body['REQUEST_URI']); + } finally { + unset($_SERVER['http_proxy']); + } } public function testNoProxy() From 4b8cd5205eb40eb063c781cb8ffd7abbd28514d0 Mon Sep 17 00:00:00 2001 From: Kevin Bond Date: Tue, 8 Mar 2022 13:07:55 -0500 Subject: [PATCH 24/91] [DI] fix `ServiceSubscriberTrait` bug where parent has `__call()` --- .../Service/ServiceSubscriberTrait.php | 4 +- .../Service/ServiceSubscriberTraitTest.php | 39 +++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Contracts/Service/ServiceSubscriberTrait.php b/src/Symfony/Contracts/Service/ServiceSubscriberTrait.php index 0c085303748d9..020887b3f5736 100644 --- a/src/Symfony/Contracts/Service/ServiceSubscriberTrait.php +++ b/src/Symfony/Contracts/Service/ServiceSubscriberTrait.php @@ -35,7 +35,7 @@ public static function getSubscribedServices(): array return $services; } - $services = \is_callable(['parent', __FUNCTION__]) ? parent::getSubscribedServices() : []; + $services = method_exists(get_parent_class(self::class) ?: '', __FUNCTION__) ? parent::getSubscribedServices() : []; foreach ((new \ReflectionClass(self::class))->getMethods() as $method) { if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { @@ -69,7 +69,7 @@ public function setContainer(ContainerInterface $container) { $this->container = $container; - if (\is_callable(['parent', __FUNCTION__])) { + if (method_exists(get_parent_class(self::class) ?: '', __FUNCTION__)) { return parent::setContainer($container); } diff --git a/src/Symfony/Contracts/Tests/Service/ServiceSubscriberTraitTest.php b/src/Symfony/Contracts/Tests/Service/ServiceSubscriberTraitTest.php index 17fd108dbd08c..1d95824baf1e1 100644 --- a/src/Symfony/Contracts/Tests/Service/ServiceSubscriberTraitTest.php +++ b/src/Symfony/Contracts/Tests/Service/ServiceSubscriberTraitTest.php @@ -36,6 +36,32 @@ public function testSetContainerIsCalledOnParent() $this->assertSame($container, (new TestService())->setContainer($container)); } + public function testParentNotCalledIfHasMagicCall() + { + $container = new class([]) implements ContainerInterface { + use ServiceLocatorTrait; + }; + $service = new class() extends ParentWithMagicCall { + use ServiceSubscriberTrait; + }; + + $this->assertNull($service->setContainer($container)); + $this->assertSame([], $service::getSubscribedServices()); + } + + public function testParentNotCalledIfNoParent() + { + $container = new class([]) implements ContainerInterface { + use ServiceLocatorTrait; + }; + $service = new class() { + use ServiceSubscriberTrait; + }; + + $this->assertNull($service->setContainer($container)); + $this->assertSame([], $service::getSubscribedServices()); + } + /** * @requires PHP 8 */ @@ -77,3 +103,16 @@ public function aChildService(): Service3 { } } + +class ParentWithMagicCall +{ + public function __call($method, $args) + { + throw new \BadMethodCallException('Should not be called.'); + } + + public static function __callStatic($method, $args) + { + throw new \BadMethodCallException('Should not be called.'); + } +} From 978d1a041a77f8593d410fda6f5f939dcf2afadf Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Wed, 9 Mar 2022 15:33:52 +0100 Subject: [PATCH 25/91] [Serializer] Fix nested deserialization_path computation when there is no metadata for the attribute --- .../Normalizer/AbstractObjectNormalizer.php | 4 +- .../Serializer/Tests/Fixtures/Php74Full.php | 7 ++ .../Serializer/Tests/SerializerTest.php | 73 ++++++++++++++----- 3 files changed, 65 insertions(+), 19 deletions(-) diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index 885e554b4593a..73446002a9ea2 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -239,12 +239,12 @@ private function getAttributeNormalizationContext(object $object, string $attrib */ private function getAttributeDenormalizationContext(string $class, string $attribute, array $context): array { + $context['deserialization_path'] = ($context['deserialization_path'] ?? false) ? $context['deserialization_path'].'.'.$attribute : $attribute; + if (null === $metadata = $this->getAttributeMetadata($class, $attribute)) { return $context; } - $context['deserialization_path'] = ($context['deserialization_path'] ?? false) ? $context['deserialization_path'].'.'.$attribute : $attribute; - return array_merge($context, $metadata->getDenormalizationContextForGroups($this->getGroups($context))); } diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/Php74Full.php b/src/Symfony/Component/Serializer/Tests/Fixtures/Php74Full.php index 4f3186c30e94b..8b53906c405dc 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/Php74Full.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/Php74Full.php @@ -29,6 +29,8 @@ final class Php74Full public array $collection; public Php74FullWithConstructor $php74FullWithConstructor; public DummyMessageInterface $dummyMessage; + /** @var TestFoo[] $nestedArray */ + public TestFoo $nestedObject; } @@ -38,3 +40,8 @@ public function __construct($constructorArgument) { } } + +final class TestFoo +{ + public int $int; +} diff --git a/src/Symfony/Component/Serializer/Tests/SerializerTest.php b/src/Symfony/Component/Serializer/Tests/SerializerTest.php index 5fc511dc8a715..4fb07fde2048e 100644 --- a/src/Symfony/Component/Serializer/Tests/SerializerTest.php +++ b/src/Symfony/Component/Serializer/Tests/SerializerTest.php @@ -739,8 +739,12 @@ public function testDeserializeAndUnwrap() ); } - /** @requires PHP 7.4 */ - public function testCollectDenormalizationErrors() + /** + * @dataProvider provideCollectDenormalizationErrors + * + * @requires PHP 7.4 + */ + public function testCollectDenormalizationErrors(?ClassMetadataFactory $classMetadataFactory) { $json = ' { @@ -764,10 +768,12 @@ public function testCollectDenormalizationErrors() ], "php74FullWithConstructor": {}, "dummyMessage": { + }, + "nestedObject": { + "int": "string" } }'; - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); $serializer = new Serializer( @@ -777,7 +783,7 @@ public function testCollectDenormalizationErrors() new DateTimeZoneNormalizer(), new DataUriNormalizer(), new UidNormalizer(), - new ObjectNormalizer($classMetadataFactory, null, null, $extractor, new ClassDiscriminatorFromClassMetadata($classMetadataFactory)), + new ObjectNormalizer($classMetadataFactory, null, null, $extractor, $classMetadataFactory ? new ClassDiscriminatorFromClassMetadata($classMetadataFactory) : null), ], ['json' => new JsonEncoder()] ); @@ -913,22 +919,45 @@ public function testCollectDenormalizationErrors() 'useMessageForUser' => true, 'message' => 'Failed to create object because the object miss the "constructorArgument" property.', ], + $classMetadataFactory ? + [ + 'currentType' => 'null', + 'expectedTypes' => [ + 'string', + ], + 'path' => 'dummyMessage.type', + 'useMessageForUser' => false, + 'message' => 'Type property "type" not found for the abstract object "Symfony\Component\Serializer\Tests\Fixtures\DummyMessageInterface".', + ] : + [ + 'currentType' => 'array', + 'expectedTypes' => [ + DummyMessageInterface::class, + ], + 'path' => 'dummyMessage', + 'useMessageForUser' => false, + 'message' => 'The type of the "dummyMessage" attribute for class "Symfony\Component\Serializer\Tests\Fixtures\Php74Full" must be one of "Symfony\Component\Serializer\Tests\Fixtures\DummyMessageInterface" ("array" given).', + ], [ - 'currentType' => 'null', + 'currentType' => 'string', 'expectedTypes' => [ - 'string', + 'int', ], - 'path' => 'dummyMessage.type', - 'useMessageForUser' => false, - 'message' => 'Type property "type" not found for the abstract object "Symfony\Component\Serializer\Tests\Fixtures\DummyMessageInterface".', + 'path' => 'nestedObject[int]', + 'useMessageForUser' => true, + 'message' => 'The type of the key "int" must be "int" ("string" given).', ], ]; $this->assertSame($expected, $exceptionsAsArray); } - /** @requires PHP 7.4 */ - public function testCollectDenormalizationErrors2() + /** + * @dataProvider provideCollectDenormalizationErrors + * + * @requires PHP 7.4 + */ + public function testCollectDenormalizationErrors2(?ClassMetadataFactory $classMetadataFactory) { $json = ' [ @@ -940,13 +969,12 @@ public function testCollectDenormalizationErrors2() } ]'; - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); $serializer = new Serializer( [ new ArrayDenormalizer(), - new ObjectNormalizer($classMetadataFactory, null, null, $extractor, new ClassDiscriminatorFromClassMetadata($classMetadataFactory)), + new ObjectNormalizer($classMetadataFactory, null, null, $extractor, $classMetadataFactory ? new ClassDiscriminatorFromClassMetadata($classMetadataFactory) : null), ], ['json' => new JsonEncoder()] ); @@ -999,17 +1027,20 @@ public function testCollectDenormalizationErrors2() $this->assertSame($expected, $exceptionsAsArray); } - /** @requires PHP 8.0 */ - public function testCollectDenormalizationErrorsWithConstructor() + /** + * @dataProvider provideCollectDenormalizationErrors + * + * @requires PHP 8.0 + */ + public function testCollectDenormalizationErrorsWithConstructor(?ClassMetadataFactory $classMetadataFactory) { $json = '{"bool": "bool"}'; - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); $serializer = new Serializer( [ - new ObjectNormalizer($classMetadataFactory, null, null, $extractor, new ClassDiscriminatorFromClassMetadata($classMetadataFactory)), + new ObjectNormalizer($classMetadataFactory, null, null, $extractor, $classMetadataFactory ? new ClassDiscriminatorFromClassMetadata($classMetadataFactory) : null), ], ['json' => new JsonEncoder()] ); @@ -1050,6 +1081,14 @@ public function testCollectDenormalizationErrorsWithConstructor() $this->assertSame($expected, $exceptionsAsArray); } + + public function provideCollectDenormalizationErrors() + { + return [ + [null], + [new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()))], + ]; + } } class Model From d892a51e44436d9f6587b83dcad7c7ba0cf6a1ee Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Thu, 10 Mar 2022 10:04:57 +0100 Subject: [PATCH 26/91] Fix return value of `NullToken::getUser()` --- .../Component/Security/Core/Authentication/Token/NullToken.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Security/Core/Authentication/Token/NullToken.php b/src/Symfony/Component/Security/Core/Authentication/Token/NullToken.php index f6a36561c19b3..1b30d5a7ccda6 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Token/NullToken.php +++ b/src/Symfony/Component/Security/Core/Authentication/Token/NullToken.php @@ -33,7 +33,7 @@ public function getCredentials() public function getUser() { - return ''; + return null; } public function setUser($user) From 542c2fbc2ee8857ea6c64e3db6de3540851006c1 Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Thu, 10 Mar 2022 12:22:15 +0100 Subject: [PATCH 27/91] Fix the usage of the Valid constraints in array-based forms --- .../Validator/Constraints/FormValidator.php | 2 +- .../FormValidatorFunctionalTest.php | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php index b0deef7f04e50..fc50af4d01d94 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php +++ b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php @@ -110,7 +110,7 @@ public function validate($form, Constraint $formConstraint) foreach ($constraints as $constraint) { // For the "Valid" constraint, validate the data in all groups if ($constraint instanceof Valid) { - if (\is_object($data)) { + if (\is_object($data) || \is_array($data)) { $validator->atPath('data')->validate($data, $constraint, $groups); } diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorFunctionalTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorFunctionalTest.php index e73947ada3081..35712707b1aca 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorFunctionalTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorFunctionalTest.php @@ -15,6 +15,7 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\CallbackTransformer; use Symfony\Component\Form\Exception\TransformationFailedException; +use Symfony\Component\Form\Extension\Core\Type\CollectionType; use Symfony\Component\Form\Extension\Core\Type\DateType; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\Core\Type\IntegerType; @@ -328,6 +329,35 @@ public function testCascadeValidationToChildFormsWithTwoValidConstraints2() $this->assertSame('children[author].data.email', $violations[1]->getPropertyPath()); } + public function testCascadeValidationToArrayChildForm() + { + $form = $this->formFactory->create(FormType::class, null, [ + 'data_class' => Review::class, + ]) + ->add('title') + ->add('customers', CollectionType::class, [ + 'mapped' => false, + 'entry_type' => CustomerType::class, + 'allow_add' => true, + 'constraints' => [new Valid()], + ]); + + $form->submit([ + 'title' => 'Sample Title', + 'customers' => [ + ['email' => null], + ], + ]); + + $violations = $this->validator->validate($form); + + $this->assertCount(2, $violations); + $this->assertSame('This value should not be blank.', $violations[0]->getMessage()); + $this->assertSame('data.rating', $violations[0]->getPropertyPath()); + $this->assertSame('This value should not be blank.', $violations[1]->getMessage()); + $this->assertSame('children[customers].data[0].email', $violations[1]->getPropertyPath()); + } + public function testCascadeValidationToChildFormsUsingPropertyPathsValidatedInSequence() { $form = $this->formFactory->create(FormType::class, null, [ From b370b322909964ca7195b07d5f7a9a40539f15b8 Mon Sep 17 00:00:00 2001 From: jmsche Date: Fri, 11 Mar 2022 15:23:30 +0100 Subject: [PATCH 28/91] Remove extra space in NotificationEmail --- src/Symfony/Bridge/Twig/Mime/NotificationEmail.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php b/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php index e6b28c4db43d2..1a58aa5e5e5bc 100644 --- a/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php +++ b/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php @@ -43,7 +43,7 @@ public function __construct(Headers $headers = null, AbstractPart $body = null) { $missingPackages = []; if (!class_exists(CssInlinerExtension::class)) { - $missingPackages['twig/cssinliner-extra'] = ' CSS Inliner'; + $missingPackages['twig/cssinliner-extra'] = 'CSS Inliner'; } if (!class_exists(InkyExtension::class)) { From 1468c5b0c0929683dc74b7a55ce8a6ac8d720bd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Ram=C5=A1ak?= Date: Fri, 11 Mar 2022 17:08:05 +0100 Subject: [PATCH 29/91] typehint of DkimOptions algorithm wrong --- src/Symfony/Component/Mime/Crypto/DkimOptions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Mime/Crypto/DkimOptions.php b/src/Symfony/Component/Mime/Crypto/DkimOptions.php index 4c51d661585c7..171bb2583b65f 100644 --- a/src/Symfony/Component/Mime/Crypto/DkimOptions.php +++ b/src/Symfony/Component/Mime/Crypto/DkimOptions.php @@ -28,7 +28,7 @@ public function toArray(): array /** * @return $this */ - public function algorithm(int $algo): self + public function algorithm(string $algo): self { $this->options['algorithm'] = $algo; From aa1e12cbf6894f1660b855661e9ec8ff1b5dd537 Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Sat, 26 Feb 2022 12:35:08 +0100 Subject: [PATCH 30/91] [symfony/mailjet-mailer] Fix invalid mailjet error managment --- .../Transport/MailjetApiTransportTest.php | 181 ++++++++++++++++++ .../Mailjet/Transport/MailjetApiTransport.php | 6 +- 2 files changed, 185 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Transport/MailjetApiTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Transport/MailjetApiTransportTest.php index c46515ef36772..64769031a8d69 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Transport/MailjetApiTransportTest.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailjet/Tests/Transport/MailjetApiTransportTest.php @@ -3,8 +3,12 @@ namespace Symfony\Component\Mailer\Bridge\Mailjet\Tests\Transport; use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\Response\MockResponse; use Symfony\Component\Mailer\Bridge\Mailjet\Transport\MailjetApiTransport; use Symfony\Component\Mailer\Envelope; +use Symfony\Component\Mailer\Exception\HttpTransportException; +use Symfony\Component\Mailer\SentMessage; use Symfony\Component\Mime\Address; use Symfony\Component\Mime\Email; @@ -85,6 +89,183 @@ public function testPayloadFormat() $this->assertEquals('Qux', $replyTo['Name']); } + public function testSendSuccess() + { + $json = json_encode([ + 'Messages' => [ + 'foo' => 'bar', + ], + ]); + + $responseHeaders = [ + 'x-mj-request-guid' => ['baz'], + ]; + + $response = new MockResponse($json, ['response_headers' => $responseHeaders]); + + $client = new MockHttpClient($response); + + $transport = new MailjetApiTransport(self::USER, self::PASSWORD, $client); + + $email = new Email(); + $email + ->from('foo@example.com') + ->to('bar@example.com') + ->text('foobar'); + + $sentMessage = $transport->send($email); + $this->assertInstanceOf(SentMessage::class, $sentMessage); + $this->assertSame('baz', $sentMessage->getMessageId()); + } + + public function testSendWithDecodingException() + { + $response = new MockResponse('cannot-be-decoded'); + + $client = new MockHttpClient($response); + + $transport = new MailjetApiTransport(self::USER, self::PASSWORD, $client); + + $email = new Email(); + $email + ->from('foo@example.com') + ->to('bar@example.com') + ->text('foobar'); + + $this->expectExceptionObject( + new HttpTransportException('Unable to send an email: "cannot-be-decoded" (code 200).', $response) + ); + + $transport->send($email); + } + + public function testSendWithTransportException() + { + $response = new MockResponse('', ['error' => 'foo']); + + $client = new MockHttpClient($response); + + $transport = new MailjetApiTransport(self::USER, self::PASSWORD, $client); + + $email = new Email(); + $email + ->from('foo@example.com') + ->to('bar@example.com') + ->text('foobar'); + + $this->expectExceptionObject( + new HttpTransportException('Could not reach the remote Mailjet server.', $response) + ); + + $transport->send($email); + } + + public function testSendWithBadRequestResponse() + { + $json = json_encode([ + 'Messages' => [ + [ + 'Errors' => [ + [ + 'ErrorIdentifier' => '8e28ac9c-1fd7-41ad-825f-1d60bc459189', + 'ErrorCode' => 'mj-0005', + 'StatusCode' => 400, + 'ErrorMessage' => 'The To is mandatory but missing from the input', + 'ErrorRelatedTo' => ['To'], + ], + ], + 'Status' => 'error', + ], + ], + ]); + + $response = new MockResponse($json, ['http_code' => 400]); + + $client = new MockHttpClient($response); + + $transport = new MailjetApiTransport(self::USER, self::PASSWORD, $client); + + $email = new Email(); + $email + ->from('foo@example.com') + ->to('bar@example.com') + ->text('foobar'); + + $this->expectExceptionObject( + new HttpTransportException('Unable to send an email: "The To is mandatory but missing from the input" (code 400).', $response) + ); + + $transport->send($email); + } + + public function testSendWithNoErrorMessageBadRequestResponse() + { + $response = new MockResponse('response-content', ['http_code' => 400]); + + $client = new MockHttpClient($response); + + $transport = new MailjetApiTransport(self::USER, self::PASSWORD, $client); + + $email = new Email(); + $email + ->from('foo@example.com') + ->to('bar@example.com') + ->text('foobar'); + + $this->expectExceptionObject( + new HttpTransportException('Unable to send an email: "response-content" (code 400).', $response) + ); + + $transport->send($email); + } + + /** + * @dataProvider getMalformedResponse + */ + public function testSendWithMalformedResponse(array $body) + { + $json = json_encode($body); + + $response = new MockResponse($json); + + $client = new MockHttpClient($response); + + $transport = new MailjetApiTransport(self::USER, self::PASSWORD, $client); + + $email = new Email(); + $email + ->from('foo@example.com') + ->to('bar@example.com') + ->text('foobar'); + + $this->expectExceptionObject( + new HttpTransportException(sprintf('Unable to send an email: "%s" malformed api response.', $json), $response) + ); + + $transport->send($email); + } + + public function getMalformedResponse(): \Generator + { + yield 'Missing Messages key' => [ + [ + 'foo' => 'bar', + ], + ]; + + yield 'Messages is not an array' => [ + [ + 'Messages' => 'bar', + ], + ]; + + yield 'Messages is an empty array' => [ + [ + 'Messages' => [], + ], + ]; + } + public function testReplyTo() { $from = 'foo@example.com'; diff --git a/src/Symfony/Component/Mailer/Bridge/Mailjet/Transport/MailjetApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailjet/Transport/MailjetApiTransport.php index 1aa3dec0daf93..8440ecf9ab3f3 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailjet/Transport/MailjetApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailjet/Transport/MailjetApiTransport.php @@ -69,13 +69,15 @@ protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $e $statusCode = $response->getStatusCode(); $result = $response->toArray(false); } catch (DecodingExceptionInterface $e) { - throw new HttpTransportException('Unable to send an email: '.$response->getContent(false).sprintf(' (code %d).', $statusCode), $response); + throw new HttpTransportException(sprintf('Unable to send an email: "%s" (code %d).', $response->getContent(false), $statusCode), $response); } catch (TransportExceptionInterface $e) { throw new HttpTransportException('Could not reach the remote Mailjet server.', $response, 0, $e); } if (200 !== $statusCode) { - throw new HttpTransportException('Unable to send an email: '.$result['Message'].sprintf(' (code %d).', $statusCode), $response); + $errorDetails = $result['Messages'][0]['Errors'][0]['ErrorMessage'] ?? $response->getContent(false); + + throw new HttpTransportException(sprintf('Unable to send an email: "%s" (code %d).', $errorDetails, $statusCode), $response); } // The response needs to contains a 'Messages' key that is an array From 08ddac5625a35980ad5cd2d39982acfc243817ea Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 10 Mar 2022 10:01:28 +0100 Subject: [PATCH 31/91] Make FormErrorIterator generic --- .../Component/Form/FormErrorIterator.php | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/Symfony/Component/Form/FormErrorIterator.php b/src/Symfony/Component/Form/FormErrorIterator.php index 70dba94fd2e41..9ee2f0e8fe7d3 100644 --- a/src/Symfony/Component/Form/FormErrorIterator.php +++ b/src/Symfony/Component/Form/FormErrorIterator.php @@ -29,9 +29,11 @@ * * @author Bernhard Schussek * - * @implements \ArrayAccess - * @implements \RecursiveIterator - * @implements \SeekableIterator + * @template T of FormError|FormErrorIterator + * + * @implements \ArrayAccess + * @implements \RecursiveIterator + * @implements \SeekableIterator */ class FormErrorIterator implements \RecursiveIterator, \SeekableIterator, \ArrayAccess, \Countable { @@ -41,10 +43,14 @@ class FormErrorIterator implements \RecursiveIterator, \SeekableIterator, \Array public const INDENTATION = ' '; private $form; + + /** + * @var list + */ private $errors; /** - * @param list $errors + * @param list $errors * * @throws InvalidArgumentException If the errors are invalid */ @@ -74,7 +80,7 @@ public function __toString() $string .= 'ERROR: '.$error->getMessage()."\n"; } else { /* @var self $error */ - $string .= $error->form->getName().":\n"; + $string .= $error->getForm()->getName().":\n"; $string .= self::indent((string) $error); } } @@ -95,7 +101,7 @@ public function getForm() /** * Returns the current element of the iterator. * - * @return FormError|self An error or an iterator containing nested errors + * @return T An error or an iterator containing nested errors */ #[\ReturnTypeWillChange] public function current() @@ -164,7 +170,7 @@ public function offsetExists($position) * * @param int $position The position * - * @return FormError|FormErrorIterator + * @return T * * @throws OutOfBoundsException If the given position does not exist */ @@ -227,7 +233,10 @@ public function getChildren() // throw new LogicException(sprintf('The current element is not iterable. Use "%s" to get the current element.', self::class.'::current()')); } - return current($this->errors); + /** @var self $children */ + $children = current($this->errors); + + return $children; } /** From 7134e46638bfc11be5f8fbf4d5a10d6cc10a9956 Mon Sep 17 00:00:00 2001 From: HypeMC Date: Wed, 9 Mar 2022 23:15:29 +0100 Subject: [PATCH 32/91] [Mailer] Use recipients in sendmail transport --- .../Transport/Fixtures/fake-sendmail.php | 5 ++ .../Tests/Transport/SendmailTransportTest.php | 68 +++++++++++++++++++ .../Mailer/Transport/SendmailTransport.php | 9 +++ 3 files changed, 82 insertions(+) create mode 100755 src/Symfony/Component/Mailer/Tests/Transport/Fixtures/fake-sendmail.php diff --git a/src/Symfony/Component/Mailer/Tests/Transport/Fixtures/fake-sendmail.php b/src/Symfony/Component/Mailer/Tests/Transport/Fixtures/fake-sendmail.php new file mode 100755 index 0000000000000..5a4bafd20f1d1 --- /dev/null +++ b/src/Symfony/Component/Mailer/Tests/Transport/Fixtures/fake-sendmail.php @@ -0,0 +1,5 @@ +#!/usr/bin/env php +argsPath = sys_get_temp_dir().\DIRECTORY_SEPARATOR.'sendmail_args'; + } + + protected function tearDown(): void + { + if (file_exists($this->argsPath)) { + @unlink($this->argsPath); + } + unset($this->argsPath); + } + public function testToString() { $t = new SendmailTransport(); $this->assertEquals('smtp://sendmail', (string) $t); } + + public function testToIsUsedWhenRecipientsAreNotSet() + { + if ('\\' === \DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Windows does not support shebangs nor non-blocking standard streams'); + } + + $mail = new Email(); + $mail + ->from('from@mail.com') + ->to('to@mail.com') + ->subject('Subject') + ->text('Some text') + ; + + $envelope = new DelayedEnvelope($mail); + + $sendmailTransport = new SendmailTransport(self::FAKE_SENDMAIL); + $sendmailTransport->send($mail, $envelope); + + $this->assertStringEqualsFile($this->argsPath, __DIR__.'/Fixtures/fake-sendmail.php -ffrom@mail.com to@mail.com'); + } + + public function testRecipientsAreUsedWhenSet() + { + if ('\\' === \DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Windows does not support shebangs nor non-blocking standard streams'); + } + + $mail = new Email(); + $mail + ->from('from@mail.com') + ->to('to@mail.com') + ->subject('Subject') + ->text('Some text') + ; + + $envelope = new DelayedEnvelope($mail); + $envelope->setRecipients([new Address('recipient@mail.com')]); + + $sendmailTransport = new SendmailTransport(self::FAKE_SENDMAIL); + $sendmailTransport->send($mail, $envelope); + + $this->assertStringEqualsFile($this->argsPath, __DIR__.'/Fixtures/fake-sendmail.php -ffrom@mail.com recipient@mail.com'); + } } diff --git a/src/Symfony/Component/Mailer/Transport/SendmailTransport.php b/src/Symfony/Component/Mailer/Transport/SendmailTransport.php index c53d72f6974d6..e272d05e0e31c 100644 --- a/src/Symfony/Component/Mailer/Transport/SendmailTransport.php +++ b/src/Symfony/Component/Mailer/Transport/SendmailTransport.php @@ -86,6 +86,11 @@ protected function doSend(SentMessage $message): void $this->getLogger()->debug(sprintf('Email transport "%s" starting', __CLASS__)); $command = $this->command; + + if (!empty($recipients = $message->getEnvelope()->getRecipients())) { + $command = str_replace(' -t', '', $command); + } + if (!str_contains($command, ' -f')) { $command .= ' -f'.escapeshellarg($message->getEnvelope()->getSender()->getEncodedAddress()); } @@ -96,6 +101,10 @@ protected function doSend(SentMessage $message): void $chunks = AbstractStream::replace("\n.", "\n..", $chunks); } + foreach ($recipients as $recipient) { + $command .= ' '.escapeshellarg($recipient->getEncodedAddress()); + } + $this->stream->setCommand($command); $this->stream->initialize(); foreach ($chunks as $chunk) { From 69c1bdbafd13c77fcea7bddc5d2261fafc902225 Mon Sep 17 00:00:00 2001 From: everyx Date: Thu, 10 Mar 2022 01:25:01 +0000 Subject: [PATCH 33/91] fix: stringify from address for ses+api transport close: #45660 --- .../Transport/SesApiAsyncAwsTransportTest.php | 4 +-- .../Transport/SesApiAsyncAwsTransport.php | 25 +++++++++++-------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesApiAsyncAwsTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesApiAsyncAwsTransportTest.php index a5e48ef966819..517c112fa6193 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesApiAsyncAwsTransportTest.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesApiAsyncAwsTransportTest.php @@ -82,7 +82,7 @@ public function testSend() $this->assertSame('Hello!', $content['Content']['Simple']['Subject']['Data']); $this->assertSame('"Saif Eddin" ', $content['Destination']['ToAddresses'][0]); $this->assertSame('=?UTF-8?B?SsOpcsOpbXk=?= ', $content['Destination']['CcAddresses'][0]); - $this->assertSame('"Fabien" ', $content['FromEmailAddress']); + $this->assertSame('=?UTF-8?B?RmFiacOpbg==?= ', $content['FromEmailAddress']); $this->assertSame('Hello There!', $content['Content']['Simple']['Body']['Text']['Data']); $this->assertSame('Hello There!', $content['Content']['Simple']['Body']['Html']['Data']); $this->assertSame(['replyto-1@example.com', 'replyto-2@example.com'], $content['ReplyToAddresses']); @@ -103,7 +103,7 @@ public function testSend() $mail->subject('Hello!') ->to(new Address('saif.gmati@symfony.com', 'Saif Eddin')) ->cc(new Address('jeremy@derusse.com', 'Jérémy')) - ->from(new Address('fabpot@symfony.com', 'Fabien')) + ->from(new Address('fabpot@symfony.com', 'Fabién')) ->text('Hello There!') ->html('Hello There!') ->replyTo(new Address('replyto-1@example.com'), new Address('replyto-2@example.com')) diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiAsyncAwsTransport.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiAsyncAwsTransport.php index 62adcf0d571d8..0413b059c42d2 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiAsyncAwsTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiAsyncAwsTransport.php @@ -53,7 +53,7 @@ protected function getRequest(SentMessage $message): SendEmailRequest $envelope = $message->getEnvelope(); $request = [ - 'FromEmailAddress' => $envelope->getSender()->toString(), + 'FromEmailAddress' => $this->stringifyAddress($envelope->getSender()), 'Destination' => [ 'ToAddresses' => $this->stringifyAddresses($this->getRecipients($email, $envelope)), ], @@ -114,15 +114,20 @@ private function getRecipients(Email $email, Envelope $envelope): array protected function stringifyAddresses(array $addresses): array { return array_map(function (Address $a) { - // AWS does not support UTF-8 address - if (preg_match('~[\x00-\x08\x10-\x19\x7F-\xFF\r\n]~', $name = $a->getName())) { - return sprintf('=?UTF-8?B?%s?= <%s>', - base64_encode($name), - $a->getEncodedAddress() - ); - } - - return $a->toString(); + return $this->stringifyAddress($a); }, $addresses); } + + protected function stringifyAddress(Address $a): string + { + // AWS does not support UTF-8 address + if (preg_match('~[\x00-\x08\x10-\x19\x7F-\xFF\r\n]~', $name = $a->getName())) { + return sprintf('=?UTF-8?B?%s?= <%s>', + base64_encode($name), + $a->getEncodedAddress() + ); + } + + return $a->toString(); + } } From 5ffedf5036f00b156ed1d071822ef1bbe1592063 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 5 Jan 2022 13:54:51 +0100 Subject: [PATCH 34/91] [Console] Fix compact table style to avoid outputting a leading space --- src/Symfony/Component/Console/Helper/Table.php | 4 ++-- .../Component/Console/Tests/Helper/TableTest.php | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Symfony/Component/Console/Helper/Table.php b/src/Symfony/Component/Console/Helper/Table.php index 1d0a22baa3245..868374ab3832c 100644 --- a/src/Symfony/Component/Console/Helper/Table.php +++ b/src/Symfony/Component/Console/Helper/Table.php @@ -804,9 +804,9 @@ private static function initStyles(): array $compact = new TableStyle(); $compact ->setHorizontalBorderChars('') - ->setVerticalBorderChars(' ') + ->setVerticalBorderChars('') ->setDefaultCrossingChar('') - ->setCellRowContentFormat('%s') + ->setCellRowContentFormat('%s ') ; $styleGuide = new TableStyle(); diff --git a/src/Symfony/Component/Console/Tests/Helper/TableTest.php b/src/Symfony/Component/Console/Tests/Helper/TableTest.php index eed0b166237fe..d69e817422056 100644 --- a/src/Symfony/Component/Console/Tests/Helper/TableTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/TableTest.php @@ -118,11 +118,11 @@ public function renderProvider() $books, 'compact', <<<'TABLE' - ISBN Title Author - 99921-58-10-7 Divine Comedy Dante Alighieri - 9971-5-0210-0 A Tale of Two Cities Charles Dickens - 960-425-059-0 The Lord of the Rings J. R. R. Tolkien - 80-902734-1-6 And Then There Were None Agatha Christie +ISBN Title Author +99921-58-10-7 Divine Comedy Dante Alighieri +9971-5-0210-0 A Tale of Two Cities Charles Dickens +960-425-059-0 The Lord of the Rings J. R. R. Tolkien +80-902734-1-6 And Then There Were None Agatha Christie TABLE ], From 53f473fb0718dbe14d650128de9ffba3587a672e Mon Sep 17 00:00:00 2001 From: VojtaB Date: Thu, 17 Mar 2022 18:57:09 +0100 Subject: [PATCH 35/91] ignoring exception from sem_get in SemaphoreStore Lock component, preventing of error: identifier removed --- src/Symfony/Component/Lock/Store/SemaphoreStore.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Lock/Store/SemaphoreStore.php b/src/Symfony/Component/Lock/Store/SemaphoreStore.php index 6fd48a4c2807e..b85a63c6b4b38 100644 --- a/src/Symfony/Component/Lock/Store/SemaphoreStore.php +++ b/src/Symfony/Component/Lock/Store/SemaphoreStore.php @@ -64,12 +64,12 @@ private function lock(Key $key, bool $blocking) } $keyId = unpack('i', md5($key, true))[1]; - $resource = sem_get($keyId); - $acquired = @sem_acquire($resource, !$blocking); + $resource = @sem_get($keyId); + $acquired = $resource && @sem_acquire($resource, !$blocking); while ($blocking && !$acquired) { - $resource = sem_get($keyId); - $acquired = @sem_acquire($resource); + $resource = @sem_get($keyId); + $acquired = $resource && @sem_acquire($resource); } if (!$acquired) { From 4810066c5cf158ee624de61dce81c4584b710d0a Mon Sep 17 00:00:00 2001 From: Sergey Melesh Date: Wed, 16 Mar 2022 18:05:39 +0300 Subject: [PATCH 36/91] [RateLimiter] Fix rate serialization for long intervals (monthly and yearly) --- .../Component/RateLimiter/Policy/Rate.php | 2 +- .../RateLimiter/Tests/Policy/RateTest.php | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Component/RateLimiter/Tests/Policy/RateTest.php diff --git a/src/Symfony/Component/RateLimiter/Policy/Rate.php b/src/Symfony/Component/RateLimiter/Policy/Rate.php index 5e559f191d3ec..2a6b6e0df3d6f 100644 --- a/src/Symfony/Component/RateLimiter/Policy/Rate.php +++ b/src/Symfony/Component/RateLimiter/Policy/Rate.php @@ -101,6 +101,6 @@ public function calculateNewTokensDuringInterval(float $duration): int public function __toString(): string { - return $this->refillTime->format('P%y%m%dDT%HH%iM%sS').'-'.$this->refillAmount; + return $this->refillTime->format('P%yY%mM%dDT%HH%iM%sS').'-'.$this->refillAmount; } } diff --git a/src/Symfony/Component/RateLimiter/Tests/Policy/RateTest.php b/src/Symfony/Component/RateLimiter/Tests/Policy/RateTest.php new file mode 100644 index 0000000000000..39a859f587555 --- /dev/null +++ b/src/Symfony/Component/RateLimiter/Tests/Policy/RateTest.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\RateLimiter\Tests\Policy; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\RateLimiter\Policy\Rate; + +class RateTest extends TestCase +{ + /** + * @dataProvider provideRate + */ + public function testFromString(Rate $rate) + { + $this->assertEquals($rate, Rate::fromString((string) $rate)); + } + + public function provideRate(): iterable + { + yield [new Rate(\DateInterval::createFromDateString('15 seconds'), 10)]; + yield [Rate::perSecond(10)]; + yield [Rate::perMinute(10)]; + yield [Rate::perHour(10)]; + yield [Rate::perDay(10)]; + yield [Rate::perMonth(10)]; + yield [Rate::perYear(10)]; + } +} From 723cd0919d69b04b8cec823c0afe09736eaf0e8e Mon Sep 17 00:00:00 2001 From: Emil Masiakowski Date: Fri, 11 Mar 2022 18:25:39 +0100 Subject: [PATCH 37/91] [PropertyInfo] strip only leading `\` when unknown docType --- .../Tests/Extractor/PhpDocExtractorTest.php | 6 ++++++ .../PropertyInfo/Tests/Fixtures/PseudoTypeDummy.php | 11 +++++++++++ .../Component/PropertyInfo/Util/PhpDocTypeHelper.php | 2 +- 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Component/PropertyInfo/Tests/Fixtures/PseudoTypeDummy.php diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php index 4a141642d0009..5737fb02cd5ef 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php @@ -18,6 +18,7 @@ use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy; +use Symfony\Component\PropertyInfo\Tests\Fixtures\PseudoTypeDummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\TraitUsage\DummyUsedInTrait; use Symfony\Component\PropertyInfo\Tests\Fixtures\TraitUsage\DummyUsingTrait; use Symfony\Component\PropertyInfo\Type; @@ -350,6 +351,11 @@ public function propertiesParentTypeProvider(): array ]; } + public function testUnknownPseudoType() + { + $this->assertEquals([new Type(Type::BUILTIN_TYPE_OBJECT, false, 'scalar')], $this->extractor->getTypes(PseudoTypeDummy::class, 'unknownPseudoType')); + } + protected function isPhpDocumentorV5() { if (class_exists(InvalidTag::class)) { diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/PseudoTypeDummy.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/PseudoTypeDummy.php new file mode 100644 index 0000000000000..71756044fafc9 --- /dev/null +++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/PseudoTypeDummy.php @@ -0,0 +1,11 @@ + Date: Fri, 18 Mar 2022 09:12:19 +0100 Subject: [PATCH 38/91] Fix CS --- src/Symfony/Component/Mailer/Transport/SendmailTransport.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Mailer/Transport/SendmailTransport.php b/src/Symfony/Component/Mailer/Transport/SendmailTransport.php index e272d05e0e31c..df3dd6b43cfe0 100644 --- a/src/Symfony/Component/Mailer/Transport/SendmailTransport.php +++ b/src/Symfony/Component/Mailer/Transport/SendmailTransport.php @@ -87,7 +87,7 @@ protected function doSend(SentMessage $message): void $command = $this->command; - if (!empty($recipients = $message->getEnvelope()->getRecipients())) { + if ($recipients = $message->getEnvelope()->getRecipients()) { $command = str_replace(' -t', '', $command); } From 074ff42e983298dc0e2263ccc7776b1b07b54ec3 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 18 Mar 2022 11:44:25 +0100 Subject: [PATCH 39/91] [GHA] Fix psalm job --- .github/psalm/.gitignore | 2 + .../psalm/stubs/ForwardCompatTestTrait.php | 38 +++++++++++++++++++ .github/psalm/stubs/SetUpTearDownTrait.php | 19 ++++++++++ .github/workflows/psalm.yml | 17 ++++----- .../Test/ForwardCompatTestTrait.php | 2 +- .../Transport/Doctrine/Connection.php | 4 +- 6 files changed, 70 insertions(+), 12 deletions(-) create mode 100644 .github/psalm/stubs/ForwardCompatTestTrait.php create mode 100644 .github/psalm/stubs/SetUpTearDownTrait.php diff --git a/.github/psalm/.gitignore b/.github/psalm/.gitignore index d6b7ef32c8478..53021ab087be4 100644 --- a/.github/psalm/.gitignore +++ b/.github/psalm/.gitignore @@ -1,2 +1,4 @@ * !.gitignore +!stubs +!stubs/* diff --git a/.github/psalm/stubs/ForwardCompatTestTrait.php b/.github/psalm/stubs/ForwardCompatTestTrait.php new file mode 100644 index 0000000000000..e3ddf4da3d431 --- /dev/null +++ b/.github/psalm/stubs/ForwardCompatTestTrait.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Test; + +use PHPUnit\Framework\TestCase; + +/** + * @internal + */ +trait ForwardCompatTestTrait +{ + private function doSetUp(): void + { + } + + private function doTearDown(): void + { + } + + protected function setUp(): void + { + $this->doSetUp(); + } + + protected function tearDown(): void + { + $this->doTearDown(); + } +} diff --git a/.github/psalm/stubs/SetUpTearDownTrait.php b/.github/psalm/stubs/SetUpTearDownTrait.php new file mode 100644 index 0000000000000..20dbe6fe73e0f --- /dev/null +++ b/.github/psalm/stubs/SetUpTearDownTrait.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit; + +use PHPUnit\Framework\TestCase; + +trait SetUpTearDownTrait +{ + use Legacy\SetUpTearDownTraitForV8; +} diff --git a/.github/workflows/psalm.yml b/.github/workflows/psalm.yml index f607bf8d5eb05..8631d6c6a2b3d 100644 --- a/.github/workflows/psalm.yml +++ b/.github/workflows/psalm.yml @@ -20,7 +20,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.0' + php-version: '8.1' extensions: "json,memcached,mongodb,redis,xsl,ldap,dom" ini-values: "memory_limit=-1" coverage: none @@ -39,18 +39,17 @@ jobs: ([ -d "$COMPOSER_HOME" ] || mkdir "$COMPOSER_HOME") && cp .github/composer-config.json "$COMPOSER_HOME/config.json" export COMPOSER_ROOT_VERSION=$(grep ' VERSION = ' src/Symfony/Component/HttpKernel/Kernel.php | grep -P -o '[0-9]+\.[0-9]+').x-dev composer remove --dev --no-update --no-interaction symfony/phpunit-bridge - composer require --no-update psalm/phar phpunit/phpunit:^9.5 php-http/discovery psr/event-dispatcher mongodb/mongodb - - echo "::group::composer update" - composer update --no-progress --ansi - git checkout composer.json - echo "::endgroup::" - - ./vendor/bin/psalm.phar --version + composer require --no-progress --ansi psalm/phar phpunit/phpunit:^9.5 php-http/discovery psr/event-dispatcher mongodb/mongodb - name: Generate Psalm baseline run: | + git checkout composer.json git checkout -m ${{ github.base_ref }} + cat .github/psalm/stubs/SetUpTearDownTrait.php > src/Symfony/Bridge/PhpUnit/SetUpTearDownTrait.php + cat .github/psalm/stubs/ForwardCompatTestTrait.php | sed 's/Component/Bundle\\FrameworkBundle/' > src/Symfony/Bundle/FrameworkBundle/Test/ForwardCompatTestTrait.php + cat .github/psalm/stubs/ForwardCompatTestTrait.php | sed 's/Component/Component\\Form/' > src/Symfony/Component/Form/Test/ForwardCompatTestTrait.php + cat .github/psalm/stubs/ForwardCompatTestTrait.php | sed 's/Component/Component\\Validator/' > src/Symfony/Component/Validator/Test/ForwardCompatTestTrait.php + ./vendor/bin/psalm.phar --set-baseline=.github/psalm/psalm.baseline.xml --no-progress git checkout -m FETCH_HEAD diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/ForwardCompatTestTrait.php b/src/Symfony/Bundle/FrameworkBundle/Test/ForwardCompatTestTrait.php index 7dd933858088d..9a0dad403063c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/ForwardCompatTestTrait.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/ForwardCompatTestTrait.php @@ -15,7 +15,7 @@ // Auto-adapt to PHPUnit 8 that added a `void` return-type to the setUp/tearDown methods -if (method_exists(\ReflectionMethod::class, 'hasReturnType') && (new \ReflectionMethod(TestCase::class, 'tearDown'))->hasReturnType()) { +if ((new \ReflectionMethod(TestCase::class, 'tearDown'))->hasReturnType()) { /** * @internal */ diff --git a/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php b/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php index 48d00c4be5ede..47ab0824a2e1b 100644 --- a/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php +++ b/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php @@ -17,7 +17,7 @@ use Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception\TableNotFoundException; use Doctrine\DBAL\LockMode; -use Doctrine\DBAL\Platforms\MySqlPlatform; +use Doctrine\DBAL\Platforms\MySQLPlatform; use Doctrine\DBAL\Query\QueryBuilder; use Doctrine\DBAL\Result; use Doctrine\DBAL\Schema\AbstractSchemaManager; @@ -397,7 +397,7 @@ private function getSchema(): Schema ->setNotnull(false); $table->setPrimaryKey(['id']); // No indices on queue_name and available_at on MySQL to prevent deadlock issues when running multiple consumers. - if (!$this->driverConnection->getDatabasePlatform() instanceof MySqlPlatform) { + if (!$this->driverConnection->getDatabasePlatform() instanceof MySQLPlatform) { $table->addIndex(['queue_name']); $table->addIndex(['available_at']); } From 79ee63639d0e001b5ca23c6e1c443a1d68e4318e Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 18 Mar 2022 11:33:36 +0100 Subject: [PATCH 40/91] [VarExporter] fix tests on Windows --- .../VarExporter/Tests/Fixtures/datetime-legacy.php | 4 ++-- .../Component/VarExporter/Tests/Fixtures/datetime.php | 4 ++-- src/Symfony/Component/VarExporter/Tests/VarExporterTest.php | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Symfony/Component/VarExporter/Tests/Fixtures/datetime-legacy.php b/src/Symfony/Component/VarExporter/Tests/Fixtures/datetime-legacy.php index 7b217c5fb21b0..64c39f75faa8b 100644 --- a/src/Symfony/Component/VarExporter/Tests/Fixtures/datetime-legacy.php +++ b/src/Symfony/Component/VarExporter/Tests/Fixtures/datetime-legacy.php @@ -7,7 +7,7 @@ clone ($p['DateTimeZone'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('DateTimeZone')), clone ($p['DateInterval'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('DateInterval')), ], [ - 4 => 'O:10:"DatePeriod":6:{s:5:"start";O:8:"DateTime":3:{s:4:"date";s:26:"2012-07-01 00:00:00.000000";s:13:"timezone_type";i:1;s:8:"timezone";s:6:"+00:00";}s:7:"current";N;s:3:"end";N;s:8:"interval";O:12:"DateInterval":16:{s:1:"y";i:0;s:1:"m";i:0;s:1:"d";i:7;s:1:"h";i:0;s:1:"i";i:0;s:1:"s";i:0;s:1:"f";d:0;s:7:"weekday";i:0;s:16:"weekday_behavior";i:0;s:17:"first_last_day_of";i:0;s:6:"invert";i:0;s:4:"days";b:0;s:12:"special_type";i:0;s:14:"special_amount";i:0;s:21:"have_weekday_relative";i:0;s:21:"have_special_relative";i:0;}s:11:"recurrences";i:5;s:18:"include_start_date";b:1;}', + 4 => 'O:10:"DatePeriod":6:{s:5:"start";O:8:"DateTime":3:{s:4:"date";s:26:"2009-10-11 00:00:00.000000";s:13:"timezone_type";i:3;s:8:"timezone";s:12:"Europe/Paris";}s:7:"current";N;s:3:"end";N;s:8:"interval";O:12:"DateInterval":16:{s:1:"y";i:0;s:1:"m";i:0;s:1:"d";i:7;s:1:"h";i:0;s:1:"i";i:0;s:1:"s";i:0;s:1:"f";d:0;s:7:"weekday";i:0;s:16:"weekday_behavior";i:0;s:17:"first_last_day_of";i:0;s:6:"invert";i:0;s:4:"days";i:7;s:12:"special_type";i:0;s:14:"special_amount";i:0;s:21:"have_weekday_relative";i:0;s:21:"have_special_relative";i:0;}s:11:"recurrences";i:5;s:18:"include_start_date";b:1;}', ]), null, [ @@ -60,7 +60,7 @@ 3 => 0, ], 'days' => [ - 3 => false, + 3 => 7, ], 'special_type' => [ 3 => 0, diff --git a/src/Symfony/Component/VarExporter/Tests/Fixtures/datetime.php b/src/Symfony/Component/VarExporter/Tests/Fixtures/datetime.php index 1de8fa03f0919..e9f41f9ade34c 100644 --- a/src/Symfony/Component/VarExporter/Tests/Fixtures/datetime.php +++ b/src/Symfony/Component/VarExporter/Tests/Fixtures/datetime.php @@ -5,8 +5,8 @@ 'O:8:"DateTime":3:{s:4:"date";s:26:"1970-01-01 00:00:00.000000";s:13:"timezone_type";i:1;s:8:"timezone";s:6:"+00:00";}', 'O:17:"DateTimeImmutable":3:{s:4:"date";s:26:"1970-01-01 00:00:00.000000";s:13:"timezone_type";i:1;s:8:"timezone";s:6:"+00:00";}', 'O:12:"DateTimeZone":2:{s:13:"timezone_type";i:3;s:8:"timezone";s:12:"Europe/Paris";}', - 'O:12:"DateInterval":16:{s:1:"y";i:0;s:1:"m";i:0;s:1:"d";i:7;s:1:"h";i:0;s:1:"i";i:0;s:1:"s";i:0;s:1:"f";d:0;s:7:"weekday";i:0;s:16:"weekday_behavior";i:0;s:17:"first_last_day_of";i:0;s:6:"invert";i:0;s:4:"days";b:0;s:12:"special_type";i:0;s:14:"special_amount";i:0;s:21:"have_weekday_relative";i:0;s:21:"have_special_relative";i:0;}', - 'O:10:"DatePeriod":6:{s:5:"start";O:8:"DateTime":3:{s:4:"date";s:26:"2012-07-01 00:00:00.000000";s:13:"timezone_type";i:1;s:8:"timezone";s:6:"+00:00";}s:7:"current";N;s:3:"end";N;s:8:"interval";O:12:"DateInterval":16:{s:1:"y";i:0;s:1:"m";i:0;s:1:"d";i:7;s:1:"h";i:0;s:1:"i";i:0;s:1:"s";i:0;s:1:"f";d:0;s:7:"weekday";i:0;s:16:"weekday_behavior";i:0;s:17:"first_last_day_of";i:0;s:6:"invert";i:0;s:4:"days";b:0;s:12:"special_type";i:0;s:14:"special_amount";i:0;s:21:"have_weekday_relative";i:0;s:21:"have_special_relative";i:0;}s:11:"recurrences";i:5;s:18:"include_start_date";b:1;}', + 'O:12:"DateInterval":16:{s:1:"y";i:0;s:1:"m";i:0;s:1:"d";i:7;s:1:"h";i:0;s:1:"i";i:0;s:1:"s";i:0;s:1:"f";d:0;s:7:"weekday";i:0;s:16:"weekday_behavior";i:0;s:17:"first_last_day_of";i:0;s:6:"invert";i:0;s:4:"days";i:7;s:12:"special_type";i:0;s:14:"special_amount";i:0;s:21:"have_weekday_relative";i:0;s:21:"have_special_relative";i:0;}', + 'O:10:"DatePeriod":6:{s:5:"start";O:8:"DateTime":3:{s:4:"date";s:26:"2009-10-11 00:00:00.000000";s:13:"timezone_type";i:3;s:8:"timezone";s:12:"Europe/Paris";}s:7:"current";N;s:3:"end";N;s:8:"interval";O:12:"DateInterval":16:{s:1:"y";i:0;s:1:"m";i:0;s:1:"d";i:7;s:1:"h";i:0;s:1:"i";i:0;s:1:"s";i:0;s:1:"f";d:0;s:7:"weekday";i:0;s:16:"weekday_behavior";i:0;s:17:"first_last_day_of";i:0;s:6:"invert";i:0;s:4:"days";i:7;s:12:"special_type";i:0;s:14:"special_amount";i:0;s:21:"have_weekday_relative";i:0;s:21:"have_special_relative";i:0;}s:11:"recurrences";i:5;s:18:"include_start_date";b:1;}', ]), null, [], diff --git a/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php b/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php index f87e4e9b01d1e..407f25af1891f 100644 --- a/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php +++ b/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php @@ -132,9 +132,9 @@ public function provideExport() yield ['datetime', [ \DateTime::createFromFormat('U', 0), \DateTimeImmutable::createFromFormat('U', 0), - new \DateTimeZone('Europe/Paris'), - new \DateInterval('P7D'), - new \DatePeriod('R4/2012-07-01T00:00:00Z/P7D'), + $tz = new \DateTimeZone('Europe/Paris'), + $interval = ($start = new \DateTime('2009-10-11', $tz))->diff(new \DateTime('2009-10-18', $tz)), + new \DatePeriod($start, $interval, 4), ]]; $value = \PHP_VERSION_ID >= 70406 ? new ArrayObject() : new \ArrayObject(); From 06b3cef1be3e39f893dd878e1933e922af68e65a Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 18 Mar 2022 17:18:39 +0100 Subject: [PATCH 41/91] [Finder] fix tests on Windows --- .../Component/Process/Tests/PhpExecutableFinderTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Symfony/Component/Process/Tests/PhpExecutableFinderTest.php b/src/Symfony/Component/Process/Tests/PhpExecutableFinderTest.php index 0ef4cf092b3ba..23de6d42eb5fb 100644 --- a/src/Symfony/Component/Process/Tests/PhpExecutableFinderTest.php +++ b/src/Symfony/Component/Process/Tests/PhpExecutableFinderTest.php @@ -65,6 +65,10 @@ public function testNotExitsBinaryFile() public function testFindWithExecutableDirectory() { + if ('\\' === \DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Directories are not executable on Windows'); + } + $originalPhpBinary = getenv('PHP_BINARY'); try { From 326944d467362e9f7a1f69ae1b80fa18028bf4cb Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 17 Jan 2022 17:25:42 +0100 Subject: [PATCH 42/91] bug #45043 [ErrorHandler] ignore ``@return`` when there is an ``@template`` (nicolas-grekas) This PR was merged into the 5.4 branch. Discussion ---------- [ErrorHandler] ignore ``@return`` when there is an ``@template`` | Q | A | ------------- | --- | Branch? | 5.4 | Bug fix? | yes | New feature? | no | Deprecations? | no | Tickets | - | License | MIT | Doc PR | - And update .github/expected-missing-return-types.diff Commits ------- d1aa50969c [ErrorHandler] ignore ``@return`` when there is an ``@template`` --- src/Symfony/Component/ErrorHandler/DebugClassLoader.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Symfony/Component/ErrorHandler/DebugClassLoader.php b/src/Symfony/Component/ErrorHandler/DebugClassLoader.php index ab6fb5f7a9944..e4388fed93256 100644 --- a/src/Symfony/Component/ErrorHandler/DebugClassLoader.php +++ b/src/Symfony/Component/ErrorHandler/DebugClassLoader.php @@ -368,9 +368,12 @@ public function checkAnnotations(\ReflectionClass $refl, string $class): array $parent = get_parent_class($class) ?: null; self::$returnTypes[$class] = []; + $classIsTemplate = false; // Detect annotations on the class if ($doc = $this->parsePhpDoc($refl)) { + $classIsTemplate = isset($doc['template']); + foreach (['final', 'deprecated', 'internal'] as $annotation) { if (null !== $description = $doc[$annotation][0] ?? null) { self::${$annotation}[$class] = '' !== $description ? ' '.$description.(preg_match('/[.!]$/', $description) ? '' : '.') : '.'; @@ -514,6 +517,10 @@ public function checkAnnotations(\ReflectionClass $refl, string $class): array // To read method annotations $doc = $this->parsePhpDoc($method); + if (($classIsTemplate || isset($doc['template'])) && $method->hasReturnType()) { + unset($doc['return']); + } + if (isset(self::$annotatedParameters[$class][$method->name])) { $definedParameters = []; foreach ($method->getParameters() as $parameter) { From be5b274dd616c136dcaba815a9e70e9c5cc8fa2d Mon Sep 17 00:00:00 2001 From: Tomasz Kusy Date: Sat, 19 Mar 2022 09:49:34 +0100 Subject: [PATCH 43/91] [Translation] [LocoProvider] Add content-type for POST translations --- src/Symfony/Component/Translation/Bridge/Loco/LocoProvider.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Symfony/Component/Translation/Bridge/Loco/LocoProvider.php b/src/Symfony/Component/Translation/Bridge/Loco/LocoProvider.php index 8709a8969ce20..ce1eee839366a 100644 --- a/src/Symfony/Component/Translation/Bridge/Loco/LocoProvider.php +++ b/src/Symfony/Component/Translation/Bridge/Loco/LocoProvider.php @@ -207,6 +207,7 @@ private function translateAssets(array $translations, string $locale): void foreach ($translations as $id => $message) { $responses[$id] = $this->client->request('POST', sprintf('translations/%s/%s', rawurlencode($id), rawurlencode($locale)), [ 'body' => $message, + 'headers' => ['Content-Type' => 'text/plain'], ]); } From 1264225f4ebc535deec491737b70d9bc1d84dc98 Mon Sep 17 00:00:00 2001 From: HypeMC Date: Sat, 19 Mar 2022 01:08:53 +0100 Subject: [PATCH 44/91] [Config] Fix using null values with config builders --- .../Component/Config/Builder/ClassBuilder.php | 7 ++- .../Config/Builder/ConfigBuilderGenerator.php | 30 +++++++--- .../AddToList/Messenger/ReceivingConfig.php | 15 +++-- .../AddToList/Messenger/RoutingConfig.php | 9 ++- .../Config/AddToList/MessengerConfig.php | 17 ++++-- .../Config/AddToList/TranslatorConfig.php | 15 +++-- .../Symfony/Config/AddToListConfig.php | 15 +++-- .../Fixtures/ArrayExtraKeys.output.php | 1 + .../Config/ArrayExtraKeys/BarConfig.php | 21 +++---- .../Config/ArrayExtraKeys/BazConfig.php | 8 +-- .../Config/ArrayExtraKeys/FooConfig.php | 21 +++---- .../Symfony/Config/ArrayExtraKeysConfig.php | 22 +++++--- .../Fixtures/NodeInitialValues.config.php | 2 +- .../Fixtures/NodeInitialValues.output.php | 1 + .../Builder/Fixtures/NodeInitialValues.php | 1 + .../Messenger/TransportsConfig.php | 21 ++++--- .../NodeInitialValues/MessengerConfig.php | 10 +++- .../SomeCleverNameConfig.php | 38 +++++++++++-- .../Config/NodeInitialValuesConfig.php | 15 +++-- .../Symfony/Config/PlaceholdersConfig.php | 21 ++++--- .../Fixtures/PrimitiveTypes.config.php | 1 + .../Fixtures/PrimitiveTypes.output.php | 1 + .../Tests/Builder/Fixtures/PrimitiveTypes.php | 1 + .../Symfony/Config/PrimitiveTypesConfig.php | 56 +++++++++++++++---- .../Symfony/Config/VariableTypeConfig.php | 9 ++- .../Tests/Builder/GeneratedConfigTest.php | 5 ++ 26 files changed, 253 insertions(+), 110 deletions(-) diff --git a/src/Symfony/Component/Config/Builder/ClassBuilder.php b/src/Symfony/Component/Config/Builder/ClassBuilder.php index 26fcab400172e..82fadf691f69d 100644 --- a/src/Symfony/Component/Config/Builder/ClassBuilder.php +++ b/src/Symfony/Component/Config/Builder/ClassBuilder.php @@ -93,7 +93,7 @@ public function build(): string USE /** - * This class is automatically generated to help creating config. + * This class is automatically generated to help in creating a config. */ class CLASS IMPLEMENTS { @@ -124,14 +124,15 @@ public function addMethod(string $name, string $body, array $params = []): void $this->methods[] = new Method(strtr($body, ['NAME' => $this->camelCase($name)] + $params)); } - public function addProperty(string $name, string $classType = null): Property + public function addProperty(string $name, string $classType = null, string $defaultValue = null): Property { $property = new Property($name, '_' !== $name[0] ? $this->camelCase($name) : $name); if (null !== $classType) { $property->setType($classType); } $this->properties[] = $property; - $property->setContent(sprintf('private $%s;', $property->getName())); + $defaultValue = null !== $defaultValue ? sprintf(' = %s', $defaultValue) : ''; + $property->setContent(sprintf('private $%s%s;', $property->getName(), $defaultValue)); return $property; } diff --git a/src/Symfony/Component/Config/Builder/ConfigBuilderGenerator.php b/src/Symfony/Component/Config/Builder/ConfigBuilderGenerator.php index 979c95522704c..920f12104f3a6 100644 --- a/src/Symfony/Component/Config/Builder/ConfigBuilderGenerator.php +++ b/src/Symfony/Component/Config/Builder/ConfigBuilderGenerator.php @@ -31,6 +31,9 @@ */ class ConfigBuilderGenerator implements ConfigBuilderGeneratorInterface { + /** + * @var ClassBuilder[] + */ private $classes; private $outputDir; @@ -89,6 +92,9 @@ private function writeClasses(): void foreach ($this->classes as $class) { $this->buildConstructor($class); $this->buildToArray($class); + if ($class->getProperties()) { + $class->addProperty('_usedProperties', null, '[]'); + } $this->buildSetExtraKey($class); file_put_contents($this->getFullPath($class), $class->build()); @@ -135,6 +141,7 @@ private function handleArrayNode(ArrayNode $node, ClassBuilder $class, string $n public function NAME(array $value = []): CLASS { if (null === $this->PROPERTY) { + $this->_usedProperties[\'PROPERTY\'] = true; $this->PROPERTY = new CLASS($value); } elseif ([] !== $value) { throw new InvalidConfigurationException(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\'); @@ -160,6 +167,7 @@ private function handleVariableNode(VariableNode $node, ClassBuilder $class): vo */ public function NAME($valueDEFAULT): self { + $this->_usedProperties[\'PROPERTY\'] = true; $this->PROPERTY = $value; return $this; @@ -186,6 +194,7 @@ private function handlePrototypedArrayNode(PrototypedArrayNode $node, ClassBuild */ public function NAME($value): self { + $this->_usedProperties[\'PROPERTY\'] = true; $this->PROPERTY = $value; return $this; @@ -200,6 +209,7 @@ public function NAME($value): self */ public function NAME(string $VAR, $VALUE): self { + $this->_usedProperties[\'PROPERTY\'] = true; $this->PROPERTY[$VAR] = $VALUE; return $this; @@ -223,6 +233,8 @@ public function NAME(string $VAR, $VALUE): self $body = ' public function NAME(array $value = []): CLASS { + $this->_usedProperties[\'PROPERTY\'] = true; + return $this->PROPERTY[] = new CLASS($value); }'; $class->addMethod($methodName, $body, ['PROPERTY' => $property->getName(), 'CLASS' => $childClass->getFqcn()]); @@ -231,9 +243,11 @@ public function NAME(array $value = []): CLASS public function NAME(string $VAR, array $VALUE = []): CLASS { if (!isset($this->PROPERTY[$VAR])) { - return $this->PROPERTY[$VAR] = new CLASS($value); + $this->_usedProperties[\'PROPERTY\'] = true; + + return $this->PROPERTY[$VAR] = new CLASS($VALUE); } - if ([] === $value) { + if ([] === $VALUE) { return $this->PROPERTY[$VAR]; } @@ -258,6 +272,7 @@ private function handleScalarNode(ScalarNode $node, ClassBuilder $class): void */ public function NAME($value): self { + $this->_usedProperties[\'PROPERTY\'] = true; $this->PROPERTY = $value; return $this; @@ -367,7 +382,7 @@ private function buildToArray(ClassBuilder $class): void } $body .= strtr(' - if (null !== $this->PROPERTY) { + if (isset($this->_usedProperties[\'PROPERTY\'])) { $output[\'ORG_NAME\'] = '.$code.'; }', ['PROPERTY' => $p->getName(), 'ORG_NAME' => $p->getOriginalName()]); } @@ -397,7 +412,8 @@ private function buildConstructor(ClassBuilder $class): void } $body .= strtr(' - if (isset($value[\'ORG_NAME\'])) { + if (array_key_exists(\'ORG_NAME\', $value)) { + $this->_usedProperties[\'PROPERTY\'] = true; $this->PROPERTY = '.$code.'; unset($value[\'ORG_NAME\']); } @@ -441,11 +457,7 @@ private function buildSetExtraKey(ClassBuilder $class): void */ public function NAME(string $key, $value): self { - if (null === $value) { - unset($this->_extraKeys[$key]); - } else { - $this->_extraKeys[$key] = $value; - } + $this->_extraKeys[$key] = $value; return $this; }'); diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList/Symfony/Config/AddToList/Messenger/ReceivingConfig.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList/Symfony/Config/AddToList/Messenger/ReceivingConfig.php index ca4db117acd37..c757266195482 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList/Symfony/Config/AddToList/Messenger/ReceivingConfig.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList/Symfony/Config/AddToList/Messenger/ReceivingConfig.php @@ -8,12 +8,13 @@ /** - * This class is automatically generated to help creating config. + * This class is automatically generated to help in creating a config. */ class ReceivingConfig { private $priority; private $color; + private $_usedProperties = []; /** * @default null @@ -22,6 +23,7 @@ class ReceivingConfig */ public function priority($value): self { + $this->_usedProperties['priority'] = true; $this->priority = $value; return $this; @@ -34,6 +36,7 @@ public function priority($value): self */ public function color($value): self { + $this->_usedProperties['color'] = true; $this->color = $value; return $this; @@ -42,12 +45,14 @@ public function color($value): self public function __construct(array $value = []) { - if (isset($value['priority'])) { + if (array_key_exists('priority', $value)) { + $this->_usedProperties['priority'] = true; $this->priority = $value['priority']; unset($value['priority']); } - if (isset($value['color'])) { + if (array_key_exists('color', $value)) { + $this->_usedProperties['color'] = true; $this->color = $value['color']; unset($value['color']); } @@ -60,10 +65,10 @@ public function __construct(array $value = []) public function toArray(): array { $output = []; - if (null !== $this->priority) { + if (isset($this->_usedProperties['priority'])) { $output['priority'] = $this->priority; } - if (null !== $this->color) { + if (isset($this->_usedProperties['color'])) { $output['color'] = $this->color; } diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList/Symfony/Config/AddToList/Messenger/RoutingConfig.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList/Symfony/Config/AddToList/Messenger/RoutingConfig.php index 7f44a8553f66f..275dca34da3af 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList/Symfony/Config/AddToList/Messenger/RoutingConfig.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList/Symfony/Config/AddToList/Messenger/RoutingConfig.php @@ -8,11 +8,12 @@ /** - * This class is automatically generated to help creating config. + * This class is automatically generated to help in creating a config. */ class RoutingConfig { private $senders; + private $_usedProperties = []; /** * @param ParamConfigurator|list $value @@ -20,6 +21,7 @@ class RoutingConfig */ public function senders($value): self { + $this->_usedProperties['senders'] = true; $this->senders = $value; return $this; @@ -28,7 +30,8 @@ public function senders($value): self public function __construct(array $value = []) { - if (isset($value['senders'])) { + if (array_key_exists('senders', $value)) { + $this->_usedProperties['senders'] = true; $this->senders = $value['senders']; unset($value['senders']); } @@ -41,7 +44,7 @@ public function __construct(array $value = []) public function toArray(): array { $output = []; - if (null !== $this->senders) { + if (isset($this->_usedProperties['senders'])) { $output['senders'] = $this->senders; } diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList/Symfony/Config/AddToList/MessengerConfig.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList/Symfony/Config/AddToList/MessengerConfig.php index 2189fde0f3bec..85b593a1b05f1 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList/Symfony/Config/AddToList/MessengerConfig.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList/Symfony/Config/AddToList/MessengerConfig.php @@ -9,16 +9,19 @@ /** - * This class is automatically generated to help creating config. + * This class is automatically generated to help in creating a config. */ class MessengerConfig { private $routing; private $receiving; + private $_usedProperties = []; public function routing(string $message_class, array $value = []): \Symfony\Config\AddToList\Messenger\RoutingConfig { if (!isset($this->routing[$message_class])) { + $this->_usedProperties['routing'] = true; + return $this->routing[$message_class] = new \Symfony\Config\AddToList\Messenger\RoutingConfig($value); } if ([] === $value) { @@ -30,18 +33,22 @@ public function routing(string $message_class, array $value = []): \Symfony\Conf public function receiving(array $value = []): \Symfony\Config\AddToList\Messenger\ReceivingConfig { + $this->_usedProperties['receiving'] = true; + return $this->receiving[] = new \Symfony\Config\AddToList\Messenger\ReceivingConfig($value); } public function __construct(array $value = []) { - if (isset($value['routing'])) { + if (array_key_exists('routing', $value)) { + $this->_usedProperties['routing'] = true; $this->routing = array_map(function ($v) { return new \Symfony\Config\AddToList\Messenger\RoutingConfig($v); }, $value['routing']); unset($value['routing']); } - if (isset($value['receiving'])) { + if (array_key_exists('receiving', $value)) { + $this->_usedProperties['receiving'] = true; $this->receiving = array_map(function ($v) { return new \Symfony\Config\AddToList\Messenger\ReceivingConfig($v); }, $value['receiving']); unset($value['receiving']); } @@ -54,10 +61,10 @@ public function __construct(array $value = []) public function toArray(): array { $output = []; - if (null !== $this->routing) { + if (isset($this->_usedProperties['routing'])) { $output['routing'] = array_map(function ($v) { return $v->toArray(); }, $this->routing); } - if (null !== $this->receiving) { + if (isset($this->_usedProperties['receiving'])) { $output['receiving'] = array_map(function ($v) { return $v->toArray(); }, $this->receiving); } diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList/Symfony/Config/AddToList/TranslatorConfig.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList/Symfony/Config/AddToList/TranslatorConfig.php index 570e415ce2830..79f041cea6da0 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList/Symfony/Config/AddToList/TranslatorConfig.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList/Symfony/Config/AddToList/TranslatorConfig.php @@ -8,12 +8,13 @@ /** - * This class is automatically generated to help creating config. + * This class is automatically generated to help in creating a config. */ class TranslatorConfig { private $fallbacks; private $sources; + private $_usedProperties = []; /** * @param ParamConfigurator|list $value @@ -21,6 +22,7 @@ class TranslatorConfig */ public function fallbacks($value): self { + $this->_usedProperties['fallbacks'] = true; $this->fallbacks = $value; return $this; @@ -32,6 +34,7 @@ public function fallbacks($value): self */ public function source(string $source_class, $value): self { + $this->_usedProperties['sources'] = true; $this->sources[$source_class] = $value; return $this; @@ -40,12 +43,14 @@ public function source(string $source_class, $value): self public function __construct(array $value = []) { - if (isset($value['fallbacks'])) { + if (array_key_exists('fallbacks', $value)) { + $this->_usedProperties['fallbacks'] = true; $this->fallbacks = $value['fallbacks']; unset($value['fallbacks']); } - if (isset($value['sources'])) { + if (array_key_exists('sources', $value)) { + $this->_usedProperties['sources'] = true; $this->sources = $value['sources']; unset($value['sources']); } @@ -58,10 +63,10 @@ public function __construct(array $value = []) public function toArray(): array { $output = []; - if (null !== $this->fallbacks) { + if (isset($this->_usedProperties['fallbacks'])) { $output['fallbacks'] = $this->fallbacks; } - if (null !== $this->sources) { + if (isset($this->_usedProperties['sources'])) { $output['sources'] = $this->sources; } diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList/Symfony/Config/AddToListConfig.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList/Symfony/Config/AddToListConfig.php index 679aa9bbc7fca..e6f0c262b88db 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList/Symfony/Config/AddToListConfig.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList/Symfony/Config/AddToListConfig.php @@ -9,16 +9,18 @@ /** - * This class is automatically generated to help creating config. + * This class is automatically generated to help in creating a config. */ class AddToListConfig implements \Symfony\Component\Config\Builder\ConfigBuilderInterface { private $translator; private $messenger; + private $_usedProperties = []; public function translator(array $value = []): \Symfony\Config\AddToList\TranslatorConfig { if (null === $this->translator) { + $this->_usedProperties['translator'] = true; $this->translator = new \Symfony\Config\AddToList\TranslatorConfig($value); } elseif ([] !== $value) { throw new InvalidConfigurationException('The node created by "translator()" has already been initialized. You cannot pass values the second time you call translator().'); @@ -30,6 +32,7 @@ public function translator(array $value = []): \Symfony\Config\AddToList\Transla public function messenger(array $value = []): \Symfony\Config\AddToList\MessengerConfig { if (null === $this->messenger) { + $this->_usedProperties['messenger'] = true; $this->messenger = new \Symfony\Config\AddToList\MessengerConfig($value); } elseif ([] !== $value) { throw new InvalidConfigurationException('The node created by "messenger()" has already been initialized. You cannot pass values the second time you call messenger().'); @@ -46,12 +49,14 @@ public function getExtensionAlias(): string public function __construct(array $value = []) { - if (isset($value['translator'])) { + if (array_key_exists('translator', $value)) { + $this->_usedProperties['translator'] = true; $this->translator = new \Symfony\Config\AddToList\TranslatorConfig($value['translator']); unset($value['translator']); } - if (isset($value['messenger'])) { + if (array_key_exists('messenger', $value)) { + $this->_usedProperties['messenger'] = true; $this->messenger = new \Symfony\Config\AddToList\MessengerConfig($value['messenger']); unset($value['messenger']); } @@ -64,10 +69,10 @@ public function __construct(array $value = []) public function toArray(): array { $output = []; - if (null !== $this->translator) { + if (isset($this->_usedProperties['translator'])) { $output['translator'] = $this->translator->toArray(); } - if (null !== $this->messenger) { + if (isset($this->_usedProperties['messenger'])) { $output['messenger'] = $this->messenger->toArray(); } diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys.output.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys.output.php index d1bdedcf8a23f..d2fdc1ef5c8e4 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys.output.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys.output.php @@ -20,6 +20,7 @@ 'corge' => 'bar2_corge', 'grault' => 'bar2_grault', 'extra1' => 'bar2_extra1', + 'extra4' => null, 'extra2' => 'bar2_extra2', 'extra3' => 'bar2_extra3', ], diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys/Symfony/Config/ArrayExtraKeys/BarConfig.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys/Symfony/Config/ArrayExtraKeys/BarConfig.php index 87eba94c6b91f..256454f164bbf 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys/Symfony/Config/ArrayExtraKeys/BarConfig.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys/Symfony/Config/ArrayExtraKeys/BarConfig.php @@ -7,12 +7,13 @@ /** - * This class is automatically generated to help creating config. + * This class is automatically generated to help in creating a config. */ class BarConfig { private $corge; private $grault; + private $_usedProperties = []; private $_extraKeys; /** @@ -22,6 +23,7 @@ class BarConfig */ public function corge($value): self { + $this->_usedProperties['corge'] = true; $this->corge = $value; return $this; @@ -34,6 +36,7 @@ public function corge($value): self */ public function grault($value): self { + $this->_usedProperties['grault'] = true; $this->grault = $value; return $this; @@ -42,12 +45,14 @@ public function grault($value): self public function __construct(array $value = []) { - if (isset($value['corge'])) { + if (array_key_exists('corge', $value)) { + $this->_usedProperties['corge'] = true; $this->corge = $value['corge']; unset($value['corge']); } - if (isset($value['grault'])) { + if (array_key_exists('grault', $value)) { + $this->_usedProperties['grault'] = true; $this->grault = $value['grault']; unset($value['grault']); } @@ -59,10 +64,10 @@ public function __construct(array $value = []) public function toArray(): array { $output = []; - if (null !== $this->corge) { + if (isset($this->_usedProperties['corge'])) { $output['corge'] = $this->corge; } - if (null !== $this->grault) { + if (isset($this->_usedProperties['grault'])) { $output['grault'] = $this->grault; } @@ -75,11 +80,7 @@ public function toArray(): array */ public function set(string $key, $value): self { - if (null === $value) { - unset($this->_extraKeys[$key]); - } else { - $this->_extraKeys[$key] = $value; - } + $this->_extraKeys[$key] = $value; return $this; } diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys/Symfony/Config/ArrayExtraKeys/BazConfig.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys/Symfony/Config/ArrayExtraKeys/BazConfig.php index fae09098ab103..d64633eab9c66 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys/Symfony/Config/ArrayExtraKeys/BazConfig.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys/Symfony/Config/ArrayExtraKeys/BazConfig.php @@ -7,7 +7,7 @@ /** - * This class is automatically generated to help creating config. + * This class is automatically generated to help in creating a config. */ class BazConfig { @@ -33,11 +33,7 @@ public function toArray(): array */ public function set(string $key, $value): self { - if (null === $value) { - unset($this->_extraKeys[$key]); - } else { - $this->_extraKeys[$key] = $value; - } + $this->_extraKeys[$key] = $value; return $this; } diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys/Symfony/Config/ArrayExtraKeys/FooConfig.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys/Symfony/Config/ArrayExtraKeys/FooConfig.php index 46632c7f9a0e7..c8f713341eda3 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys/Symfony/Config/ArrayExtraKeys/FooConfig.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys/Symfony/Config/ArrayExtraKeys/FooConfig.php @@ -7,12 +7,13 @@ /** - * This class is automatically generated to help creating config. + * This class is automatically generated to help in creating a config. */ class FooConfig { private $baz; private $qux; + private $_usedProperties = []; private $_extraKeys; /** @@ -22,6 +23,7 @@ class FooConfig */ public function baz($value): self { + $this->_usedProperties['baz'] = true; $this->baz = $value; return $this; @@ -34,6 +36,7 @@ public function baz($value): self */ public function qux($value): self { + $this->_usedProperties['qux'] = true; $this->qux = $value; return $this; @@ -42,12 +45,14 @@ public function qux($value): self public function __construct(array $value = []) { - if (isset($value['baz'])) { + if (array_key_exists('baz', $value)) { + $this->_usedProperties['baz'] = true; $this->baz = $value['baz']; unset($value['baz']); } - if (isset($value['qux'])) { + if (array_key_exists('qux', $value)) { + $this->_usedProperties['qux'] = true; $this->qux = $value['qux']; unset($value['qux']); } @@ -59,10 +64,10 @@ public function __construct(array $value = []) public function toArray(): array { $output = []; - if (null !== $this->baz) { + if (isset($this->_usedProperties['baz'])) { $output['baz'] = $this->baz; } - if (null !== $this->qux) { + if (isset($this->_usedProperties['qux'])) { $output['qux'] = $this->qux; } @@ -75,11 +80,7 @@ public function toArray(): array */ public function set(string $key, $value): self { - if (null === $value) { - unset($this->_extraKeys[$key]); - } else { - $this->_extraKeys[$key] = $value; - } + $this->_extraKeys[$key] = $value; return $this; } diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys/Symfony/Config/ArrayExtraKeysConfig.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys/Symfony/Config/ArrayExtraKeysConfig.php index 20ff730475f54..3d8adb7095b33 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys/Symfony/Config/ArrayExtraKeysConfig.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys/Symfony/Config/ArrayExtraKeysConfig.php @@ -10,17 +10,19 @@ /** - * This class is automatically generated to help creating config. + * This class is automatically generated to help in creating a config. */ class ArrayExtraKeysConfig implements \Symfony\Component\Config\Builder\ConfigBuilderInterface { private $foo; private $bar; private $baz; + private $_usedProperties = []; public function foo(array $value = []): \Symfony\Config\ArrayExtraKeys\FooConfig { if (null === $this->foo) { + $this->_usedProperties['foo'] = true; $this->foo = new \Symfony\Config\ArrayExtraKeys\FooConfig($value); } elseif ([] !== $value) { throw new InvalidConfigurationException('The node created by "foo()" has already been initialized. You cannot pass values the second time you call foo().'); @@ -31,12 +33,15 @@ public function foo(array $value = []): \Symfony\Config\ArrayExtraKeys\FooConfig public function bar(array $value = []): \Symfony\Config\ArrayExtraKeys\BarConfig { + $this->_usedProperties['bar'] = true; + return $this->bar[] = new \Symfony\Config\ArrayExtraKeys\BarConfig($value); } public function baz(array $value = []): \Symfony\Config\ArrayExtraKeys\BazConfig { if (null === $this->baz) { + $this->_usedProperties['baz'] = true; $this->baz = new \Symfony\Config\ArrayExtraKeys\BazConfig($value); } elseif ([] !== $value) { throw new InvalidConfigurationException('The node created by "baz()" has already been initialized. You cannot pass values the second time you call baz().'); @@ -53,17 +58,20 @@ public function getExtensionAlias(): string public function __construct(array $value = []) { - if (isset($value['foo'])) { + if (array_key_exists('foo', $value)) { + $this->_usedProperties['foo'] = true; $this->foo = new \Symfony\Config\ArrayExtraKeys\FooConfig($value['foo']); unset($value['foo']); } - if (isset($value['bar'])) { + if (array_key_exists('bar', $value)) { + $this->_usedProperties['bar'] = true; $this->bar = array_map(function ($v) { return new \Symfony\Config\ArrayExtraKeys\BarConfig($v); }, $value['bar']); unset($value['bar']); } - if (isset($value['baz'])) { + if (array_key_exists('baz', $value)) { + $this->_usedProperties['baz'] = true; $this->baz = new \Symfony\Config\ArrayExtraKeys\BazConfig($value['baz']); unset($value['baz']); } @@ -76,13 +84,13 @@ public function __construct(array $value = []) public function toArray(): array { $output = []; - if (null !== $this->foo) { + if (isset($this->_usedProperties['foo'])) { $output['foo'] = $this->foo->toArray(); } - if (null !== $this->bar) { + if (isset($this->_usedProperties['bar'])) { $output['bar'] = array_map(function ($v) { return $v->toArray(); }, $this->bar); } - if (null !== $this->baz) { + if (isset($this->_usedProperties['baz'])) { $output['baz'] = $this->baz->toArray(); } diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues.config.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues.config.php index c51bd764e00e6..4b86755c91a5b 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues.config.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues.config.php @@ -3,7 +3,7 @@ use Symfony\Config\NodeInitialValuesConfig; return static function (NodeInitialValuesConfig $config) { - $config->someCleverName(['second' => 'foo'])->first('bar'); + $config->someCleverName(['second' => 'foo', 'third' => null])->first('bar'); $config->messenger() ->transports('fast_queue', ['dsn' => 'sync://']) ->serializer('acme'); diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues.output.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues.output.php index ec8fee9a6d1d1..7fe70f9645b9e 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues.output.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues.output.php @@ -4,6 +4,7 @@ 'some_clever_name' => [ 'first' => 'bar', 'second' => 'foo', + 'third' => null, ], 'messenger' => [ 'transports' => [ diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues.php index 13fdf1ae81d13..c290cf9730670 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues.php @@ -17,6 +17,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->children() ->scalarNode('first')->end() ->scalarNode('second')->end() + ->scalarNode('third')->end() ->end() ->end() diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues/Symfony/Config/NodeInitialValues/Messenger/TransportsConfig.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues/Symfony/Config/NodeInitialValues/Messenger/TransportsConfig.php index a3fe5218f0678..3acc0247ac726 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues/Symfony/Config/NodeInitialValues/Messenger/TransportsConfig.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues/Symfony/Config/NodeInitialValues/Messenger/TransportsConfig.php @@ -8,13 +8,14 @@ /** - * This class is automatically generated to help creating config. + * This class is automatically generated to help in creating a config. */ class TransportsConfig { private $dsn; private $serializer; private $options; + private $_usedProperties = []; /** * @default null @@ -23,6 +24,7 @@ class TransportsConfig */ public function dsn($value): self { + $this->_usedProperties['dsn'] = true; $this->dsn = $value; return $this; @@ -35,6 +37,7 @@ public function dsn($value): self */ public function serializer($value): self { + $this->_usedProperties['serializer'] = true; $this->serializer = $value; return $this; @@ -46,6 +49,7 @@ public function serializer($value): self */ public function options($value): self { + $this->_usedProperties['options'] = true; $this->options = $value; return $this; @@ -54,17 +58,20 @@ public function options($value): self public function __construct(array $value = []) { - if (isset($value['dsn'])) { + if (array_key_exists('dsn', $value)) { + $this->_usedProperties['dsn'] = true; $this->dsn = $value['dsn']; unset($value['dsn']); } - if (isset($value['serializer'])) { + if (array_key_exists('serializer', $value)) { + $this->_usedProperties['serializer'] = true; $this->serializer = $value['serializer']; unset($value['serializer']); } - if (isset($value['options'])) { + if (array_key_exists('options', $value)) { + $this->_usedProperties['options'] = true; $this->options = $value['options']; unset($value['options']); } @@ -77,13 +84,13 @@ public function __construct(array $value = []) public function toArray(): array { $output = []; - if (null !== $this->dsn) { + if (isset($this->_usedProperties['dsn'])) { $output['dsn'] = $this->dsn; } - if (null !== $this->serializer) { + if (isset($this->_usedProperties['serializer'])) { $output['serializer'] = $this->serializer; } - if (null !== $this->options) { + if (isset($this->_usedProperties['options'])) { $output['options'] = $this->options; } diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues/Symfony/Config/NodeInitialValues/MessengerConfig.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues/Symfony/Config/NodeInitialValues/MessengerConfig.php index 8e59732f2d024..12ff61109cae7 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues/Symfony/Config/NodeInitialValues/MessengerConfig.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues/Symfony/Config/NodeInitialValues/MessengerConfig.php @@ -8,15 +8,18 @@ /** - * This class is automatically generated to help creating config. + * This class is automatically generated to help in creating a config. */ class MessengerConfig { private $transports; + private $_usedProperties = []; public function transports(string $name, array $value = []): \Symfony\Config\NodeInitialValues\Messenger\TransportsConfig { if (!isset($this->transports[$name])) { + $this->_usedProperties['transports'] = true; + return $this->transports[$name] = new \Symfony\Config\NodeInitialValues\Messenger\TransportsConfig($value); } if ([] === $value) { @@ -29,7 +32,8 @@ public function transports(string $name, array $value = []): \Symfony\Config\Nod public function __construct(array $value = []) { - if (isset($value['transports'])) { + if (array_key_exists('transports', $value)) { + $this->_usedProperties['transports'] = true; $this->transports = array_map(function ($v) { return new \Symfony\Config\NodeInitialValues\Messenger\TransportsConfig($v); }, $value['transports']); unset($value['transports']); } @@ -42,7 +46,7 @@ public function __construct(array $value = []) public function toArray(): array { $output = []; - if (null !== $this->transports) { + if (isset($this->_usedProperties['transports'])) { $output['transports'] = array_map(function ($v) { return $v->toArray(); }, $this->transports); } diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues/Symfony/Config/NodeInitialValues/SomeCleverNameConfig.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues/Symfony/Config/NodeInitialValues/SomeCleverNameConfig.php index 2db3d4cf95578..3ca87c25eec12 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues/Symfony/Config/NodeInitialValues/SomeCleverNameConfig.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues/Symfony/Config/NodeInitialValues/SomeCleverNameConfig.php @@ -8,12 +8,14 @@ /** - * This class is automatically generated to help creating config. + * This class is automatically generated to help in creating a config. */ class SomeCleverNameConfig { private $first; private $second; + private $third; + private $_usedProperties = []; /** * @default null @@ -22,6 +24,7 @@ class SomeCleverNameConfig */ public function first($value): self { + $this->_usedProperties['first'] = true; $this->first = $value; return $this; @@ -34,24 +37,46 @@ public function first($value): self */ public function second($value): self { + $this->_usedProperties['second'] = true; $this->second = $value; return $this; } + /** + * @default null + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function third($value): self + { + $this->_usedProperties['third'] = true; + $this->third = $value; + + return $this; + } + public function __construct(array $value = []) { - if (isset($value['first'])) { + if (array_key_exists('first', $value)) { + $this->_usedProperties['first'] = true; $this->first = $value['first']; unset($value['first']); } - if (isset($value['second'])) { + if (array_key_exists('second', $value)) { + $this->_usedProperties['second'] = true; $this->second = $value['second']; unset($value['second']); } + if (array_key_exists('third', $value)) { + $this->_usedProperties['third'] = true; + $this->third = $value['third']; + unset($value['third']); + } + if ([] !== $value) { throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); } @@ -60,12 +85,15 @@ public function __construct(array $value = []) public function toArray(): array { $output = []; - if (null !== $this->first) { + if (isset($this->_usedProperties['first'])) { $output['first'] = $this->first; } - if (null !== $this->second) { + if (isset($this->_usedProperties['second'])) { $output['second'] = $this->second; } + if (isset($this->_usedProperties['third'])) { + $output['third'] = $this->third; + } return $output; } diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues/Symfony/Config/NodeInitialValuesConfig.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues/Symfony/Config/NodeInitialValuesConfig.php index d2f8bc654cfde..1ba307fb491eb 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues/Symfony/Config/NodeInitialValuesConfig.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues/Symfony/Config/NodeInitialValuesConfig.php @@ -9,16 +9,18 @@ /** - * This class is automatically generated to help creating config. + * This class is automatically generated to help in creating a config. */ class NodeInitialValuesConfig implements \Symfony\Component\Config\Builder\ConfigBuilderInterface { private $someCleverName; private $messenger; + private $_usedProperties = []; public function someCleverName(array $value = []): \Symfony\Config\NodeInitialValues\SomeCleverNameConfig { if (null === $this->someCleverName) { + $this->_usedProperties['someCleverName'] = true; $this->someCleverName = new \Symfony\Config\NodeInitialValues\SomeCleverNameConfig($value); } elseif ([] !== $value) { throw new InvalidConfigurationException('The node created by "someCleverName()" has already been initialized. You cannot pass values the second time you call someCleverName().'); @@ -30,6 +32,7 @@ public function someCleverName(array $value = []): \Symfony\Config\NodeInitialVa public function messenger(array $value = []): \Symfony\Config\NodeInitialValues\MessengerConfig { if (null === $this->messenger) { + $this->_usedProperties['messenger'] = true; $this->messenger = new \Symfony\Config\NodeInitialValues\MessengerConfig($value); } elseif ([] !== $value) { throw new InvalidConfigurationException('The node created by "messenger()" has already been initialized. You cannot pass values the second time you call messenger().'); @@ -46,12 +49,14 @@ public function getExtensionAlias(): string public function __construct(array $value = []) { - if (isset($value['some_clever_name'])) { + if (array_key_exists('some_clever_name', $value)) { + $this->_usedProperties['someCleverName'] = true; $this->someCleverName = new \Symfony\Config\NodeInitialValues\SomeCleverNameConfig($value['some_clever_name']); unset($value['some_clever_name']); } - if (isset($value['messenger'])) { + if (array_key_exists('messenger', $value)) { + $this->_usedProperties['messenger'] = true; $this->messenger = new \Symfony\Config\NodeInitialValues\MessengerConfig($value['messenger']); unset($value['messenger']); } @@ -64,10 +69,10 @@ public function __construct(array $value = []) public function toArray(): array { $output = []; - if (null !== $this->someCleverName) { + if (isset($this->_usedProperties['someCleverName'])) { $output['some_clever_name'] = $this->someCleverName->toArray(); } - if (null !== $this->messenger) { + if (isset($this->_usedProperties['messenger'])) { $output['messenger'] = $this->messenger->toArray(); } diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/Placeholders/Symfony/Config/PlaceholdersConfig.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/Placeholders/Symfony/Config/PlaceholdersConfig.php index 909c95585b441..15fe9b492270d 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/Placeholders/Symfony/Config/PlaceholdersConfig.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/Placeholders/Symfony/Config/PlaceholdersConfig.php @@ -8,13 +8,14 @@ /** - * This class is automatically generated to help creating config. + * This class is automatically generated to help in creating a config. */ class PlaceholdersConfig implements \Symfony\Component\Config\Builder\ConfigBuilderInterface { private $enabled; private $favoriteFloat; private $goodIntegers; + private $_usedProperties = []; /** * @default false @@ -23,6 +24,7 @@ class PlaceholdersConfig implements \Symfony\Component\Config\Builder\ConfigBuil */ public function enabled($value): self { + $this->_usedProperties['enabled'] = true; $this->enabled = $value; return $this; @@ -35,6 +37,7 @@ public function enabled($value): self */ public function favoriteFloat($value): self { + $this->_usedProperties['favoriteFloat'] = true; $this->favoriteFloat = $value; return $this; @@ -46,6 +49,7 @@ public function favoriteFloat($value): self */ public function goodIntegers($value): self { + $this->_usedProperties['goodIntegers'] = true; $this->goodIntegers = $value; return $this; @@ -59,17 +63,20 @@ public function getExtensionAlias(): string public function __construct(array $value = []) { - if (isset($value['enabled'])) { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; $this->enabled = $value['enabled']; unset($value['enabled']); } - if (isset($value['favorite_float'])) { + if (array_key_exists('favorite_float', $value)) { + $this->_usedProperties['favoriteFloat'] = true; $this->favoriteFloat = $value['favorite_float']; unset($value['favorite_float']); } - if (isset($value['good_integers'])) { + if (array_key_exists('good_integers', $value)) { + $this->_usedProperties['goodIntegers'] = true; $this->goodIntegers = $value['good_integers']; unset($value['good_integers']); } @@ -82,13 +89,13 @@ public function __construct(array $value = []) public function toArray(): array { $output = []; - if (null !== $this->enabled) { + if (isset($this->_usedProperties['enabled'])) { $output['enabled'] = $this->enabled; } - if (null !== $this->favoriteFloat) { + if (isset($this->_usedProperties['favoriteFloat'])) { $output['favorite_float'] = $this->favoriteFloat; } - if (null !== $this->goodIntegers) { + if (isset($this->_usedProperties['goodIntegers'])) { $output['good_integers'] = $this->goodIntegers; } diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/PrimitiveTypes.config.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/PrimitiveTypes.config.php index 6ca25d66a87c6..b4498957057c4 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/PrimitiveTypes.config.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/PrimitiveTypes.config.php @@ -8,4 +8,5 @@ $config->floatNode(47.11); $config->integerNode(1337); $config->scalarNode('foobar'); + $config->scalarNodeWithDefault(null); }; diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/PrimitiveTypes.output.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/PrimitiveTypes.output.php index 6d3e12c5637c4..366fd5c19f4cb 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/PrimitiveTypes.output.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/PrimitiveTypes.output.php @@ -6,4 +6,5 @@ 'float_node' => 47.11, 'integer_node' => 1337, 'scalar_node' => 'foobar', + 'scalar_node_with_default' => null, ]; diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/PrimitiveTypes.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/PrimitiveTypes.php index aecdbe7953da5..3d36f72bff2db 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/PrimitiveTypes.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/PrimitiveTypes.php @@ -18,6 +18,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->floatNode('float_node')->end() ->integerNode('integer_node')->end() ->scalarNode('scalar_node')->end() + ->scalarNode('scalar_node_with_default')->defaultTrue()->end() ->end() ; diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/PrimitiveTypes/Symfony/Config/PrimitiveTypesConfig.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/PrimitiveTypes/Symfony/Config/PrimitiveTypesConfig.php index fd802032c28f6..8a1be4e46a204 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/PrimitiveTypes/Symfony/Config/PrimitiveTypesConfig.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/PrimitiveTypes/Symfony/Config/PrimitiveTypesConfig.php @@ -8,7 +8,7 @@ /** - * This class is automatically generated to help creating config. + * This class is automatically generated to help in creating a config. */ class PrimitiveTypesConfig implements \Symfony\Component\Config\Builder\ConfigBuilderInterface { @@ -17,6 +17,8 @@ class PrimitiveTypesConfig implements \Symfony\Component\Config\Builder\ConfigBu private $floatNode; private $integerNode; private $scalarNode; + private $scalarNodeWithDefault; + private $_usedProperties = []; /** * @default null @@ -25,6 +27,7 @@ class PrimitiveTypesConfig implements \Symfony\Component\Config\Builder\ConfigBu */ public function booleanNode($value): self { + $this->_usedProperties['booleanNode'] = true; $this->booleanNode = $value; return $this; @@ -37,6 +40,7 @@ public function booleanNode($value): self */ public function enumNode($value): self { + $this->_usedProperties['enumNode'] = true; $this->enumNode = $value; return $this; @@ -49,6 +53,7 @@ public function enumNode($value): self */ public function floatNode($value): self { + $this->_usedProperties['floatNode'] = true; $this->floatNode = $value; return $this; @@ -61,6 +66,7 @@ public function floatNode($value): self */ public function integerNode($value): self { + $this->_usedProperties['integerNode'] = true; $this->integerNode = $value; return $this; @@ -73,11 +79,25 @@ public function integerNode($value): self */ public function scalarNode($value): self { + $this->_usedProperties['scalarNode'] = true; $this->scalarNode = $value; return $this; } + /** + * @default true + * @param ParamConfigurator|mixed $value + * @return $this + */ + public function scalarNodeWithDefault($value): self + { + $this->_usedProperties['scalarNodeWithDefault'] = true; + $this->scalarNodeWithDefault = $value; + + return $this; + } + public function getExtensionAlias(): string { return 'primitive_types'; @@ -86,31 +106,42 @@ public function getExtensionAlias(): string public function __construct(array $value = []) { - if (isset($value['boolean_node'])) { + if (array_key_exists('boolean_node', $value)) { + $this->_usedProperties['booleanNode'] = true; $this->booleanNode = $value['boolean_node']; unset($value['boolean_node']); } - if (isset($value['enum_node'])) { + if (array_key_exists('enum_node', $value)) { + $this->_usedProperties['enumNode'] = true; $this->enumNode = $value['enum_node']; unset($value['enum_node']); } - if (isset($value['float_node'])) { + if (array_key_exists('float_node', $value)) { + $this->_usedProperties['floatNode'] = true; $this->floatNode = $value['float_node']; unset($value['float_node']); } - if (isset($value['integer_node'])) { + if (array_key_exists('integer_node', $value)) { + $this->_usedProperties['integerNode'] = true; $this->integerNode = $value['integer_node']; unset($value['integer_node']); } - if (isset($value['scalar_node'])) { + if (array_key_exists('scalar_node', $value)) { + $this->_usedProperties['scalarNode'] = true; $this->scalarNode = $value['scalar_node']; unset($value['scalar_node']); } + if (array_key_exists('scalar_node_with_default', $value)) { + $this->_usedProperties['scalarNodeWithDefault'] = true; + $this->scalarNodeWithDefault = $value['scalar_node_with_default']; + unset($value['scalar_node_with_default']); + } + if ([] !== $value) { throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); } @@ -119,21 +150,24 @@ public function __construct(array $value = []) public function toArray(): array { $output = []; - if (null !== $this->booleanNode) { + if (isset($this->_usedProperties['booleanNode'])) { $output['boolean_node'] = $this->booleanNode; } - if (null !== $this->enumNode) { + if (isset($this->_usedProperties['enumNode'])) { $output['enum_node'] = $this->enumNode; } - if (null !== $this->floatNode) { + if (isset($this->_usedProperties['floatNode'])) { $output['float_node'] = $this->floatNode; } - if (null !== $this->integerNode) { + if (isset($this->_usedProperties['integerNode'])) { $output['integer_node'] = $this->integerNode; } - if (null !== $this->scalarNode) { + if (isset($this->_usedProperties['scalarNode'])) { $output['scalar_node'] = $this->scalarNode; } + if (isset($this->_usedProperties['scalarNodeWithDefault'])) { + $output['scalar_node_with_default'] = $this->scalarNodeWithDefault; + } return $output; } diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/VariableType/Symfony/Config/VariableTypeConfig.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/VariableType/Symfony/Config/VariableTypeConfig.php index 0ee7efe7f362b..a36bf5f31c966 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/VariableType/Symfony/Config/VariableTypeConfig.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/VariableType/Symfony/Config/VariableTypeConfig.php @@ -8,11 +8,12 @@ /** - * This class is automatically generated to help creating config. + * This class is automatically generated to help in creating a config. */ class VariableTypeConfig implements \Symfony\Component\Config\Builder\ConfigBuilderInterface { private $anyValue; + private $_usedProperties = []; /** * @default null @@ -21,6 +22,7 @@ class VariableTypeConfig implements \Symfony\Component\Config\Builder\ConfigBuil */ public function anyValue($value): self { + $this->_usedProperties['anyValue'] = true; $this->anyValue = $value; return $this; @@ -34,7 +36,8 @@ public function getExtensionAlias(): string public function __construct(array $value = []) { - if (isset($value['any_value'])) { + if (array_key_exists('any_value', $value)) { + $this->_usedProperties['anyValue'] = true; $this->anyValue = $value['any_value']; unset($value['any_value']); } @@ -47,7 +50,7 @@ public function __construct(array $value = []) public function toArray(): array { $output = []; - if (null !== $this->anyValue) { + if (isset($this->_usedProperties['anyValue'])) { $output['any_value'] = $this->anyValue; } diff --git a/src/Symfony/Component/Config/Tests/Builder/GeneratedConfigTest.php b/src/Symfony/Component/Config/Tests/Builder/GeneratedConfigTest.php index 83b98d12ac363..e22b3123910ed 100644 --- a/src/Symfony/Component/Config/Tests/Builder/GeneratedConfigTest.php +++ b/src/Symfony/Component/Config/Tests/Builder/GeneratedConfigTest.php @@ -19,6 +19,11 @@ * Test to use the generated config and test its output. * * @author Tobias Nyholm + * + * @covers \Symfony\Component\Config\Builder\ClassBuilder + * @covers \Symfony\Component\Config\Builder\ConfigBuilderGenerator + * @covers \Symfony\Component\Config\Builder\Method + * @covers \Symfony\Component\Config\Builder\Property */ class GeneratedConfigTest extends TestCase { From 01f674975a2dd27340aab9b6e7402bc0aee8423f Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 22 Mar 2022 10:50:51 +0100 Subject: [PATCH 45/91] [HttpClient] minor cs fix --- src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php b/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php index 189788cce8ca9..989c6f32af2e6 100644 --- a/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php +++ b/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php @@ -205,9 +205,9 @@ public function testNegativeTimeout() public function testNullBody() { - $httpClient = $this->getHttpClient(__FUNCTION__); + $client = $this->getHttpClient(__FUNCTION__); - $httpClient->request('POST', 'http://localhost:8057/post', [ + $client->request('POST', 'http://localhost:8057/post', [ 'body' => null, ]); From b87868a6e7d9daf90d2cfd664b6ef28cbe837e58 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 22 Mar 2022 11:25:55 +0100 Subject: [PATCH 46/91] [HttpClient] Move Content-Type after Content-Length --- .../Component/HttpClient/HttpClientTrait.php | 17 ++++++++++++----- .../HttpClient/Tests/ScopingHttpClientTest.php | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Component/HttpClient/HttpClientTrait.php b/src/Symfony/Component/HttpClient/HttpClientTrait.php index 57d48aade8db2..20522255e9a97 100644 --- a/src/Symfony/Component/HttpClient/HttpClientTrait.php +++ b/src/Symfony/Component/HttpClient/HttpClientTrait.php @@ -76,12 +76,12 @@ private static function prepareRequest(?string $method, ?string $url, array $opt unset($options['json']); if (!isset($options['normalized_headers']['content-type'])) { - $options['normalized_headers']['content-type'] = [$options['headers'][] = 'Content-Type: application/json']; + $options['normalized_headers']['content-type'] = ['Content-Type: application/json']; } } if (!isset($options['normalized_headers']['accept'])) { - $options['normalized_headers']['accept'] = [$options['headers'][] = 'Accept: */*']; + $options['normalized_headers']['accept'] = ['Accept: */*']; } if (isset($options['body'])) { @@ -92,7 +92,6 @@ private static function prepareRequest(?string $method, ?string $url, array $opt && ('' !== $h || ('' !== $options['body'] && !isset($options['normalized_headers']['transfer-encoding']))) ) { $options['normalized_headers']['content-length'] = [substr_replace($h ?: 'Content-Length: ', \strlen($options['body']), 16)]; - $options['headers'] = array_merge(...array_values($options['normalized_headers'])); } } @@ -134,11 +133,11 @@ private static function prepareRequest(?string $method, ?string $url, array $opt if (null !== $url) { // Merge auth with headers if (($options['auth_basic'] ?? false) && !($options['normalized_headers']['authorization'] ?? false)) { - $options['normalized_headers']['authorization'] = [$options['headers'][] = 'Authorization: Basic '.base64_encode($options['auth_basic'])]; + $options['normalized_headers']['authorization'] = ['Authorization: Basic '.base64_encode($options['auth_basic'])]; } // Merge bearer with headers if (($options['auth_bearer'] ?? false) && !($options['normalized_headers']['authorization'] ?? false)) { - $options['normalized_headers']['authorization'] = [$options['headers'][] = 'Authorization: Bearer '.$options['auth_bearer']]; + $options['normalized_headers']['authorization'] = ['Authorization: Bearer '.$options['auth_bearer']]; } unset($options['auth_basic'], $options['auth_bearer']); @@ -161,6 +160,14 @@ private static function prepareRequest(?string $method, ?string $url, array $opt $options['max_duration'] = isset($options['max_duration']) ? (float) $options['max_duration'] : 0; + if (isset($options['normalized_headers']['content-length']) && $contentType = $options['normalized_headers']['content-type'] ?? null) { + // Move Content-Type after Content-Length, see https://bugs.php.net/44603 + unset($options['normalized_headers']['content-type']); + $options['normalized_headers']['content-type'] = $contentType; + } + + $options['headers'] = array_merge(...array_values($options['normalized_headers'])); + return [$url, $options]; } diff --git a/src/Symfony/Component/HttpClient/Tests/ScopingHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/ScopingHttpClientTest.php index bfca02b3581aa..a3e3099953ffe 100644 --- a/src/Symfony/Component/HttpClient/Tests/ScopingHttpClientTest.php +++ b/src/Symfony/Component/HttpClient/Tests/ScopingHttpClientTest.php @@ -73,7 +73,7 @@ public function testMatchingUrlsAndOptions() $response = $client->request('GET', 'http://example.com/foo-bar', ['json' => ['url' => 'http://example.com']]); $requestOptions = $response->getRequestOptions(); - $this->assertSame('Content-Type: application/json', $requestOptions['headers'][1]); + $this->assertSame('Content-Type: application/json', $requestOptions['headers'][3]); $requestJson = json_decode($requestOptions['body'], true); $this->assertSame('http://example.com', $requestJson['url']); $this->assertSame('X-FooBar: '.$defaultOptions['.*/foo-bar']['headers']['X-FooBar'], $requestOptions['headers'][0]); From 99c69e1f1c1219b3c5c6e2fb3de76c92c210d747 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Vu=C5=A1ak?= Date: Fri, 18 Mar 2022 21:43:27 +0100 Subject: [PATCH 47/91] Improve testsuite --- .github/workflows/unit-tests.yml | 2 +- src/Symfony/Component/Lock/Tests/Store/CombinedStoreTest.php | 4 +++- src/Symfony/Component/Lock/Tests/Store/PdoStoreTest.php | 1 + src/Symfony/Component/Lock/Tests/Store/ZookeeperStoreTest.php | 1 + 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 49806815338d4..6be35796056d5 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -66,7 +66,7 @@ jobs: ([ -d "$COMPOSER_HOME" ] || mkdir "$COMPOSER_HOME") && cp .github/composer-config.json "$COMPOSER_HOME/config.json" echo COLUMNS=120 >> $GITHUB_ENV - echo PHPUNIT="$(pwd)/phpunit --exclude-group tty,benchmark,intl-data" >> $GITHUB_ENV + echo PHPUNIT="$(pwd)/phpunit --exclude-group tty,benchmark,intl-data,integration" >> $GITHUB_ENV echo COMPOSER_UP='composer update --no-progress --ansi' >> $GITHUB_ENV SYMFONY_VERSIONS=$(git ls-remote -q --heads | cut -f2 | grep -o '/[1-9][0-9]*\.[0-9].*' | sort -V) diff --git a/src/Symfony/Component/Lock/Tests/Store/CombinedStoreTest.php b/src/Symfony/Component/Lock/Tests/Store/CombinedStoreTest.php index 991208f8b204e..ebb8199382d77 100644 --- a/src/Symfony/Component/Lock/Tests/Store/CombinedStoreTest.php +++ b/src/Symfony/Component/Lock/Tests/Store/CombinedStoreTest.php @@ -23,6 +23,7 @@ /** * @author Jérémy Derussé + * @group integration */ class CombinedStoreTest extends AbstractStoreTest { @@ -41,7 +42,8 @@ protected function getClockDelay() */ public function getStore(): PersistingStoreInterface { - $redis = new \Predis\Client(array_combine(['host', 'port'], explode(':', getenv('REDIS_HOST')) + [1 => null])); + $redis = new \Predis\Client(array_combine(['host', 'port'], explode(':', getenv('REDIS_HOST')) + [1 => 6379])); + try { $redis->connect(); } catch (\Exception $e) { diff --git a/src/Symfony/Component/Lock/Tests/Store/PdoStoreTest.php b/src/Symfony/Component/Lock/Tests/Store/PdoStoreTest.php index 082f50aa75e63..0562800f28cc2 100644 --- a/src/Symfony/Component/Lock/Tests/Store/PdoStoreTest.php +++ b/src/Symfony/Component/Lock/Tests/Store/PdoStoreTest.php @@ -20,6 +20,7 @@ * @author Jérémy Derussé * * @requires extension pdo_sqlite + * @group integration */ class PdoStoreTest extends AbstractStoreTest { diff --git a/src/Symfony/Component/Lock/Tests/Store/ZookeeperStoreTest.php b/src/Symfony/Component/Lock/Tests/Store/ZookeeperStoreTest.php index b3cd685a23160..2e63b08eb5ad3 100644 --- a/src/Symfony/Component/Lock/Tests/Store/ZookeeperStoreTest.php +++ b/src/Symfony/Component/Lock/Tests/Store/ZookeeperStoreTest.php @@ -20,6 +20,7 @@ * @author Ganesh Chandrasekaran * * @requires extension zookeeper + * @group integration */ class ZookeeperStoreTest extends AbstractStoreTest { From 63f8f1edb609e18ed8b8e71b3ce618236fe6eaf7 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 22 Mar 2022 15:33:05 +0100 Subject: [PATCH 48/91] [HttpClient] Let curl handle Content-Length headers --- src/Symfony/Component/HttpClient/CurlHttpClient.php | 7 ++++++- .../Component/HttpClient/Tests/HttpClientTestCase.php | 11 +++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/HttpClient/CurlHttpClient.php b/src/Symfony/Component/HttpClient/CurlHttpClient.php index 660ba61ee9045..e02f2c8b17d50 100644 --- a/src/Symfony/Component/HttpClient/CurlHttpClient.php +++ b/src/Symfony/Component/HttpClient/CurlHttpClient.php @@ -202,7 +202,12 @@ public function request(string $method, string $url, array $options = []): Respo $options['headers'][] = 'Accept-Encoding: gzip'; // Expose only one encoding, some servers mess up when more are provided } + $hasContentLength = isset($options['normalized_headers']['content-length'][0]); + foreach ($options['headers'] as $header) { + if ($hasContentLength && 0 === stripos($header, 'Content-Length:')) { + continue; // Let curl handle Content-Length headers + } if (':' === $header[-2] && \strlen($header) - 2 === strpos($header, ': ')) { // curl requires a special syntax to send empty headers $curlopts[\CURLOPT_HTTPHEADER][] = substr_replace($header, ';', -2); @@ -229,7 +234,7 @@ public function request(string $method, string $url, array $options = []): Respo }; } - if (isset($options['normalized_headers']['content-length'][0])) { + if ($hasContentLength) { $curlopts[\CURLOPT_INFILESIZE] = substr($options['normalized_headers']['content-length'][0], \strlen('Content-Length: ')); } elseif (!isset($options['normalized_headers']['transfer-encoding'])) { $curlopts[\CURLOPT_HTTPHEADER][] = 'Transfer-Encoding: chunked'; // Enable chunked request bodies diff --git a/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php b/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php index 989c6f32af2e6..148d3e762cd7b 100644 --- a/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php +++ b/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php @@ -203,6 +203,17 @@ public function testNegativeTimeout() ])->getStatusCode()); } + public function testRedirectAfterPost() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('POST', 'http://localhost:8057/302/relative', [ + 'body' => 'abc', + ]); + + $this->assertSame(200, $response->getStatusCode()); + } + public function testNullBody() { $client = $this->getHttpClient(__FUNCTION__); From a64c6e6836b6caf781992d1a8976d27ef0dd6ea5 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 22 Mar 2022 16:23:06 +0100 Subject: [PATCH 49/91] [Mailer] Preserve case of headers --- .../Bridge/Mailchimp/Transport/MandrillApiTransport.php | 2 +- .../Mailgun/Tests/Transport/MailgunApiTransportTest.php | 4 ++-- .../Mailer/Bridge/Mailgun/Transport/MailgunApiTransport.php | 2 +- .../Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php | 2 +- .../Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillApiTransport.php index 068da4d7eb7a0..469354d67bbe7 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillApiTransport.php @@ -121,7 +121,7 @@ private function getPayload(Email $email, Envelope $envelope): array continue; } - $payload['message']['headers'][$name] = $header->getBodyAsString(); + $payload['message']['headers'][$header->getName()] = $header->getBodyAsString(); } return $payload; diff --git a/src/Symfony/Component/Mailer/Bridge/Mailgun/Tests/Transport/MailgunApiTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Mailgun/Tests/Transport/MailgunApiTransportTest.php index 2e4c53b83fc98..5f5b8f32362a1 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailgun/Tests/Transport/MailgunApiTransportTest.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailgun/Tests/Transport/MailgunApiTransportTest.php @@ -65,8 +65,8 @@ public function testCustomHeader() $method->setAccessible(true); $payload = $method->invoke($transport, $email, $envelope); - $this->assertArrayHasKey('h:x-mailgun-variables', $payload); - $this->assertEquals($json, $payload['h:x-mailgun-variables']); + $this->assertArrayHasKey('h:X-Mailgun-Variables', $payload); + $this->assertEquals($json, $payload['h:X-Mailgun-Variables']); } public function testSend() diff --git a/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunApiTransport.php index 329b6eeaef449..4607a07037d16 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunApiTransport.php @@ -120,7 +120,7 @@ private function getPayload(Email $email, Envelope $envelope): array continue; } - $payload['h:'.$name] = $header->getBodyAsString(); + $payload['h:'.$header->getName()] = $header->getBodyAsString(); } return $payload; diff --git a/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php index 4ccf8762b7a85..e1b4cd3dda8d4 100644 --- a/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php @@ -93,7 +93,7 @@ private function getPayload(Email $email, Envelope $envelope): array } $payload['Headers'][] = [ - 'Name' => $name, + 'Name' => $header->getName(), 'Value' => $header->getBodyAsString(), ]; } diff --git a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php index 07108f19aab3c..d198be82d7b35 100644 --- a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php @@ -121,7 +121,7 @@ private function getPayload(Email $email, Envelope $envelope): array continue; } - $payload['headers'][$name] = $header->getBodyAsString(); + $payload['headers'][$header->getName()] = $header->getBodyAsString(); } return $payload; From 1643e250a83cac714c05ba69a5281a4a5d43b0d9 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Thu, 10 Mar 2022 13:18:09 +0100 Subject: [PATCH 50/91] [Cache] Declaratively declare/hide DoctrineProvider to avoid breaking static analysis --- src/Symfony/Component/Cache/DoctrineProvider.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Symfony/Component/Cache/DoctrineProvider.php b/src/Symfony/Component/Cache/DoctrineProvider.php index e4255f0177b37..f6ac5745dfd12 100644 --- a/src/Symfony/Component/Cache/DoctrineProvider.php +++ b/src/Symfony/Component/Cache/DoctrineProvider.php @@ -15,6 +15,10 @@ use Psr\Cache\CacheItemPoolInterface; use Symfony\Contracts\Service\ResetInterface; +if (!class_exists(CacheProvider::class)) { + return; +} + /** * @author Nicolas Grekas */ From 5439bf299b1adeb22a37d98d5ef482d86904e6b4 Mon Sep 17 00:00:00 2001 From: gndk <25748293+gndk@users.noreply.github.com> Date: Fri, 18 Mar 2022 21:40:00 +0100 Subject: [PATCH 51/91] [FrameworkBundle] Fix exit codes in debug:translation command The `--only-missing` and `--only-unused` options should be independent of each other. When using the `--only-missing` option, only **missing** messages should be relevant to the outcome of the execution. If there are no missing messages, but some unused messages, the execution of the command was still successful and no non-zero exit code should be returned. The same applies when using the `--only-unused` option. In this case, only **unused** messages should be relevant to the execution result, even if there are some missing messages. --- .../Command/TranslationDebugCommand.php | 15 ++++++--- .../Command/TranslationDebugCommandTest.php | 31 ++++++++++++++----- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php index d56897d76029e..006fd250550b8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php @@ -133,7 +133,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $locale = $input->getArgument('locale'); $domain = $input->getOption('domain'); - $exitCode = 0; + $exitCode = self::SUCCESS; /** @var KernelInterface $kernel */ $kernel = $this->getApplication()->getKernel(); @@ -219,16 +219,21 @@ protected function execute(InputInterface $input, OutputInterface $output): int if (!$currentCatalogue->defines($messageId, $domain)) { $states[] = self::MESSAGE_MISSING; - $exitCode = $exitCode | self::EXIT_CODE_MISSING; + if (!$input->getOption('only-unused')) { + $exitCode = $exitCode | self::EXIT_CODE_MISSING; + } } } elseif ($currentCatalogue->defines($messageId, $domain)) { $states[] = self::MESSAGE_UNUSED; - $exitCode = $exitCode | self::EXIT_CODE_UNUSED; + if (!$input->getOption('only-missing')) { + $exitCode = $exitCode | self::EXIT_CODE_UNUSED; + } } - if (!\in_array(self::MESSAGE_UNUSED, $states) && true === $input->getOption('only-unused') - || !\in_array(self::MESSAGE_MISSING, $states) && true === $input->getOption('only-missing')) { + if (!\in_array(self::MESSAGE_UNUSED, $states) && $input->getOption('only-unused') + || !\in_array(self::MESSAGE_MISSING, $states) && $input->getOption('only-missing') + ) { continue; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationDebugCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationDebugCommandTest.php index d755e11e730af..70f94d6a34d48 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationDebugCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationDebugCommandTest.php @@ -15,6 +15,7 @@ use Symfony\Bundle\FrameworkBundle\Command\TranslationDebugCommand; use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\ExtensionWithoutConfigTestBundle\ExtensionWithoutConfigTestBundle; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Tester\CommandCompletionTester; use Symfony\Component\Console\Tester\CommandTester; use Symfony\Component\DependencyInjection\Container; @@ -36,7 +37,7 @@ public function testDebugMissingMessages() $res = $tester->execute(['locale' => 'en', 'bundle' => 'foo']); $this->assertMatchesRegularExpression('/missing/', $tester->getDisplay()); - $this->assertEquals(TranslationDebugCommand::EXIT_CODE_MISSING, $res); + $this->assertSame(TranslationDebugCommand::EXIT_CODE_MISSING, $res); } public function testDebugUnusedMessages() @@ -45,7 +46,7 @@ public function testDebugUnusedMessages() $res = $tester->execute(['locale' => 'en', 'bundle' => 'foo']); $this->assertMatchesRegularExpression('/unused/', $tester->getDisplay()); - $this->assertEquals(TranslationDebugCommand::EXIT_CODE_UNUSED, $res); + $this->assertSame(TranslationDebugCommand::EXIT_CODE_UNUSED, $res); } public function testDebugFallbackMessages() @@ -54,7 +55,7 @@ public function testDebugFallbackMessages() $res = $tester->execute(['locale' => 'fr', 'bundle' => 'foo']); $this->assertMatchesRegularExpression('/fallback/', $tester->getDisplay()); - $this->assertEquals(TranslationDebugCommand::EXIT_CODE_FALLBACK, $res); + $this->assertSame(TranslationDebugCommand::EXIT_CODE_FALLBACK, $res); } public function testNoDefinedMessages() @@ -63,7 +64,7 @@ public function testNoDefinedMessages() $res = $tester->execute(['locale' => 'fr', 'bundle' => 'test']); $this->assertMatchesRegularExpression('/No defined or extracted messages for locale "fr"/', $tester->getDisplay()); - $this->assertEquals(TranslationDebugCommand::EXIT_CODE_GENERAL_ERROR, $res); + $this->assertSame(TranslationDebugCommand::EXIT_CODE_GENERAL_ERROR, $res); } public function testDebugDefaultDirectory() @@ -74,7 +75,7 @@ public function testDebugDefaultDirectory() $this->assertMatchesRegularExpression('/missing/', $tester->getDisplay()); $this->assertMatchesRegularExpression('/unused/', $tester->getDisplay()); - $this->assertEquals($expectedExitStatus, $res); + $this->assertSame($expectedExitStatus, $res); } public function testDebugDefaultRootDirectory() @@ -92,7 +93,7 @@ public function testDebugDefaultRootDirectory() $this->assertMatchesRegularExpression('/missing/', $tester->getDisplay()); $this->assertMatchesRegularExpression('/unused/', $tester->getDisplay()); - $this->assertEquals($expectedExitStatus, $res); + $this->assertSame($expectedExitStatus, $res); } public function testDebugCustomDirectory() @@ -112,7 +113,7 @@ public function testDebugCustomDirectory() $this->assertMatchesRegularExpression('/missing/', $tester->getDisplay()); $this->assertMatchesRegularExpression('/unused/', $tester->getDisplay()); - $this->assertEquals($expectedExitStatus, $res); + $this->assertSame($expectedExitStatus, $res); } public function testDebugInvalidDirectory() @@ -128,6 +129,22 @@ public function testDebugInvalidDirectory() $tester->execute(['locale' => 'en', 'bundle' => 'dir']); } + public function testNoErrorWithOnlyMissingOptionAndNoResults() + { + $tester = $this->createCommandTester([], ['foo' => 'foo']); + $res = $tester->execute(['locale' => 'en', '--only-missing' => true]); + + $this->assertSame(Command::SUCCESS, $res); + } + + public function testNoErrorWithOnlyUnusedOptionAndNoResults() + { + $tester = $this->createCommandTester(['foo' => 'foo']); + $res = $tester->execute(['locale' => 'en', '--only-unused' => true]); + + $this->assertSame(Command::SUCCESS, $res); + } + protected function setUp(): void { $this->fs = new Filesystem(); From 67dd69b29da8b2b01620d083efa8b689279713cf Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 22 Mar 2022 17:01:27 +0100 Subject: [PATCH 52/91] Revert "bug #45813 [HttpClient] Move Content-Type after Content-Length (nicolas-grekas)" This reverts commit 13e0671ff9222d6797c5a44593c1a09f65e8a738, reversing changes made to 01f674975a2dd27340aab9b6e7402bc0aee8423f. --- src/Symfony/Component/HttpClient/HttpClientTrait.php | 7 ------- .../Component/HttpClient/Tests/ScopingHttpClientTest.php | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/Symfony/Component/HttpClient/HttpClientTrait.php b/src/Symfony/Component/HttpClient/HttpClientTrait.php index 20522255e9a97..8c9bd8d35526e 100644 --- a/src/Symfony/Component/HttpClient/HttpClientTrait.php +++ b/src/Symfony/Component/HttpClient/HttpClientTrait.php @@ -159,13 +159,6 @@ private static function prepareRequest(?string $method, ?string $url, array $opt } $options['max_duration'] = isset($options['max_duration']) ? (float) $options['max_duration'] : 0; - - if (isset($options['normalized_headers']['content-length']) && $contentType = $options['normalized_headers']['content-type'] ?? null) { - // Move Content-Type after Content-Length, see https://bugs.php.net/44603 - unset($options['normalized_headers']['content-type']); - $options['normalized_headers']['content-type'] = $contentType; - } - $options['headers'] = array_merge(...array_values($options['normalized_headers'])); return [$url, $options]; diff --git a/src/Symfony/Component/HttpClient/Tests/ScopingHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/ScopingHttpClientTest.php index a3e3099953ffe..bfca02b3581aa 100644 --- a/src/Symfony/Component/HttpClient/Tests/ScopingHttpClientTest.php +++ b/src/Symfony/Component/HttpClient/Tests/ScopingHttpClientTest.php @@ -73,7 +73,7 @@ public function testMatchingUrlsAndOptions() $response = $client->request('GET', 'http://example.com/foo-bar', ['json' => ['url' => 'http://example.com']]); $requestOptions = $response->getRequestOptions(); - $this->assertSame('Content-Type: application/json', $requestOptions['headers'][3]); + $this->assertSame('Content-Type: application/json', $requestOptions['headers'][1]); $requestJson = json_decode($requestOptions['body'], true); $this->assertSame('http://example.com', $requestJson['url']); $this->assertSame('X-FooBar: '.$defaultOptions['.*/foo-bar']['headers']['X-FooBar'], $requestOptions['headers'][0]); From 20d0806f2c2ef033deb4a3418669397c3f691ad3 Mon Sep 17 00:00:00 2001 From: Laurent VOULLEMIER Date: Sun, 20 Feb 2022 14:24:12 +0100 Subject: [PATCH 53/91] Allow to use a middleware instead of DbalLogger --- phpunit.xml.dist | 15 +- .../DataCollector/DoctrineDataCollector.php | 35 ++- .../Doctrine/Middleware/Debug/Connection.php | 186 +++++++++++++ .../Middleware/Debug/DebugDataHolder.php | 48 ++++ .../Doctrine/Middleware/Debug/Driver.php | 47 ++++ .../Doctrine/Middleware/Debug/Middleware.php | 40 +++ .../Doctrine/Middleware/Debug/Query.php | 121 +++++++++ .../Doctrine/Middleware/Debug/Statement.php | 72 +++++ .../DoctrineDataCollectorTest.php | 168 ++++-------- .../DoctrineDataCollectorTestTrait.php | 79 ++++++ ...octrineDataCollectorWithDebugStackTest.php | 195 ++++++++++++++ .../Tests/Middleware/Debug/MiddlewareTest.php | 253 ++++++++++++++++++ src/Symfony/Bridge/Doctrine/composer.json | 1 + src/Symfony/Bridge/Doctrine/phpunit.xml.dist | 10 + 14 files changed, 1134 insertions(+), 136 deletions(-) create mode 100644 src/Symfony/Bridge/Doctrine/Middleware/Debug/Connection.php create mode 100644 src/Symfony/Bridge/Doctrine/Middleware/Debug/DebugDataHolder.php create mode 100644 src/Symfony/Bridge/Doctrine/Middleware/Debug/Driver.php create mode 100644 src/Symfony/Bridge/Doctrine/Middleware/Debug/Middleware.php create mode 100644 src/Symfony/Bridge/Doctrine/Middleware/Debug/Query.php create mode 100644 src/Symfony/Bridge/Doctrine/Middleware/Debug/Statement.php create mode 100644 src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTestTrait.php create mode 100644 src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorWithDebugStackTest.php create mode 100644 src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 20df2e87ee3cd..dd3fac396ecf8 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -75,13 +75,14 @@ Cache\IntegrationTests Doctrine\Common\Cache - Symfony\Component\Cache - Symfony\Component\Cache\Tests\Fixtures - Symfony\Component\Cache\Tests\Traits - Symfony\Component\Cache\Traits - Symfony\Component\Console - Symfony\Component\HttpFoundation - Symfony\Component\Uid + Symfony\Bridge\Doctrine\Middleware\Debug + Symfony\Component\Cache + Symfony\Component\Cache\Tests\Fixtures + Symfony\Component\Cache\Tests\Traits + Symfony\Component\Cache\Traits + Symfony\Component\Console + Symfony\Component\HttpFoundation + Symfony\Component\Uid diff --git a/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php b/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php index 8e292cf36b8d7..8e500b56c1fe3 100644 --- a/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php +++ b/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php @@ -15,6 +15,7 @@ use Doctrine\DBAL\Types\ConversionException; use Doctrine\DBAL\Types\Type; use Doctrine\Persistence\ManagerRegistry; +use Symfony\Bridge\Doctrine\Middleware\Debug\DebugDataHolder; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\DataCollector\DataCollector; @@ -31,17 +32,19 @@ class DoctrineDataCollector extends DataCollector private $registry; private $connections; private $managers; + private $debugDataHolder; /** * @var DebugStack[] */ private $loggers = []; - public function __construct(ManagerRegistry $registry) + public function __construct(ManagerRegistry $registry, DebugDataHolder $debugDataHolder = null) { $this->registry = $registry; $this->connections = $registry->getConnectionNames(); $this->managers = $registry->getManagerNames(); + $this->debugDataHolder = $debugDataHolder; } /** @@ -56,23 +59,43 @@ public function addLogger(string $name, DebugStack $logger) * {@inheritdoc} */ public function collect(Request $request, Response $response, \Throwable $exception = null) + { + $this->data = [ + 'queries' => $this->collectQueries(), + 'connections' => $this->connections, + 'managers' => $this->managers, + ]; + } + + private function collectQueries(): array { $queries = []; + + if (null !== $this->debugDataHolder) { + foreach ($this->debugDataHolder->getData() as $name => $data) { + $queries[$name] = $this->sanitizeQueries($name, $data); + } + + return $queries; + } + foreach ($this->loggers as $name => $logger) { $queries[$name] = $this->sanitizeQueries($name, $logger->queries); } - $this->data = [ - 'queries' => $queries, - 'connections' => $this->connections, - 'managers' => $this->managers, - ]; + return $queries; } public function reset() { $this->data = []; + if (null !== $this->debugDataHolder) { + $this->debugDataHolder->reset(); + + return; + } + foreach ($this->loggers as $logger) { $logger->queries = []; $logger->currentQuery = 0; diff --git a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Connection.php b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Connection.php new file mode 100644 index 0000000000000..d085b0af0e3de --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Connection.php @@ -0,0 +1,186 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Middleware\Debug; + +use Doctrine\DBAL\Driver\Connection as ConnectionInterface; +use Doctrine\DBAL\Driver\Middleware\AbstractConnectionMiddleware; +use Doctrine\DBAL\Driver\Result; +use Doctrine\DBAL\Driver\Statement as DriverStatement; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * @author Laurent VOULLEMIER + * + * @internal + */ +final class Connection extends AbstractConnectionMiddleware +{ + private $nestingLevel = 0; + private $debugDataHolder; + private $stopwatch; + private $connectionName; + + public function __construct(ConnectionInterface $connection, DebugDataHolder $debugDataHolder, ?Stopwatch $stopwatch, string $connectionName) + { + parent::__construct($connection); + + $this->debugDataHolder = $debugDataHolder; + $this->stopwatch = $stopwatch; + $this->connectionName = $connectionName; + } + + public function prepare(string $sql): DriverStatement + { + return new Statement( + parent::prepare($sql), + $this->debugDataHolder, + $this->connectionName, + $sql + ); + } + + public function query(string $sql): Result + { + $this->debugDataHolder->addQuery($this->connectionName, $query = new Query($sql)); + + if (null !== $this->stopwatch) { + $this->stopwatch->start('doctrine', 'doctrine'); + } + + $query->start(); + + try { + $result = parent::query($sql); + } finally { + $query->stop(); + + if (null !== $this->stopwatch) { + $this->stopwatch->stop('doctrine'); + } + } + + return $result; + } + + public function exec(string $sql): int + { + $this->debugDataHolder->addQuery($this->connectionName, $query = new Query($sql)); + + if (null !== $this->stopwatch) { + $this->stopwatch->start('doctrine', 'doctrine'); + } + + $query->start(); + + try { + $affectedRows = parent::exec($sql); + } finally { + $query->stop(); + + if (null !== $this->stopwatch) { + $this->stopwatch->stop('doctrine'); + } + } + + return $affectedRows; + } + + public function beginTransaction(): bool + { + $query = null; + if (1 === ++$this->nestingLevel) { + $this->debugDataHolder->addQuery($this->connectionName, $query = new Query('"START TRANSACTION"')); + } + + if (null !== $this->stopwatch) { + $this->stopwatch->start('doctrine', 'doctrine'); + } + + if (null !== $query) { + $query->start(); + } + + try { + $ret = parent::beginTransaction(); + } finally { + if (null !== $query) { + $query->stop(); + } + + if (null !== $this->stopwatch) { + $this->stopwatch->stop('doctrine'); + } + } + + return $ret; + } + + public function commit(): bool + { + $query = null; + if (1 === $this->nestingLevel--) { + $this->debugDataHolder->addQuery($this->connectionName, $query = new Query('"COMMIT"')); + } + + if (null !== $this->stopwatch) { + $this->stopwatch->start('doctrine', 'doctrine'); + } + + if (null !== $query) { + $query->start(); + } + + try { + $ret = parent::commit(); + } finally { + if (null !== $query) { + $query->stop(); + } + + if (null !== $this->stopwatch) { + $this->stopwatch->stop('doctrine'); + } + } + + return $ret; + } + + public function rollBack(): bool + { + $query = null; + if (1 === $this->nestingLevel--) { + $this->debugDataHolder->addQuery($this->connectionName, $query = new Query('"ROLLBACK"')); + } + + if (null !== $this->stopwatch) { + $this->stopwatch->start('doctrine', 'doctrine'); + } + + if (null !== $query) { + $query->start(); + } + + try { + $ret = parent::rollBack(); + } finally { + if (null !== $query) { + $query->stop(); + } + + if (null !== $this->stopwatch) { + $this->stopwatch->stop('doctrine'); + } + } + + return $ret; + } +} diff --git a/src/Symfony/Bridge/Doctrine/Middleware/Debug/DebugDataHolder.php b/src/Symfony/Bridge/Doctrine/Middleware/Debug/DebugDataHolder.php new file mode 100644 index 0000000000000..2643cc7493830 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/DebugDataHolder.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Middleware\Debug; + +/** + * @author Laurent VOULLEMIER + */ +class DebugDataHolder +{ + private $data = []; + + public function addQuery(string $connectionName, Query $query): void + { + $this->data[$connectionName][] = [ + 'sql' => $query->getSql(), + 'params' => $query->getParams(), + 'types' => $query->getTypes(), + 'executionMS' => [$query, 'getDuration'], // stop() may not be called at this point + ]; + } + + public function getData(): array + { + foreach ($this->data as $connectionName => $dataForConn) { + foreach ($dataForConn as $idx => $data) { + if (\is_callable($data['executionMS'])) { + $this->data[$connectionName][$idx]['executionMS'] = $data['executionMS'](); + } + } + } + + return $this->data; + } + + public function reset(): void + { + $this->data = []; + } +} diff --git a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Driver.php b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Driver.php new file mode 100644 index 0000000000000..7f7fdd3bf0d8d --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Driver.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Middleware\Debug; + +use Doctrine\DBAL\Driver as DriverInterface; +use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * @author Laurent VOULLEMIER + * + * @internal + */ +final class Driver extends AbstractDriverMiddleware +{ + private $debugDataHolder; + private $stopwatch; + private $connectionName; + + public function __construct(DriverInterface $driver, DebugDataHolder $debugDataHolder, ?Stopwatch $stopwatch, string $connectionName) + { + parent::__construct($driver); + + $this->debugDataHolder = $debugDataHolder; + $this->stopwatch = $stopwatch; + $this->connectionName = $connectionName; + } + + public function connect(array $params): Connection + { + return new Connection( + parent::connect($params), + $this->debugDataHolder, + $this->stopwatch, + $this->connectionName + ); + } +} diff --git a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Middleware.php b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Middleware.php new file mode 100644 index 0000000000000..18f6a58d5e7a2 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Middleware.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Middleware\Debug; + +use Doctrine\DBAL\Driver as DriverInterface; +use Doctrine\DBAL\Driver\Middleware as MiddlewareInterface; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * Middleware to collect debug data. + * + * @author Laurent VOULLEMIER + */ +final class Middleware implements MiddlewareInterface +{ + private $debugDataHolder; + private $stopwatch; + private $connectionName; + + public function __construct(DebugDataHolder $debugDataHolder, ?Stopwatch $stopwatch, string $connectionName = 'default') + { + $this->debugDataHolder = $debugDataHolder; + $this->stopwatch = $stopwatch; + $this->connectionName = $connectionName; + } + + public function wrap(DriverInterface $driver): DriverInterface + { + return new Driver($driver, $this->debugDataHolder, $this->stopwatch, $this->connectionName); + } +} diff --git a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Query.php b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Query.php new file mode 100644 index 0000000000000..d652f620ce2e8 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Query.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Middleware\Debug; + +use Doctrine\DBAL\ParameterType; + +/** + * @author Laurent VOULLEMIER + * + * @internal + */ +class Query +{ + private $params = []; + private $types = []; + + private $start; + private $duration; + + private $sql; + + public function __construct(string $sql) + { + $this->sql = $sql; + } + + public function start(): void + { + $this->start = microtime(true); + } + + public function stop(): void + { + if (null !== $this->start) { + $this->duration = microtime(true) - $this->start; + } + } + + /** + * @param string|int $param + * @param string|int|float|bool|null $variable + */ + public function setParam($param, &$variable, int $type): void + { + // Numeric indexes start at 0 in profiler + $idx = \is_int($param) ? $param - 1 : $param; + + $this->params[$idx] = &$variable; + $this->types[$idx] = $type; + } + + /** + * @param string|int $param + * @param string|int|float|bool|null $value + */ + public function setValue($param, $value, int $type): void + { + // Numeric indexes start at 0 in profiler + $idx = \is_int($param) ? $param - 1 : $param; + + $this->params[$idx] = $value; + $this->types[$idx] = $type; + } + + /** + * @param array $values + */ + public function setValues(array $values): void + { + foreach ($values as $param => $value) { + $this->setValue($param, $value, ParameterType::STRING); + } + } + + public function getSql(): string + { + return $this->sql; + } + + /** + * @return array + */ + public function getParams(): array + { + return $this->params; + } + + /** + * @return array + */ + public function getTypes(): array + { + return $this->types; + } + + /** + * Query duration in seconds. + */ + public function getDuration(): ?float + { + return $this->duration; + } + + public function __clone() + { + $copy = []; + foreach ($this->params as $param => $valueOrVariable) { + $copy[$param] = $valueOrVariable; + } + $this->params = $copy; + } +} diff --git a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Statement.php b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Statement.php new file mode 100644 index 0000000000000..e52530e906dc2 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Statement.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Middleware\Debug; + +use Doctrine\DBAL\Driver\Middleware\AbstractStatementMiddleware; +use Doctrine\DBAL\Driver\Result as ResultInterface; +use Doctrine\DBAL\Driver\Statement as StatementInterface; +use Doctrine\DBAL\ParameterType; + +/** + * @author Laurent VOULLEMIER + * + * @internal + */ +final class Statement extends AbstractStatementMiddleware +{ + private $debugDataHolder; + private $connectionName; + private $query; + + public function __construct(StatementInterface $statement, DebugDataHolder $debugDataHolder, string $connectionName, string $sql) + { + parent::__construct($statement); + + $this->debugDataHolder = $debugDataHolder; + $this->connectionName = $connectionName; + $this->query = new Query($sql); + } + + public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool + { + $this->query->setParam($param, $variable, $type); + + return parent::bindParam($param, $variable, $type, ...\array_slice(\func_get_args(), 3)); + } + + public function bindValue($param, $value, $type = ParameterType::STRING): bool + { + $this->query->setValue($param, $value, $type); + + return parent::bindValue($param, $value, $type); + } + + public function execute($params = null): ResultInterface + { + if (null !== $params) { + $this->query->setValues($params); + } + + // clone to prevent variables by reference to change + $this->debugDataHolder->addQuery($this->connectionName, $query = clone $this->query); + + $query->start(); + + try { + $result = parent::execute($params); + } finally { + $query->stop(); + } + + return $result; + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTest.php b/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTest.php index 35fc48ff1536f..25cc33fb4ae9f 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTest.php @@ -12,11 +12,14 @@ namespace Symfony\Bridge\Doctrine\Tests\DataCollector; use Doctrine\DBAL\Connection; -use Doctrine\DBAL\Logging\DebugStack; +use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\MySQLPlatform; use Doctrine\Persistence\ManagerRegistry; use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\DataCollector\DoctrineDataCollector; +use Symfony\Bridge\Doctrine\Middleware\Debug\DebugDataHolder; +use Symfony\Bridge\Doctrine\Middleware\Debug\Query; +use Symfony\Bridge\PhpUnit\ClockMock; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\VarDumper\Cloner\Data; @@ -27,66 +30,40 @@ class_exists(\Doctrine\DBAL\Platforms\MySqlPlatform::class); class DoctrineDataCollectorTest extends TestCase { - public function testCollectConnections() - { - $c = $this->createCollector([]); - $c->collect(new Request(), new Response()); - $this->assertEquals(['default' => 'doctrine.dbal.default_connection'], $c->getConnections()); - } - - public function testCollectManagers() - { - $c = $this->createCollector([]); - $c->collect(new Request(), new Response()); - $this->assertEquals(['default' => 'doctrine.orm.default_entity_manager'], $c->getManagers()); - } + use DoctrineDataCollectorTestTrait; - public function testCollectQueryCount() + protected function setUp(): void { - $c = $this->createCollector([]); - $c->collect(new Request(), new Response()); - $this->assertEquals(0, $c->getQueryCount()); - - $queries = [ - ['sql' => 'SELECT * FROM table1', 'params' => [], 'types' => [], 'executionMS' => 0], - ]; - $c = $this->createCollector($queries); - $c->collect(new Request(), new Response()); - $this->assertEquals(1, $c->getQueryCount()); + ClockMock::register(self::class); + ClockMock::withClockMock(1500000000); } - public function testCollectTime() + public function testReset() { - $c = $this->createCollector([]); - $c->collect(new Request(), new Response()); - $this->assertEquals(0, $c->getTime()); - $queries = [ ['sql' => 'SELECT * FROM table1', 'params' => [], 'types' => [], 'executionMS' => 1], ]; $c = $this->createCollector($queries); $c->collect(new Request(), new Response()); - $this->assertEquals(1, $c->getTime()); - $queries = [ - ['sql' => 'SELECT * FROM table1', 'params' => [], 'types' => [], 'executionMS' => 1], - ['sql' => 'SELECT * FROM table2', 'params' => [], 'types' => [], 'executionMS' => 2], - ]; - $c = $this->createCollector($queries); + $c->reset(); $c->collect(new Request(), new Response()); - $this->assertEquals(3, $c->getTime()); + $c = unserialize(serialize($c)); + + $this->assertEquals([], $c->getQueries()); } /** * @dataProvider paramProvider */ - public function testCollectQueries($param, $types, $expected, $explainable, bool $runnable = true) + public function testCollectQueries($param, $types, $expected) { $queries = [ ['sql' => 'SELECT * FROM table1 WHERE field1 = ?1', 'params' => [$param], 'types' => $types, 'executionMS' => 1], ]; $c = $this->createCollector($queries); $c->collect(new Request(), new Response()); + $c = unserialize(serialize($c)); $collectedQueries = $c->getQueries(); @@ -102,8 +79,8 @@ public function testCollectQueries($param, $types, $expected, $explainable, bool $this->assertEquals($expected, $collectedParam); } - $this->assertEquals($explainable, $collectedQueries['default'][0]['explainable']); - $this->assertSame($runnable, $collectedQueries['default'][0]['runnable']); + $this->assertTrue($collectedQueries['default'][0]['explainable']); + $this->assertTrue($collectedQueries['default'][0]['runnable']); } public function testCollectQueryWithNoParams() @@ -114,6 +91,7 @@ public function testCollectQueryWithNoParams() ]; $c = $this->createCollector($queries); $c->collect(new Request(), new Response()); + $c = unserialize(serialize($c)); $collectedQueries = $c->getQueries(); $this->assertInstanceOf(Data::class, $collectedQueries['default'][0]['params']); @@ -126,36 +104,10 @@ public function testCollectQueryWithNoParams() $this->assertTrue($collectedQueries['default'][1]['runnable']); } - public function testCollectQueryWithNoTypes() - { - $queries = [ - ['sql' => 'SET sql_mode=(SELECT REPLACE(@@sql_mode, \'ONLY_FULL_GROUP_BY\', \'\'))', 'params' => [], 'types' => null, 'executionMS' => 1], - ]; - $c = $this->createCollector($queries); - $c->collect(new Request(), new Response()); - - $collectedQueries = $c->getQueries(); - $this->assertSame([], $collectedQueries['default'][0]['types']); - } - - public function testReset() - { - $queries = [ - ['sql' => 'SELECT * FROM table1', 'params' => [], 'types' => [], 'executionMS' => 1], - ]; - $c = $this->createCollector($queries); - $c->collect(new Request(), new Response()); - - $c->reset(); - $c->collect(new Request(), new Response()); - - $this->assertEquals(['default' => []], $c->getQueries()); - } - /** * @dataProvider paramProvider */ - public function testSerialization($param, array $types, $expected, $explainable, bool $runnable = true) + public function testSerialization($param, array $types, $expected) { $queries = [ ['sql' => 'SELECT * FROM table1 WHERE field1 = ?1', 'params' => [$param], 'types' => $types, 'executionMS' => 1], @@ -178,55 +130,17 @@ public function testSerialization($param, array $types, $expected, $explainable, $this->assertEquals($expected, $collectedParam); } - $this->assertEquals($explainable, $collectedQueries['default'][0]['explainable']); - $this->assertSame($runnable, $collectedQueries['default'][0]['runnable']); + $this->assertTrue($collectedQueries['default'][0]['explainable']); + $this->assertTrue($collectedQueries['default'][0]['runnable']); } public function paramProvider(): array { return [ - ['some value', [], 'some value', true], - [1, [], 1, true], - [true, [], true, true], - [null, [], null, true], - [new \DateTime('2011-09-11'), ['date'], '2011-09-11', true], - [fopen(__FILE__, 'r'), [], '/* Resource(stream) */', false, false], - [ - new \stdClass(), - [], - <<method('getConnection') ->willReturn($connection); - $logger = $this->createMock(DebugStack::class); - $logger->queries = $queries; + $debugDataHolder = new DebugDataHolder(); + $collector = new DoctrineDataCollector($registry, $debugDataHolder); + foreach ($queries as $queryData) { + $query = new Query($queryData['sql'] ?? ''); + foreach (($queryData['params'] ?? []) as $key => $value) { + if (\is_int($key)) { + ++$key; + } - $collector = new DoctrineDataCollector($registry); - $collector->addLogger('default', $logger); + $query->setValue($key, $value, $queryData['type'][$key] ?? ParameterType::STRING); + } - return $collector; - } -} + $query->start(); -class StringRepresentableClass -{ - public function __toString(): string - { - return 'string representation'; + $debugDataHolder->addQuery('default', $query); + + if (isset($queryData['executionMS'])) { + sleep($queryData['executionMS']); + } + $query->stop(); + } + + return $collector; } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTestTrait.php b/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTestTrait.php new file mode 100644 index 0000000000000..23977a3be9881 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTestTrait.php @@ -0,0 +1,79 @@ +createCollector([]); + $c->collect(new Request(), new Response()); + $c = unserialize(serialize($c)); + $this->assertEquals(['default' => 'doctrine.dbal.default_connection'], $c->getConnections()); + } + + public function testCollectManagers() + { + $c = $this->createCollector([]); + $c->collect(new Request(), new Response()); + $c = unserialize(serialize($c)); + $this->assertEquals(['default' => 'doctrine.orm.default_entity_manager'], $c->getManagers()); + } + + public function testCollectQueryCount() + { + $c = $this->createCollector([]); + $c->collect(new Request(), new Response()); + $c = unserialize(serialize($c)); + $this->assertEquals(0, $c->getQueryCount()); + + $queries = [ + ['sql' => 'SELECT * FROM table1', 'params' => [], 'types' => [], 'executionMS' => 0], + ]; + $c = $this->createCollector($queries); + $c->collect(new Request(), new Response()); + $c = unserialize(serialize($c)); + $this->assertEquals(1, $c->getQueryCount()); + } + + public function testCollectTime() + { + $c = $this->createCollector([]); + $c->collect(new Request(), new Response()); + $c = unserialize(serialize($c)); + $this->assertEquals(0, $c->getTime()); + + $queries = [ + ['sql' => 'SELECT * FROM table1', 'params' => [], 'types' => [], 'executionMS' => 1], + ]; + $c = $this->createCollector($queries); + $c->collect(new Request(), new Response()); + $c = unserialize(serialize($c)); + $this->assertEquals(1, $c->getTime()); + + $queries = [ + ['sql' => 'SELECT * FROM table1', 'params' => [], 'types' => [], 'executionMS' => 1], + ['sql' => 'SELECT * FROM table2', 'params' => [], 'types' => [], 'executionMS' => 2], + ]; + $c = $this->createCollector($queries); + $c->collect(new Request(), new Response()); + $c = unserialize(serialize($c)); + $this->assertEquals(3, $c->getTime()); + } + + public function testCollectQueryWithNoTypes() + { + $queries = [ + ['sql' => 'SET sql_mode=(SELECT REPLACE(@@sql_mode, \'ONLY_FULL_GROUP_BY\', \'\'))', 'params' => [], 'types' => null, 'executionMS' => 1], + ]; + $c = $this->createCollector($queries); + $c->collect(new Request(), new Response()); + $c = unserialize(serialize($c)); + + $collectedQueries = $c->getQueries(); + $this->assertSame([], $collectedQueries['default'][0]['types']); + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorWithDebugStackTest.php b/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorWithDebugStackTest.php new file mode 100644 index 0000000000000..f0962eff3132d --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorWithDebugStackTest.php @@ -0,0 +1,195 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\DataCollector; + +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Logging\DebugStack; +use Doctrine\DBAL\Platforms\MySQLPlatform; +use Doctrine\Persistence\ManagerRegistry; +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\Doctrine\DataCollector\DoctrineDataCollector; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\CliDumper; + +// Doctrine DBAL 2 compatibility +class_exists(\Doctrine\DBAL\Platforms\MySqlPlatform::class); + +/** + * @group legacy + */ +class DoctrineDataCollectorWithDebugStackTest extends TestCase +{ + use DoctrineDataCollectorTestTrait; + + public function testReset() + { + $queries = [ + ['sql' => 'SELECT * FROM table1', 'params' => [], 'types' => [], 'executionMS' => 1], + ]; + $c = $this->createCollector($queries); + $c->collect(new Request(), new Response()); + + $c->reset(); + $c->collect(new Request(), new Response()); + $c = unserialize(serialize($c)); + + $this->assertEquals(['default' => []], $c->getQueries()); + } + + /** + * @dataProvider paramProvider + */ + public function testCollectQueries($param, $types, $expected, $explainable, bool $runnable = true) + { + $queries = [ + ['sql' => 'SELECT * FROM table1 WHERE field1 = ?1', 'params' => [$param], 'types' => $types, 'executionMS' => 1], + ]; + $c = $this->createCollector($queries); + $c->collect(new Request(), new Response()); + $c = unserialize(serialize($c)); + + $collectedQueries = $c->getQueries(); + + $collectedParam = $collectedQueries['default'][0]['params'][0]; + if ($collectedParam instanceof Data) { + $dumper = new CliDumper($out = fopen('php://memory', 'r+')); + $dumper->setColors(false); + $collectedParam->dump($dumper); + $this->assertStringMatchesFormat($expected, print_r(stream_get_contents($out, -1, 0), true)); + } elseif (\is_string($expected)) { + $this->assertStringMatchesFormat($expected, $collectedParam); + } else { + $this->assertEquals($expected, $collectedParam); + } + + $this->assertEquals($explainable, $collectedQueries['default'][0]['explainable']); + $this->assertSame($runnable, $collectedQueries['default'][0]['runnable']); + } + + /** + * @dataProvider paramProvider + */ + public function testSerialization($param, array $types, $expected, $explainable, bool $runnable = true) + { + $queries = [ + ['sql' => 'SELECT * FROM table1 WHERE field1 = ?1', 'params' => [$param], 'types' => $types, 'executionMS' => 1], + ]; + $c = $this->createCollector($queries); + $c->collect(new Request(), new Response()); + $c = unserialize(serialize($c)); + + $collectedQueries = $c->getQueries(); + + $collectedParam = $collectedQueries['default'][0]['params'][0]; + if ($collectedParam instanceof Data) { + $dumper = new CliDumper($out = fopen('php://memory', 'r+')); + $dumper->setColors(false); + $collectedParam->dump($dumper); + $this->assertStringMatchesFormat($expected, print_r(stream_get_contents($out, -1, 0), true)); + } elseif (\is_string($expected)) { + $this->assertStringMatchesFormat($expected, $collectedParam); + } else { + $this->assertEquals($expected, $collectedParam); + } + + $this->assertEquals($explainable, $collectedQueries['default'][0]['explainable']); + $this->assertSame($runnable, $collectedQueries['default'][0]['runnable']); + } + + public function paramProvider(): array + { + return [ + ['some value', [], 'some value', true], + [1, [], 1, true], + [true, [], true, true], + [null, [], null, true], + [new \DateTime('2011-09-11'), ['date'], '2011-09-11', true], + [fopen(__FILE__, 'r'), [], '/* Resource(stream) */', false, false], + [ + new \stdClass(), + [], + <<getMockBuilder(Connection::class) + ->disableOriginalConstructor() + ->getMock(); + $connection->expects($this->any()) + ->method('getDatabasePlatform') + ->willReturn(new MySqlPlatform()); + + $registry = $this->createMock(ManagerRegistry::class); + $registry + ->expects($this->any()) + ->method('getConnectionNames') + ->willReturn(['default' => 'doctrine.dbal.default_connection']); + $registry + ->expects($this->any()) + ->method('getManagerNames') + ->willReturn(['default' => 'doctrine.orm.default_entity_manager']); + $registry->expects($this->any()) + ->method('getConnection') + ->willReturn($connection); + + $collector = new DoctrineDataCollector($registry); + $logger = $this->createMock(DebugStack::class); + $logger->queries = $queries; + $collector->addLogger('default', $logger); + + return $collector; + } +} + +class StringRepresentableClass +{ + public function __toString(): string + { + return 'string representation'; + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php b/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php new file mode 100644 index 0000000000000..e43cec8b98650 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php @@ -0,0 +1,253 @@ +markTestSkipped(sprintf('%s needed to run this test', MiddlewareInterface::class)); + } + + ClockMock::withClockMock(false); + } + + private function init(bool $withStopwatch = true): void + { + $this->stopwatch = $withStopwatch ? new Stopwatch() : null; + + $configuration = new Configuration(); + $this->debugDataHolder = new DebugDataHolder(); + $configuration->setMiddlewares([new Middleware($this->debugDataHolder, $this->stopwatch)]); + + $this->conn = DriverManager::getConnection([ + 'driver' => 'pdo_sqlite', + 'memory' => true, + ], $configuration); + + $this->conn->executeQuery(<< [ + static function(object $target, ...$args) { + return $target->executeStatement(...$args); + }, + ], + 'executeQuery' => [ + static function(object $target, ...$args): Result { + return $target->executeQuery(...$args); + }, + ], + ]; + } + + /** + * @dataProvider provideExecuteMethod + */ + public function testWithoutBinding(callable $executeMethod) + { + $this->init(); + + $executeMethod($this->conn, 'INSERT INTO products(name, price, stock) VALUES ("product1", 12.5, 5)'); + + $debug = $this->debugDataHolder->getData()['default'] ?? []; + $this->assertCount(2, $debug); + $this->assertSame('INSERT INTO products(name, price, stock) VALUES ("product1", 12.5, 5)', $debug[1]['sql']); + $this->assertSame([], $debug[1]['params']); + $this->assertSame([], $debug[1]['types']); + $this->assertGreaterThan(0, $debug[1]['executionMS']); + } + + /** + * @dataProvider provideExecuteMethod + */ + public function testWithValueBound(callable $executeMethod) + { + $this->init(); + + $stmt = $this->conn->prepare('INSERT INTO products(name, price, stock) VALUES (?, ?, ?)'); + $stmt->bindValue(1, 'product1'); + $stmt->bindValue(2, 12.5); + $stmt->bindValue(3, 5, ParameterType::INTEGER); + + $executeMethod($stmt); + + $debug = $this->debugDataHolder->getData()['default'] ?? []; + $this->assertCount(2, $debug); + $this->assertSame('INSERT INTO products(name, price, stock) VALUES (?, ?, ?)', $debug[1]['sql']); + $this->assertSame(['product1', 12.5, 5], $debug[1]['params']); + $this->assertSame([ParameterType::STRING, ParameterType::STRING, ParameterType::INTEGER], $debug[1]['types']); + $this->assertGreaterThan(0, $debug[1]['executionMS']); + } + + /** + * @dataProvider provideExecuteMethod + */ + public function testWithParamBound(callable $executeMethod) + { + $this->init(); + + $product = 'product1'; + $price = 12.5; + $stock = 5; + + $stmt = $this->conn->prepare('INSERT INTO products(name, price, stock) VALUES (?, ?, ?)'); + $stmt->bindParam(1, $product); + $stmt->bindParam(2, $price); + $stmt->bindParam(3, $stock, ParameterType::INTEGER); + + $executeMethod($stmt); + + // Debug data should not be affected by these changes + $product = 'product2'; + $price = 13.5; + $stock = 4; + + $debug = $this->debugDataHolder->getData()['default'] ?? []; + $this->assertCount(2, $debug); + $this->assertSame('INSERT INTO products(name, price, stock) VALUES (?, ?, ?)', $debug[1]['sql']); + $this->assertSame(['product1', '12.5', 5], $debug[1]['params']); + $this->assertSame([ParameterType::STRING, ParameterType::STRING, ParameterType::INTEGER], $debug[1]['types']); + $this->assertGreaterThan(0, $debug[1]['executionMS']); + } + + public function provideEndTransactionMethod(): array + { + return [ + 'commit' => [ + static function(Connection $conn): bool { + return $conn->commit(); + }, + '"COMMIT"', + ], + 'rollback' => [ + static function(Connection $conn): bool { + return $conn->rollBack(); + }, + '"ROLLBACK"', + ], + ]; + } + + /** + * @dataProvider provideEndTransactionMethod + */ + public function testTransaction(callable $endTransactionMethod, string $expectedEndTransactionDebug) + { + $this->init(); + + $this->conn->beginTransaction(); + $this->conn->beginTransaction(); + $this->conn->executeStatement('INSERT INTO products(name, price, stock) VALUES ("product1", 12.5, 5)'); + $endTransactionMethod($this->conn); + $endTransactionMethod($this->conn); + $this->conn->beginTransaction(); + $this->conn->executeStatement('INSERT INTO products(name, price, stock) VALUES ("product2", 15.5, 12)'); + $endTransactionMethod($this->conn); + + $debug = $this->debugDataHolder->getData()['default'] ?? []; + $this->assertCount(7, $debug); + $this->assertSame('"START TRANSACTION"', $debug[1]['sql']); + $this->assertGreaterThan(0, $debug[1]['executionMS']); + $this->assertSame('INSERT INTO products(name, price, stock) VALUES ("product1", 12.5, 5)', $debug[2]['sql']); + $this->assertGreaterThan(0, $debug[2]['executionMS']); + $this->assertSame($expectedEndTransactionDebug, $debug[3]['sql']); + $this->assertGreaterThan(0, $debug[3]['executionMS']); + $this->assertSame('"START TRANSACTION"', $debug[4]['sql']); + $this->assertGreaterThan(0, $debug[4]['executionMS']); + $this->assertSame('INSERT INTO products(name, price, stock) VALUES ("product2", 15.5, 12)', $debug[5]['sql']); + $this->assertGreaterThan(0, $debug[5]['executionMS']); + $this->assertSame($expectedEndTransactionDebug, $debug[6]['sql']); + $this->assertGreaterThan(0, $debug[6]['executionMS']); + } + + public function provideExecuteAndEndTransactionMethods(): array + { + return [ + 'commit and exec' => [ + static function(Connection $conn, string $sql) { + return $conn->executeStatement($sql); + }, + static function(Connection $conn): bool { + return $conn->commit(); + }, + ], + 'rollback and query' => [ + static function(Connection $conn, string $sql): Result { + return $conn->executeQuery($sql); + }, + static function(Connection $conn): bool { + return $conn->rollBack(); + }, + ], + ]; + } + + /** + * @dataProvider provideExecuteAndEndTransactionMethods + */ + public function testGlobalDoctrineDuration(callable $sqlMethod, callable $endTransactionMethod) + { + $this->init(); + + $periods = $this->stopwatch->getEvent('doctrine')->getPeriods(); + $this->assertCount(1, $periods); + + $this->conn->beginTransaction(); + + $this->assertFalse($this->stopwatch->getEvent('doctrine')->isStarted()); + $this->assertCount(2, $this->stopwatch->getEvent('doctrine')->getPeriods()); + + $sqlMethod($this->conn, 'SELECT * FROM products'); + + $this->assertFalse($this->stopwatch->getEvent('doctrine')->isStarted()); + $this->assertCount(3, $this->stopwatch->getEvent('doctrine')->getPeriods()); + + $endTransactionMethod($this->conn); + + $this->assertFalse($this->stopwatch->getEvent('doctrine')->isStarted()); + $this->assertCount(4, $this->stopwatch->getEvent('doctrine')->getPeriods()); + } + + /** + * @dataProvider provideExecuteAndEndTransactionMethods + */ + public function testWithoutStopwatch(callable $sqlMethod, callable $endTransactionMethod) + { + $this->init(false); + + $this->conn->beginTransaction(); + $sqlMethod($this->conn, 'SELECT * FROM products'); + $endTransactionMethod($this->conn); + } +} diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index 5d8e8485c73e9..dadb456cc2029 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -34,6 +34,7 @@ "symfony/http-kernel": "^5.0|^6.0", "symfony/messenger": "^4.4|^5.0|^6.0", "symfony/doctrine-messenger": "^5.1|^6.0", + "symfony/phpunit-bridge": "^4.4|^5.4|^6.0", "symfony/property-access": "^4.4|^5.0|^6.0", "symfony/property-info": "^5.0|^6.0", "symfony/proxy-manager-bridge": "^4.4|^5.0|^6.0", diff --git a/src/Symfony/Bridge/Doctrine/phpunit.xml.dist b/src/Symfony/Bridge/Doctrine/phpunit.xml.dist index fa76fa9b500e7..31a2546b47ec4 100644 --- a/src/Symfony/Bridge/Doctrine/phpunit.xml.dist +++ b/src/Symfony/Bridge/Doctrine/phpunit.xml.dist @@ -28,4 +28,14 @@ + + + + + + Symfony\Bridge\Doctrine\Middleware\Debug + + + + From cd4d65553067c64b580bc0ef0a1c85f6754f0c7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Wed, 23 Mar 2022 09:05:11 +0100 Subject: [PATCH 54/91] Add missing upgrade note for ldap --- UPGRADE-5.4.md | 5 +++++ UPGRADE-6.0.md | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/UPGRADE-5.4.md b/UPGRADE-5.4.md index 97f221ae6f9a8..97fda0a80e38f 100644 --- a/UPGRADE-5.4.md +++ b/UPGRADE-5.4.md @@ -52,6 +52,11 @@ HttpFoundation * Mark `Request::get()` internal, use explicit input sources instead * Deprecate `upload_progress.*` and `url_rewriter.tags` session options +Ldap +---- + + * Deprecate `LdapAuthenticator::createAuthenticatedToken()`, use `LdapAuthenticator::createToken()` instead + Lock ---- diff --git a/UPGRADE-6.0.md b/UPGRADE-6.0.md index e70e3e5d2a917..5f617233855b7 100644 --- a/UPGRADE-6.0.md +++ b/UPGRADE-6.0.md @@ -146,6 +146,11 @@ Inflector * The component has been removed, use `EnglishInflector` from the String component instead. +Ldap +---- + +* Remove `LdapAuthenticator::createAuthenticatedToken()`, use `LdapAuthenticator::createToken()` instead + Lock ---- From c00e58ceec72aed0d8c524f32ce4bce7ecb080e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Mon, 21 Mar 2022 20:06:53 +0100 Subject: [PATCH 55/91] Fix compatibility of ldap 6.0 with security 5.x --- .../Ldap/Security/LdapAuthenticator.php | 9 ++++ .../Tests/Security/LdapAuthenticatorTest.php | 45 +++++++++++++++++++ src/Symfony/Component/Ldap/composer.json | 3 +- 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Component/Ldap/Tests/Security/LdapAuthenticatorTest.php diff --git a/src/Symfony/Component/Ldap/Security/LdapAuthenticator.php b/src/Symfony/Component/Ldap/Security/LdapAuthenticator.php index 02a423c239ce5..ac915c0c47d43 100644 --- a/src/Symfony/Component/Ldap/Security/LdapAuthenticator.php +++ b/src/Symfony/Component/Ldap/Security/LdapAuthenticator.php @@ -18,6 +18,7 @@ use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; use Symfony\Component\Security\Http\Authenticator\InteractiveAuthenticatorInterface; use Symfony\Component\Security\Http\Authenticator\Passport\Passport; +use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface; use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; use Symfony\Component\Security\Http\EntryPoint\Exception\NotAnEntryPointException; @@ -64,6 +65,14 @@ public function authenticate(Request $request): Passport return $passport; } + /** + * @internal + */ + public function createAuthenticatedToken(PassportInterface $passport, string $firewallName): TokenInterface + { + throw new \BadMethodCallException(sprintf('The "%s()" method cannot be called.', __METHOD__)); + } + public function createToken(Passport $passport, string $firewallName): TokenInterface { return $this->authenticator->createToken($passport, $firewallName); diff --git a/src/Symfony/Component/Ldap/Tests/Security/LdapAuthenticatorTest.php b/src/Symfony/Component/Ldap/Tests/Security/LdapAuthenticatorTest.php new file mode 100644 index 0000000000000..52857cc496792 --- /dev/null +++ b/src/Symfony/Component/Ldap/Tests/Security/LdapAuthenticatorTest.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Ldap\Tests\Security; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Ldap\Security\LdapAuthenticator; +use Symfony\Component\Ldap\Security\LdapBadge; +use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials; +use Symfony\Component\Security\Http\Authenticator\Passport\Passport; + +class LdapAuthenticatorTest extends TestCase +{ + public function testAuthenticate() + { + $decorated = $this->createMock(AuthenticatorInterface::class); + $passport = new Passport(new UserBadge('test'), new PasswordCredentials('s3cret')); + $decorated + ->expects($this->once()) + ->method('authenticate') + ->willReturn($passport) + ; + + $authenticator = new LdapAuthenticator($decorated, 'serviceId'); + $request = new Request(); + + $authenticator->authenticate($request); + + /** @var LdapBadge $badge */ + $badge = $passport->getBadge(LdapBadge::class); + $this->assertNotNull($badge); + $this->assertSame('serviceId', $badge->getLdapServiceId()); + } +} diff --git a/src/Symfony/Component/Ldap/composer.json b/src/Symfony/Component/Ldap/composer.json index 51b2570a1870c..073c7c8ff692a 100644 --- a/src/Symfony/Component/Ldap/composer.json +++ b/src/Symfony/Component/Ldap/composer.json @@ -21,7 +21,8 @@ "symfony/options-resolver": "^5.4|^6.0" }, "require-dev": { - "symfony/security-core": "^5.4|^6.0" + "symfony/security-core": "^5.4|^6.0", + "symfony/security-http": "^5.4|^6.0" }, "conflict": { "symfony/options-resolver": "<5.4", From 409897b68e308a88be67031d88846afaa8545d58 Mon Sep 17 00:00:00 2001 From: Ivan Kurnosov Date: Thu, 24 Mar 2022 14:01:56 +1300 Subject: [PATCH 56/91] bug #42637 [Security] Fixed TOCTOU in RememberMe cache token verifier --- .../Core/Authentication/RememberMe/CacheTokenVerifier.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Security/Core/Authentication/RememberMe/CacheTokenVerifier.php b/src/Symfony/Component/Security/Core/Authentication/RememberMe/CacheTokenVerifier.php index dabc719055fcf..340bc87c2e32e 100644 --- a/src/Symfony/Component/Security/Core/Authentication/RememberMe/CacheTokenVerifier.php +++ b/src/Symfony/Component/Security/Core/Authentication/RememberMe/CacheTokenVerifier.php @@ -45,11 +45,11 @@ public function verifyToken(PersistentTokenInterface $token, string $tokenValue) } $cacheKey = $this->getCacheKey($token); - if (!$this->cache->hasItem($cacheKey)) { + $item = $this->cache->getItem($cacheKey); + if (!$item->isHit()) { return false; } - $item = $this->cache->getItem($cacheKey); $outdatedToken = $item->get(); return hash_equals($outdatedToken, $tokenValue); From 2b257e2b526a4849bb0fb492882aba83a66909b6 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Thu, 24 Mar 2022 14:38:54 +0100 Subject: [PATCH 57/91] [HttpFoundation] Remove obsolete override --- src/Symfony/Component/HttpFoundation/InputBag.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Symfony/Component/HttpFoundation/InputBag.php b/src/Symfony/Component/HttpFoundation/InputBag.php index a270831c3ee9f..569fdfc241820 100644 --- a/src/Symfony/Component/HttpFoundation/InputBag.php +++ b/src/Symfony/Component/HttpFoundation/InputBag.php @@ -40,14 +40,6 @@ public function get(string $key, mixed $default = null): string|int|float|bool|n return $this === $value ? $default : $value; } - /** - * {@inheritdoc} - */ - public function all(string $key = null): array - { - return parent::all($key); - } - /** * Replaces the current input values by a new set. */ From 98acd62d25c5c8217ab269088c90537f7bfa4b81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20Nagy=20=28T-bond=29?= Date: Thu, 24 Mar 2022 15:51:32 +0100 Subject: [PATCH 58/91] [Serializer] Fix denormalizing union types Fixes symfony#45818 --- .../Normalizer/AbstractObjectNormalizer.php | 52 +++++++++++------- .../Serializer/Tests/SerializerTest.php | 54 +++++++++++++++++++ 2 files changed, 86 insertions(+), 20 deletions(-) diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index 5a6d3b78e61d9..e29a7cd18e6b6 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -404,6 +404,7 @@ private function validateAndDenormalize(string $currentClass, string $attribute, } $expectedTypes = []; + $isUnionType = \count($types) > 1; foreach ($types as $type) { if (null === $data && $type->isNullable()) { return null; @@ -455,29 +456,40 @@ private function validateAndDenormalize(string $currentClass, string $attribute, $expectedTypes[Type::BUILTIN_TYPE_OBJECT === $builtinType && $class ? $class : $builtinType] = true; - if (Type::BUILTIN_TYPE_OBJECT === $builtinType) { - if (!$this->serializer instanceof DenormalizerInterface) { - throw new LogicException(sprintf('Cannot denormalize attribute "%s" for class "%s" because injected serializer is not a denormalizer.', $attribute, $class)); + // This try-catch should cover all NotNormalizableValueException (and all return branches after the first + // exception) so we could try denormalizing all types of an union type. If the target type is not an union + // type, we will just re-throw the catched exception. + // In the case of no denormalization succeeds with an union type, it will fall back to the default exception + // with the acceptable types list. + try { + if (Type::BUILTIN_TYPE_OBJECT === $builtinType) { + if (!$this->serializer instanceof DenormalizerInterface) { + throw new LogicException(sprintf('Cannot denormalize attribute "%s" for class "%s" because injected serializer is not a denormalizer.', $attribute, $class)); + } + + $childContext = $this->createChildContext($context, $attribute, $format); + if ($this->serializer->supportsDenormalization($data, $class, $format, $childContext)) { + return $this->serializer->denormalize($data, $class, $format, $childContext); + } } - $childContext = $this->createChildContext($context, $attribute, $format); - if ($this->serializer->supportsDenormalization($data, $class, $format, $childContext)) { - return $this->serializer->denormalize($data, $class, $format, $childContext); + // JSON only has a Number type corresponding to both int and float PHP types. + // PHP's json_encode, JavaScript's JSON.stringify, Go's json.Marshal as well as most other JSON encoders convert + // floating-point numbers like 12.0 to 12 (the decimal part is dropped when possible). + // PHP's json_decode automatically converts Numbers without a decimal part to integers. + // To circumvent this behavior, integers are converted to floats when denormalizing JSON based formats and when + // a float is expected. + if (Type::BUILTIN_TYPE_FLOAT === $builtinType && \is_int($data) && null !== $format && str_contains($format, JsonEncoder::FORMAT)) { + return (float) $data; } - } - - // JSON only has a Number type corresponding to both int and float PHP types. - // PHP's json_encode, JavaScript's JSON.stringify, Go's json.Marshal as well as most other JSON encoders convert - // floating-point numbers like 12.0 to 12 (the decimal part is dropped when possible). - // PHP's json_decode automatically converts Numbers without a decimal part to integers. - // To circumvent this behavior, integers are converted to floats when denormalizing JSON based formats and when - // a float is expected. - if (Type::BUILTIN_TYPE_FLOAT === $builtinType && \is_int($data) && null !== $format && str_contains($format, JsonEncoder::FORMAT)) { - return (float) $data; - } - if (('is_'.$builtinType)($data)) { - return $data; + if (('is_'.$builtinType)($data)) { + return $data; + } + } catch (NotNormalizableValueException $e) { + if (!$isUnionType) { + throw $e; + } } } @@ -639,7 +651,7 @@ private function getCacheKey(?string $format, array $context) 'ignored' => $this->ignoredAttributes, 'camelized' => $this->camelizedAttributes, ])); - } catch (\Exception $exception) { + } catch (\Exception $e) { // The context cannot be serialized, skip the cache return false; } diff --git a/src/Symfony/Component/Serializer/Tests/SerializerTest.php b/src/Symfony/Component/Serializer/Tests/SerializerTest.php index e5a8acbf03543..a95811a46f8d9 100644 --- a/src/Symfony/Component/Serializer/Tests/SerializerTest.php +++ b/src/Symfony/Component/Serializer/Tests/SerializerTest.php @@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; +use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\Serializer\Encoder\DecoderInterface; use Symfony\Component\Serializer\Encoder\EncoderInterface; use Symfony\Component\Serializer\Encoder\JsonEncoder; @@ -33,6 +34,7 @@ use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; use Symfony\Component\Serializer\Normalizer\CustomNormalizer; +use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; @@ -538,6 +540,38 @@ public function testNormalizePreserveEmptyArrayObject() $this->assertEquals('{"foo":{},"bar":["notempty"],"baz":{"nested":{}}}', $serializer->serialize($object, 'json', [AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS => true])); } + public function testUnionTypeDeserializable() + { + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); + $serializer = new Serializer( + [ + new DateTimeNormalizer(), + new ObjectNormalizer($classMetadataFactory, null, null, $extractor, new ClassDiscriminatorFromClassMetadata($classMetadataFactory)), + ], + ['json' => new JsonEncoder()] + ); + + $actual = $serializer->deserialize('{ "changed": null }', DummyUnionType::class, 'json', [ + DateTimeNormalizer::FORMAT_KEY => \DateTime::ISO8601, + ]); + + $this->assertEquals((new DummyUnionType())->setChanged(null), $actual, 'Union type denormalization first case failed.'); + + $actual = $serializer->deserialize('{ "changed": "2022-03-22T16:15:05+0000" }', DummyUnionType::class, 'json', [ + DateTimeNormalizer::FORMAT_KEY => \DateTime::ISO8601, + ]); + + $expectedDateTime = \DateTime::createFromFormat(\DateTime::ISO8601, '2022-03-22T16:15:05+0000'); + $this->assertEquals((new DummyUnionType())->setChanged($expectedDateTime), $actual, 'Union type denormalization second case failed.'); + + $actual = $serializer->deserialize('{ "changed": false }', DummyUnionType::class, 'json', [ + DateTimeNormalizer::FORMAT_KEY => \DateTime::ISO8601, + ]); + + $this->assertEquals(new DummyUnionType(), $actual, 'Union type denormalization third case failed.'); + } + private function serializerWithClassDiscriminator() { $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); @@ -610,6 +644,26 @@ public function __construct($value) } } +class DummyUnionType +{ + /** + * @var \DateTime|bool|null + */ + public $changed = false; + + /** + * @param \DateTime|bool|null + * + * @return $this + */ + public function setChanged($changed): self + { + $this->changed = $changed; + + return $this; + } +} + interface NormalizerAwareNormalizer extends NormalizerInterface, NormalizerAwareInterface { } From 363b151f62b3c85b8a90cd28ff3c7710168bab4f Mon Sep 17 00:00:00 2001 From: Gocha Ossinkine Date: Thu, 24 Mar 2022 19:09:09 +0200 Subject: [PATCH 59/91] Fix intersect in TranslatorBag --- .../Component/Translation/Tests/TranslatorBagTest.php | 4 ++-- src/Symfony/Component/Translation/TranslatorBag.php | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/Translation/Tests/TranslatorBagTest.php b/src/Symfony/Component/Translation/Tests/TranslatorBagTest.php index a202bc65caa5f..58b8fa02bdc1b 100644 --- a/src/Symfony/Component/Translation/Tests/TranslatorBagTest.php +++ b/src/Symfony/Component/Translation/Tests/TranslatorBagTest.php @@ -82,8 +82,8 @@ public function testIntersect() $this->assertEquals([ 'en' => [ - 'domain1' => ['bar' => 'bar'], - 'domain2' => ['qux' => 'qux'], + 'domain1' => ['foo' => 'foo'], + 'domain2' => ['baz' => 'baz'], ], ], $this->getAllMessagesFromTranslatorBag($bagResult)); } diff --git a/src/Symfony/Component/Translation/TranslatorBag.php b/src/Symfony/Component/Translation/TranslatorBag.php index 6d98455e5b78a..555a9e8147fd2 100644 --- a/src/Symfony/Component/Translation/TranslatorBag.php +++ b/src/Symfony/Component/Translation/TranslatorBag.php @@ -94,7 +94,10 @@ public function intersect(TranslatorBagInterface $intersectBag): self $obsoleteCatalogue = new MessageCatalogue($locale); foreach ($operation->getDomains() as $domain) { - $obsoleteCatalogue->add($operation->getObsoleteMessages($domain), $domain); + $obsoleteCatalogue->add( + array_diff($operation->getMessages($domain), $operation->getNewMessages($domain)), + $domain + ); } $diff->addCatalogue($obsoleteCatalogue); From 1bd3af4ec6303a4cf3de0218abc29ca9fdd7a793 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 24 Mar 2022 19:04:39 +0100 Subject: [PATCH 60/91] Fix merge --- src/Symfony/Component/Serializer/Tests/SerializerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Serializer/Tests/SerializerTest.php b/src/Symfony/Component/Serializer/Tests/SerializerTest.php index 715449b1d63c1..67e36ef1f77a9 100644 --- a/src/Symfony/Component/Serializer/Tests/SerializerTest.php +++ b/src/Symfony/Component/Serializer/Tests/SerializerTest.php @@ -1193,7 +1193,7 @@ class DummyUnionType * * @return $this */ - public function setChanged($changed): self + public function setChanged($changed): static { $this->changed = $changed; From 5fab2d379b4990ec8b5e27b25280a6101de5811c Mon Sep 17 00:00:00 2001 From: Gocha Ossinkine Date: Thu, 24 Mar 2022 19:23:22 +0200 Subject: [PATCH 61/91] Fix locales format in CrowdinProvider --- .../Bridge/Crowdin/CrowdinProvider.php | 4 +- .../Crowdin/Tests/CrowdinProviderTest.php | 132 +++++++++++++----- 2 files changed, 101 insertions(+), 35 deletions(-) diff --git a/src/Symfony/Component/Translation/Bridge/Crowdin/CrowdinProvider.php b/src/Symfony/Component/Translation/Bridge/Crowdin/CrowdinProvider.php index 4d505de0ebf73..a865de1202076 100644 --- a/src/Symfony/Component/Translation/Bridge/Crowdin/CrowdinProvider.php +++ b/src/Symfony/Component/Translation/Bridge/Crowdin/CrowdinProvider.php @@ -278,7 +278,7 @@ private function uploadTranslations(int $fileId, string $domain, string $content * @see https://support.crowdin.com/api/v2/#operation/api.projects.translations.postOnLanguage (Crowdin API) * @see https://support.crowdin.com/enterprise/api/#operation/api.projects.translations.postOnLanguage (Crowdin Enterprise API) */ - return $this->client->request('POST', 'translations/'.$locale, [ + return $this->client->request('POST', 'translations/'.str_replace('_', '-', $locale), [ 'json' => [ 'storageId' => $storageId, 'fileId' => $fileId, @@ -294,7 +294,7 @@ private function exportProjectTranslations(string $languageId, int $fileId): Res */ return $this->client->request('POST', 'translations/exports', [ 'json' => [ - 'targetLanguageId' => $languageId, + 'targetLanguageId' => str_replace('_', '-', $languageId), 'fileIds' => [$fileId], ], ]); diff --git a/src/Symfony/Component/Translation/Bridge/Crowdin/Tests/CrowdinProviderTest.php b/src/Symfony/Component/Translation/Bridge/Crowdin/Tests/CrowdinProviderTest.php index aa8624dd18913..2fd4d33f5cc5e 100644 --- a/src/Symfony/Component/Translation/Bridge/Crowdin/Tests/CrowdinProviderTest.php +++ b/src/Symfony/Component/Translation/Bridge/Crowdin/Tests/CrowdinProviderTest.php @@ -217,7 +217,10 @@ public function testCompleteWriteProcessUpdateFiles() $provider->write($translatorBag); } - public function testCompleteWriteProcessAddFileAndUploadTranslations() + /** + * @dataProvider getResponsesForProcessAddFileAndUploadTranslations + */ + public function testCompleteWriteProcessAddFileAndUploadTranslations(TranslatorBag $translatorBag, string $expectedLocale, string $expectedMessagesTranslationsContent) { $this->xliffFileDumper = new XliffFileDumper(); @@ -237,24 +240,6 @@ public function testCompleteWriteProcessAddFileAndUploadTranslations() -XLIFF; - - $expectedMessagesTranslationsContent = <<<'XLIFF' - - - -
- -
- - - a - trans_fr_a - - -
-
- XLIFF; $responses = [ @@ -296,23 +281,15 @@ public function testCompleteWriteProcessAddFileAndUploadTranslations() return new MockResponse(json_encode(['data' => ['id' => 19]]), ['http_code' => 201]); }, - 'UploadTranslations' => function (string $method, string $url, array $options = []): ResponseInterface { + 'UploadTranslations' => function (string $method, string $url, array $options = []) use ($expectedLocale): ResponseInterface { $this->assertSame('POST', $method); - $this->assertSame('https://api.crowdin.com/api/v2/projects/1/translations/fr', $url); + $this->assertSame(sprintf('https://api.crowdin.com/api/v2/projects/1/translations/%s', $expectedLocale), $url); $this->assertSame('{"storageId":19,"fileId":12}', $options['body']); return new MockResponse(); }, ]; - $translatorBag = new TranslatorBag(); - $translatorBag->addCatalogue(new MessageCatalogue('en', [ - 'messages' => ['a' => 'trans_en_a'], - ])); - $translatorBag->addCatalogue(new MessageCatalogue('fr', [ - 'messages' => ['a' => 'trans_fr_a'], - ])); - $provider = $this->createProvider((new MockHttpClient($responses))->withOptions([ 'base_uri' => 'https://api.crowdin.com/api/v2/projects/1/', 'auth_bearer' => 'API_TOKEN', @@ -321,10 +298,69 @@ public function testCompleteWriteProcessAddFileAndUploadTranslations() $provider->write($translatorBag); } + public function getResponsesForProcessAddFileAndUploadTranslations(): \Generator + { + $arrayLoader = new ArrayLoader(); + + $translatorBagFr = new TranslatorBag(); + $translatorBagFr->addCatalogue($arrayLoader->load([ + 'a' => 'trans_en_a', + ], 'en')); + $translatorBagFr->addCatalogue($arrayLoader->load([ + 'a' => 'trans_fr_a', + ], 'fr')); + + yield [$translatorBagFr, 'fr', <<<'XLIFF' + + + +
+ +
+ + + a + trans_fr_a + + +
+
+ +XLIFF + ]; + + $translatorBagEnGb = new TranslatorBag(); + $translatorBagEnGb->addCatalogue($arrayLoader->load([ + 'a' => 'trans_en_a', + ], 'en')); + $translatorBagEnGb->addCatalogue($arrayLoader->load([ + 'a' => 'trans_en_gb_a', + ], 'en_GB')); + + yield [$translatorBagEnGb, 'en-GB', <<<'XLIFF' + + + +
+ +
+ + + a + trans_en_gb_a + + +
+
+ +XLIFF + ]; + } + /** * @dataProvider getResponsesForOneLocaleAndOneDomain */ - public function testReadForOneLocaleAndOneDomain(string $locale, string $domain, string $responseContent, TranslatorBag $expectedTranslatorBag) + public function testReadForOneLocaleAndOneDomain(string $locale, string $domain, string $responseContent, TranslatorBag $expectedTranslatorBag, string $expectedTargetLanguageId) { $responses = [ 'listFiles' => function (string $method, string $url): ResponseInterface { @@ -340,10 +376,10 @@ public function testReadForOneLocaleAndOneDomain(string $locale, string $domain, ], ])); }, - 'exportProjectTranslations' => function (string $method, string $url, array $options = []): ResponseInterface { + 'exportProjectTranslations' => function (string $method, string $url, array $options = []) use ($expectedTargetLanguageId): ResponseInterface { $this->assertSame('POST', $method); $this->assertSame('https://api.crowdin.com/api/v2/projects/1/translations/exports', $url); - $this->assertSame('{"targetLanguageId":"fr","fileIds":[12]}', $options['body']); + $this->assertSame(sprintf('{"targetLanguageId":"%s","fileIds":[12]}', $expectedTargetLanguageId), $options['body']); return new MockResponse(json_encode(['data' => ['url' => 'https://file.url']])); }, @@ -401,7 +437,37 @@ public function getResponsesForOneLocaleAndOneDomain(): \Generator XLIFF , - $expectedTranslatorBagFr, + $expectedTranslatorBagFr, 'fr', + ]; + + $expectedTranslatorBagEnUs = new TranslatorBag(); + $expectedTranslatorBagEnUs->addCatalogue($arrayLoader->load([ + 'index.hello' => 'Hello', + 'index.greetings' => 'Welcome, {firstname}!', + ], 'en_GB')); + + yield ['en_GB', 'messages', <<<'XLIFF' + + + +
+ +
+ + + index.hello + Hello + + + index.greetings + Welcome, {firstname}! + + +
+
+XLIFF + , + $expectedTranslatorBagEnUs, 'en-GB', ]; } From bc0e2fc8b097ca3630488b0dd309e97c662b5be9 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 25 Mar 2022 13:55:41 +0100 Subject: [PATCH 62/91] [HttpClient] fix sending PUT requests with curl --- src/Symfony/Component/HttpClient/CurlHttpClient.php | 6 +++++- .../Component/HttpClient/NativeHttpClient.php | 2 +- .../HttpClient/Tests/HttpClientTestCase.php | 12 ++++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpClient/CurlHttpClient.php b/src/Symfony/Component/HttpClient/CurlHttpClient.php index e02f2c8b17d50..4a764414b7604 100644 --- a/src/Symfony/Component/HttpClient/CurlHttpClient.php +++ b/src/Symfony/Component/HttpClient/CurlHttpClient.php @@ -243,8 +243,12 @@ public function request(string $method, string $url, array $options = []): Respo if ('POST' !== $method) { $curlopts[\CURLOPT_UPLOAD] = true; } - } elseif ('' !== $body || 'POST' === $method) { + } elseif ('' !== $body || 'POST' === $method || $hasContentLength) { $curlopts[\CURLOPT_POSTFIELDS] = $body; + + if ('' === $body && !isset($options['normalized_headers']['content-type'])) { + $curlopts[\CURLOPT_HTTPHEADER][] = 'Content-Type:'; + } } if ($options['peer_fingerprint']) { diff --git a/src/Symfony/Component/HttpClient/NativeHttpClient.php b/src/Symfony/Component/HttpClient/NativeHttpClient.php index 4394e51400cec..a955bbb98026e 100644 --- a/src/Symfony/Component/HttpClient/NativeHttpClient.php +++ b/src/Symfony/Component/HttpClient/NativeHttpClient.php @@ -82,7 +82,7 @@ public function request(string $method, string $url, array $options = []): Respo $options['body'] = self::getBodyAsString($options['body']); - if ('' !== $options['body'] && 'POST' === $method && !isset($options['normalized_headers']['content-type'])) { + if ('' !== $options['body'] && !isset($options['normalized_headers']['content-type'])) { $options['headers'][] = 'Content-Type: application/x-www-form-urlencoded'; } diff --git a/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php b/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php index 148d3e762cd7b..faed27aed24a7 100644 --- a/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php +++ b/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php @@ -214,6 +214,18 @@ public function testRedirectAfterPost() $this->assertSame(200, $response->getStatusCode()); } + public function testEmptyPut() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('PUT', 'http://localhost:8057/post', [ + 'headers' => ['Content-Length' => '0'], + ]); + + $this->assertSame(200, $response->getStatusCode()); + $this->assertStringContainsString("\r\nContent-Length: ", $response->getInfo('debug')); + } + public function testNullBody() { $client = $this->getHttpClient(__FUNCTION__); From 2b57beb1a25f582713bf9b03879089432ebe4c4f Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 25 Mar 2022 15:07:44 +0100 Subject: [PATCH 63/91] Fix merge --- src/Symfony/Component/HttpClient/AmpHttpClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/HttpClient/AmpHttpClient.php b/src/Symfony/Component/HttpClient/AmpHttpClient.php index da533d53693be..f137c89d179a1 100644 --- a/src/Symfony/Component/HttpClient/AmpHttpClient.php +++ b/src/Symfony/Component/HttpClient/AmpHttpClient.php @@ -92,7 +92,7 @@ public function request(string $method, string $url, array $options = []): Respo } } - if (\is_string($options['body']) && '' !== $options['body'] && !isset($options['normalized_headers']['content-type'])) { + if ('' !== $options['body'] && !isset($options['normalized_headers']['content-type'])) { $options['headers'][] = 'Content-Type: application/x-www-form-urlencoded'; } From ee6717595bec2922e877ff3ade338064561a49cb Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 25 Mar 2022 15:31:06 +0100 Subject: [PATCH 64/91] [HttpClient] fix sending Content-Length/Type for POST --- src/Symfony/Component/HttpClient/CurlHttpClient.php | 2 +- src/Symfony/Component/HttpClient/NativeHttpClient.php | 5 ++++- .../Component/HttpClient/Tests/HttpClientTestCase.php | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/HttpClient/CurlHttpClient.php b/src/Symfony/Component/HttpClient/CurlHttpClient.php index 4a764414b7604..bb0b68918bfd1 100644 --- a/src/Symfony/Component/HttpClient/CurlHttpClient.php +++ b/src/Symfony/Component/HttpClient/CurlHttpClient.php @@ -246,7 +246,7 @@ public function request(string $method, string $url, array $options = []): Respo } elseif ('' !== $body || 'POST' === $method || $hasContentLength) { $curlopts[\CURLOPT_POSTFIELDS] = $body; - if ('' === $body && !isset($options['normalized_headers']['content-type'])) { + if ('' === $body && 'POST' !== $method && !isset($options['normalized_headers']['content-type'])) { $curlopts[\CURLOPT_HTTPHEADER][] = 'Content-Type:'; } } diff --git a/src/Symfony/Component/HttpClient/NativeHttpClient.php b/src/Symfony/Component/HttpClient/NativeHttpClient.php index a955bbb98026e..54fd512570487 100644 --- a/src/Symfony/Component/HttpClient/NativeHttpClient.php +++ b/src/Symfony/Component/HttpClient/NativeHttpClient.php @@ -82,7 +82,10 @@ public function request(string $method, string $url, array $options = []): Respo $options['body'] = self::getBodyAsString($options['body']); - if ('' !== $options['body'] && !isset($options['normalized_headers']['content-type'])) { + if ('' === $options['body'] && 'POST' === $method && !isset($options['normalized_headers']['content-length'])) { + $options['headers'][] = 'Content-Length: 0'; + } + if (('' !== $options['body'] || 'POST' === $method) && !isset($options['normalized_headers']['content-type'])) { $options['headers'][] = 'Content-Type: application/x-www-form-urlencoded'; } diff --git a/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php b/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php index faed27aed24a7..cf33bd9816e86 100644 --- a/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php +++ b/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php @@ -208,10 +208,11 @@ public function testRedirectAfterPost() $client = $this->getHttpClient(__FUNCTION__); $response = $client->request('POST', 'http://localhost:8057/302/relative', [ - 'body' => 'abc', + 'body' => '', ]); $this->assertSame(200, $response->getStatusCode()); + $this->assertStringContainsString("\r\nContent-Length: 0", $response->getInfo('debug')); } public function testEmptyPut() From 66f39b0fe61c51f824c3fa816a62a782be1173d3 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 25 Mar 2022 15:52:11 +0100 Subject: [PATCH 65/91] [HttpClient] always send Content-Length when a body is passed --- src/Symfony/Component/HttpClient/NativeHttpClient.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/HttpClient/NativeHttpClient.php b/src/Symfony/Component/HttpClient/NativeHttpClient.php index 54fd512570487..0ecc471202dfe 100644 --- a/src/Symfony/Component/HttpClient/NativeHttpClient.php +++ b/src/Symfony/Component/HttpClient/NativeHttpClient.php @@ -80,9 +80,11 @@ public function request(string $method, string $url, array $options = []): Respo } } + $sendContentLength = !\is_string($options['body']) || 'POST' === $method; + $options['body'] = self::getBodyAsString($options['body']); - if ('' === $options['body'] && 'POST' === $method && !isset($options['normalized_headers']['content-length'])) { + if ('' === $options['body'] && $sendContentLength && !isset($options['normalized_headers']['content-length'])) { $options['headers'][] = 'Content-Length: 0'; } if (('' !== $options['body'] || 'POST' === $method) && !isset($options['normalized_headers']['content-type'])) { From 31a1a2b61f2bf6d7efc0aff9716769484d5beeea Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 25 Mar 2022 16:07:14 +0100 Subject: [PATCH 66/91] [HttpClient] always send Content-Type when a body is passed --- src/Symfony/Component/HttpClient/CurlHttpClient.php | 8 ++++---- src/Symfony/Component/HttpClient/NativeHttpClient.php | 7 ++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Symfony/Component/HttpClient/CurlHttpClient.php b/src/Symfony/Component/HttpClient/CurlHttpClient.php index bb0b68918bfd1..2830c5025063e 100644 --- a/src/Symfony/Component/HttpClient/CurlHttpClient.php +++ b/src/Symfony/Component/HttpClient/CurlHttpClient.php @@ -242,13 +242,13 @@ public function request(string $method, string $url, array $options = []): Respo if ('POST' !== $method) { $curlopts[\CURLOPT_UPLOAD] = true; + + if (!isset($options['normalized_headers']['content-type'])) { + $curlopts[\CURLOPT_HTTPHEADER][] = 'Content-Type: application/x-www-form-urlencoded'; + } } } elseif ('' !== $body || 'POST' === $method || $hasContentLength) { $curlopts[\CURLOPT_POSTFIELDS] = $body; - - if ('' === $body && 'POST' !== $method && !isset($options['normalized_headers']['content-type'])) { - $curlopts[\CURLOPT_HTTPHEADER][] = 'Content-Type:'; - } } if ($options['peer_fingerprint']) { diff --git a/src/Symfony/Component/HttpClient/NativeHttpClient.php b/src/Symfony/Component/HttpClient/NativeHttpClient.php index 0ecc471202dfe..9f3737efb3b84 100644 --- a/src/Symfony/Component/HttpClient/NativeHttpClient.php +++ b/src/Symfony/Component/HttpClient/NativeHttpClient.php @@ -80,14 +80,15 @@ public function request(string $method, string $url, array $options = []): Respo } } - $sendContentLength = !\is_string($options['body']) || 'POST' === $method; + $hasContentLength = isset($options['normalized_headers']['content-length']); + $hasBody = '' !== $options['body'] || 'POST' === $method || $hasContentLength; $options['body'] = self::getBodyAsString($options['body']); - if ('' === $options['body'] && $sendContentLength && !isset($options['normalized_headers']['content-length'])) { + if ('' === $options['body'] && $hasBody && !$hasContentLength) { $options['headers'][] = 'Content-Length: 0'; } - if (('' !== $options['body'] || 'POST' === $method) && !isset($options['normalized_headers']['content-type'])) { + if ($hasBody && !isset($options['normalized_headers']['content-type'])) { $options['headers'][] = 'Content-Type: application/x-www-form-urlencoded'; } From 56b428f1f89894b6c1163105e60c134d5f8d45ba Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 25 Mar 2022 17:56:10 +0100 Subject: [PATCH 67/91] [HttpClient] fix 303 after PUT and sending chunked requests --- .../Component/HttpClient/HttpClientTrait.php | 24 ++++++++++++++++++- .../Component/HttpClient/NativeHttpClient.php | 5 ++++ .../HttpClient/Response/CurlResponse.php | 1 + .../HttpClient/Tests/MockHttpClientTest.php | 4 ++-- 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/HttpClient/HttpClientTrait.php b/src/Symfony/Component/HttpClient/HttpClientTrait.php index 8c9bd8d35526e..e616ca1f9c01b 100644 --- a/src/Symfony/Component/HttpClient/HttpClientTrait.php +++ b/src/Symfony/Component/HttpClient/HttpClientTrait.php @@ -12,6 +12,7 @@ namespace Symfony\Component\HttpClient; use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Component\HttpClient\Exception\TransportException; /** * Provides the common logic from writing HttpClientInterface implementations. @@ -89,8 +90,13 @@ private static function prepareRequest(?string $method, ?string $url, array $opt if (\is_string($options['body']) && (string) \strlen($options['body']) !== substr($h = $options['normalized_headers']['content-length'][0] ?? '', 16) - && ('' !== $h || ('' !== $options['body'] && !isset($options['normalized_headers']['transfer-encoding']))) + && ('' !== $h || '' !== $options['body']) ) { + if (isset($options['normalized_headers']['transfer-encoding'])) { + unset($options['normalized_headers']['transfer-encoding']); + $options['body'] = self::dechunk($options['body']); + } + $options['normalized_headers']['content-length'] = [substr_replace($h ?: 'Content-Length: ', \strlen($options['body']), 16)]; } } @@ -329,6 +335,22 @@ private static function normalizeBody($body) return $body; } + private static function dechunk(string $body): string + { + $h = fopen('php://temp', 'w+'); + stream_filter_append($h, 'dechunk', \STREAM_FILTER_WRITE); + fwrite($h, $body); + $body = stream_get_contents($h, -1, 0); + rewind($h); + ftruncate($h, 0); + + if (fwrite($h, '-') && '' !== stream_get_contents($h, -1, 0)) { + throw new TransportException('Request body has broken chunked encoding.'); + } + + return $body; + } + /** * @param string|string[] $fingerprint * diff --git a/src/Symfony/Component/HttpClient/NativeHttpClient.php b/src/Symfony/Component/HttpClient/NativeHttpClient.php index 9f3737efb3b84..939eb425c7672 100644 --- a/src/Symfony/Component/HttpClient/NativeHttpClient.php +++ b/src/Symfony/Component/HttpClient/NativeHttpClient.php @@ -85,6 +85,11 @@ public function request(string $method, string $url, array $options = []): Respo $options['body'] = self::getBodyAsString($options['body']); + if (isset($options['normalized_headers']['transfer-encoding'])) { + unset($options['normalized_headers']['transfer-encoding']); + $options['headers'] = array_merge(...array_values($options['normalized_headers'])); + $options['body'] = self::dechunk($options['body']); + } if ('' === $options['body'] && $hasBody && !$hasContentLength) { $options['headers'][] = 'Content-Length: 0'; } diff --git a/src/Symfony/Component/HttpClient/Response/CurlResponse.php b/src/Symfony/Component/HttpClient/Response/CurlResponse.php index ee0d3f655a6fb..e065c4aa17f0e 100644 --- a/src/Symfony/Component/HttpClient/Response/CurlResponse.php +++ b/src/Symfony/Component/HttpClient/Response/CurlResponse.php @@ -363,6 +363,7 @@ private static function parseHeaderLine($ch, string $data, array &$info, array & } elseif (303 === $info['http_code'] || ('POST' === $info['http_method'] && \in_array($info['http_code'], [301, 302], true))) { $info['http_method'] = 'HEAD' === $info['http_method'] ? 'HEAD' : 'GET'; curl_setopt($ch, \CURLOPT_POSTFIELDS, ''); + curl_setopt($ch, \CURLOPT_CUSTOMREQUEST, $info['http_method']); } } diff --git a/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php index e324dc107be3f..4e8dbf26d7e29 100644 --- a/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php +++ b/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php @@ -96,12 +96,12 @@ public function testFixContentLength() $this->assertSame(['Content-Length: 7'], $requestOptions['normalized_headers']['content-length']); $response = $client->request('POST', 'http://localhost:8057/post', [ - 'body' => 'abc=def', + 'body' => "8\r\nSymfony \r\n5\r\nis aw\r\n6\r\nesome!\r\n0\r\n\r\n", 'headers' => ['Transfer-Encoding: chunked'], ]); $requestOptions = $response->getRequestOptions(); - $this->assertFalse(isset($requestOptions['normalized_headers']['content-length'])); + $this->assertSame(['Content-Length: 19'], $requestOptions['normalized_headers']['content-length']); $response = $client->request('POST', 'http://localhost:8057/post', [ 'body' => '', From d774fe35c31715b3246be29c31271da8dda325c0 Mon Sep 17 00:00:00 2001 From: "charly.terrier" Date: Sun, 13 Mar 2022 20:47:22 +0100 Subject: [PATCH 68/91] [Validator] fix #43345 @Assert\DivisibleBy --- .../Validator/Constraints/DivisibleByValidator.php | 6 ++++++ .../Tests/Constraints/DivisibleByValidatorTest.php | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/Symfony/Component/Validator/Constraints/DivisibleByValidator.php b/src/Symfony/Component/Validator/Constraints/DivisibleByValidator.php index 53b8d38930c90..de7743010b354 100644 --- a/src/Symfony/Component/Validator/Constraints/DivisibleByValidator.php +++ b/src/Symfony/Component/Validator/Constraints/DivisibleByValidator.php @@ -42,6 +42,12 @@ protected function compareValues($value1, $value2) if (!$remainder = fmod($value1, $value2)) { return true; } + if (\is_float($value2) && \INF !== $value2) { + $quotient = $value1 / $value2; + $rounded = round($quotient); + + return sprintf('%.12e', $quotient) === sprintf('%.12e', $rounded); + } return sprintf('%.12e', $value2) === sprintf('%.12e', $remainder); } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/DivisibleByValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/DivisibleByValidatorTest.php index 7612ada32b530..4ce2723c0d845 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/DivisibleByValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/DivisibleByValidatorTest.php @@ -46,6 +46,18 @@ public function provideValidComparisons(): array [0, 3.1415], [42, 42], [42, 21], + [10.12, 0.01], + [10.12, 0.001], + [1.133, 0.001], + [1.1331, 0.0001], + [1.13331, 0.00001], + [1.13331, 0.000001], + [1, 0.1], + [1, 0.01], + [1, 0.001], + [1, 0.0001], + [1, 0.00001], + [1, 0.000001], [3.25, 0.25], ['100', '10'], [4.1, 0.1], @@ -74,6 +86,7 @@ public function provideInvalidComparisons(): array [10, '10', 0, '0', 'int'], [42, '42', \INF, 'INF', 'float'], [4.15, '4.15', 0.1, '0.1', 'float'], + [10.123, '10.123', 0.01, '0.01', 'float'], ['22', '"22"', '10', '"10"', 'string'], ]; } From e7072aa5cc5be00fcbdcbfce524b4da3e9beaa50 Mon Sep 17 00:00:00 2001 From: acoulton Date: Sat, 26 Mar 2022 00:17:43 +0000 Subject: [PATCH 69/91] [Console] Fix exit status on uncaught exception with negative code As described in #45850, if an application threw an exception with the `code` property set to a negative number this could in some cases cause the process to appear to exit 'successfully' with a zero exit status. Exiting with any negative number produces potentially unexpected results - the reported exit status will not appear to match the value that was set. This is due to the binary handling / truncation of exit codes. This may theoretically break BC for applications that were intentionally throwing exceptions with negative codes and performing some action based on that status. However, given they would have had to implement an algorithm to map e.g. `-10` in PHP to `246` as the actual exit status, it seems unlikely this is a common usage. It is certainly outside the defined behaviour of POSIX exit codes. Therefore I believe it is essentially safe to assume that exceptions with negative codes are e.g. being thrown by lower-level components, and are not intended to set a shell exit status. Coalescing all negative numbers to 1 matches the existing behaviour with other 'invalid' exception codes e.g. empty / zero / non-numeric. This therefore feels the most robust fix and eliminates any potential for confusion. Fixes #45850 --- src/Symfony/Component/Console/Application.php | 2 +- .../Console/Tests/ApplicationTest.php | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php index 15d537dacb3a6..1021a900f0972 100644 --- a/src/Symfony/Component/Console/Application.php +++ b/src/Symfony/Component/Console/Application.php @@ -157,7 +157,7 @@ public function run(InputInterface $input = null, OutputInterface $output = null $exitCode = $e->getCode(); if (is_numeric($exitCode)) { $exitCode = (int) $exitCode; - if (0 === $exitCode) { + if ($exitCode <= 0) { $exitCode = 1; } } else { diff --git a/src/Symfony/Component/Console/Tests/ApplicationTest.php b/src/Symfony/Component/Console/Tests/ApplicationTest.php index 6e9953dd27a7b..fae8d2dcf743b 100644 --- a/src/Symfony/Component/Console/Tests/ApplicationTest.php +++ b/src/Symfony/Component/Console/Tests/ApplicationTest.php @@ -1175,6 +1175,25 @@ public function testRunDispatchesExitCodeOneForExceptionCodeZero() $this->assertTrue($passedRightValue, '-> exit code 1 was passed in the console.terminate event'); } + /** + * @testWith [-1] + * [-32000] + */ + public function testRunReturnsExitCodeOneForNegativeExceptionCode($exceptionCode) + { + $exception = new \Exception('', $exceptionCode); + + $application = $this->getMockBuilder(Application::class)->setMethods(['doRun'])->getMock(); + $application->setAutoExit(false); + $application->expects($this->once()) + ->method('doRun') + ->willThrowException($exception); + + $exitCode = $application->run(new ArrayInput([]), new NullOutput()); + + $this->assertSame(1, $exitCode, '->run() returns exit code 1 when exception code is '.$exceptionCode); + } + public function testAddingOptionWithDuplicateShortcut() { $this->expectException(\LogicException::class); From a9e781feb1f38608811df5b4978bab88b7886fdd Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Mon, 28 Mar 2022 10:53:28 +0200 Subject: [PATCH 70/91] [Validator] Fix File constraint invalid max size exception message --- src/Symfony/Component/Validator/Constraints/File.php | 2 +- .../Validator/Tests/Constraints/FileValidatorTest.php | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Validator/Constraints/File.php b/src/Symfony/Component/Validator/Constraints/File.php index 29844a4c95476..377be1f2ec979 100644 --- a/src/Symfony/Component/Validator/Constraints/File.php +++ b/src/Symfony/Component/Validator/Constraints/File.php @@ -117,7 +117,7 @@ private function normalizeBinaryFormat($maxSize) $this->maxSize = $matches[1] * $factors[$unit = strtolower($matches[2])]; $this->binaryFormat = $this->binaryFormat ?? (2 === \strlen($unit)); } else { - throw new ConstraintDefinitionException(sprintf('"%s" is not a valid maximum size.', $this->maxSize)); + throw new ConstraintDefinitionException(sprintf('"%s" is not a valid maximum size.', $maxSize)); } } } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/FileValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/FileValidatorTest.php index 72bdc950343b9..5e98ef64313a4 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/FileValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/FileValidatorTest.php @@ -479,5 +479,14 @@ public function uploadedFileErrorProvider() return $tests; } + public function testNegativeMaxSize() + { + $this->expectException(ConstraintDefinitionException::class); + $this->expectExceptionMessage('"-1" is not a valid maximum size.'); + + $file = new File(); + $file->maxSize = -1; + } + abstract protected function getFile($filename); } From 54cacfeb1df48b1c590d792cf6e5682da742d8eb Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 12 Jan 2022 14:29:06 +0100 Subject: [PATCH 71/91] [RateLimiter] Always store SlidingWindows with an expiration set --- .../RateLimiter/Policy/SlidingWindow.php | 41 ++++++++----------- .../RateLimiter/Policy/TokenBucket.php | 34 ++++++++------- .../Component/RateLimiter/Policy/Window.php | 15 +++++++ .../Tests/Policy/SlidingWindowTest.php | 3 +- 4 files changed, 55 insertions(+), 38 deletions(-) diff --git a/src/Symfony/Component/RateLimiter/Policy/SlidingWindow.php b/src/Symfony/Component/RateLimiter/Policy/SlidingWindow.php index 7bc85e522613b..8739e41c281cf 100644 --- a/src/Symfony/Component/RateLimiter/Policy/SlidingWindow.php +++ b/src/Symfony/Component/RateLimiter/Policy/SlidingWindow.php @@ -43,11 +43,6 @@ final class SlidingWindow implements LimiterStateInterface */ private $windowEndAt; - /** - * @var bool true if this window has been cached - */ - private $cached = true; - public function __construct(string $id, int $intervalInSeconds) { if ($intervalInSeconds < 1) { @@ -56,7 +51,6 @@ public function __construct(string $id, int $intervalInSeconds) $this->id = $id; $this->intervalInSeconds = $intervalInSeconds; $this->windowEndAt = microtime(true) + $intervalInSeconds; - $this->cached = false; } public static function createFromPreviousWindow(self $window, int $intervalInSeconds): self @@ -72,31 +66,17 @@ public static function createFromPreviousWindow(self $window, int $intervalInSec return $new; } - /** - * @internal - */ - public function __sleep(): array - { - // $cached is not serialized, it should only be set - // upon first creation of the window. - return ['id', 'hitCount', 'intervalInSeconds', 'hitCountForLastWindow', 'windowEndAt']; - } - public function getId(): string { return $this->id; } /** - * Store for the rest of this time frame and next. + * Returns the remaining of this timeframe and the next one. */ - public function getExpirationTime(): ?int + public function getExpirationTime(): int { - if ($this->cached) { - return null; - } - - return 2 * $this->intervalInSeconds; + return $this->windowEndAt + $this->intervalInSeconds - microtime(true); } public function isExpired(): bool @@ -124,4 +104,19 @@ public function getRetryAfter(): \DateTimeImmutable { return \DateTimeImmutable::createFromFormat('U.u', sprintf('%.6F', $this->windowEndAt)); } + + public function __serialize(): array + { + return [ + pack('NNN', $this->hitCount, $this->hitCountForLastWindow, $this->intervalInSeconds).$this->id => $this->windowEndAt, + ]; + } + + public function __unserialize(array $data): void + { + $pack = key($data); + $this->windowEndAt = $data[$pack]; + ['a' => $this->hitCount, 'b' => $this->hitCountForLastWindow, 'c' => $this->intervalInSeconds] = unpack('Na/Nb/Nc', $pack); + $this->id = substr($pack, 12); + } } diff --git a/src/Symfony/Component/RateLimiter/Policy/TokenBucket.php b/src/Symfony/Component/RateLimiter/Policy/TokenBucket.php index c703a71a7f38f..5d420e9665c85 100644 --- a/src/Symfony/Component/RateLimiter/Policy/TokenBucket.php +++ b/src/Symfony/Component/RateLimiter/Policy/TokenBucket.php @@ -20,7 +20,6 @@ */ final class TokenBucket implements LimiterStateInterface { - private $stringRate; private $id; private $rate; @@ -47,8 +46,6 @@ final class TokenBucket implements LimiterStateInterface */ public function __construct(string $id, int $initialTokens, Rate $rate, float $timer = null) { - unset($this->stringRate); - if ($initialTokens < 1) { throw new \InvalidArgumentException(sprintf('Cannot set the limit of "%s" to 0, as that would never accept any hit.', TokenBucketLimiter::class)); } @@ -91,9 +88,23 @@ public function getExpirationTime(): int return $this->rate->calculateTimeForTokens($this->burstSize); } - /** - * @internal - */ + public function __serialize(): array + { + return [ + pack('N', $this->burstSize).$this->id => $this->tokens, + (string) $this->rate => $this->timer, + ]; + } + + public function __unserialize(array $data): void + { + [$this->tokens, $this->timer] = array_values($data); + [$pack, $rate] = array_keys($data); + $this->rate = Rate::fromString($rate); + $this->burstSize = unpack('Na', $pack)['a']; + $this->id = substr($pack, 4); + } + public function __sleep(): array { $this->stringRate = (string) $this->rate; @@ -101,16 +112,11 @@ public function __sleep(): array return ['id', 'tokens', 'timer', 'burstSize', 'stringRate']; } - /** - * @internal - */ public function __wakeup(): void { - if (!\is_string($this->stringRate)) { - throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + if (\is_string($rate = $this->stringRate ?? null)) { + $this->rate = Rate::fromString($rate); + unset($this->stringRate); } - - $this->rate = Rate::fromString($this->stringRate); - unset($this->stringRate); } } diff --git a/src/Symfony/Component/RateLimiter/Policy/Window.php b/src/Symfony/Component/RateLimiter/Policy/Window.php index 686bb3fdbb164..02533a9a95dd9 100644 --- a/src/Symfony/Component/RateLimiter/Policy/Window.php +++ b/src/Symfony/Component/RateLimiter/Policy/Window.php @@ -85,4 +85,19 @@ public function calculateTimeForTokens(int $tokens): int return $cyclesRequired * $this->intervalInSeconds; } + + public function __serialize(): array + { + return [ + $this->id => $this->timer, + pack('NN', $this->hitCount, $this->intervalInSeconds) => $this->maxSize, + ]; + } + + public function __unserialize(array $data): void + { + [$this->timer, $this->maxSize] = array_values($data); + [$this->id, $pack] = array_keys($data); + ['a' => $this->hitCount, 'b' => $this->intervalInSeconds] = unpack('Na/Nb', $pack); + } } diff --git a/src/Symfony/Component/RateLimiter/Tests/Policy/SlidingWindowTest.php b/src/Symfony/Component/RateLimiter/Tests/Policy/SlidingWindowTest.php index df1d01499679b..f63ec433e6344 100644 --- a/src/Symfony/Component/RateLimiter/Tests/Policy/SlidingWindowTest.php +++ b/src/Symfony/Component/RateLimiter/Tests/Policy/SlidingWindowTest.php @@ -28,8 +28,9 @@ public function testGetExpirationTime() $this->assertSame(2 * 10, $window->getExpirationTime()); $data = serialize($window); + sleep(10); $cachedWindow = unserialize($data); - $this->assertNull($cachedWindow->getExpirationTime()); + $this->assertSame(10, $cachedWindow->getExpirationTime()); $new = SlidingWindow::createFromPreviousWindow($cachedWindow, 15); $this->assertSame(2 * 15, $new->getExpirationTime()); From f039ee68cfb625ba0ef74d87cfde59b765d2595d Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 29 Mar 2022 11:20:51 +0200 Subject: [PATCH 72/91] Add BC layer to handle old objects already present in cache --- .../Component/RateLimiter/Policy/SlidingWindow.php | 12 ++++++++++++ .../Component/RateLimiter/Policy/TokenBucket.php | 12 ++++++++++++ src/Symfony/Component/RateLimiter/Policy/Window.php | 12 ++++++++++++ 3 files changed, 36 insertions(+) diff --git a/src/Symfony/Component/RateLimiter/Policy/SlidingWindow.php b/src/Symfony/Component/RateLimiter/Policy/SlidingWindow.php index 8739e41c281cf..1eed0cbc6ec42 100644 --- a/src/Symfony/Component/RateLimiter/Policy/SlidingWindow.php +++ b/src/Symfony/Component/RateLimiter/Policy/SlidingWindow.php @@ -114,6 +114,18 @@ public function __serialize(): array public function __unserialize(array $data): void { + // BC layer for old objects serialized via __sleep + if (5 === \count($data)) { + $data = array_values($data); + $this->id = $data[0]; + $this->hitCount = $data[1]; + $this->intervalInSeconds = $data[2]; + $this->hitCountForLastWindow = $data[3]; + $this->windowEndAt = $data[4]; + + return; + } + $pack = key($data); $this->windowEndAt = $data[$pack]; ['a' => $this->hitCount, 'b' => $this->hitCountForLastWindow, 'c' => $this->intervalInSeconds] = unpack('Na/Nb/Nc', $pack); diff --git a/src/Symfony/Component/RateLimiter/Policy/TokenBucket.php b/src/Symfony/Component/RateLimiter/Policy/TokenBucket.php index 5d420e9665c85..520be6ed691cf 100644 --- a/src/Symfony/Component/RateLimiter/Policy/TokenBucket.php +++ b/src/Symfony/Component/RateLimiter/Policy/TokenBucket.php @@ -98,6 +98,18 @@ public function __serialize(): array public function __unserialize(array $data): void { + // BC layer for old objects serialized via __sleep + if (5 === \count($data)) { + $data = array_values($data); + $this->id = $data[0]; + $this->tokens = $data[1]; + $this->timer = $data[2]; + $this->burstSize = $data[3]; + $this->rate = Rate::fromString($data[4]); + + return; + } + [$this->tokens, $this->timer] = array_values($data); [$pack, $rate] = array_keys($data); $this->rate = Rate::fromString($rate); diff --git a/src/Symfony/Component/RateLimiter/Policy/Window.php b/src/Symfony/Component/RateLimiter/Policy/Window.php index 02533a9a95dd9..93452797075a0 100644 --- a/src/Symfony/Component/RateLimiter/Policy/Window.php +++ b/src/Symfony/Component/RateLimiter/Policy/Window.php @@ -96,6 +96,18 @@ public function __serialize(): array public function __unserialize(array $data): void { + // BC layer for old objects serialized via __sleep + if (5 === \count($data)) { + $data = array_values($data); + $this->id = $data[0]; + $this->hitCount = $data[1]; + $this->intervalInSeconds = $data[2]; + $this->maxSize = $data[3]; + $this->timer = $data[4]; + + return; + } + [$this->timer, $this->maxSize] = array_values($data); [$this->id, $pack] = array_keys($data); ['a' => $this->hitCount, 'b' => $this->intervalInSeconds] = unpack('Na/Nb', $pack); From 12271a44cfe0b4ea7fdcbfc20a1ebf588aa54410 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 30 Mar 2022 12:19:22 +0200 Subject: [PATCH 73/91] [Messenger] Add mysql indexes back and work around deadlocks using soft-delete --- .../Transport/Doctrine/ConnectionTest.php | 58 ------------------- .../Transport/Doctrine/Connection.php | 25 ++++++-- 2 files changed, 20 insertions(+), 63 deletions(-) diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/ConnectionTest.php b/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/ConnectionTest.php index 5bc7dddcd2934..9d9ad3c4d2c4c 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/ConnectionTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/ConnectionTest.php @@ -12,7 +12,6 @@ namespace Symfony\Component\Messenger\Tests\Transport\Doctrine; use Doctrine\DBAL\Abstraction\Result as AbstractionResult; -use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Connection as DBALConnection; use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Driver\Result as DriverResult; @@ -24,11 +23,8 @@ use Doctrine\DBAL\Query\QueryBuilder; use Doctrine\DBAL\Result; use Doctrine\DBAL\Schema\AbstractSchemaManager; -use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\SchemaConfig; -use Doctrine\DBAL\Schema\TableDiff; use Doctrine\DBAL\Statement; -use Doctrine\DBAL\Types\Types; use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Exception\InvalidArgumentException; use Symfony\Component\Messenger\Exception\TransportException; @@ -410,58 +406,4 @@ public function providePlatformSql(): iterable 'SELECT m.* FROM messenger_messages m WITH (UPDLOCK, ROWLOCK) WHERE (m.delivered_at is null OR m.delivered_at < ?) AND (m.available_at <= ?) AND (m.queue_name = ?) ORDER BY available_at ASC OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY ', ]; } - - /** - * @dataProvider setupIndicesProvider - */ - public function testSetupIndices(string $platformClass, array $expectedIndices) - { - $driverConnection = $this->createMock(DBALConnection::class); - $driverConnection->method('getConfiguration')->willReturn(new Configuration()); - - $schemaManager = $this->createMock(AbstractSchemaManager::class); - $schema = new Schema(); - $expectedTable = $schema->createTable('messenger_messages'); - $expectedTable->addColumn('id', Types::BIGINT); - $expectedTable->setPrimaryKey(['id']); - // Make sure columns for indices exists so addIndex() will not throw - foreach (array_unique(array_merge(...$expectedIndices)) as $columnName) { - $expectedTable->addColumn($columnName, Types::STRING); - } - foreach ($expectedIndices as $indexColumns) { - $expectedTable->addIndex($indexColumns); - } - $schemaManager->method('createSchema')->willReturn($schema); - if (method_exists(DBALConnection::class, 'createSchemaManager')) { - $driverConnection->method('createSchemaManager')->willReturn($schemaManager); - } else { - $driverConnection->method('getSchemaManager')->willReturn($schemaManager); - } - - $platformMock = $this->createMock($platformClass); - $platformMock - ->expects(self::once()) - ->method('getAlterTableSQL') - ->with(self::callback(static function (TableDiff $tableDiff): bool { - return 0 === \count($tableDiff->addedIndexes) && 0 === \count($tableDiff->changedIndexes) && 0 === \count($tableDiff->removedIndexes); - })) - ->willReturn([]); - $driverConnection->method('getDatabasePlatform')->willReturn($platformMock); - - $connection = new Connection([], $driverConnection); - $connection->setup(); - } - - public function setupIndicesProvider(): iterable - { - yield 'MySQL' => [ - MySQL57Platform::class, - [['delivered_at']], - ]; - - yield 'Other platforms' => [ - AbstractPlatform::class, - [['queue_name'], ['available_at'], ['delivered_at']], - ]; - } } diff --git a/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php b/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php index 47ab0824a2e1b..7ed376f61ce36 100644 --- a/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php +++ b/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php @@ -13,6 +13,7 @@ use Doctrine\DBAL\Connection as DBALConnection; use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Driver\Exception as DriverException; use Doctrine\DBAL\Driver\Result as DriverResult; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception\TableNotFoundException; @@ -157,6 +158,14 @@ public function send(string $body, array $headers, int $delay = 0): string public function get(): ?array { + if ($this->driverConnection->getDatabasePlatform() instanceof MySQLPlatform) { + try { + $this->driverConnection->delete($this->configuration['table_name'], ['delivered_at' => '9999-12-31']); + } catch (DriverException $e) { + // Ignore the exception + } + } + get: $this->driverConnection->beginTransaction(); try { @@ -224,6 +233,10 @@ public function get(): ?array public function ack(string $id): bool { try { + if ($this->driverConnection->getDatabasePlatform() instanceof MySQLPlatform) { + return $this->driverConnection->update($this->configuration['table_name'], ['delivered_at' => '9999-12-31'], ['id' => $id]) > 0; + } + return $this->driverConnection->delete($this->configuration['table_name'], ['id' => $id]) > 0; } catch (DBALException|Exception $exception) { throw new TransportException($exception->getMessage(), 0, $exception); @@ -233,6 +246,10 @@ public function ack(string $id): bool public function reject(string $id): bool { try { + if ($this->driverConnection->getDatabasePlatform() instanceof MySQLPlatform) { + return $this->driverConnection->update($this->configuration['table_name'], ['delivered_at' => '9999-12-31'], ['id' => $id]) > 0; + } + return $this->driverConnection->delete($this->configuration['table_name'], ['id' => $id]) > 0; } catch (DBALException|Exception $exception) { throw new TransportException($exception->getMessage(), 0, $exception); @@ -388,6 +405,7 @@ private function getSchema(): Schema $table->addColumn('headers', self::$useDeprecatedConstants ? Type::TEXT : Types::TEXT) ->setNotnull(true); $table->addColumn('queue_name', self::$useDeprecatedConstants ? Type::STRING : Types::STRING) + ->setLength(190) // MySQL 5.6 only supports 191 characters on an indexed column in utf8mb4 mode ->setNotnull(true); $table->addColumn('created_at', self::$useDeprecatedConstants ? Type::DATETIME : Types::DATETIME_MUTABLE) ->setNotnull(true); @@ -396,11 +414,8 @@ private function getSchema(): Schema $table->addColumn('delivered_at', self::$useDeprecatedConstants ? Type::DATETIME : Types::DATETIME_MUTABLE) ->setNotnull(false); $table->setPrimaryKey(['id']); - // No indices on queue_name and available_at on MySQL to prevent deadlock issues when running multiple consumers. - if (!$this->driverConnection->getDatabasePlatform() instanceof MySQLPlatform) { - $table->addIndex(['queue_name']); - $table->addIndex(['available_at']); - } + $table->addIndex(['queue_name']); + $table->addIndex(['available_at']); $table->addIndex(['delivered_at']); return $schema; From f09da16f31f8bc75b80e04c6dae8e695c8e1e3c8 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 28 Mar 2022 22:04:49 +0200 Subject: [PATCH 74/91] [ExpressionLanguage] Fix matches when the regexp is not valid --- .../ExpressionLanguage/Node/BinaryNode.php | 21 +++++++++- .../Tests/Node/BinaryNodeTest.php | 42 ++++++++++++++++++- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php b/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php index 3820f880e7f31..2f49bd90c6cae 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php @@ -12,6 +12,7 @@ namespace Symfony\Component\ExpressionLanguage\Node; use Symfony\Component\ExpressionLanguage\Compiler; +use Symfony\Component\ExpressionLanguage\SyntaxError; /** * @author Fabien Potencier @@ -46,8 +47,12 @@ public function compile(Compiler $compiler) $operator = $this->attributes['operator']; if ('matches' == $operator) { + if ($this->nodes['right'] instanceof ConstantNode) { + $this->evaluateMatches($this->nodes['right']->evaluate([], []), ''); + } + $compiler - ->raw('preg_match(') + ->raw('(static function ($regexp, $str) { set_error_handler(function ($t, $m) use ($regexp, $str) { throw new \Symfony\Component\ExpressionLanguage\SyntaxError(sprintf(\'Regexp "%s" passed to "matches" is not valid\', $regexp).substr($m, 12)); }); try { return preg_match($regexp, $str); } finally { restore_error_handler(); } })(') ->compile($this->nodes['right']) ->raw(', ') ->compile($this->nodes['left']) @@ -159,7 +164,7 @@ public function evaluate($functions, $values) return $left % $right; case 'matches': - return preg_match($right, $left); + return $this->evaluateMatches($right, $left); } } @@ -167,4 +172,16 @@ public function toArray() { return ['(', $this->nodes['left'], ' '.$this->attributes['operator'].' ', $this->nodes['right'], ')']; } + + private function evaluateMatches(string $regexp, string $str): int + { + set_error_handler(function ($t, $m) use ($regexp) { + throw new SyntaxError(sprintf('Regexp "%s" passed to "matches" is not valid', $regexp).substr($m, 12)); + }); + try { + return preg_match($regexp, $str); + } finally { + restore_error_handler(); + } + } } diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/Node/BinaryNodeTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/Node/BinaryNodeTest.php index b45a1e57b9b17..fccc04abce4b8 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/Node/BinaryNodeTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/Node/BinaryNodeTest.php @@ -11,9 +11,12 @@ namespace Symfony\Component\ExpressionLanguage\Tests\Node; +use Symfony\Component\ExpressionLanguage\Compiler; use Symfony\Component\ExpressionLanguage\Node\ArrayNode; use Symfony\Component\ExpressionLanguage\Node\BinaryNode; use Symfony\Component\ExpressionLanguage\Node\ConstantNode; +use Symfony\Component\ExpressionLanguage\Node\NameNode; +use Symfony\Component\ExpressionLanguage\SyntaxError; class BinaryNodeTest extends AbstractNodeTest { @@ -111,7 +114,7 @@ public function getCompileData() ['range(1, 3)', new BinaryNode('..', new ConstantNode(1), new ConstantNode(3))], - ['preg_match("/^[a-z]+/i\$/", "abc")', new BinaryNode('matches', new ConstantNode('abc'), new ConstantNode('/^[a-z]+/i$/'))], + ['(static function ($regexp, $str) { set_error_handler(function ($t, $m) use ($regexp, $str) { throw new \Symfony\Component\ExpressionLanguage\SyntaxError(sprintf(\'Regexp "%s" passed to "matches" is not valid\', $regexp).substr($m, 12)); }); try { return preg_match($regexp, $str); } finally { restore_error_handler(); } })("/^[a-z]+\$/", "abc")', new BinaryNode('matches', new ConstantNode('abc'), new ConstantNode('/^[a-z]+$/'))], ]; } @@ -160,7 +163,42 @@ public function getDumpData() ['(1 .. 3)', new BinaryNode('..', new ConstantNode(1), new ConstantNode(3))], - ['("abc" matches "/^[a-z]+/i$/")', new BinaryNode('matches', new ConstantNode('abc'), new ConstantNode('/^[a-z]+/i$/'))], + ['("abc" matches "/^[a-z]+$/")', new BinaryNode('matches', new ConstantNode('abc'), new ConstantNode('/^[a-z]+$/'))], ]; } + + public function testEvaluateMatchesWithInvalidRegexp() + { + $node = new BinaryNode('matches', new ConstantNode('abc'), new ConstantNode('this is not a regexp')); + + $this->expectExceptionObject(new SyntaxError('Regexp "this is not a regexp" passed to "matches" is not valid: Delimiter must not be alphanumeric or backslash')); + $node->evaluate([], []); + } + + public function testEvaluateMatchesWithInvalidRegexpAsExpression() + { + $node = new BinaryNode('matches', new ConstantNode('abc'), new NameNode('regexp')); + + $this->expectExceptionObject(new SyntaxError('Regexp "this is not a regexp" passed to "matches" is not valid: Delimiter must not be alphanumeric or backslash')); + $node->evaluate([], ['regexp' => 'this is not a regexp']); + } + + public function testCompileMatchesWithInvalidRegexp() + { + $node = new BinaryNode('matches', new ConstantNode('abc'), new ConstantNode('this is not a regexp')); + + $this->expectExceptionObject(new SyntaxError('Regexp "this is not a regexp" passed to "matches" is not valid: Delimiter must not be alphanumeric or backslash')); + $compiler = new Compiler([]); + $node->compile($compiler); + } + + public function testCompileMatchesWithInvalidRegexpAsExpression() + { + $node = new BinaryNode('matches', new ConstantNode('abc'), new NameNode('regexp')); + + $this->expectExceptionObject(new SyntaxError('Regexp "this is not a regexp" passed to "matches" is not valid: Delimiter must not be alphanumeric or backslash')); + $compiler = new Compiler([]); + $node->compile($compiler); + eval('$regexp = "this is not a regexp"; '.$compiler->getSource().';'); + } } From 8a9b841985e2535b842b8f23a666b855f1de6da2 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 30 Mar 2022 14:43:12 +0200 Subject: [PATCH 75/91] [VarExporter] Fix exporting objects with readonly properties --- .../VarExporter/Internal/Exporter.php | 2 +- .../Tests/Fixtures/FooReadonly.php | 21 +++++++++++++++++++ .../VarExporter/Tests/Fixtures/readonly.php | 20 ++++++++++++++++++ .../VarExporter/Tests/VarExporterTest.php | 8 +++++-- 4 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 src/Symfony/Component/VarExporter/Tests/Fixtures/FooReadonly.php create mode 100644 src/Symfony/Component/VarExporter/Tests/Fixtures/readonly.php diff --git a/src/Symfony/Component/VarExporter/Internal/Exporter.php b/src/Symfony/Component/VarExporter/Internal/Exporter.php index c46eb50aa988d..7141b4e6d96c1 100644 --- a/src/Symfony/Component/VarExporter/Internal/Exporter.php +++ b/src/Symfony/Component/VarExporter/Internal/Exporter.php @@ -140,7 +140,7 @@ public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount $i = 0; $n = (string) $name; if ('' === $n || "\0" !== $n[0]) { - $c = 'stdClass'; + $c = \PHP_VERSION_ID >= 80100 && $reflector->hasProperty($n) && ($p = $reflector->getProperty($n))->isReadOnly() ? $p->class : 'stdClass'; } elseif ('*' === $n[1]) { $n = substr($n, 3); $c = $reflector->getProperty($n)->class; diff --git a/src/Symfony/Component/VarExporter/Tests/Fixtures/FooReadonly.php b/src/Symfony/Component/VarExporter/Tests/Fixtures/FooReadonly.php new file mode 100644 index 0000000000000..8e41de95958bc --- /dev/null +++ b/src/Symfony/Component/VarExporter/Tests/Fixtures/FooReadonly.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Tests\Fixtures; + +class FooReadonly +{ + public function __construct( + public readonly string $name, + public readonly string $value, + ) { + } +} diff --git a/src/Symfony/Component/VarExporter/Tests/Fixtures/readonly.php b/src/Symfony/Component/VarExporter/Tests/Fixtures/readonly.php new file mode 100644 index 0000000000000..3b3db27305859 --- /dev/null +++ b/src/Symfony/Component/VarExporter/Tests/Fixtures/readonly.php @@ -0,0 +1,20 @@ + [ + 'name' => [ + 'k', + ], + 'value' => [ + 'v', + ], + ], + ], + $o[0], + [] +); diff --git a/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php b/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php index 407f25af1891f..f90737da2e8cf 100644 --- a/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php +++ b/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php @@ -16,6 +16,7 @@ use Symfony\Component\VarExporter\Exception\ClassNotFoundException; use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException; use Symfony\Component\VarExporter\Internal\Registry; +use Symfony\Component\VarExporter\Tests\Fixtures\FooReadonly; use Symfony\Component\VarExporter\Tests\Fixtures\FooSerializable; use Symfony\Component\VarExporter\Tests\Fixtures\FooUnitEnum; use Symfony\Component\VarExporter\Tests\Fixtures\MySerializable; @@ -244,9 +245,12 @@ public function provideExport() yield ['php74-serializable', new Php74Serializable()]; - if (\PHP_VERSION_ID >= 80100) { - yield ['unit-enum', [FooUnitEnum::Bar], true]; + if (\PHP_VERSION_ID < 80100) { + return; } + + yield ['unit-enum', [FooUnitEnum::Bar], true]; + yield ['readonly', new FooReadonly('k', 'v')]; } public function testUnicodeDirectionality() From d46b6764eb781154ae325ccffce4d6d9e3c920b3 Mon Sep 17 00:00:00 2001 From: Baptiste Leduc Date: Wed, 30 Mar 2022 13:25:30 +0200 Subject: [PATCH 76/91] [PropertyInfo] PhpStanExtractor namespace missmatch issue --- .../Extractor/PhpStanExtractor.php | 33 ++++++++++--------- .../PropertyInfo/PhpStan/NameScope.php | 8 ++--- .../PropertyInfo/PhpStan/NameScopeFactory.php | 16 +++++---- .../Tests/Extractor/PhpStanExtractorTest.php | 18 ++++++++++ .../Fixtures/Extractor/DummyNamespace.php | 20 +++++++++++ 5 files changed, 69 insertions(+), 26 deletions(-) create mode 100644 src/Symfony/Component/PropertyInfo/Tests/Fixtures/Extractor/DummyNamespace.php diff --git a/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php index 4a6a296784d6d..f833731aa6dee 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php @@ -45,7 +45,7 @@ final class PhpStanExtractor implements PropertyTypeExtractorInterface, Construc /** @var NameScopeFactory */ private $nameScopeFactory; - /** @var array */ + /** @var array */ private $docBlocks = []; private $phpStanTypeHelper; private $mutatorPrefixes; @@ -72,8 +72,8 @@ public function __construct(array $mutatorPrefixes = null, array $accessorPrefix public function getTypes(string $class, string $property, array $context = []): ?array { /** @var PhpDocNode|null $docNode */ - [$docNode, $source, $prefix] = $this->getDocBlock($class, $property); - $nameScope = $this->nameScopeFactory->create($class); + [$docNode, $source, $prefix, $declaringClass] = $this->getDocBlock($class, $property); + $nameScope = $this->nameScopeFactory->create($class, $declaringClass); if (null === $docNode) { return null; } @@ -184,7 +184,7 @@ private function filterDocBlockParams(PhpDocNode $docNode, string $allowedParam) } /** - * @return array{PhpDocNode|null, int|null, string|null} + * @return array{PhpDocNode|null, int|null, string|null, string|null} */ private function getDocBlock(string $class, string $property): array { @@ -196,20 +196,23 @@ private function getDocBlock(string $class, string $property): array $ucFirstProperty = ucfirst($property); - if ($docBlock = $this->getDocBlockFromProperty($class, $property)) { - $data = [$docBlock, self::PROPERTY, null]; - } elseif ([$docBlock] = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::ACCESSOR)) { - $data = [$docBlock, self::ACCESSOR, null]; - } elseif ([$docBlock, $prefix] = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::MUTATOR)) { - $data = [$docBlock, self::MUTATOR, $prefix]; + if ([$docBlock, $declaringClass] = $this->getDocBlockFromProperty($class, $property)) { + $data = [$docBlock, self::PROPERTY, null, $declaringClass]; + } elseif ([$docBlock, $_, $declaringClass] = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::ACCESSOR)) { + $data = [$docBlock, self::ACCESSOR, null, $declaringClass]; + } elseif ([$docBlock, $prefix, $declaringClass] = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::MUTATOR)) { + $data = [$docBlock, self::MUTATOR, $prefix, $declaringClass]; } else { - $data = [null, null, null]; + $data = [null, null, null, null]; } return $this->docBlocks[$propertyHash] = $data; } - private function getDocBlockFromProperty(string $class, string $property): ?PhpDocNode + /** + * @return array{PhpDocNode, string}|null + */ + private function getDocBlockFromProperty(string $class, string $property): ?array { // Use a ReflectionProperty instead of $class to get the parent class if applicable try { @@ -226,11 +229,11 @@ private function getDocBlockFromProperty(string $class, string $property): ?PhpD $phpDocNode = $this->phpDocParser->parse($tokens); $tokens->consumeTokenType(Lexer::TOKEN_END); - return $phpDocNode; + return [$phpDocNode, $reflectionProperty->class]; } /** - * @return array{PhpDocNode, string}|null + * @return array{PhpDocNode, string, string}|null */ private function getDocBlockFromMethod(string $class, string $ucFirstProperty, int $type): ?array { @@ -269,6 +272,6 @@ private function getDocBlockFromMethod(string $class, string $ucFirstProperty, i $phpDocNode = $this->phpDocParser->parse($tokens); $tokens->consumeTokenType(Lexer::TOKEN_END); - return [$phpDocNode, $prefix]; + return [$phpDocNode, $prefix, $reflectionMethod->class]; } } diff --git a/src/Symfony/Component/PropertyInfo/PhpStan/NameScope.php b/src/Symfony/Component/PropertyInfo/PhpStan/NameScope.php index 6722c0fb01f60..7d9a5f9ac1a58 100644 --- a/src/Symfony/Component/PropertyInfo/PhpStan/NameScope.php +++ b/src/Symfony/Component/PropertyInfo/PhpStan/NameScope.php @@ -22,14 +22,14 @@ */ final class NameScope { - private $className; + private $calledClassName; private $namespace; /** @var array alias(string) => fullName(string) */ private $uses; - public function __construct(string $className, string $namespace, array $uses = []) + public function __construct(string $calledClassName, string $namespace, array $uses = []) { - $this->className = $className; + $this->calledClassName = $calledClassName; $this->namespace = $namespace; $this->uses = $uses; } @@ -60,6 +60,6 @@ public function resolveStringName(string $name): string public function resolveRootClass(): string { - return $this->resolveStringName($this->className); + return $this->resolveStringName($this->calledClassName); } } diff --git a/src/Symfony/Component/PropertyInfo/PhpStan/NameScopeFactory.php b/src/Symfony/Component/PropertyInfo/PhpStan/NameScopeFactory.php index 1243259607c22..32f2f330eafcb 100644 --- a/src/Symfony/Component/PropertyInfo/PhpStan/NameScopeFactory.php +++ b/src/Symfony/Component/PropertyInfo/PhpStan/NameScopeFactory.php @@ -20,16 +20,18 @@ */ final class NameScopeFactory { - public function create(string $fullClassName): NameScope + public function create(string $calledClassName, string $declaringClassName = null): NameScope { - $reflection = new \ReflectionClass($fullClassName); - $path = explode('\\', $fullClassName); - $className = array_pop($path); - [$namespace, $uses] = $this->extractFromFullClassName($reflection); + $declaringClassName = $declaringClassName ?? $calledClassName; - $uses = array_merge($uses, $this->collectUses($reflection)); + $path = explode('\\', $calledClassName); + $calledClassName = array_pop($path); - return new NameScope($className, $namespace, $uses); + $declaringReflection = new \ReflectionClass($declaringClassName); + [$declaringNamespace, $declaringUses] = $this->extractFromFullClassName($declaringReflection); + $declaringUses = array_merge($declaringUses, $this->collectUses($declaringReflection)); + + return new NameScope($calledClassName, $declaringNamespace, $declaringUses); } private function collectUses(\ReflectionClass $reflection): array diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php index 30f6b831ac748..d3c2c950963b1 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\PropertyInfo\Tests\Extractor; use PHPUnit\Framework\TestCase; +use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor; use Symfony\Component\PropertyInfo\Tests\Fixtures\DefaultValue; use Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy; @@ -21,6 +22,8 @@ use Symfony\Component\PropertyInfo\Tests\Fixtures\TraitUsage\DummyUsingTrait; use Symfony\Component\PropertyInfo\Type; +require_once __DIR__.'/../Fixtures/Extractor/DummyNamespace.php'; + /** * @author Baptiste Leduc */ @@ -31,9 +34,15 @@ class PhpStanExtractorTest extends TestCase */ private $extractor; + /** + * @var PhpDocExtractor + */ + private $phpDocExtractor; + protected function setUp(): void { $this->extractor = new PhpStanExtractor(); + $this->phpDocExtractor = new PhpDocExtractor(); } /** @@ -383,6 +392,15 @@ public function testDummyNamespace() $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\DummyNamespace', 'dummy') ); } + + public function testDummyNamespaceWithProperty() + { + $phpStanTypes = $this->extractor->getTypes(\B\Dummy::class, 'property'); + $phpDocTypes = $this->phpDocExtractor->getTypes(\B\Dummy::class, 'property'); + + $this->assertEquals('A\Property', $phpStanTypes[0]->getClassName()); + $this->assertEquals($phpDocTypes[0]->getClassName(), $phpStanTypes[0]->getClassName()); + } } class PhpStanOmittedParamTagTypeDocBlock diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Extractor/DummyNamespace.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Extractor/DummyNamespace.php new file mode 100644 index 0000000000000..fd590af64709e --- /dev/null +++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Extractor/DummyNamespace.php @@ -0,0 +1,20 @@ + Date: Wed, 30 Mar 2022 18:27:26 +0200 Subject: [PATCH 77/91] [PropertyAccess] Fix typo in PropertyAccessor::readProperty() DocBlock --- src/Symfony/Component/PropertyAccess/PropertyAccessor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php index f73e148a89b74..607c8fad571ac 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php +++ b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php @@ -384,7 +384,7 @@ private function readIndex(array $zval, $index): array } /** - * Reads the a property from an object. + * Reads the value of a property from an object. * * @throws NoSuchPropertyException If $ignoreInvalidProperty is false and the property does not exist or is not public */ From 63df978b59bd96c75b0a125f78743542ca0bb482 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 31 Mar 2022 10:18:50 +0200 Subject: [PATCH 78/91] [Messenger][Security/Core] Remove legacy class aliases --- .../Messenger/Bridge/Amqp/Transport/AmqpFactory.php | 4 ---- .../Messenger/Bridge/Amqp/Transport/AmqpReceivedStamp.php | 4 ---- .../Messenger/Bridge/Amqp/Transport/AmqpReceiver.php | 4 ---- .../Messenger/Bridge/Amqp/Transport/AmqpSender.php | 4 ---- .../Messenger/Bridge/Amqp/Transport/AmqpStamp.php | 4 ---- .../Messenger/Bridge/Amqp/Transport/AmqpTransport.php | 4 ---- .../Bridge/Amqp/Transport/AmqpTransportFactory.php | 4 ---- .../Messenger/Bridge/Amqp/Transport/Connection.php | 4 ---- .../Messenger/Bridge/Doctrine/Transport/Connection.php | 4 ---- .../Bridge/Doctrine/Transport/DoctrineReceivedStamp.php | 4 ---- .../Bridge/Doctrine/Transport/DoctrineReceiver.php | 4 ---- .../Bridge/Doctrine/Transport/DoctrineSender.php | 4 ---- .../Bridge/Doctrine/Transport/DoctrineTransport.php | 4 ---- .../Doctrine/Transport/DoctrineTransportFactory.php | 4 ---- .../Messenger/Bridge/Redis/Transport/Connection.php | 4 ---- .../Bridge/Redis/Transport/RedisReceivedStamp.php | 4 ---- .../Messenger/Bridge/Redis/Transport/RedisReceiver.php | 4 ---- .../Messenger/Bridge/Redis/Transport/RedisSender.php | 4 ---- .../Messenger/Bridge/Redis/Transport/RedisTransport.php | 4 ---- .../Bridge/Redis/Transport/RedisTransportFactory.php | 4 ---- .../Core/Authorization/TraceableAccessDecisionManager.php | 4 ---- .../Security/Core/Exception/UserNotFoundException.php | 4 ---- .../Authorization/TraceableAccessDecisionManagerTest.php | 8 -------- .../Security/Http/Authentication/AuthenticatorManager.php | 4 ++-- .../Http/Authenticator/RememberMeAuthenticator.php | 4 ++-- 25 files changed, 4 insertions(+), 100 deletions(-) diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpFactory.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpFactory.php index 47d8487c29863..5cf6b069d898a 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpFactory.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpFactory.php @@ -33,7 +33,3 @@ public function createExchange(\AMQPChannel $channel): \AMQPExchange return new \AMQPExchange($channel); } } - -if (!class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpFactory::class, false)) { - class_alias(AmqpFactory::class, \Symfony\Component\Messenger\Transport\AmqpExt\AmqpFactory::class); -} diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpReceivedStamp.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpReceivedStamp.php index d99c182da5b8e..3b633d962c911 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpReceivedStamp.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpReceivedStamp.php @@ -37,7 +37,3 @@ public function getQueueName(): string return $this->queueName; } } - -if (!class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpReceivedStamp::class, false)) { - class_alias(AmqpReceivedStamp::class, \Symfony\Component\Messenger\Transport\AmqpExt\AmqpReceivedStamp::class); -} diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpReceiver.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpReceiver.php index 141ab8cdce5e6..713eaeff1f6dd 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpReceiver.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpReceiver.php @@ -144,7 +144,3 @@ private function findAmqpStamp(Envelope $envelope): AmqpReceivedStamp return $amqpReceivedStamp; } } - -if (!class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpReceiver::class, false)) { - class_alias(AmqpReceiver::class, \Symfony\Component\Messenger\Transport\AmqpExt\AmqpReceiver::class); -} diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpSender.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpSender.php index 5fdfdffaf15f6..7af8a87557edd 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpSender.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpSender.php @@ -80,7 +80,3 @@ public function send(Envelope $envelope): Envelope return $envelope; } } - -if (!class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpSender::class, false)) { - class_alias(AmqpSender::class, \Symfony\Component\Messenger\Transport\AmqpExt\AmqpSender::class); -} diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpStamp.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpStamp.php index d065aaa7d3e46..d7ef18ba06f2c 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpStamp.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpStamp.php @@ -88,7 +88,3 @@ public static function createWithAttributes(array $attributes, self $previousSta ); } } - -if (!class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpStamp::class, false)) { - class_alias(AmqpStamp::class, \Symfony\Component\Messenger\Transport\AmqpExt\AmqpStamp::class); -} diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpTransport.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpTransport.php index 9ffda47cee42c..b33105e6c628d 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpTransport.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpTransport.php @@ -101,7 +101,3 @@ private function getSender(): AmqpSender return $this->sender = new AmqpSender($this->connection, $this->serializer); } } - -if (!class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransport::class, false)) { - class_alias(AmqpTransport::class, \Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransport::class); -} diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpTransportFactory.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpTransportFactory.php index 420cf7a22c297..f032309ac7168 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpTransportFactory.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpTransportFactory.php @@ -32,7 +32,3 @@ public function supports(string $dsn, array $options): bool return 0 === strpos($dsn, 'amqp://') || 0 === strpos($dsn, 'amqps://'); } } - -if (!class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransportFactory::class, false)) { - class_alias(AmqpTransportFactory::class, \Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransportFactory::class); -} diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php index 7b742ce7198cc..102d7aedc2566 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php @@ -559,7 +559,3 @@ private function getRoutingKeyForMessage(?AmqpStamp $amqpStamp): ?string return (null !== $amqpStamp ? $amqpStamp->getRoutingKey() : null) ?? $this->getDefaultPublishRoutingKey(); } } - -if (!class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\Connection::class, false)) { - class_alias(Connection::class, \Symfony\Component\Messenger\Transport\AmqpExt\Connection::class); -} diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php index 4ef0b86aeecc4..9eebe0ff03db9 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php @@ -454,7 +454,3 @@ private function createSchemaManager(): AbstractSchemaManager : $this->driverConnection->getSchemaManager(); } } - -if (!class_exists(\Symfony\Component\Messenger\Transport\Doctrine\Connection::class, false)) { - class_alias(Connection::class, \Symfony\Component\Messenger\Transport\Doctrine\Connection::class); -} diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineReceivedStamp.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineReceivedStamp.php index a5be1c8650899..513f4b9f01c4c 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineReceivedStamp.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineReceivedStamp.php @@ -30,7 +30,3 @@ public function getId(): string return $this->id; } } - -if (!class_exists(\Symfony\Component\Messenger\Transport\Doctrine\DoctrineReceivedStamp::class, false)) { - class_alias(DoctrineReceivedStamp::class, \Symfony\Component\Messenger\Transport\Doctrine\DoctrineReceivedStamp::class); -} diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineReceiver.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineReceiver.php index fa62dc39e68ff..11c0e201cf1e4 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineReceiver.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineReceiver.php @@ -170,7 +170,3 @@ private function createEnvelopeFromData(array $data): Envelope ); } } - -if (!class_exists(\Symfony\Component\Messenger\Transport\Doctrine\DoctrineReceiver::class, false)) { - class_alias(DoctrineReceiver::class, \Symfony\Component\Messenger\Transport\Doctrine\DoctrineReceiver::class); -} diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineSender.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineSender.php index 6e5aa608528c9..39105d1b28985 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineSender.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineSender.php @@ -54,7 +54,3 @@ public function send(Envelope $envelope): Envelope return $envelope->with(new TransportMessageIdStamp($id)); } } - -if (!class_exists(\Symfony\Component\Messenger\Transport\Doctrine\DoctrineSender::class, false)) { - class_alias(DoctrineSender::class, \Symfony\Component\Messenger\Transport\Doctrine\DoctrineSender::class); -} diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineTransport.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineTransport.php index 997ea10797da2..72fdb10ee0a02 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineTransport.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineTransport.php @@ -129,7 +129,3 @@ private function getSender(): DoctrineSender return $this->sender = new DoctrineSender($this->connection, $this->serializer); } } - -if (!class_exists(\Symfony\Component\Messenger\Transport\Doctrine\DoctrineTransport::class, false)) { - class_alias(DoctrineTransport::class, \Symfony\Component\Messenger\Transport\Doctrine\DoctrineTransport::class); -} diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineTransportFactory.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineTransportFactory.php index b712da7475713..7e128cd14dde5 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineTransportFactory.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineTransportFactory.php @@ -57,7 +57,3 @@ public function supports(string $dsn, array $options): bool return 0 === strpos($dsn, 'doctrine://'); } } - -if (!class_exists(\Symfony\Component\Messenger\Transport\Doctrine\DoctrineTransportFactory::class, false)) { - class_alias(DoctrineTransportFactory::class, \Symfony\Component\Messenger\Transport\Doctrine\DoctrineTransportFactory::class); -} diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php index c8c3ea1f26157..788c754916518 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php @@ -573,7 +573,3 @@ private function rawCommand(string $command, ...$arguments): mixed return $result; } } - -if (!class_exists(\Symfony\Component\Messenger\Transport\RedisExt\Connection::class, false)) { - class_alias(Connection::class, \Symfony\Component\Messenger\Transport\RedisExt\Connection::class); -} diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisReceivedStamp.php b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisReceivedStamp.php index 9c4f34d9fedd0..4773d821f91f7 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisReceivedStamp.php +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisReceivedStamp.php @@ -30,7 +30,3 @@ public function getId(): string return $this->id; } } - -if (!class_exists(\Symfony\Component\Messenger\Transport\RedisExt\RedisReceivedStamp::class, false)) { - class_alias(RedisReceivedStamp::class, \Symfony\Component\Messenger\Transport\RedisExt\RedisReceivedStamp::class); -} diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisReceiver.php b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisReceiver.php index 1a63d334db0dd..0085338d6977a 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisReceiver.php +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisReceiver.php @@ -96,7 +96,3 @@ private function findRedisReceivedStamp(Envelope $envelope): RedisReceivedStamp return $redisReceivedStamp; } } - -if (!class_exists(\Symfony\Component\Messenger\Transport\RedisExt\RedisReceiver::class, false)) { - class_alias(RedisReceiver::class, \Symfony\Component\Messenger\Transport\RedisExt\RedisReceiver::class); -} diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisSender.php b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisSender.php index 433cfe924105c..dbbfe7770d3d4 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisSender.php +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisSender.php @@ -47,7 +47,3 @@ public function send(Envelope $envelope): Envelope return $envelope; } } - -if (!class_exists(\Symfony\Component\Messenger\Transport\RedisExt\RedisSender::class, false)) { - class_alias(RedisSender::class, \Symfony\Component\Messenger\Transport\RedisExt\RedisSender::class); -} diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisTransport.php b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisTransport.php index 88daa22c5366f..36215383eca8b 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisTransport.php +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisTransport.php @@ -84,7 +84,3 @@ private function getSender(): RedisSender return $this->sender = new RedisSender($this->connection, $this->serializer); } } - -if (!class_exists(\Symfony\Component\Messenger\Transport\RedisExt\RedisTransport::class, false)) { - class_alias(RedisTransport::class, \Symfony\Component\Messenger\Transport\RedisExt\RedisTransport::class); -} diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisTransportFactory.php b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisTransportFactory.php index 88cead8a934f4..1a362030cbfb9 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisTransportFactory.php +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisTransportFactory.php @@ -33,7 +33,3 @@ public function supports(string $dsn, array $options): bool return 0 === strpos($dsn, 'redis://') || 0 === strpos($dsn, 'rediss://'); } } - -if (!class_exists(\Symfony\Component\Messenger\Transport\RedisExt\RedisTransportFactory::class, false)) { - class_alias(RedisTransportFactory::class, \Symfony\Component\Messenger\Transport\RedisExt\RedisTransportFactory::class); -} diff --git a/src/Symfony/Component/Security/Core/Authorization/TraceableAccessDecisionManager.php b/src/Symfony/Component/Security/Core/Authorization/TraceableAccessDecisionManager.php index 7a85198dbbf8e..3a44f67afb782 100644 --- a/src/Symfony/Component/Security/Core/Authorization/TraceableAccessDecisionManager.php +++ b/src/Symfony/Component/Security/Core/Authorization/TraceableAccessDecisionManager.php @@ -109,7 +109,3 @@ public function getDecisionLog(): array return $this->decisionLog; } } - -if (!class_exists(DebugAccessDecisionManager::class, false)) { - class_alias(TraceableAccessDecisionManager::class, DebugAccessDecisionManager::class); -} diff --git a/src/Symfony/Component/Security/Core/Exception/UserNotFoundException.php b/src/Symfony/Component/Security/Core/Exception/UserNotFoundException.php index 98ebf5388a5c0..17f488b6dd0ec 100644 --- a/src/Symfony/Component/Security/Core/Exception/UserNotFoundException.php +++ b/src/Symfony/Component/Security/Core/Exception/UserNotFoundException.php @@ -71,7 +71,3 @@ public function __unserialize(array $data): void parent::__unserialize($parentData); } } - -if (!class_exists(UsernameNotFoundException::class, false)) { - class_alias(UserNotFoundException::class, UsernameNotFoundException::class); -} diff --git a/src/Symfony/Component/Security/Core/Tests/Authorization/TraceableAccessDecisionManagerTest.php b/src/Symfony/Component/Security/Core/Tests/Authorization/TraceableAccessDecisionManagerTest.php index 4b97885eb7ad0..30d2dfd35e8cf 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authorization/TraceableAccessDecisionManagerTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authorization/TraceableAccessDecisionManagerTest.php @@ -15,7 +15,6 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\AccessDecisionManager; use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; -use Symfony\Component\Security\Core\Authorization\DebugAccessDecisionManager; use Symfony\Component\Security\Core\Authorization\TraceableAccessDecisionManager; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; @@ -171,13 +170,6 @@ public function provideObjectsAndLogs(): \Generator ]; } - public function testDebugAccessDecisionManagerAliasExistsForBC() - { - $adm = new TraceableAccessDecisionManager(new AccessDecisionManager()); - - $this->assertInstanceOf(DebugAccessDecisionManager::class, $adm, 'For BC, TraceableAccessDecisionManager must be an instance of DebugAccessDecisionManager'); - } - /** * Tests decision log returned when a voter call decide method of AccessDecisionManager. */ diff --git a/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php b/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php index 5f47604f2e9c1..78dffe5e243a1 100644 --- a/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php +++ b/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php @@ -22,7 +22,7 @@ use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\BadCredentialsException; use Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; use Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticator; @@ -261,7 +261,7 @@ private function handleAuthenticationFailure(AuthenticationException $authentica // Avoid leaking error details in case of invalid user (e.g. user not found or invalid account status) // to prevent user enumeration via response content comparison - if ($this->hideUserNotFoundExceptions && ($authenticationException instanceof UsernameNotFoundException || ($authenticationException instanceof AccountStatusException && !$authenticationException instanceof CustomUserMessageAccountStatusException))) { + if ($this->hideUserNotFoundExceptions && ($authenticationException instanceof UserNotFoundException || ($authenticationException instanceof AccountStatusException && !$authenticationException instanceof CustomUserMessageAccountStatusException))) { $authenticationException = new BadCredentialsException('Bad credentials.', 0, $authenticationException); } diff --git a/src/Symfony/Component/Security/Http/Authenticator/RememberMeAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/RememberMeAuthenticator.php index d1e3c9ccd12d9..57f123c6b3004 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/RememberMeAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/RememberMeAuthenticator.php @@ -20,7 +20,7 @@ use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\CookieTheftException; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use Symfony\Component\Security\Http\Authenticator\Passport\Passport; use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; @@ -108,7 +108,7 @@ public function onAuthenticationSuccess(Request $request, TokenInterface $token, public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response { if (null !== $this->logger) { - if ($exception instanceof UsernameNotFoundException) { + if ($exception instanceof UserNotFoundException) { $this->logger->info('User for remember-me cookie not found.', ['exception' => $exception]); } elseif ($exception instanceof UnsupportedUserException) { $this->logger->warning('User class for remember-me cookie not supported.', ['exception' => $exception]); From a8465895ac65fb75ca5b46826c0f8a5c3c47b13b Mon Sep 17 00:00:00 2001 From: Yohann Tilotti Date: Thu, 31 Mar 2022 15:36:44 +0200 Subject: [PATCH 79/91] fix bootstrap_3_layout ChoiceType's expanded label_html --- .../views/Form/bootstrap_3_layout.html.twig | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig index 34cbc76074acd..8e2b607de80e3 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig @@ -101,7 +101,22 @@ {%- endif -%} {%- endif -%} - {{- widget|raw }} {{ label is not same as(false) ? (translation_domain is same as(false) ? (label_html is same as(false) ? label : label|raw) : (label_html is same as(false) ? label|trans(label_translation_parameters, translation_domain) : label|trans(label_translation_parameters, translation_domain)|raw)) -}} + {{- widget|raw }} + {%- if label is not same as(false) -%} + {%- if translation_domain is same as(false) -%} + {%- if label_html is same as(false) -%} + {{ label -}} + {%- else -%} + {{ label|raw -}} + {%- endif -%} + {%- else -%} + {%- if label_html is same as(false) -%} + {{ label|trans(label_translation_parameters, translation_domain) -}} + {%- else -%} + {{ label|trans(label_translation_parameters, translation_domain)|raw -}} + {%- endif -%} + {%- endif -%} + {%- endif -%} {%- endif -%} {%- endblock checkbox_radio_label %} From 80162936b65006abc053ce670a86692c3369ad7d Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 31 Mar 2022 18:54:39 +0200 Subject: [PATCH 80/91] Fix composer on appveyor --- .appveyor.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 889aafe26929b..494dce0423fe3 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -47,8 +47,7 @@ install: - echo extension=php_curl.dll >> php.ini-max - copy /Y php.ini-max php.ini - cd c:\projects\symfony - - IF NOT EXIST composer.phar (appveyor DownloadFile https://github.com/composer/composer/releases/download/2.0.0/composer.phar) - - php composer.phar self-update --2 + - IF NOT EXIST composer.phar (appveyor DownloadFile https://github.com/composer/composer/releases/download/2.2.10/composer.phar) - copy /Y .github\composer-config.json %APPDATA%\Composer\config.json - git config --global user.email "" - git config --global user.name "Symfony" From 235262eebd111b224b583564e74c423c1acd31cb Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 31 Mar 2022 19:20:31 +0200 Subject: [PATCH 81/91] Revert "minor #45899 [Messenger] [Security/Core] Remove legacy class aliases (nicolas-grekas)" This reverts commit 758ed6bf66648a17432496162cd02215b8809df4, reversing changes made to 1bd3af4ec6303a4cf3de0218abc29ca9fdd7a793. --- .../Messenger/Bridge/Amqp/Transport/AmqpFactory.php | 4 ++++ .../Messenger/Bridge/Amqp/Transport/AmqpReceivedStamp.php | 4 ++++ .../Messenger/Bridge/Amqp/Transport/AmqpReceiver.php | 4 ++++ .../Messenger/Bridge/Amqp/Transport/AmqpSender.php | 4 ++++ .../Messenger/Bridge/Amqp/Transport/AmqpStamp.php | 4 ++++ .../Messenger/Bridge/Amqp/Transport/AmqpTransport.php | 4 ++++ .../Bridge/Amqp/Transport/AmqpTransportFactory.php | 4 ++++ .../Messenger/Bridge/Amqp/Transport/Connection.php | 4 ++++ .../Messenger/Bridge/Doctrine/Transport/Connection.php | 4 ++++ .../Bridge/Doctrine/Transport/DoctrineReceivedStamp.php | 4 ++++ .../Bridge/Doctrine/Transport/DoctrineReceiver.php | 4 ++++ .../Bridge/Doctrine/Transport/DoctrineSender.php | 4 ++++ .../Bridge/Doctrine/Transport/DoctrineTransport.php | 4 ++++ .../Doctrine/Transport/DoctrineTransportFactory.php | 4 ++++ .../Messenger/Bridge/Redis/Transport/Connection.php | 4 ++++ .../Bridge/Redis/Transport/RedisReceivedStamp.php | 4 ++++ .../Messenger/Bridge/Redis/Transport/RedisReceiver.php | 4 ++++ .../Messenger/Bridge/Redis/Transport/RedisSender.php | 4 ++++ .../Messenger/Bridge/Redis/Transport/RedisTransport.php | 4 ++++ .../Bridge/Redis/Transport/RedisTransportFactory.php | 4 ++++ .../Core/Authorization/TraceableAccessDecisionManager.php | 4 ++++ .../Security/Core/Exception/UserNotFoundException.php | 4 ++++ .../Authorization/TraceableAccessDecisionManagerTest.php | 8 ++++++++ 23 files changed, 96 insertions(+) diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpFactory.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpFactory.php index 5cf6b069d898a..47d8487c29863 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpFactory.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpFactory.php @@ -33,3 +33,7 @@ public function createExchange(\AMQPChannel $channel): \AMQPExchange return new \AMQPExchange($channel); } } + +if (!class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpFactory::class, false)) { + class_alias(AmqpFactory::class, \Symfony\Component\Messenger\Transport\AmqpExt\AmqpFactory::class); +} diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpReceivedStamp.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpReceivedStamp.php index 3b633d962c911..d99c182da5b8e 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpReceivedStamp.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpReceivedStamp.php @@ -37,3 +37,7 @@ public function getQueueName(): string return $this->queueName; } } + +if (!class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpReceivedStamp::class, false)) { + class_alias(AmqpReceivedStamp::class, \Symfony\Component\Messenger\Transport\AmqpExt\AmqpReceivedStamp::class); +} diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpReceiver.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpReceiver.php index 713eaeff1f6dd..141ab8cdce5e6 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpReceiver.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpReceiver.php @@ -144,3 +144,7 @@ private function findAmqpStamp(Envelope $envelope): AmqpReceivedStamp return $amqpReceivedStamp; } } + +if (!class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpReceiver::class, false)) { + class_alias(AmqpReceiver::class, \Symfony\Component\Messenger\Transport\AmqpExt\AmqpReceiver::class); +} diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpSender.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpSender.php index 7af8a87557edd..5fdfdffaf15f6 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpSender.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpSender.php @@ -80,3 +80,7 @@ public function send(Envelope $envelope): Envelope return $envelope; } } + +if (!class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpSender::class, false)) { + class_alias(AmqpSender::class, \Symfony\Component\Messenger\Transport\AmqpExt\AmqpSender::class); +} diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpStamp.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpStamp.php index d7ef18ba06f2c..d065aaa7d3e46 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpStamp.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpStamp.php @@ -88,3 +88,7 @@ public static function createWithAttributes(array $attributes, self $previousSta ); } } + +if (!class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpStamp::class, false)) { + class_alias(AmqpStamp::class, \Symfony\Component\Messenger\Transport\AmqpExt\AmqpStamp::class); +} diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpTransport.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpTransport.php index b33105e6c628d..9ffda47cee42c 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpTransport.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpTransport.php @@ -101,3 +101,7 @@ private function getSender(): AmqpSender return $this->sender = new AmqpSender($this->connection, $this->serializer); } } + +if (!class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransport::class, false)) { + class_alias(AmqpTransport::class, \Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransport::class); +} diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpTransportFactory.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpTransportFactory.php index f032309ac7168..420cf7a22c297 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpTransportFactory.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpTransportFactory.php @@ -32,3 +32,7 @@ public function supports(string $dsn, array $options): bool return 0 === strpos($dsn, 'amqp://') || 0 === strpos($dsn, 'amqps://'); } } + +if (!class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransportFactory::class, false)) { + class_alias(AmqpTransportFactory::class, \Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransportFactory::class); +} diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php index 102d7aedc2566..7b742ce7198cc 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php @@ -559,3 +559,7 @@ private function getRoutingKeyForMessage(?AmqpStamp $amqpStamp): ?string return (null !== $amqpStamp ? $amqpStamp->getRoutingKey() : null) ?? $this->getDefaultPublishRoutingKey(); } } + +if (!class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\Connection::class, false)) { + class_alias(Connection::class, \Symfony\Component\Messenger\Transport\AmqpExt\Connection::class); +} diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php index 570c3653d7e68..df02081c657ce 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php @@ -469,3 +469,7 @@ private function createSchemaManager(): AbstractSchemaManager : $this->driverConnection->getSchemaManager(); } } + +if (!class_exists(\Symfony\Component\Messenger\Transport\Doctrine\Connection::class, false)) { + class_alias(Connection::class, \Symfony\Component\Messenger\Transport\Doctrine\Connection::class); +} diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineReceivedStamp.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineReceivedStamp.php index 513f4b9f01c4c..a5be1c8650899 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineReceivedStamp.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineReceivedStamp.php @@ -30,3 +30,7 @@ public function getId(): string return $this->id; } } + +if (!class_exists(\Symfony\Component\Messenger\Transport\Doctrine\DoctrineReceivedStamp::class, false)) { + class_alias(DoctrineReceivedStamp::class, \Symfony\Component\Messenger\Transport\Doctrine\DoctrineReceivedStamp::class); +} diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineReceiver.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineReceiver.php index 11c0e201cf1e4..fa62dc39e68ff 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineReceiver.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineReceiver.php @@ -170,3 +170,7 @@ private function createEnvelopeFromData(array $data): Envelope ); } } + +if (!class_exists(\Symfony\Component\Messenger\Transport\Doctrine\DoctrineReceiver::class, false)) { + class_alias(DoctrineReceiver::class, \Symfony\Component\Messenger\Transport\Doctrine\DoctrineReceiver::class); +} diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineSender.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineSender.php index 39105d1b28985..6e5aa608528c9 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineSender.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineSender.php @@ -54,3 +54,7 @@ public function send(Envelope $envelope): Envelope return $envelope->with(new TransportMessageIdStamp($id)); } } + +if (!class_exists(\Symfony\Component\Messenger\Transport\Doctrine\DoctrineSender::class, false)) { + class_alias(DoctrineSender::class, \Symfony\Component\Messenger\Transport\Doctrine\DoctrineSender::class); +} diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineTransport.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineTransport.php index 72fdb10ee0a02..997ea10797da2 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineTransport.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineTransport.php @@ -129,3 +129,7 @@ private function getSender(): DoctrineSender return $this->sender = new DoctrineSender($this->connection, $this->serializer); } } + +if (!class_exists(\Symfony\Component\Messenger\Transport\Doctrine\DoctrineTransport::class, false)) { + class_alias(DoctrineTransport::class, \Symfony\Component\Messenger\Transport\Doctrine\DoctrineTransport::class); +} diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineTransportFactory.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineTransportFactory.php index 7e128cd14dde5..b712da7475713 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineTransportFactory.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineTransportFactory.php @@ -57,3 +57,7 @@ public function supports(string $dsn, array $options): bool return 0 === strpos($dsn, 'doctrine://'); } } + +if (!class_exists(\Symfony\Component\Messenger\Transport\Doctrine\DoctrineTransportFactory::class, false)) { + class_alias(DoctrineTransportFactory::class, \Symfony\Component\Messenger\Transport\Doctrine\DoctrineTransportFactory::class); +} diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php index 788c754916518..c8c3ea1f26157 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php @@ -573,3 +573,7 @@ private function rawCommand(string $command, ...$arguments): mixed return $result; } } + +if (!class_exists(\Symfony\Component\Messenger\Transport\RedisExt\Connection::class, false)) { + class_alias(Connection::class, \Symfony\Component\Messenger\Transport\RedisExt\Connection::class); +} diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisReceivedStamp.php b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisReceivedStamp.php index 4773d821f91f7..9c4f34d9fedd0 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisReceivedStamp.php +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisReceivedStamp.php @@ -30,3 +30,7 @@ public function getId(): string return $this->id; } } + +if (!class_exists(\Symfony\Component\Messenger\Transport\RedisExt\RedisReceivedStamp::class, false)) { + class_alias(RedisReceivedStamp::class, \Symfony\Component\Messenger\Transport\RedisExt\RedisReceivedStamp::class); +} diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisReceiver.php b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisReceiver.php index 0085338d6977a..1a63d334db0dd 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisReceiver.php +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisReceiver.php @@ -96,3 +96,7 @@ private function findRedisReceivedStamp(Envelope $envelope): RedisReceivedStamp return $redisReceivedStamp; } } + +if (!class_exists(\Symfony\Component\Messenger\Transport\RedisExt\RedisReceiver::class, false)) { + class_alias(RedisReceiver::class, \Symfony\Component\Messenger\Transport\RedisExt\RedisReceiver::class); +} diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisSender.php b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisSender.php index dbbfe7770d3d4..433cfe924105c 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisSender.php +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisSender.php @@ -47,3 +47,7 @@ public function send(Envelope $envelope): Envelope return $envelope; } } + +if (!class_exists(\Symfony\Component\Messenger\Transport\RedisExt\RedisSender::class, false)) { + class_alias(RedisSender::class, \Symfony\Component\Messenger\Transport\RedisExt\RedisSender::class); +} diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisTransport.php b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisTransport.php index 36215383eca8b..88daa22c5366f 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisTransport.php +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisTransport.php @@ -84,3 +84,7 @@ private function getSender(): RedisSender return $this->sender = new RedisSender($this->connection, $this->serializer); } } + +if (!class_exists(\Symfony\Component\Messenger\Transport\RedisExt\RedisTransport::class, false)) { + class_alias(RedisTransport::class, \Symfony\Component\Messenger\Transport\RedisExt\RedisTransport::class); +} diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisTransportFactory.php b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisTransportFactory.php index 1a362030cbfb9..88cead8a934f4 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisTransportFactory.php +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisTransportFactory.php @@ -33,3 +33,7 @@ public function supports(string $dsn, array $options): bool return 0 === strpos($dsn, 'redis://') || 0 === strpos($dsn, 'rediss://'); } } + +if (!class_exists(\Symfony\Component\Messenger\Transport\RedisExt\RedisTransportFactory::class, false)) { + class_alias(RedisTransportFactory::class, \Symfony\Component\Messenger\Transport\RedisExt\RedisTransportFactory::class); +} diff --git a/src/Symfony/Component/Security/Core/Authorization/TraceableAccessDecisionManager.php b/src/Symfony/Component/Security/Core/Authorization/TraceableAccessDecisionManager.php index 3a44f67afb782..7a85198dbbf8e 100644 --- a/src/Symfony/Component/Security/Core/Authorization/TraceableAccessDecisionManager.php +++ b/src/Symfony/Component/Security/Core/Authorization/TraceableAccessDecisionManager.php @@ -109,3 +109,7 @@ public function getDecisionLog(): array return $this->decisionLog; } } + +if (!class_exists(DebugAccessDecisionManager::class, false)) { + class_alias(TraceableAccessDecisionManager::class, DebugAccessDecisionManager::class); +} diff --git a/src/Symfony/Component/Security/Core/Exception/UserNotFoundException.php b/src/Symfony/Component/Security/Core/Exception/UserNotFoundException.php index 17f488b6dd0ec..98ebf5388a5c0 100644 --- a/src/Symfony/Component/Security/Core/Exception/UserNotFoundException.php +++ b/src/Symfony/Component/Security/Core/Exception/UserNotFoundException.php @@ -71,3 +71,7 @@ public function __unserialize(array $data): void parent::__unserialize($parentData); } } + +if (!class_exists(UsernameNotFoundException::class, false)) { + class_alias(UserNotFoundException::class, UsernameNotFoundException::class); +} diff --git a/src/Symfony/Component/Security/Core/Tests/Authorization/TraceableAccessDecisionManagerTest.php b/src/Symfony/Component/Security/Core/Tests/Authorization/TraceableAccessDecisionManagerTest.php index 30d2dfd35e8cf..4b97885eb7ad0 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authorization/TraceableAccessDecisionManagerTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authorization/TraceableAccessDecisionManagerTest.php @@ -15,6 +15,7 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\AccessDecisionManager; use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; +use Symfony\Component\Security\Core\Authorization\DebugAccessDecisionManager; use Symfony\Component\Security\Core\Authorization\TraceableAccessDecisionManager; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; @@ -170,6 +171,13 @@ public function provideObjectsAndLogs(): \Generator ]; } + public function testDebugAccessDecisionManagerAliasExistsForBC() + { + $adm = new TraceableAccessDecisionManager(new AccessDecisionManager()); + + $this->assertInstanceOf(DebugAccessDecisionManager::class, $adm, 'For BC, TraceableAccessDecisionManager must be an instance of DebugAccessDecisionManager'); + } + /** * Tests decision log returned when a voter call decide method of AccessDecisionManager. */ From 6f87f9126d12dad06c61895bd7cd67653fbaf914 Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Thu, 31 Mar 2022 22:10:53 +0200 Subject: [PATCH 82/91] [TwigBridge] Fix the build --- src/Symfony/Bridge/Doctrine/composer.json | 1 - .../Twig/Resources/views/Form/bootstrap_3_layout.html.twig | 4 ++-- src/Symfony/Bundle/FrameworkBundle/composer.json | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index dadb456cc2029..5d8e8485c73e9 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -34,7 +34,6 @@ "symfony/http-kernel": "^5.0|^6.0", "symfony/messenger": "^4.4|^5.0|^6.0", "symfony/doctrine-messenger": "^5.1|^6.0", - "symfony/phpunit-bridge": "^4.4|^5.4|^6.0", "symfony/property-access": "^4.4|^5.0|^6.0", "symfony/property-info": "^5.0|^6.0", "symfony/proxy-manager-bridge": "^4.4|^5.0|^6.0", diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig index 8e2b607de80e3..865f9078a9658 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig @@ -101,8 +101,8 @@ {%- endif -%} {%- endif -%} - {{- widget|raw }} - {%- if label is not same as(false) -%} + {#- if statement must be kept on the same line, to force the space between widget and label -#} + {{- widget|raw }} {% if label is not same as(false) -%} {%- if translation_domain is same as(false) -%} {%- if label_html is same as(false) -%} {{ label -}} diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 36e77447ca657..467044dd25017 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -66,8 +66,7 @@ "symfony/property-info": "^4.4|^5.0|^6.0", "symfony/web-link": "^4.4|^5.0|^6.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "twig/twig": "^2.10|^3.0", - "symfony/phpunit-bridge": "^5.3|^6.0" + "twig/twig": "^2.10|^3.0" }, "conflict": { "doctrine/annotations": "<1.13.1", From 98279474183701aaa965bcd2cefbceae78429b9e Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 1 Apr 2022 09:35:56 +0200 Subject: [PATCH 83/91] Fix appveyor --- .appveyor.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 494dce0423fe3..e60c3e2632184 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -2,10 +2,6 @@ build: false clone_depth: 2 clone_folder: c:\projects\symfony -cache: - - composer.phar - - .phpunit -> phpunit - init: - SET PATH=c:\php;%PATH% - SET COMPOSER_NO_INTERACTION=1 @@ -47,8 +43,8 @@ install: - echo extension=php_curl.dll >> php.ini-max - copy /Y php.ini-max php.ini - cd c:\projects\symfony - - IF NOT EXIST composer.phar (appveyor DownloadFile https://github.com/composer/composer/releases/download/2.2.10/composer.phar) - - copy /Y .github\composer-config.json %APPDATA%\Composer\config.json + - appveyor DownloadFile https://github.com/composer/composer/releases/download/2.2.10/composer.phar + - mkdir %APPDATA%\Composer && copy /Y .github\composer-config.json %APPDATA%\Composer\config.json - git config --global user.email "" - git config --global user.name "Symfony" - FOR /F "tokens=* USEBACKQ" %%F IN (`bash -c "grep ' VERSION = ' src/Symfony/Component/HttpKernel/Kernel.php | grep -o '[0-9][0-9]*\.[0-9]'"`) DO (SET SYMFONY_VERSION=%%F) From f55927a06663c9025c8fd3be86e1ba0c3c191d6a Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 1 Apr 2022 09:54:10 +0200 Subject: [PATCH 84/91] Fix URL to get composer.phar --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index e60c3e2632184..dfc01ccce97de 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -43,7 +43,7 @@ install: - echo extension=php_curl.dll >> php.ini-max - copy /Y php.ini-max php.ini - cd c:\projects\symfony - - appveyor DownloadFile https://github.com/composer/composer/releases/download/2.2.10/composer.phar + - appveyor DownloadFile https://getcomposer.org/download/latest-2.2.x/composer.phar - mkdir %APPDATA%\Composer && copy /Y .github\composer-config.json %APPDATA%\Composer\config.json - git config --global user.email "" - git config --global user.name "Symfony" From a0746ec6932e586d3a1c346a096923b10b7ee28e Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 1 Apr 2022 10:27:19 +0200 Subject: [PATCH 85/91] [Bridge/Doctrine] Fix tests --- .../Doctrine/Tests/Middleware/Debug/MiddlewareTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php b/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php index e43cec8b98650..d5b5dd119f1e8 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php @@ -11,9 +11,11 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\Middleware\Debug\DebugDataHolder; use Symfony\Bridge\Doctrine\Middleware\Debug\Middleware; -use Symfony\Bridge\PhpUnit\ClockMock; use Symfony\Component\Stopwatch\Stopwatch; +/** + * @requires extension pdo_sqlite + */ class MiddlewareTest extends TestCase { private $debugDataHolder; @@ -27,8 +29,6 @@ protected function setUp(): void if (!interface_exists(MiddlewareInterface::class)) { $this->markTestSkipped(sprintf('%s needed to run this test', MiddlewareInterface::class)); } - - ClockMock::withClockMock(false); } private function init(bool $withStopwatch = true): void From 911fee39132ddb74f906ffe2086d7525e8083c50 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 1 Apr 2022 10:40:14 +0200 Subject: [PATCH 86/91] [Bridge/Doctrine] fix tests --- .../Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php b/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php index d5b5dd119f1e8..46e85784e821b 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php @@ -11,6 +11,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\Middleware\Debug\DebugDataHolder; use Symfony\Bridge\Doctrine\Middleware\Debug\Middleware; +use Symfony\Bridge\PhpUnit\ClockMock; use Symfony\Component\Stopwatch\Stopwatch; /** @@ -29,6 +30,8 @@ protected function setUp(): void if (!interface_exists(MiddlewareInterface::class)) { $this->markTestSkipped(sprintf('%s needed to run this test', MiddlewareInterface::class)); } + + ClockMock::withClockMock(false); } private function init(bool $withStopwatch = true): void From d4a695fe143864b949faa26b088ba4f809c30a81 Mon Sep 17 00:00:00 2001 From: Robert-Jan de Dreu Date: Fri, 11 Mar 2022 14:46:27 +0100 Subject: [PATCH 87/91] [Messenger] Fix cannot select FOR UPDATE from view on Oracle --- .../Messenger/Transport/Doctrine/Connection.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php b/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php index 7ed376f61ce36..379132c9c98da 100644 --- a/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php +++ b/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php @@ -19,6 +19,7 @@ use Doctrine\DBAL\Exception\TableNotFoundException; use Doctrine\DBAL\LockMode; use Doctrine\DBAL\Platforms\MySQLPlatform; +use Doctrine\DBAL\Platforms\OraclePlatform; use Doctrine\DBAL\Query\QueryBuilder; use Doctrine\DBAL\Result; use Doctrine\DBAL\Schema\AbstractSchemaManager; @@ -187,6 +188,18 @@ public function get(): ?array ); } + // Wrap the rownum query in a sub-query to allow writelocks without ORA-02014 error + if ($this->driverConnection->getDatabasePlatform() instanceof OraclePlatform) { + $sql = str_replace('SELECT a.* FROM', 'SELECT a.id FROM', $sql); + + $wrappedQuery = $this->driverConnection->createQueryBuilder() + ->select('w.*') + ->from($this->configuration['table_name'], 'w') + ->where('w.id IN('.$sql.')'); + + $sql = $wrappedQuery->getSQL(); + } + // use SELECT ... FOR UPDATE to lock table $stmt = $this->executeQuery( $sql.' '.$this->driverConnection->getDatabasePlatform()->getWriteLockSQL(), From d7c13610acc586f31a38e559c1a144a10ab914b3 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 21 Mar 2022 10:27:48 +0100 Subject: [PATCH 88/91] [HttpClient] On redirections don't send content-related request headers --- src/Symfony/Component/HttpClient/CurlHttpClient.php | 6 ++++-- .../Component/HttpClient/NativeHttpClient.php | 7 +++++-- .../HttpClient/Tests/HttpClientTestCase.php | 12 ++++++++++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/HttpClient/CurlHttpClient.php b/src/Symfony/Component/HttpClient/CurlHttpClient.php index 2830c5025063e..3b63addec8865 100644 --- a/src/Symfony/Component/HttpClient/CurlHttpClient.php +++ b/src/Symfony/Component/HttpClient/CurlHttpClient.php @@ -204,9 +204,11 @@ public function request(string $method, string $url, array $options = []): Respo $hasContentLength = isset($options['normalized_headers']['content-length'][0]); - foreach ($options['headers'] as $header) { + foreach ($options['headers'] as $i => $header) { if ($hasContentLength && 0 === stripos($header, 'Content-Length:')) { - continue; // Let curl handle Content-Length headers + // Let curl handle Content-Length headers + unset($options['headers'][$i]); + continue; } if (':' === $header[-2] && \strlen($header) - 2 === strpos($header, ': ')) { // curl requires a special syntax to send empty headers diff --git a/src/Symfony/Component/HttpClient/NativeHttpClient.php b/src/Symfony/Component/HttpClient/NativeHttpClient.php index 939eb425c7672..f52d93d5eb7e0 100644 --- a/src/Symfony/Component/HttpClient/NativeHttpClient.php +++ b/src/Symfony/Component/HttpClient/NativeHttpClient.php @@ -430,9 +430,12 @@ private static function createRedirectResolver(array $options, string $host, ?ar if ('POST' === $options['method'] || 303 === $info['http_code']) { $info['http_method'] = $options['method'] = 'HEAD' === $options['method'] ? 'HEAD' : 'GET'; $options['content'] = ''; - $options['header'] = array_filter($options['header'], static function ($h) { + $filterContentHeaders = static function ($h) { return 0 !== stripos($h, 'Content-Length:') && 0 !== stripos($h, 'Content-Type:'); - }); + }; + $options['header'] = array_filter($options['header'], $filterContentHeaders); + $redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], $filterContentHeaders); + $redirectHeaders['with_auth'] = array_filter($redirectHeaders['with_auth'], $filterContentHeaders); stream_context_set_option($context, ['http' => $options]); } diff --git a/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php b/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php index cf33bd9816e86..d36e7f70b72ca 100644 --- a/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php +++ b/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php @@ -194,6 +194,18 @@ public function testFixContentLength() $this->assertSame(['abc' => 'def', 'REQUEST_METHOD' => 'POST'], $body); } + public function testDropContentRelatedHeadersWhenFollowingRequestIsUsingGet() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('POST', 'http://localhost:8057/302', [ + 'body' => 'foo', + 'headers' => ['Content-Length: 3'], + ]); + + $this->assertSame(200, $response->getStatusCode()); + } + public function testNegativeTimeout() { $client = $this->getHttpClient(__FUNCTION__); From 40751b866f681ce7f64ce731fe237371b9aa7b18 Mon Sep 17 00:00:00 2001 From: Blackfelix Date: Fri, 1 Apr 2022 14:33:59 +0200 Subject: [PATCH 89/91] Missing return type in getFilenameWithoutExtension function --- src/Symfony/Component/Filesystem/Path.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Filesystem/Path.php b/src/Symfony/Component/Filesystem/Path.php index 6ccb2e99aa07f..0bbd5b4772aff 100644 --- a/src/Symfony/Component/Filesystem/Path.php +++ b/src/Symfony/Component/Filesystem/Path.php @@ -257,7 +257,7 @@ public static function getRoot(string $path): string * @param string|null $extension if specified, only that extension is cut * off (may contain leading dot) */ - public static function getFilenameWithoutExtension(string $path, string $extension = null) + public static function getFilenameWithoutExtension(string $path, string $extension = null): string { if ('' === $path) { return ''; From 8398724e4ea8d8678d8862b3787f5bab06cfee4c Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 2 Apr 2022 08:35:07 +0200 Subject: [PATCH 90/91] Update CHANGELOG for 6.0.7 --- CHANGELOG-6.0.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/CHANGELOG-6.0.md b/CHANGELOG-6.0.md index f92727fee106c..f39d45b692902 100644 --- a/CHANGELOG-6.0.md +++ b/CHANGELOG-6.0.md @@ -7,6 +7,52 @@ in 6.0 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/v6.0.0...v6.0.1 +* 6.0.7 (2022-04-02) + + * bug #45906 [HttpClient] on redirections don't send content related request headers (xabbuh) + * bug #45714 [Messenger] Fix cannot select FOR UPDATE from view on Oracle (rjd22) + * bug #45905 [TwigBridge] Fix the build (wouterj) + * bug #45888 [Messenger] Add mysql indexes back and work around deadlocks using soft-delete (nicolas-grekas) + * bug #45890 [PropertyInfo] PhpStanExtractor namespace missmatch issue (Korbeil) + * bug #45897 [TwigBridge] fix bootstrap_3_layout ChoiceType's expanded label_html (ytilotti) + * bug #45891 [HttpClient] Fix exporting objects with readonly properties (nicolas-grekas) + * bug #45875 [ExpressionLanguage] Fix matches when the regexp is not valid (fabpot) + * bug #44996 [RateLimiter] Always store SlidingWindows with an expiration set (Seldaek) + * bug #45870 [Validator] Fix File constraint invalid max size exception message (fancyweb) + * bug #45851 [Console] Fix exit status on uncaught exception with negative code (acoulton) + * bug #45733 [Validator] fix #43345 @Assert\DivisibleBy (CharlyPoppins) + * bug #45791 [Translation] [LocoProvider] Add content-type for POST translations (Tomasz Kusy) + * bug #45840 [Translation] Fix locales format in CrowdinProvider (ossinkine) + * bug #45491 [DoctrineBridge] Allow to use a middleware instead of DbalLogger (l-vo) + * bug #45839 [Translation] Fix intersect in TranslatorBag (ossinkine) + * bug #45838 [Serializer] Fix denormalizing union types (T-bond) + * bug #45804 Fix compatibility of ldap 6.0 with security 5.x (jderusse) + * bug #45808 [Security] Fixed TOCTOU in RememberMe cache token verifier (Ivan Kurnosov) + * bug #45816 [Mailer] Preserve case of headers (nicolas-grekas) + * bug #45787 [FrameworkBundle] Fix exit codes in debug:translation command (gndk) + * bug #45789 [Config] Fix using null values with config builders (HypeMC) + * bug #45814 [HttpClient] Let curl handle Content-Length headers (nicolas-grekas) + * bug #45813 [HttpClient] Move Content-Type after Content-Length (nicolas-grekas) + * bug #45737 [Lock] SemaphoreStore catching exception from sem_get (Triplkrypl) + * bug #45690 [Mailer] Use recipients in sendmail transport (HypeMC) + * bug #45720 [PropertyInfo] strip only leading `\` when unknown docType (EmilMassey) + * bug #45764 [RateLimiter] Fix rate serialization for long intervals (monthly and yearly) (smelesh) + * bug #45684 [Serializer] Fix nested deserialization_path computation when there is no metadata for the attribute (fancyweb) + * bug #44915 [Console] Fix compact table style to avoid outputting a leading space (Seldaek) + * bug #45691 [Mailer] fix: stringify from address for ses+api transport (everyx) + * bug #45696 Make FormErrorIterator generic (VincentLanglet) + * bug #45676 [Process] Don't return executable directories in PhpExecutableFinder (fancyweb) + * bug #45564 [symfony/mailjet-mailer] Fix invalid mailjet error managment (alamirault, fancyweb) + * bug #45697 [Security] Fix return value of `NullToken::getUser()` (chalasr) + * bug #45719 typehint of DkimOptions algorithm wrong (markusramsak) + * bug #45702 [Form] Fix the usage of the Valid constraints in array-based forms (stof) + * bug #45677 [DependencyInjection] fix `ServiceSubscriberTrait` bug where parent has `__call()` (kbond) + * bug #45678 [HttpClient] Fix reading proxy settings from dotenv when curl is used (nicolas-grekas) + * bug #45675 [Runtime] Fix passing $debug parameter to `ErrorHandler` (Kocal) + * bug #45629 [FrameworkBundle] Fix container:lint and #[Autoconfigure(binds: ...)] failing (LANGERGabrielle) + * bug #45671 [FrameworkBundle] Ensure container is reset between tests (nicolas-grekas) + * bug #45572 [HttpKernel] fix using Target attribute with controller arguments (kbond) + * 6.0.6 (2022-03-05) * bug #45619 [redis-messenger] remove undefined array key warnings (PhilETaylor) From 5ea52145b9b3f84676ed019bbe2968f123396640 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 2 Apr 2022 08:35:11 +0200 Subject: [PATCH 91/91] Update VERSION for 6.0.7 --- src/Symfony/Component/HttpKernel/Kernel.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index cee5a4d5e74ff..496c7fd81331e 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -78,12 +78,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl */ private static array $freshCache = []; - public const VERSION = '6.0.7-DEV'; + public const VERSION = '6.0.7'; public const VERSION_ID = 60007; public const MAJOR_VERSION = 6; public const MINOR_VERSION = 0; public const RELEASE_VERSION = 7; - public const EXTRA_VERSION = 'DEV'; + public const EXTRA_VERSION = ''; public const END_OF_MAINTENANCE = '01/2023'; public const END_OF_LIFE = '01/2023';