From ca9f2a521a21ec918dfb3b1aa60cdb271edaa872 Mon Sep 17 00:00:00 2001 From: HypeMC Date: Wed, 1 May 2024 22:12:44 +0200 Subject: [PATCH 01/53] [PhpUnitBridge] Fix `DeprecationErrorHandler` with PhpUnit 10 --- src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php index adddfe6f76995..05c67b7b37e6e 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php @@ -368,6 +368,12 @@ private static function getPhpUnitErrorHandler() if ('PHPUnit\Util\ErrorHandler::handleError' === $eh) { return $eh; + } elseif (ErrorHandler::class === $eh) { + return function (int $errorNumber, string $errorString, string $errorFile, int $errorLine) { + ErrorHandler::instance()($errorNumber, $errorString, $errorFile, $errorLine); + + return true; + }; } foreach (debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS) as $frame) { From 5ab2d9053f7f3fc21d91342e0b33af2ca3b8bed4 Mon Sep 17 00:00:00 2001 From: mfettig Date: Fri, 24 Mar 2023 15:07:33 -0400 Subject: [PATCH 02/53] [Cache] Fix support for predis/predis:^2.0 --- composer.json | 2 +- src/Symfony/Component/Cache/Traits/RedisTrait.php | 10 ++++++---- src/Symfony/Component/Cache/composer.json | 2 +- .../Handler/PredisClusterSessionHandlerTest.php | 5 ++++- src/Symfony/Component/HttpFoundation/composer.json | 2 +- src/Symfony/Component/Lock/composer.json | 2 +- src/Symfony/Component/Semaphore/composer.json | 2 +- 7 files changed, 15 insertions(+), 10 deletions(-) diff --git a/composer.json b/composer.json index 9bc012d6cee49..ba4ebe8ed6dff 100644 --- a/composer.json +++ b/composer.json @@ -138,7 +138,7 @@ "php-http/httplug": "^1.0|^2.0", "php-http/message-factory": "^1.0", "phpstan/phpdoc-parser": "^1.0", - "predis/predis": "~1.1", + "predis/predis": "^1.1|^2.0", "psr/http-client": "^1.0", "psr/simple-cache": "^1.0|^2.0", "egulias/email-validator": "^2.1.10|^3.1|^4", diff --git a/src/Symfony/Component/Cache/Traits/RedisTrait.php b/src/Symfony/Component/Cache/Traits/RedisTrait.php index 33f37d828ea81..518a563060240 100644 --- a/src/Symfony/Component/Cache/Traits/RedisTrait.php +++ b/src/Symfony/Component/Cache/Traits/RedisTrait.php @@ -15,6 +15,8 @@ use Predis\Connection\Aggregate\ClusterInterface; use Predis\Connection\Aggregate\RedisCluster; use Predis\Connection\Aggregate\ReplicationInterface; +use Predis\Connection\Cluster\ClusterInterface as Predis2ClusterInterface; +use Predis\Connection\Cluster\RedisCluster as Predis2RedisCluster; use Predis\Response\ErrorInterface; use Predis\Response\Status; use Symfony\Component\Cache\Exception\CacheException; @@ -414,7 +416,7 @@ protected function doFetch(array $ids) $result = []; - if ($this->redis instanceof \Predis\ClientInterface && $this->redis->getConnection() instanceof ClusterInterface) { + if ($this->redis instanceof \Predis\ClientInterface && ($this->redis->getConnection() instanceof ClusterInterface || $this->redis->getConnection() instanceof Predis2ClusterInterface)) { $values = $this->pipeline(function () use ($ids) { foreach ($ids as $id) { yield 'get' => [$id]; @@ -511,7 +513,7 @@ protected function doDelete(array $ids) return true; } - if ($this->redis instanceof \Predis\ClientInterface && $this->redis->getConnection() instanceof ClusterInterface) { + if ($this->redis instanceof \Predis\ClientInterface && ($this->redis->getConnection() instanceof ClusterInterface || $this->redis->getConnection() instanceof Predis2ClusterInterface)) { static $del; $del = $del ?? (class_exists(UNLINK::class) ? 'unlink' : 'del'); @@ -569,7 +571,7 @@ private function pipeline(\Closure $generator, ?object $redis = null): \Generato $ids = []; $redis = $redis ?? $this->redis; - if ($redis instanceof RedisClusterProxy || $redis instanceof \RedisCluster || ($redis instanceof \Predis\ClientInterface && $redis->getConnection() instanceof RedisCluster)) { + if ($redis instanceof RedisClusterProxy || $redis instanceof \RedisCluster || ($redis instanceof \Predis\ClientInterface && ($redis->getConnection() instanceof RedisCluster || $redis->getConnection() instanceof Predis2RedisCluster))) { // phpredis & predis don't support pipelining with RedisCluster // see https://github.com/phpredis/phpredis/blob/develop/cluster.markdown#pipelining // see https://github.com/nrk/predis/issues/267#issuecomment-123781423 @@ -631,7 +633,7 @@ private function getHosts(): array $hosts = [$this->redis]; if ($this->redis instanceof \Predis\ClientInterface) { $connection = $this->redis->getConnection(); - if ($connection instanceof ClusterInterface && $connection instanceof \Traversable) { + if (($connection instanceof ClusterInterface || $connection instanceof Predis2ClusterInterface) && $connection instanceof \Traversable) { $hosts = []; foreach ($connection as $c) { $hosts[] = new \Predis\Client($c); diff --git a/src/Symfony/Component/Cache/composer.json b/src/Symfony/Component/Cache/composer.json index e3526bb8158b4..fdf794fb3b368 100644 --- a/src/Symfony/Component/Cache/composer.json +++ b/src/Symfony/Component/Cache/composer.json @@ -35,7 +35,7 @@ "cache/integration-tests": "dev-master", "doctrine/cache": "^1.6|^2.0", "doctrine/dbal": "^2.13.1|^3|^4", - "predis/predis": "^1.1", + "predis/predis": "^1.1|^2.0", "psr/simple-cache": "^1.0|^2.0", "symfony/config": "^4.4|^5.0|^6.0", "symfony/dependency-injection": "^4.4|^5.0|^6.0", diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PredisClusterSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PredisClusterSessionHandlerTest.php index 4990b1a1fc091..1712dcc491fc6 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PredisClusterSessionHandlerTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PredisClusterSessionHandlerTest.php @@ -23,6 +23,9 @@ class PredisClusterSessionHandlerTest extends AbstractRedisSessionHandlerTestCas */ protected function createRedisClient(string $host): object { - return new Client([array_combine(['host', 'port'], explode(':', getenv('REDIS_HOST')) + [1 => 6379])]); + return new Client( + [array_combine(['host', 'port'], explode(':', getenv('REDIS_HOST')) + [1 => 6379])], + ['cluster' => 'redis'] + ); } } diff --git a/src/Symfony/Component/HttpFoundation/composer.json b/src/Symfony/Component/HttpFoundation/composer.json index cb8d59ffed0d5..a2e43a99cfaae 100644 --- a/src/Symfony/Component/HttpFoundation/composer.json +++ b/src/Symfony/Component/HttpFoundation/composer.json @@ -22,7 +22,7 @@ "symfony/polyfill-php80": "^1.16" }, "require-dev": { - "predis/predis": "~1.0", + "predis/predis": "^1.0|^2.0", "symfony/cache": "^4.4|^5.0|^6.0", "symfony/dependency-injection": "^5.4|^6.0", "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4", diff --git a/src/Symfony/Component/Lock/composer.json b/src/Symfony/Component/Lock/composer.json index b7e2d0c0d87ea..f2558dbb7459a 100644 --- a/src/Symfony/Component/Lock/composer.json +++ b/src/Symfony/Component/Lock/composer.json @@ -23,7 +23,7 @@ }, "require-dev": { "doctrine/dbal": "^2.13|^3|^4", - "predis/predis": "~1.0" + "predis/predis": "^1.0|^2.0" }, "conflict": { "doctrine/dbal": "<2.13" diff --git a/src/Symfony/Component/Semaphore/composer.json b/src/Symfony/Component/Semaphore/composer.json index cbbd11c7ffcb4..3927cb71d964d 100644 --- a/src/Symfony/Component/Semaphore/composer.json +++ b/src/Symfony/Component/Semaphore/composer.json @@ -24,7 +24,7 @@ "psr/log": "^1|^2|^3" }, "require-dev": { - "predis/predis": "~1.0" + "predis/predis": "^1.1|^2.0" }, "autoload": { "psr-4": { "Symfony\\Component\\Semaphore\\": "" }, From eecb3bb2ae9b7db0c337cbf47e6e0f98822d5375 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Thu, 2 May 2024 15:06:35 +0200 Subject: [PATCH 03/53] fix Symfony version --- src/Symfony/Component/Translation/Bridge/Crowdin/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Translation/Bridge/Crowdin/README.md b/src/Symfony/Component/Translation/Bridge/Crowdin/README.md index a1b8a1a6cc46c..21192bc33a336 100644 --- a/src/Symfony/Component/Translation/Bridge/Crowdin/README.md +++ b/src/Symfony/Component/Translation/Bridge/Crowdin/README.md @@ -23,7 +23,7 @@ where: Sponsor ------- -This bridge for Symfony 5.4/6.0 is [backed][1] by [Crowdin][2]. +This bridge for Symfony 7.1 is [backed][1] by [Crowdin][2]. Crowdin is a cloud-based localization management software helping teams to go global and stay agile. From 820382cf9148356bb5f0acd4526030f60373009d Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 2 May 2024 15:07:24 +0200 Subject: [PATCH 04/53] Bump Symfony version to 7.1.0 --- 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 25e6151533c2d..6816457cbee87 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -73,12 +73,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl */ private static array $freshCache = []; - public const VERSION = '7.1.0-BETA1'; + public const VERSION = '7.1.0-DEV'; public const VERSION_ID = 70100; public const MAJOR_VERSION = 7; public const MINOR_VERSION = 1; public const RELEASE_VERSION = 0; - public const EXTRA_VERSION = 'BETA1'; + public const EXTRA_VERSION = 'DEV'; public const END_OF_MAINTENANCE = '01/2025'; public const END_OF_LIFE = '01/2025'; From a6fe93c707f2c7fa2fe0139e2ba8abbc00d3cac4 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Thu, 2 May 2024 13:02:31 +0200 Subject: [PATCH 05/53] Fix various warnings across components test suite --- .../Tests/Functional/AbstractWebTestCase.php | 2 +- ...ContainerParametersResourceCheckerTest.php | 8 ++-- .../Tests/Loader/PhpFileLoaderTest.php | 2 + .../Dumper/CompiledUrlGeneratorDumperTest.php | 2 + .../Features/CallbacksTestTrait.php | 48 +++++++++---------- .../Features/CircularReferenceTestTrait.php | 2 +- .../SkipUninitializedValuesTestTrait.php | 2 +- 7 files changed, 35 insertions(+), 31 deletions(-) diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AbstractWebTestCase.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AbstractWebTestCase.php index f9363e8290dc0..51d4d781fc233 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AbstractWebTestCase.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AbstractWebTestCase.php @@ -33,7 +33,7 @@ public static function tearDownAfterClass(): void static::deleteTmpDir(); } - public function provideSecuritySystems() + public static function provideSecuritySystems() { yield [['enable_authenticator_manager' => true]]; yield [['enable_authenticator_manager' => false]]; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Config/ContainerParametersResourceCheckerTest.php b/src/Symfony/Component/DependencyInjection/Tests/Config/ContainerParametersResourceCheckerTest.php index f13acc8f140e2..a5efc89a7c710 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Config/ContainerParametersResourceCheckerTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Config/ContainerParametersResourceCheckerTest.php @@ -46,7 +46,7 @@ public function testSupports() */ public function testIsFresh(callable $mockContainer, $expected) { - $mockContainer($this->container); + $mockContainer($this->container, $this); $this->assertSame($expected, $this->resourceChecker->isFresh($this->resource, time())); } @@ -61,9 +61,9 @@ public static function isFreshProvider() $container->method('getParameter')->with('locales')->willReturn(['nl', 'es']); }, false]; - yield 'fresh on every identical parameters' => [function (MockObject $container) { - $container->expects(self::exactly(2))->method('hasParameter')->willReturn(true); - $container->expects(self::exactly(2))->method('getParameter') + yield 'fresh on every identical parameters' => [function (MockObject $container, TestCase $testCase) { + $container->expects($testCase->exactly(2))->method('hasParameter')->willReturn(true); + $container->expects($testCase->exactly(2))->method('getParameter') ->willReturnCallback(function (...$args) { static $series = [ [['locales'], ['fr', 'en']], diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/PhpFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/PhpFileLoaderTest.php index 8b141d2577ee3..6f15b22b95cab 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/PhpFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/PhpFileLoaderTest.php @@ -194,6 +194,8 @@ public function testNestedBundleConfigNotAllowed() */ public function testWhenEnv() { + $this->expectNotToPerformAssertions(); + $fixtures = realpath(__DIR__.'/../Fixtures'); $container = new ContainerBuilder(); $loader = new PhpFileLoader($container, new FileLocator(), 'dev', new ConfigBuilderGenerator(sys_get_temp_dir())); diff --git a/src/Symfony/Component/Routing/Tests/Generator/Dumper/CompiledUrlGeneratorDumperTest.php b/src/Symfony/Component/Routing/Tests/Generator/Dumper/CompiledUrlGeneratorDumperTest.php index 64e47438386d4..ef3061db7b9ec 100644 --- a/src/Symfony/Component/Routing/Tests/Generator/Dumper/CompiledUrlGeneratorDumperTest.php +++ b/src/Symfony/Component/Routing/Tests/Generator/Dumper/CompiledUrlGeneratorDumperTest.php @@ -63,10 +63,12 @@ protected function tearDown(): void parent::tearDown(); @unlink($this->testTmpFilepath); + @unlink($this->largeTestTmpFilepath); $this->routeCollection = null; $this->generatorDumper = null; $this->testTmpFilepath = null; + $this->largeTestTmpFilepath = null; } public function testDumpWithRoutes() diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/Features/CallbacksTestTrait.php b/src/Symfony/Component/Serializer/Tests/Normalizer/Features/CallbacksTestTrait.php index db7b226c3e14b..e573c8c227001 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/Features/CallbacksTestTrait.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/Features/CallbacksTestTrait.php @@ -126,13 +126,13 @@ public function testUncallableCallbacks($callbacks) $normalizer->normalize($obj, null, ['callbacks' => $callbacks]); } - public function provideNormalizeCallbacks() + public static function provideNormalizeCallbacks() { return [ 'Change a string' => [ [ 'bar' => function ($bar) { - $this->assertEquals('baz', $bar); + static::assertEquals('baz', $bar); return 'baz'; }, @@ -143,11 +143,11 @@ public function provideNormalizeCallbacks() 'Null an item' => [ [ 'bar' => function ($value, $object, $attributeName, $format, $context) { - $this->assertSame('baz', $value); - $this->assertInstanceOf(CallbacksObject::class, $object); - $this->assertSame('bar', $attributeName); - $this->assertSame('any', $format); - $this->assertArrayHasKey('circular_reference_limit_counters', $context); + static::assertSame('baz', $value); + static::assertInstanceOf(CallbacksObject::class, $object); + static::assertSame('bar', $attributeName); + static::assertSame('any', $format); + static::assertArrayHasKey('circular_reference_limit_counters', $context); }, ], 'baz', @@ -156,7 +156,7 @@ public function provideNormalizeCallbacks() 'Format a date' => [ [ 'bar' => function ($bar) { - $this->assertInstanceOf(\DateTime::class, $bar); + static::assertInstanceOf(\DateTime::class, $bar); return $bar->format('d-m-Y H:i:s'); }, @@ -190,13 +190,13 @@ public function provideNormalizeCallbacks() ]; } - public function provideDenormalizeCallbacks(): array + public static function provideDenormalizeCallbacks(): array { return [ 'Change a string' => [ [ 'bar' => function ($bar) { - $this->assertEquals('bar', $bar); + static::assertEquals('bar', $bar); return $bar; }, @@ -207,11 +207,11 @@ public function provideDenormalizeCallbacks(): array 'Null an item' => [ [ 'bar' => function ($value, $object, $attributeName, $format, $context) { - $this->assertSame('baz', $value); - $this->assertTrue(is_a($object, CallbacksObject::class, true)); - $this->assertSame('bar', $attributeName); - $this->assertSame('any', $format); - $this->assertIsArray($context); + static::assertSame('baz', $value); + static::assertTrue(is_a($object, CallbacksObject::class, true)); + static::assertSame('bar', $attributeName); + static::assertSame('any', $format); + static::assertIsArray($context); }, ], 'baz', @@ -220,7 +220,7 @@ public function provideDenormalizeCallbacks(): array 'Format a date' => [ [ 'bar' => function ($bar) { - $this->assertIsString($bar); + static::assertIsString($bar); return \DateTime::createFromFormat('d-m-Y H:i:s', $bar); }, @@ -254,13 +254,13 @@ public function provideDenormalizeCallbacks(): array ]; } - public function providerDenormalizeCallbacksWithTypedProperty(): array + public static function providerDenormalizeCallbacksWithTypedProperty(): array { return [ 'Change a typed string' => [ [ 'foo' => function ($foo) { - $this->assertEquals('foo', $foo); + static::assertEquals('foo', $foo); return $foo; }, @@ -271,11 +271,11 @@ public function providerDenormalizeCallbacksWithTypedProperty(): array 'Null an typed item' => [ [ 'foo' => function ($value, $object, $attributeName, $format, $context) { - $this->assertSame('fool', $value); - $this->assertTrue(is_a($object, CallbacksObject::class, true)); - $this->assertSame('foo', $attributeName); - $this->assertSame('any', $format); - $this->assertIsArray($context); + static::assertSame('fool', $value); + static::assertTrue(is_a($object, CallbacksObject::class, true)); + static::assertSame('foo', $attributeName); + static::assertSame('any', $format); + static::assertIsArray($context); }, ], 'fool', @@ -284,7 +284,7 @@ public function providerDenormalizeCallbacksWithTypedProperty(): array ]; } - public function provideInvalidCallbacks() + public static function provideInvalidCallbacks() { return [ [['bar' => null]], diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/Features/CircularReferenceTestTrait.php b/src/Symfony/Component/Serializer/Tests/Normalizer/Features/CircularReferenceTestTrait.php index 1996e80e98a38..ffbddf2ab3f29 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/Features/CircularReferenceTestTrait.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/Features/CircularReferenceTestTrait.php @@ -23,7 +23,7 @@ abstract protected function getNormalizerForCircularReference(array $defaultCont abstract protected function getSelfReferencingModel(); - public function provideUnableToNormalizeCircularReference(): array + public static function provideUnableToNormalizeCircularReference(): array { return [ [[], [], 1], diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/Features/SkipUninitializedValuesTestTrait.php b/src/Symfony/Component/Serializer/Tests/Normalizer/Features/SkipUninitializedValuesTestTrait.php index 596b3404b7e24..02707768fc880 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/Features/SkipUninitializedValuesTestTrait.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/Features/SkipUninitializedValuesTestTrait.php @@ -44,7 +44,7 @@ public function testSkipUninitializedValues(array $context) $this->assertSame('value', $objectToPopulate->getUninitialized()); } - public function skipUninitializedValuesFlagProvider(): iterable + public static function skipUninitializedValuesFlagProvider(): iterable { yield 'passed manually' => [['skip_uninitialized_values' => true, 'groups' => ['foo']]]; yield 'using default context value' => [['groups' => ['foo']]]; From 74bc0ebb2f3e47b80eac4da5de0f12a8c13a5701 Mon Sep 17 00:00:00 2001 From: HypeMC Date: Thu, 2 May 2024 22:30:26 +0200 Subject: [PATCH 06/53] [Serializer] Fix `GetSetMethodNormalizer` not working with setters with optional args --- .../Normalizer/GetSetMethodNormalizer.php | 2 +- .../Normalizer/GetSetMethodNormalizerTest.php | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php index 9aaac706f2133..619500828d506 100644 --- a/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php @@ -107,7 +107,7 @@ private function isSetMethod(\ReflectionMethod $method): bool { return !$method->isStatic() && (\PHP_VERSION_ID < 80000 || !$method->getAttributes(Ignore::class)) - && 1 === $method->getNumberOfRequiredParameters() + && 0 < $method->getNumberOfParameters() && str_starts_with($method->name, 'set'); } diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php index e7c23cf58a574..424dd215e7952 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php @@ -538,6 +538,18 @@ public function testSupportsAndDenormalizeWithOnlyParentSetter() $obj = $this->normalizer->denormalize(['foo' => 'foo'], GetSetDummyChild::class); $this->assertSame('foo', $obj->getFoo()); } + + /** + * @testWith [{"foo":"foo"}, "getFoo", "foo"] + * [{"bar":"bar"}, "getBar", "bar"] + */ + public function testSupportsAndDenormalizeWithOptionalSetterArgument(array $data, string $method, string $expected) + { + $this->assertTrue($this->normalizer->supportsDenormalization($data, GetSetDummyWithOptionalAndMultipleSetterArgs::class)); + + $obj = $this->normalizer->denormalize($data, GetSetDummyWithOptionalAndMultipleSetterArgs::class); + $this->assertSame($expected, $obj->$method()); + } } class GetSetDummy @@ -861,3 +873,29 @@ public function setFoo($foo) $this->foo = $foo; } } + +class GetSetDummyWithOptionalAndMultipleSetterArgs +{ + private $foo; + private $bar; + + public function getFoo() + { + return $this->foo; + } + + public function setFoo($foo = null) + { + $this->foo = $foo; + } + + public function getBar() + { + return $this->bar; + } + + public function setBar($bar = null, $other = true) + { + $this->bar = $bar; + } +} From 59ec34b5813d61b552d7472faa7221c633446e11 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 3 May 2024 08:54:07 +0200 Subject: [PATCH 07/53] remove no longer needed PHP version check --- .../LazyProxy/PhpDumper/LazyServiceDumper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/LazyServiceDumper.php b/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/LazyServiceDumper.php index 3e9d5dd5ca1e9..1bb5d9e67e0b6 100644 --- a/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/LazyServiceDumper.php +++ b/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/LazyServiceDumper.php @@ -105,7 +105,7 @@ public function getProxyCode(Definition $definition, ?string $id = null): string if ($asGhostObject) { try { - return (\PHP_VERSION_ID >= 80200 && $class?->isReadOnly() ? 'readonly ' : '').'class '.$proxyClass.ProxyHelper::generateLazyGhost($class); + return ($class?->isReadOnly() ? 'readonly ' : '').'class '.$proxyClass.ProxyHelper::generateLazyGhost($class); } catch (LogicException $e) { throw new InvalidArgumentException(sprintf('Cannot generate lazy ghost for service "%s".', $id ?? $definition->getClass()), 0, $e); } From cc0b95749fe9fd4a628bba9a048d44bd020447ee Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Fri, 3 May 2024 10:27:17 +0200 Subject: [PATCH 08/53] [HttpClient] Fix cURL default options --- src/Symfony/Component/HttpClient/Response/CurlResponse.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Symfony/Component/HttpClient/Response/CurlResponse.php b/src/Symfony/Component/HttpClient/Response/CurlResponse.php index 633b74a1256ed..f36a05f9d6ae2 100644 --- a/src/Symfony/Component/HttpClient/Response/CurlResponse.php +++ b/src/Symfony/Component/HttpClient/Response/CurlResponse.php @@ -174,10 +174,6 @@ public function __construct(CurlClientState $multi, $ch, ?array $options = null, curl_multi_remove_handle($multi->handle, $ch); curl_setopt_array($ch, [ \CURLOPT_NOPROGRESS => true, - \CURLOPT_PROGRESSFUNCTION => null, - \CURLOPT_HEADERFUNCTION => null, - \CURLOPT_WRITEFUNCTION => null, - \CURLOPT_READFUNCTION => null, \CURLOPT_INFILE => null, ]); From 2c845fab1f23518ba3a19e558eebf87b92f4d8fe Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Fri, 3 May 2024 13:18:44 +0200 Subject: [PATCH 09/53] [Validator] Check `Locale` class existence before using it --- .../Validator/Test/ConstraintValidatorTestCase.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/Validator/Test/ConstraintValidatorTestCase.php b/src/Symfony/Component/Validator/Test/ConstraintValidatorTestCase.php index 89f8d0008e75a..0e89e78b1a039 100644 --- a/src/Symfony/Component/Validator/Test/ConstraintValidatorTestCase.php +++ b/src/Symfony/Component/Validator/Test/ConstraintValidatorTestCase.php @@ -80,8 +80,10 @@ protected function setUp(): void $this->validator = $this->createValidator(); $this->validator->initialize($this->context); - $this->defaultLocale = \Locale::getDefault(); - \Locale::setDefault('en'); + if (class_exists(\Locale::class)) { + $this->defaultLocale = \Locale::getDefault(); + \Locale::setDefault('en'); + } $this->expectedViolations = []; $this->call = 0; @@ -93,7 +95,9 @@ protected function tearDown(): void { $this->restoreDefaultTimezone(); - \Locale::setDefault($this->defaultLocale); + if (class_exists(\Locale::class)) { + \Locale::setDefault($this->defaultLocale); + } } protected function setDefaultTimezone(?string $defaultTimezone) From d19dfafd54beaf0b331c04b3597f7a23e50fe767 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Thu, 2 May 2024 16:14:51 +0200 Subject: [PATCH 10/53] fix setting validated fields not using the options array --- .../Constraints/UniqueEntityValidatorTest.php | 239 ++++++++++++++++++ .../Validator/Constraints/UniqueEntity.php | 4 +- 2 files changed, 241 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php index abe31d474dfab..93fb0e897d92a 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php @@ -951,6 +951,40 @@ public function rewind(): void } public function testValidateDTOUniqueness() + { + $constraint = new UniqueEntity( + message: 'myMessage', + fields: ['name'], + em: self::EM_NAME, + entityClass: Person::class, + ); + + $entity = new Person(1, 'Foo'); + $dto = new HireAnEmployee('Foo'); + + $this->validator->validate($entity, $constraint); + + $this->assertNoViolation(); + + $this->em->persist($entity); + $this->em->flush(); + + $this->validator->validate($entity, $constraint); + + $this->assertNoViolation(); + + $this->validator->validate($dto, $constraint); + + $this->buildViolation('myMessage') + ->atPath('property.path.name') + ->setInvalidValue('Foo') + ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) + ->setCause([$entity]) + ->setParameters(['{{ value }}' => '"Foo"']) + ->assertRaised(); + } + + public function testValidateDTOUniquenessDoctrineStyle() { $constraint = new UniqueEntity([ 'message' => 'myMessage', @@ -985,6 +1019,32 @@ public function testValidateDTOUniqueness() } public function testValidateMappingOfFieldNames() + { + $constraint = new UniqueEntity( + message: 'myMessage', + fields: ['primaryName' => 'name', 'secondaryName' => 'name2'], + em: self::EM_NAME, + entityClass: DoubleNameEntity::class, + ); + + $entity = new DoubleNameEntity(1, 'Foo', 'Bar'); + $dto = new CreateDoubleNameEntity('Foo', 'Bar'); + + $this->em->persist($entity); + $this->em->flush(); + + $this->validator->validate($dto, $constraint); + + $this->buildViolation('myMessage') + ->atPath('property.path.name') + ->setParameter('{{ value }}', '"Foo"') + ->setInvalidValue('Foo') + ->setCause([$entity]) + ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) + ->assertRaised(); + } + + public function testValidateMappingOfFieldNamesDoctrineStyle() { $constraint = new UniqueEntity([ 'message' => 'myMessage', @@ -1011,6 +1071,21 @@ public function testValidateMappingOfFieldNames() } public function testInvalidateDTOFieldName() + { + $this->expectException(ConstraintDefinitionException::class); + $this->expectExceptionMessage('The field "primaryName" is not a property of class "Symfony\Bridge\Doctrine\Tests\Fixtures\HireAnEmployee".'); + $constraint = new UniqueEntity( + message: 'myMessage', + fields: ['primaryName' => 'name'], + em: self::EM_NAME, + entityClass: SingleStringIdEntity::class, + ); + + $dto = new HireAnEmployee('Foo'); + $this->validator->validate($dto, $constraint); + } + + public function testInvalidateDTOFieldNameDoctrineStyle() { $this->expectException(ConstraintDefinitionException::class); $this->expectExceptionMessage('The field "primaryName" is not a property of class "Symfony\Bridge\Doctrine\Tests\Fixtures\HireAnEmployee".'); @@ -1026,6 +1101,21 @@ public function testInvalidateDTOFieldName() } public function testInvalidateEntityFieldName() + { + $this->expectException(ConstraintDefinitionException::class); + $this->expectExceptionMessage('The field "name2" is not mapped by Doctrine, so it cannot be validated for uniqueness.'); + $constraint = new UniqueEntity( + message: 'myMessage', + fields: ['name2'], + em: self::EM_NAME, + entityClass: SingleStringIdEntity::class, + ); + + $dto = new HireAnEmployee('Foo'); + $this->validator->validate($dto, $constraint); + } + + public function testInvalidateEntityFieldNameDoctrineStyle() { $this->expectException(ConstraintDefinitionException::class); $this->expectExceptionMessage('The field "name2" is not mapped by Doctrine, so it cannot be validated for uniqueness.'); @@ -1041,6 +1131,36 @@ public function testInvalidateEntityFieldName() } public function testValidateDTOUniquenessWhenUpdatingEntity() + { + $constraint = new UniqueEntity( + message: 'myMessage', + fields: ['name'], + em: self::EM_NAME, + entityClass: Person::class, + identifierFieldNames: ['id'], + ); + + $entity1 = new Person(1, 'Foo'); + $entity2 = new Person(2, 'Bar'); + + $this->em->persist($entity1); + $this->em->persist($entity2); + $this->em->flush(); + + $dto = new UpdateEmployeeProfile(2, 'Foo'); + + $this->validator->validate($dto, $constraint); + + $this->buildViolation('myMessage') + ->atPath('property.path.name') + ->setInvalidValue('Foo') + ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) + ->setCause([$entity1]) + ->setParameters(['{{ value }}' => '"Foo"']) + ->assertRaised(); + } + + public function testValidateDTOUniquenessWhenUpdatingEntityDoctrineStyle() { $constraint = new UniqueEntity([ 'message' => 'myMessage', @@ -1071,6 +1191,28 @@ public function testValidateDTOUniquenessWhenUpdatingEntity() } public function testValidateDTOUniquenessWhenUpdatingEntityWithTheSameValue() + { + $constraint = new UniqueEntity( + message: 'myMessage', + fields: ['name'], + em: self::EM_NAME, + entityClass: CompositeIntIdEntity::class, + identifierFieldNames: ['id1', 'id2'], + ); + + $entity = new CompositeIntIdEntity(1, 2, 'Foo'); + + $this->em->persist($entity); + $this->em->flush(); + + $dto = new UpdateCompositeIntIdEntity(1, 2, 'Foo'); + + $this->validator->validate($dto, $constraint); + + $this->assertNoViolation(); + } + + public function testValidateDTOUniquenessWhenUpdatingEntityWithTheSameValueDoctrineStyle() { $constraint = new UniqueEntity([ 'message' => 'myMessage', @@ -1093,6 +1235,35 @@ public function testValidateDTOUniquenessWhenUpdatingEntityWithTheSameValue() } public function testValidateIdentifierMappingOfFieldNames() + { + $constraint = new UniqueEntity( + message: 'myMessage', + fields: ['object1' => 'objectOne', 'object2' => 'objectTwo'], + em: self::EM_NAME, + entityClass: CompositeObjectNoToStringIdEntity::class, + identifierFieldNames: ['object1' => 'objectOne', 'object2' => 'objectTwo'], + ); + + $objectOne = new SingleIntIdNoToStringEntity(1, 'foo'); + $objectTwo = new SingleIntIdNoToStringEntity(2, 'bar'); + + $this->em->persist($objectOne); + $this->em->persist($objectTwo); + $this->em->flush(); + + $entity = new CompositeObjectNoToStringIdEntity($objectOne, $objectTwo); + + $this->em->persist($entity); + $this->em->flush(); + + $dto = new UpdateCompositeObjectNoToStringIdEntity($objectOne, $objectTwo, 'Foo'); + + $this->validator->validate($dto, $constraint); + + $this->assertNoViolation(); + } + + public function testValidateIdentifierMappingOfFieldNamesDoctrineStyle() { $constraint = new UniqueEntity([ 'message' => 'myMessage', @@ -1122,6 +1293,34 @@ public function testValidateIdentifierMappingOfFieldNames() } public function testInvalidateMissingIdentifierFieldName() + { + $this->expectException(ConstraintDefinitionException::class); + $this->expectExceptionMessage('The "Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeObjectNoToStringIdEntity" entity identifier field names should be "objectOne, objectTwo", not "objectTwo".'); + $constraint = new UniqueEntity( + message: 'myMessage', + fields: ['object1' => 'objectOne', 'object2' => 'objectTwo'], + em: self::EM_NAME, + entityClass: CompositeObjectNoToStringIdEntity::class, + identifierFieldNames: ['object2' => 'objectTwo'], + ); + + $objectOne = new SingleIntIdNoToStringEntity(1, 'foo'); + $objectTwo = new SingleIntIdNoToStringEntity(2, 'bar'); + + $this->em->persist($objectOne); + $this->em->persist($objectTwo); + $this->em->flush(); + + $entity = new CompositeObjectNoToStringIdEntity($objectOne, $objectTwo); + + $this->em->persist($entity); + $this->em->flush(); + + $dto = new UpdateCompositeObjectNoToStringIdEntity($objectOne, $objectTwo, 'Foo'); + $this->validator->validate($dto, $constraint); + } + + public function testInvalidateMissingIdentifierFieldNameDoctrineStyle() { $this->expectException(ConstraintDefinitionException::class); $this->expectExceptionMessage('The "Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeObjectNoToStringIdEntity" entity identifier field names should be "objectOne, objectTwo", not "objectTwo".'); @@ -1150,6 +1349,25 @@ public function testInvalidateMissingIdentifierFieldName() } public function testUninitializedValueThrowException() + { + $this->expectExceptionMessage('Typed property Symfony\Bridge\Doctrine\Tests\Fixtures\Dto::$foo must not be accessed before initialization'); + $constraint = new UniqueEntity( + message: 'myMessage', + fields: ['foo' => 'name'], + em: self::EM_NAME, + entityClass: DoubleNameEntity::class, + ); + + $entity = new DoubleNameEntity(1, 'Foo', 'Bar'); + $dto = new Dto(); + + $this->em->persist($entity); + $this->em->flush(); + + $this->validator->validate($dto, $constraint); + } + + public function testUninitializedValueThrowExceptionDoctrineStyle() { $this->expectExceptionMessage('Typed property Symfony\Bridge\Doctrine\Tests\Fixtures\Dto::$foo must not be accessed before initialization'); $constraint = new UniqueEntity([ @@ -1169,6 +1387,27 @@ public function testUninitializedValueThrowException() } public function testEntityManagerNullObjectWhenDTO() + { + $this->expectException(ConstraintDefinitionException::class); + $this->expectExceptionMessage('Unable to find the object manager associated with an entity of class "Symfony\Bridge\Doctrine\Tests\Fixtures\Person"'); + $constraint = new UniqueEntity( + message: 'myMessage', + fields: ['name'], + entityClass: Person::class, + // no "em" option set + ); + + $this->em = null; + $this->registry = $this->createRegistryMock($this->em); + $this->validator = $this->createValidator(); + $this->validator->initialize($this->context); + + $dto = new HireAnEmployee('Foo'); + + $this->validator->validate($dto, $constraint); + } + + public function testEntityManagerNullObjectWhenDTODoctrineStyle() { $this->expectException(ConstraintDefinitionException::class); $this->expectExceptionMessage('Unable to find the object manager associated with an entity of class "Symfony\Bridge\Doctrine\Tests\Fixtures\Person"'); diff --git a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php index 5786d4c224fce..edc9011b27ea1 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php +++ b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php @@ -60,9 +60,9 @@ public function __construct( $payload = null, array $options = [], ) { - if (\is_array($fields) && \is_string(key($fields))) { + if (\is_array($fields) && \is_string(key($fields)) && [] === array_diff(array_keys($fields), array_keys(get_class_vars(static::class)))) { $options = array_merge($fields, $options); - } elseif (null !== $fields) { + } else { $options['fields'] = $fields; } From 1211fbedf33db47d000a0ad876b2f1825269371e Mon Sep 17 00:00:00 2001 From: Sherin Bloemendaal Date: Fri, 3 May 2024 18:54:46 +0200 Subject: [PATCH 11/53] [Mailer] [Sendgrid] Use DataPart::getContentId() when DataPart::setContentId() is used. --- .../Transport/SendgridApiTransportTest.php | 41 +++++++++++++++++++ .../Transport/SendgridApiTransport.php | 2 +- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Transport/SendgridApiTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Transport/SendgridApiTransportTest.php index 86d33241a1816..4196d0897172f 100644 --- a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Transport/SendgridApiTransportTest.php +++ b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Transport/SendgridApiTransportTest.php @@ -245,4 +245,45 @@ public function testTagAndMetadataHeaders() $this->assertSame('blue', $payload['personalizations'][0]['custom_args']['Color']); $this->assertSame('12345', $payload['personalizations'][0]['custom_args']['Client-ID']); } + + public function testInlineWithCustomContentId() + { + $imagePart = (new DataPart('text-contents', 'text.txt')); + $imagePart->asInline(); + $imagePart->setContentId('content-identifier@symfony'); + + $email = new Email(); + $email->addPart($imagePart); + $envelope = new Envelope(new Address('alice@system.com'), [new Address('bob@system.com')]); + + $transport = new SendgridApiTransport('ACCESS_KEY'); + $method = new \ReflectionMethod(SendgridApiTransport::class, 'getPayload'); + $payload = $method->invoke($transport, $email, $envelope); + + $this->assertArrayHasKey('attachments', $payload); + $this->assertCount(1, $payload['attachments']); + $this->assertArrayHasKey('content_id', $payload['attachments'][0]); + + $this->assertSame('content-identifier@symfony', $payload['attachments'][0]['content_id']); + } + + public function testInlineWithoutCustomContentId() + { + $imagePart = (new DataPart('text-contents', 'text.txt')); + $imagePart->asInline(); + + $email = new Email(); + $email->addPart($imagePart); + $envelope = new Envelope(new Address('alice@system.com'), [new Address('bob@system.com')]); + + $transport = new SendgridApiTransport('ACCESS_KEY'); + $method = new \ReflectionMethod(SendgridApiTransport::class, 'getPayload'); + $payload = $method->invoke($transport, $email, $envelope); + + $this->assertArrayHasKey('attachments', $payload); + $this->assertCount(1, $payload['attachments']); + $this->assertArrayHasKey('content_id', $payload['attachments'][0]); + + $this->assertSame('text.txt', $payload['attachments'][0]['content_id']); + } } diff --git a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php index 08a337ec412ab..a59255b37ce6e 100644 --- a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php @@ -179,7 +179,7 @@ private function getAttachments(Email $email): array ]; if ('inline' === $disposition) { - $att['content_id'] = $filename; + $att['content_id'] = $attachment->hasContentId() ? $attachment->getContentId() : $filename; } $attachments[] = $att; From 88a8d216a8ae73825254ee4ffebf66e8f6280773 Mon Sep 17 00:00:00 2001 From: HypeMC Date: Sat, 4 May 2024 04:04:19 +0200 Subject: [PATCH 12/53] [WebProfilerBundle] Fix assignment to constant variable --- .../Resources/views/Profiler/base_js.html.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig index 8a669a5c6caa9..f81781066e0b6 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig @@ -75,7 +75,7 @@ } tab.addEventListener('click', function(e) { - const activeTab = e.target || e.srcElement; + let activeTab = e.target || e.srcElement; /* needed because when the tab contains HTML contents, user can click */ /* on any of those elements instead of their parent '