From 5e56886963196c91ee9b0e640ff49fc92d4d3c89 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Wed, 25 Oct 2023 16:20:52 +0200 Subject: [PATCH 01/10] Add ChainNormalizer and ChainDenormalizer --- UPGRADE-7.2.md | 5 + .../Extension/SerializerExtensionTest.php | 2 +- .../Twig/Tests/Mime/TemplatedEmailTest.php | 17 +- src/Symfony/Bridge/Twig/composer.json | 2 +- .../Resources/config/serializer.php | 12 +- .../Bundle/FrameworkBundle/composer.json | 2 +- .../SerializerErrorRendererTest.php | 2 +- .../Component/ErrorHandler/composer.json | 2 +- .../RequestPayloadValueResolverTest.php | 61 +++- .../Component/HttpKernel/composer.json | 2 +- .../Tests/Transport/AmazonSqsReceiverTest.php | 2 +- .../Messenger/Bridge/AmazonSqs/composer.json | 2 +- .../Amqp/Tests/Fixtures/long_receiver.php | 2 +- .../Transport/AmqpExtIntegrationTest.php | 2 +- .../Amqp/Tests/Transport/AmqpReceiverTest.php | 2 +- .../Messenger/Bridge/Amqp/composer.json | 2 +- .../Transport/BeanstalkdReceiverTest.php | 2 +- .../Messenger/Bridge/Beanstalkd/composer.json | 2 +- .../Tests/Transport/DoctrineReceiverTest.php | 2 +- .../Messenger/Bridge/Doctrine/composer.json | 2 +- .../Tests/Transport/RedisReceiverTest.php | 2 +- .../Messenger/Bridge/Redis/composer.json | 2 +- .../Tests/Stamp/ErrorDetailsStampTest.php | 13 +- .../Tests/Stamp/TransportNamesStampTest.php | 12 +- .../Transport/Serialization/Serializer.php | 7 +- src/Symfony/Component/Messenger/composer.json | 4 +- .../Component/Mime/Tests/EmailTest.php | 23 +- .../Component/Mime/Tests/MessageTest.php | 23 +- src/Symfony/Component/Mime/composer.json | 2 +- src/Symfony/Component/Serializer/CHANGELOG.md | 6 + .../Serializer/Debug/TraceableEncoder.php | 13 +- .../DependencyInjection/SerializerPass.php | 34 +- .../Serializer/Encoder/XmlEncoder.php | 17 +- .../Normalizer/ChainDenormalizer.php | 207 ++++++++++++ .../Serializer/Normalizer/ChainNormalizer.php | 213 +++++++++++++ .../Component/Serializer/Serializer.php | 266 +++------------- .../Serializer/SerializerAwareInterface.php | 2 + .../Serializer/SerializerAwareTrait.php | 2 + .../DeserializeNestedArrayOfObjectsTest.php | 11 +- .../Tests/Encoder/JsonEncoderTest.php | 4 +- .../Tests/Encoder/XmlEncoderTest.php | 29 +- .../Normalizer/AbstractNormalizerTest.php | 11 +- .../AbstractObjectNormalizerTest.php | 47 +-- .../Features/ContextMetadataTestTrait.php | 8 +- .../Normalizer/GetSetMethodNormalizerTest.php | 19 +- .../JsonSerializableNormalizerTest.php | 3 +- .../Normalizer/MapDenormalizationTest.php | 4 +- .../Tests/Normalizer/ObjectNormalizerTest.php | 47 ++- .../Normalizer/ProblemNormalizerTest.php | 7 +- .../Normalizer/PropertyNormalizerTest.php | 18 +- .../Serializer/Tests/SerializerTest.php | 300 +++++++++++------- 51 files changed, 983 insertions(+), 500 deletions(-) create mode 100644 src/Symfony/Component/Serializer/Normalizer/ChainDenormalizer.php create mode 100644 src/Symfony/Component/Serializer/Normalizer/ChainNormalizer.php diff --git a/UPGRADE-7.2.md b/UPGRADE-7.2.md index 9395804d14bac..2ab176d78f94e 100644 --- a/UPGRADE-7.2.md +++ b/UPGRADE-7.2.md @@ -12,3 +12,8 @@ Security -------- * Deprecate argument `$secret` of `RememberMeToken` and `RememberMeAuthenticator` + +Serializer +---------- + + * Deprecated argument `$normalizers` of `Serializer::__construct()`, use arguments `$normalizer` and `$denormalizer` instead diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/SerializerExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/SerializerExtensionTest.php index 610030cec5a9f..0d4f5765d7dc7 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/SerializerExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/SerializerExtensionTest.php @@ -50,7 +50,7 @@ public static function serializerDataProvider(): \Generator private function getTwig(string $template): Environment { $meta = new ClassMetadataFactory(new AttributeLoader()); - $runtime = new SerializerRuntime(new Serializer([new ObjectNormalizer($meta)], [new JsonEncoder(), new YamlEncoder()])); + $runtime = new SerializerRuntime(new Serializer([], [new JsonEncoder(), new YamlEncoder()], new ObjectNormalizer($meta), new ObjectNormalizer($meta))); $mockRuntimeLoader = $this->createMock(RuntimeLoaderInterface::class); $mockRuntimeLoader diff --git a/src/Symfony/Bridge/Twig/Tests/Mime/TemplatedEmailTest.php b/src/Symfony/Bridge/Twig/Tests/Mime/TemplatedEmailTest.php index f77b3ad4b5337..300b1f6702bef 100644 --- a/src/Symfony/Bridge/Twig/Tests/Mime/TemplatedEmailTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Mime/TemplatedEmailTest.php @@ -17,6 +17,8 @@ use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; +use Symfony\Component\Serializer\Normalizer\ChainDenormalizer; +use Symfony\Component\Serializer\Normalizer\ChainNormalizer; use Symfony\Component\Serializer\Normalizer\MimeMessageNormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Normalizer\PropertyNormalizer; @@ -105,12 +107,23 @@ public function testSymfonySerialize() $extractor = new PhpDocExtractor(); $propertyNormalizer = new PropertyNormalizer(null, null, $extractor); - $serializer = new Serializer([ + $normalizers = [ + new MimeMessageNormalizer($propertyNormalizer), + new ObjectNormalizer(null, null, null, $extractor), + $propertyNormalizer, + ]; + $denormalizers = [ new ArrayDenormalizer(), new MimeMessageNormalizer($propertyNormalizer), new ObjectNormalizer(null, null, null, $extractor), $propertyNormalizer, - ], [new JsonEncoder()]); + ]; + $serializer = new Serializer( + [], + [new JsonEncoder()], + new ChainNormalizer($normalizers), + new ChainDenormalizer($denormalizers), + ); $serialized = $serializer->serialize($e, 'json', [ObjectNormalizer::IGNORED_ATTRIBUTES => ['cachedBody']]); $this->assertStringMatchesFormat($expectedJson, json_encode(json_decode($serialized), \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index f7f8d32d620ea..3a0206cb60d29 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -44,7 +44,7 @@ "symfony/security-core": "^6.4|^7.0", "symfony/security-csrf": "^6.4|^7.0", "symfony/security-http": "^6.4|^7.0", - "symfony/serializer": "^6.4.3|^7.0.3", + "symfony/serializer": "^7.2", "symfony/stopwatch": "^6.4|^7.0", "symfony/console": "^6.4|^7.0", "symfony/expression-language": "^6.4|^7.0", diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php index 1135d37525340..44db339ef2afa 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php @@ -33,6 +33,8 @@ use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; use Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer; +use Symfony\Component\Serializer\Normalizer\ChainDenormalizer; +use Symfony\Component\Serializer\Normalizer\ChainNormalizer; use Symfony\Component\Serializer\Normalizer\ConstraintViolationListNormalizer; use Symfony\Component\Serializer\Normalizer\DataUriNormalizer; use Symfony\Component\Serializer\Normalizer\DateIntervalNormalizer; @@ -58,12 +60,16 @@ ; $container->services() + ->set('serializer.normalizer', ChainNormalizer::class) + ->args([[]]) + ->set('serializer.denormalizer', ChainDenormalizer::class) + ->args([[]]) ->set('serializer', Serializer::class) - ->args([[], []]) + ->args([[], [], service('serializer.normalizer'), service('serializer.denormalizer')]) ->alias(SerializerInterface::class, 'serializer') - ->alias(NormalizerInterface::class, 'serializer') - ->alias(DenormalizerInterface::class, 'serializer') + ->alias(NormalizerInterface::class, 'serializer.normalizer') + ->alias(DenormalizerInterface::class, 'serializer.denormalizer') ->alias(EncoderInterface::class, 'serializer') ->alias(DecoderInterface::class, 'serializer') diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index af934f35df91f..80b2c35e98eec 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -59,7 +59,7 @@ "symfony/scheduler": "^6.4.4|^7.0.4", "symfony/security-bundle": "^6.4|^7.0", "symfony/semaphore": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0", + "symfony/serializer": "^7.2", "symfony/stopwatch": "^6.4|^7.0", "symfony/string": "^6.4|^7.0", "symfony/translation": "^6.4|^7.0", diff --git a/src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/SerializerErrorRendererTest.php b/src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/SerializerErrorRendererTest.php index 211ed3177568a..86ff26923ca4b 100644 --- a/src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/SerializerErrorRendererTest.php +++ b/src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/SerializerErrorRendererTest.php @@ -30,7 +30,7 @@ public function testSerializerContent() { $exception = new \RuntimeException('Foo'); $errorRenderer = new SerializerErrorRenderer( - new Serializer([new ProblemNormalizer()], [new JsonEncoder()]), + new Serializer([], [new JsonEncoder()], new ProblemNormalizer()), fn () => 'json' ); diff --git a/src/Symfony/Component/ErrorHandler/composer.json b/src/Symfony/Component/ErrorHandler/composer.json index 987a68f641448..70e4856c87594 100644 --- a/src/Symfony/Component/ErrorHandler/composer.json +++ b/src/Symfony/Component/ErrorHandler/composer.json @@ -22,7 +22,7 @@ }, "require-dev": { "symfony/http-kernel": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0", + "symfony/serializer": "^7.1", "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php index b277650b44b45..3b7385bb8eee7 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php @@ -27,6 +27,8 @@ use Symfony\Component\Serializer\Exception\NotNormalizableValueException; use Symfony\Component\Serializer\Exception\PartialDenormalizationException; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; +use Symfony\Component\Serializer\Normalizer\ChainDenormalizer; +use Symfony\Component\Serializer\Normalizer\ChainNormalizer; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Serializer; @@ -204,7 +206,10 @@ public function testQueryNullPayloadAndNotDefaultOrNullableArgument() } } - public function testWithoutValidatorAndCouldNotDenormalize() + /** + * @group legacy + */ + public function testWithoutValidatorAndCouldNotDenormalizeWithLegacySerializer() { $content = '{"price": 50, "title": ["not a string"]}'; $serializer = new Serializer([new ObjectNormalizer()], ['json' => new JsonEncoder()]); @@ -228,10 +233,34 @@ public function testWithoutValidatorAndCouldNotDenormalize() } } + public function testWithoutValidatorAndCouldNotDenormalize() + { + $content = '{"price": 50, "title": ["not a string"]}'; + $serializer = new Serializer([], ['json' => new JsonEncoder()], new ObjectNormalizer(), new ObjectNormalizer()); + + $resolver = new RequestPayloadValueResolver($serializer); + + $argument = new ArgumentMetadata('invalid', RequestPayload::class, false, false, null, false, [ + MapRequestPayload::class => new MapRequestPayload(), + ]); + $request = Request::create('/', 'POST', server: ['CONTENT_TYPE' => 'application/json'], content: $content); + + $kernel = $this->createMock(HttpKernelInterface::class); + $arguments = $resolver->resolve($request, $argument); + $event = new ControllerArgumentsEvent($kernel, function () {}, $arguments, $request, HttpKernelInterface::MAIN_REQUEST); + + try { + $resolver->onKernelControllerArguments($event); + $this->fail(sprintf('Expected "%s" to be thrown.', HttpException::class)); + } catch (HttpException $e) { + $this->assertInstanceOf(PartialDenormalizationException::class, $e->getPrevious()); + } + } + public function testValidationNotPassed() { $content = '{"price": 50, "title": ["not a string"]}'; - $serializer = new Serializer([new ObjectNormalizer()], ['json' => new JsonEncoder()]); + $serializer = new Serializer([], ['json' => new JsonEncoder()], new ObjectNormalizer(), new ObjectNormalizer()); $validator = $this->createMock(ValidatorInterface::class); $validator->expects($this->never()) @@ -262,7 +291,7 @@ public function testValidationNotPassed() public function testValidationNotPerformedWhenPartialDenormalizationReturnsViolation() { $content = '{"password": "abc"}'; - $serializer = new Serializer([new ObjectNormalizer()], ['json' => new JsonEncoder()]); + $serializer = new Serializer([], ['json' => new JsonEncoder()], new ObjectNormalizer(), new ObjectNormalizer()); $validator = $this->createMock(ValidatorInterface::class); $validator->expects($this->never()) @@ -316,7 +345,7 @@ public function testRequestContentValidationPassed() { $content = '{"price": 50}'; $payload = new RequestPayload(50); - $serializer = new Serializer([new ObjectNormalizer()], ['json' => new JsonEncoder()]); + $serializer = new Serializer([], ['json' => new JsonEncoder()], new ObjectNormalizer(), new ObjectNormalizer()); $validator = $this->createMock(ValidatorInterface::class); $validator->expects($this->once()) @@ -372,7 +401,7 @@ public function testQueryStringValidationPassed() $payload = new RequestPayload(50); $query = ['price' => '50']; - $serializer = new Serializer([new ObjectNormalizer()], ['json' => new JsonEncoder()]); + $serializer = new Serializer([], ['json' => new JsonEncoder()], new ObjectNormalizer(), new ObjectNormalizer()); $validator = $this->createMock(ValidatorInterface::class); $validator->expects($this->once()) @@ -400,7 +429,7 @@ public function testRequestInputValidationPassed() $input = ['price' => '50']; $payload = new RequestPayload(50); - $serializer = new Serializer([new ObjectNormalizer()], ['json' => new JsonEncoder()]); + $serializer = new Serializer([], ['json' => new JsonEncoder()], new ObjectNormalizer(), new ObjectNormalizer()); $validator = $this->createMock(ValidatorInterface::class); $validator->expects($this->once()) @@ -434,7 +463,7 @@ public function testRequestArrayDenormalization() new RequestPayload(23), ]; - $serializer = new Serializer([new ArrayDenormalizer(), new ObjectNormalizer()], ['json' => new JsonEncoder()]); + $serializer = new Serializer([], ['json' => new JsonEncoder()], null, new ChainDenormalizer([new ArrayDenormalizer(), new ObjectNormalizer()])); $validator = $this->createMock(ValidatorInterface::class); $validator->expects($this->once()) @@ -513,7 +542,7 @@ public function testItThrowsOnVariadicArgument() public function testAcceptFormatPassed(mixed $acceptFormat, string $contentType, string $content) { $encoders = ['json' => new JsonEncoder(), 'xml' => new XmlEncoder()]; - $serializer = new Serializer([new ObjectNormalizer()], $encoders); + $serializer = new Serializer([], $encoders, new ObjectNormalizer(), new ObjectNormalizer()); $validator = (new ValidatorBuilder())->getValidator(); $resolver = new RequestPayloadValueResolver($serializer, $validator); @@ -576,7 +605,7 @@ public static function provideMatchedFormatContext(): iterable */ public function testAcceptFormatNotPassed(mixed $acceptFormat, string $contentType, string $content, string $expectedExceptionMessage) { - $serializer = new Serializer([new ObjectNormalizer()]); + $serializer = new Serializer([], [], new ObjectNormalizer(), new ObjectNormalizer()); $validator = (new ValidatorBuilder())->getValidator(); $resolver = new RequestPayloadValueResolver($serializer, $validator); @@ -647,7 +676,7 @@ public function testValidationGroupsPassed(string $method, ValueResolver $attrib $payload = new RequestPayload(50); $payload->title = 'A long title, so the validation passes'; - $serializer = new Serializer([new ObjectNormalizer()]); + $serializer = new Serializer([], [], new ObjectNormalizer(), new ObjectNormalizer()); $validator = (new ValidatorBuilder())->enableAttributeMapping()->getValidator(); $resolver = new RequestPayloadValueResolver($serializer, $validator); @@ -673,7 +702,7 @@ public function testValidationGroupsNotPassed(string $method, ValueResolver $att { $input = ['price' => '50', 'title' => 'Too short']; - $serializer = new Serializer([new ObjectNormalizer()]); + $serializer = new Serializer([], [], new ObjectNormalizer(), new ObjectNormalizer()); $validator = (new ValidatorBuilder())->enableAttributeMapping()->getValidator(); $resolver = new RequestPayloadValueResolver($serializer, $validator); @@ -732,7 +761,7 @@ public static function provideValidationGroupsOnManyTypes(): iterable public function testQueryValidationErrorCustomStatusCode() { - $serializer = new Serializer([new ObjectNormalizer()], []); + $serializer = new Serializer([], [], new ObjectNormalizer(), new ObjectNormalizer()); $validator = $this->createMock(ValidatorInterface::class); @@ -765,7 +794,7 @@ public function testQueryValidationErrorCustomStatusCode() public function testRequestPayloadValidationErrorCustomStatusCode() { $content = '{"price": 50, "title": ["not a string"]}'; - $serializer = new Serializer([new ObjectNormalizer()], ['json' => new JsonEncoder()]); + $serializer = new Serializer([], ['json' => new JsonEncoder()], new ObjectNormalizer(), new ObjectNormalizer()); $validator = $this->createMock(ValidatorInterface::class); $validator->expects($this->never()) @@ -798,7 +827,7 @@ public function testRequestPayloadValidationErrorCustomStatusCode() */ public function testBoolArgumentInQueryString(mixed $expectedValue, ?string $parameterValue) { - $serializer = new Serializer([new ObjectNormalizer()]); + $serializer = new Serializer([], [], new ObjectNormalizer(), new ObjectNormalizer()); $validator = $this->createMock(ValidatorInterface::class); $resolver = new RequestPayloadValueResolver($serializer, $validator); @@ -821,7 +850,7 @@ public function testBoolArgumentInQueryString(mixed $expectedValue, ?string $par */ public function testBoolArgumentInBody(mixed $expectedValue, ?string $parameterValue) { - $serializer = new Serializer([new ObjectNormalizer()]); + $serializer = new Serializer([], [], new ObjectNormalizer(), new ObjectNormalizer()); $validator = $this->createMock(ValidatorInterface::class); $resolver = new RequestPayloadValueResolver($serializer, $validator); @@ -857,7 +886,7 @@ public static function provideBoolArgument() */ public function testBoolArgumentInJsonBody() { - $serializer = new Serializer([new ObjectNormalizer()]); + $serializer = new Serializer([], [], new ObjectNormalizer(), new ObjectNormalizer()); $validator = $this->createMock(ValidatorInterface::class); $resolver = new RequestPayloadValueResolver($serializer, $validator); diff --git a/src/Symfony/Component/HttpKernel/composer.json b/src/Symfony/Component/HttpKernel/composer.json index 8e54c82c9ae02..de6b2bf169436 100644 --- a/src/Symfony/Component/HttpKernel/composer.json +++ b/src/Symfony/Component/HttpKernel/composer.json @@ -38,7 +38,7 @@ "symfony/process": "^6.4|^7.0", "symfony/property-access": "^7.1", "symfony/routing": "^6.4|^7.0", - "symfony/serializer": "^7.1", + "symfony/serializer": "^7.2", "symfony/stopwatch": "^6.4|^7.0", "symfony/translation": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3", diff --git a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Tests/Transport/AmazonSqsReceiverTest.php b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Tests/Transport/AmazonSqsReceiverTest.php index 96cf25f80d500..58153a0f4e822 100644 --- a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Tests/Transport/AmazonSqsReceiverTest.php +++ b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Tests/Transport/AmazonSqsReceiverTest.php @@ -68,7 +68,7 @@ private function createSqsEnvelope() private function createSerializer(): Serializer { $serializer = new Serializer( - new SerializerComponent\Serializer([new ObjectNormalizer()], ['json' => new JsonEncoder()]) + new SerializerComponent\Serializer([], ['json' => new JsonEncoder()], new ObjectNormalizer(), new ObjectNormalizer()) ); return $serializer; diff --git a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/composer.json b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/composer.json index 18b24c66dcf25..e1537ce540bef 100644 --- a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/composer.json +++ b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/composer.json @@ -26,7 +26,7 @@ "require-dev": { "symfony/http-client-contracts": "^2.5|^3", "symfony/property-access": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0" + "symfony/serializer": "^7.2" }, "conflict": { "symfony/http-client-contracts": "<2.5" diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Fixtures/long_receiver.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Fixtures/long_receiver.php index 35dccc1ae26e6..f7ac37b963c3a 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Fixtures/long_receiver.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Fixtures/long_receiver.php @@ -26,7 +26,7 @@ use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; $serializer = new Serializer( - new SerializerComponent\Serializer([new ObjectNormalizer(), new ArrayDenormalizer()], ['json' => new JsonEncoder()]) + new SerializerComponent\Serializer([], ['json' => new JsonEncoder()], new ObjectNormalizer(), new SerializerComponent\Normalizer\ChainDenormalizer([new ObjectNormalizer(), new ArrayDenormalizer()])) ); $connection = Connection::fromDsn(getenv('DSN')); diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/AmqpExtIntegrationTest.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/AmqpExtIntegrationTest.php index cfa93698bacf5..e6eff726cab96 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/AmqpExtIntegrationTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/AmqpExtIntegrationTest.php @@ -262,7 +262,7 @@ private function waitForOutput(Process $process, string $output, $timeoutInSecon private function createSerializer(): SerializerInterface { return new Serializer( - new SerializerComponent\Serializer([new ObjectNormalizer(), new ArrayDenormalizer()], ['json' => new JsonEncoder()]) + new SerializerComponent\Serializer([], ['json' => new JsonEncoder()], new ObjectNormalizer(), new SerializerComponent\Normalizer\ChainDenormalizer([new ObjectNormalizer(), new ArrayDenormalizer()])) ); } diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/AmqpReceiverTest.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/AmqpReceiverTest.php index 9dd86dcd07b42..bf9a2918d6d6c 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/AmqpReceiverTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/AmqpReceiverTest.php @@ -32,7 +32,7 @@ class AmqpReceiverTest extends TestCase public function testItReturnsTheDecodedMessageToTheHandler() { $serializer = new Serializer( - new SerializerComponent\Serializer([new ObjectNormalizer()], ['json' => new JsonEncoder()]) + new SerializerComponent\Serializer([], ['json' => new JsonEncoder()], new ObjectNormalizer(), new ObjectNormalizer()) ); $amqpEnvelope = $this->createAMQPEnvelope(); diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/composer.json b/src/Symfony/Component/Messenger/Bridge/Amqp/composer.json index 9bcbe024eb918..ed86243602c8b 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/composer.json +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/composer.json @@ -24,7 +24,7 @@ "symfony/event-dispatcher": "^6.4|^7.0", "symfony/process": "^6.4|^7.0", "symfony/property-access": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0" + "symfony/serializer": "^7.2" }, "autoload": { "psr-4": { "Symfony\\Component\\Messenger\\Bridge\\Amqp\\": "" }, diff --git a/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Tests/Transport/BeanstalkdReceiverTest.php b/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Tests/Transport/BeanstalkdReceiverTest.php index ed3c7f2d7eb4e..2a01837ea573f 100644 --- a/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Tests/Transport/BeanstalkdReceiverTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Tests/Transport/BeanstalkdReceiverTest.php @@ -92,7 +92,7 @@ private function createBeanstalkdEnvelope(): array private function createSerializer(): Serializer { $serializer = new Serializer( - new SerializerComponent\Serializer([new ObjectNormalizer()], ['json' => new JsonEncoder()]) + new SerializerComponent\Serializer([], ['json' => new JsonEncoder()], new ObjectNormalizer(), new ObjectNormalizer()) ); return $serializer; diff --git a/src/Symfony/Component/Messenger/Bridge/Beanstalkd/composer.json b/src/Symfony/Component/Messenger/Bridge/Beanstalkd/composer.json index e0b0eedbf933e..023683747ee83 100644 --- a/src/Symfony/Component/Messenger/Bridge/Beanstalkd/composer.json +++ b/src/Symfony/Component/Messenger/Bridge/Beanstalkd/composer.json @@ -18,7 +18,7 @@ }, "require-dev": { "symfony/property-access": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0" + "symfony/serializer": "^7.2" }, "autoload": { "psr-4": { "Symfony\\Component\\Messenger\\Bridge\\Beanstalkd\\": "" }, diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineReceiverTest.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineReceiverTest.php index b37b7db25fe0f..f3bc42b37d475 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineReceiverTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineReceiverTest.php @@ -315,7 +315,7 @@ private function createDoctrineEnvelope(): array private function createSerializer(): Serializer { $serializer = new Serializer( - new SerializerComponent\Serializer([new ObjectNormalizer()], ['json' => new JsonEncoder()]) + new SerializerComponent\Serializer([], ['json' => new JsonEncoder()], new ObjectNormalizer(), new ObjectNormalizer()) ); return $serializer; diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/composer.json b/src/Symfony/Component/Messenger/Bridge/Doctrine/composer.json index 23be7241e04ed..7af223980cc9c 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/composer.json +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/composer.json @@ -24,7 +24,7 @@ "require-dev": { "doctrine/persistence": "^1.3|^2|^3", "symfony/property-access": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0" + "symfony/serializer": "^7.2" }, "conflict": { "doctrine/persistence": "<1.3" diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisReceiverTest.php b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisReceiverTest.php index 903428ab3772c..fa26f3ad8f7c2 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisReceiverTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisReceiverTest.php @@ -75,7 +75,7 @@ public static function redisEnvelopeProvider(): \Generator ], new DummyMessage('Hi'), new Serializer( - new SerializerComponent\Serializer([new ObjectNormalizer()], ['json' => new JsonEncoder()]) + new SerializerComponent\Serializer([], ['json' => new JsonEncoder()], new ObjectNormalizer(), new ObjectNormalizer()) ), ]; diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/composer.json b/src/Symfony/Component/Messenger/Bridge/Redis/composer.json index f322f27c2107d..a1ecf02036fcd 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/composer.json +++ b/src/Symfony/Component/Messenger/Bridge/Redis/composer.json @@ -22,7 +22,7 @@ }, "require-dev": { "symfony/property-access": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0" + "symfony/serializer": "^7.2" }, "autoload": { "psr-4": { "Symfony\\Component\\Messenger\\Bridge\\Redis\\": "" }, diff --git a/src/Symfony/Component/Messenger/Tests/Stamp/ErrorDetailsStampTest.php b/src/Symfony/Component/Messenger/Tests/Stamp/ErrorDetailsStampTest.php index 8d66537afa0c8..8eeb63ddfd43c 100644 --- a/src/Symfony/Component/Messenger/Tests/Stamp/ErrorDetailsStampTest.php +++ b/src/Symfony/Component/Messenger/Tests/Stamp/ErrorDetailsStampTest.php @@ -20,6 +20,8 @@ use Symfony\Component\Messenger\Transport\Serialization\Serializer; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; +use Symfony\Component\Serializer\Normalizer\ChainDenormalizer; +use Symfony\Component\Serializer\Normalizer\ChainNormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Serializer as SymfonySerializer; @@ -57,11 +59,12 @@ public function testDeserialization() $exception = new \Exception('exception message'); $stamp = ErrorDetailsStamp::create($exception); $serializer = new Serializer( - new SymfonySerializer([ - new ArrayDenormalizer(), - new FlattenExceptionNormalizer(), - new ObjectNormalizer(), - ], [new JsonEncoder()]) + new SymfonySerializer( + [], + [new JsonEncoder()], + new ChainNormalizer([new FlattenExceptionNormalizer(), new ObjectNormalizer()]), + new ChainDenormalizer([new ArrayDenormalizer(), new FlattenExceptionNormalizer(), new ObjectNormalizer()]) + ) ); $deserializedEnvelope = $serializer->decode($serializer->encode(new Envelope(new \stdClass(), [$stamp]))); diff --git a/src/Symfony/Component/Messenger/Tests/Stamp/TransportNamesStampTest.php b/src/Symfony/Component/Messenger/Tests/Stamp/TransportNamesStampTest.php index fde3e000cc68d..dc5567149488f 100644 --- a/src/Symfony/Component/Messenger/Tests/Stamp/TransportNamesStampTest.php +++ b/src/Symfony/Component/Messenger/Tests/Stamp/TransportNamesStampTest.php @@ -17,6 +17,8 @@ use Symfony\Component\Messenger\Transport\Serialization\Serializer; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; +use Symfony\Component\Serializer\Normalizer\ChainDenormalizer; +use Symfony\Component\Serializer\Normalizer\ChainNormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Serializer as SymfonySerializer; @@ -38,10 +40,12 @@ public function testDeserialization() { $stamp = new TransportNamesStamp(['foo']); $serializer = new Serializer( - new SymfonySerializer([ - new ArrayDenormalizer(), - new ObjectNormalizer(), - ], [new JsonEncoder()]) + new SymfonySerializer( + [], + [new JsonEncoder()], + new ChainNormalizer([new ObjectNormalizer()]), + new ChainDenormalizer([new ArrayDenormalizer(), new ObjectNormalizer()]), + ) ); $deserializedEnvelope = $serializer->decode($serializer->encode(new Envelope(new \stdClass(), [$stamp]))); diff --git a/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php b/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php index 03542af425564..ba7543db4c432 100644 --- a/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php +++ b/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php @@ -22,6 +22,8 @@ use Symfony\Component\Serializer\Encoder\XmlEncoder; use Symfony\Component\Serializer\Exception\ExceptionInterface; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; +use Symfony\Component\Serializer\Normalizer\ChainDenormalizer; +use Symfony\Component\Serializer\Normalizer\ChainNormalizer; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Serializer as SymfonySerializer; @@ -53,8 +55,9 @@ public static function create(): self } $encoders = [new XmlEncoder(), new JsonEncoder()]; - $normalizers = [new DateTimeNormalizer(), new ArrayDenormalizer(), new ObjectNormalizer()]; - $serializer = new SymfonySerializer($normalizers, $encoders); + $normalizers = [new DateTimeNormalizer(), new ObjectNormalizer()]; + $denormalizers = [new DateTimeNormalizer(), new ArrayDenormalizer(), new ObjectNormalizer()]; + $serializer = new SymfonySerializer([], $encoders, new ChainNormalizer($normalizers), new ChainDenormalizer($denormalizers)); return new self($serializer); } diff --git a/src/Symfony/Component/Messenger/composer.json b/src/Symfony/Component/Messenger/composer.json index c51fdbfb58161..d8049be57add8 100644 --- a/src/Symfony/Component/Messenger/composer.json +++ b/src/Symfony/Component/Messenger/composer.json @@ -30,7 +30,7 @@ "symfony/property-access": "^6.4|^7.0", "symfony/rate-limiter": "^6.4|^7.0", "symfony/routing": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0", + "symfony/serializer": "^7.2", "symfony/service-contracts": "^2.5|^3", "symfony/stopwatch": "^6.4|^7.0", "symfony/validator": "^6.4|^7.0" @@ -41,7 +41,7 @@ "symfony/event-dispatcher-contracts": "<2.5", "symfony/framework-bundle": "<6.4", "symfony/http-kernel": "<6.4", - "symfony/serializer": "<6.4" + "symfony/serializer": "<7.1" }, "autoload": { "psr-4": { "Symfony\\Component\\Messenger\\": "" }, diff --git a/src/Symfony/Component/Mime/Tests/EmailTest.php b/src/Symfony/Component/Mime/Tests/EmailTest.php index ae61f26f605b4..f8a886d23d87c 100644 --- a/src/Symfony/Component/Mime/Tests/EmailTest.php +++ b/src/Symfony/Component/Mime/Tests/EmailTest.php @@ -26,6 +26,8 @@ use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; +use Symfony\Component\Serializer\Normalizer\ChainDenormalizer; +use Symfony\Component\Serializer\Normalizer\ChainNormalizer; use Symfony\Component\Serializer\Normalizer\MimeMessageNormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Normalizer\PropertyNormalizer; @@ -570,12 +572,21 @@ public function testSymfonySerialize() $extractor = new PhpDocExtractor(); $propertyNormalizer = new PropertyNormalizer(null, null, $extractor); - $serializer = new Serializer([ - new ArrayDenormalizer(), - new MimeMessageNormalizer($propertyNormalizer), - new ObjectNormalizer(null, null, null, $extractor), - $propertyNormalizer, - ], [new JsonEncoder()]); + $serializer = new Serializer( + [], + [new JsonEncoder()], + new ChainNormalizer([ + new MimeMessageNormalizer($propertyNormalizer), + new ObjectNormalizer(null, null, null, $extractor), + $propertyNormalizer, + ]), + new ChainDenormalizer([ + new ArrayDenormalizer(), + new MimeMessageNormalizer($propertyNormalizer), + new ObjectNormalizer(null, null, null, $extractor), + $propertyNormalizer, + ]) + ); $serialized = $serializer->serialize($e, 'json', [ObjectNormalizer::IGNORED_ATTRIBUTES => ['cachedBody']]); $this->assertSame($expectedJson, json_encode(json_decode($serialized), \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); diff --git a/src/Symfony/Component/Mime/Tests/MessageTest.php b/src/Symfony/Component/Mime/Tests/MessageTest.php index 1d01fa4519e93..d1714db2fc629 100644 --- a/src/Symfony/Component/Mime/Tests/MessageTest.php +++ b/src/Symfony/Component/Mime/Tests/MessageTest.php @@ -25,6 +25,8 @@ use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; +use Symfony\Component\Serializer\Normalizer\ChainDenormalizer; +use Symfony\Component\Serializer\Normalizer\ChainNormalizer; use Symfony\Component\Serializer\Normalizer\MimeMessageNormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Normalizer\PropertyNormalizer; @@ -260,12 +262,21 @@ public function testSymfonySerialize() $extractor = new PhpDocExtractor(); $propertyNormalizer = new PropertyNormalizer(null, null, $extractor); - $serializer = new Serializer([ - new ArrayDenormalizer(), - new MimeMessageNormalizer($propertyNormalizer), - new ObjectNormalizer(null, null, null, $extractor), - $propertyNormalizer, - ], [new JsonEncoder()]); + $serializer = new Serializer( + [], + [new JsonEncoder()], + new ChainNormalizer([ + new MimeMessageNormalizer($propertyNormalizer), + new ObjectNormalizer(null, null, null, $extractor), + $propertyNormalizer, + ]), + new ChainDenormalizer([ + new ArrayDenormalizer(), + new MimeMessageNormalizer($propertyNormalizer), + new ObjectNormalizer(null, null, null, $extractor), + $propertyNormalizer, + ]) + ); $serialized = $serializer->serialize($e, 'json'); $this->assertStringMatchesFormat($expectedJson, json_encode(json_decode($serialized), \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); diff --git a/src/Symfony/Component/Mime/composer.json b/src/Symfony/Component/Mime/composer.json index 5304bdf36d90b..31cc7ab4d15c9 100644 --- a/src/Symfony/Component/Mime/composer.json +++ b/src/Symfony/Component/Mime/composer.json @@ -28,7 +28,7 @@ "symfony/process": "^6.4|^7.0", "symfony/property-access": "^6.4|^7.0", "symfony/property-info": "^6.4|^7.0", - "symfony/serializer": "^6.4.3|^7.0.3" + "symfony/serializer": "^7.2" }, "conflict": { "egulias/email-validator": "~3.0.0", diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md index 3118834d80175..d270e7a830cef 100644 --- a/src/Symfony/Component/Serializer/CHANGELOG.md +++ b/src/Symfony/Component/Serializer/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +7.2 +--- + + * Add `ChainNormalizer` and `ChainDenormalizer` + * Add two more constructor arguments for `Serializer` to accept a `NormalizerInterface` and `DenormalizerInterface` + 7.1 --- diff --git a/src/Symfony/Component/Serializer/Debug/TraceableEncoder.php b/src/Symfony/Component/Serializer/Debug/TraceableEncoder.php index 0795d14ca0cfa..d6e9b4d0ec41e 100644 --- a/src/Symfony/Component/Serializer/Debug/TraceableEncoder.php +++ b/src/Symfony/Component/Serializer/Debug/TraceableEncoder.php @@ -15,6 +15,8 @@ use Symfony\Component\Serializer\Encoder\DecoderInterface; use Symfony\Component\Serializer\Encoder\EncoderInterface; use Symfony\Component\Serializer\Encoder\NormalizationAwareInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\SerializerAwareInterface; use Symfony\Component\Serializer\SerializerInterface; @@ -25,7 +27,7 @@ * * @internal */ -class TraceableEncoder implements EncoderInterface, DecoderInterface, SerializerAwareInterface +class TraceableEncoder implements EncoderInterface, DecoderInterface, SerializerAwareInterface, NormalizerAwareInterface { public function __construct( private EncoderInterface|DecoderInterface $encoder, @@ -94,6 +96,15 @@ public function setSerializer(SerializerInterface $serializer): void $this->encoder->setSerializer($serializer); } + public function setNormalizer(NormalizerInterface $normalizer): void + { + if (!$this->encoder instanceof NormalizerAwareInterface) { + return; + } + + $this->encoder->setNormalizer($normalizer); + } + public function needsNormalization(): bool { return !$this->encoder instanceof NormalizationAwareInterface; diff --git a/src/Symfony/Component/Serializer/DependencyInjection/SerializerPass.php b/src/Symfony/Component/Serializer/DependencyInjection/SerializerPass.php index 2a429054b0c7b..6d68bf6f53b3a 100644 --- a/src/Symfony/Component/Serializer/DependencyInjection/SerializerPass.php +++ b/src/Symfony/Component/Serializer/DependencyInjection/SerializerPass.php @@ -15,10 +15,13 @@ use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\Serializer\Debug\TraceableEncoder; use Symfony\Component\Serializer\Debug\TraceableNormalizer; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; /** * Adds all services with the tags "serializer.encoder" and "serializer.normalizer" as @@ -67,8 +70,37 @@ public function process(ContainerBuilder $container): void } } + $denormalizers = []; + $trueNormalizers = []; + /** @var Reference|Definition $definition */ + foreach ($normalizers as $definition) { + $reference = null; + if ($definition instanceof Reference) { + $reference = $definition; + $definition = $container->getDefinition($definition); + } + + $class = $definition->getClass(); + if (null === $class) { + continue; + } + $interfaces = class_implements($class); + if (isset($interfaces[NormalizerInterface::class])) { + $trueNormalizers[] = $reference ?? $definition; + } + if (isset($interfaces[DenormalizerInterface::class])) { + $denormalizers[] = $reference ?? $definition; + } + } + $serializerDefinition = $container->getDefinition('serializer'); - $serializerDefinition->replaceArgument(0, $normalizers); $serializerDefinition->replaceArgument(1, $encoders); + + if ($container->hasDefinition('serializer.normalizer') && $container->hasDefinition('serializer.denormalizer')) { + $container->getDefinition('serializer.normalizer')->replaceArgument(0, $trueNormalizers); + $container->getDefinition('serializer.denormalizer')->replaceArgument(0, $denormalizers); + } else { + $serializerDefinition->replaceArgument(0, $normalizers); + } } } diff --git a/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php b/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php index 5dcb2ba7e9919..832c7354907b2 100644 --- a/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php +++ b/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php @@ -13,6 +13,8 @@ use Symfony\Component\Serializer\Exception\BadMethodCallException; use Symfony\Component\Serializer\Exception\NotEncodableValueException; +use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; use Symfony\Component\Serializer\SerializerAwareInterface; use Symfony\Component\Serializer\SerializerAwareTrait; @@ -23,8 +25,9 @@ * @author Kévin Dunglas * @author Dany Maillard */ -class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwareInterface, SerializerAwareInterface +class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwareInterface, SerializerAwareInterface, NormalizerAwareInterface { + use NormalizerAwareTrait; use SerializerAwareTrait; public const FORMAT = 'xml'; @@ -346,12 +349,12 @@ private function buildXml(\DOMNode $parentNode, mixed $data, string $format, arr $removeEmptyTags = $context[self::REMOVE_EMPTY_TAGS] ?? $this->defaultContext[self::REMOVE_EMPTY_TAGS] ?? false; $encoderIgnoredNodeTypes = $context[self::ENCODER_IGNORED_NODE_TYPES] ?? $this->defaultContext[self::ENCODER_IGNORED_NODE_TYPES]; - if (\is_array($data) || ($data instanceof \Traversable && (null === $this->serializer || !$this->serializer->supportsNormalization($data, $format)))) { + if (\is_array($data) || ($data instanceof \Traversable && (null === $this->normalizer || !$this->normalizer->supportsNormalization($data, $format)))) { foreach ($data as $key => $data) { // Ah this is the magic @ attribute types. if (str_starts_with($key, '@') && $this->isElementNameValid($attributeName = substr($key, 1))) { if (!\is_scalar($data)) { - $data = $this->serializer->normalize($data, $format, $context); + $data = $this->normalizer->normalize($data, $format, $context); } if (\is_bool($data)) { $data = (int) $data; @@ -388,11 +391,11 @@ private function buildXml(\DOMNode $parentNode, mixed $data, string $format, arr } if (\is_object($data)) { - if (null === $this->serializer) { + if (null === $this->normalizer) { throw new BadMethodCallException(sprintf('The serializer needs to be set to allow "%s()" to be used with object data.', __METHOD__)); } - $data = $this->serializer->normalize($data, $format, $context); + $data = $this->normalizer->normalize($data, $format, $context); if (null !== $data && !\is_scalar($data)) { return $this->buildXml($parentNode, $data, $format, $context, $xmlRootNodeName); } @@ -456,11 +459,11 @@ private function selectNodeType(\DOMNode $node, mixed $val, string $format, arra $child = $node->ownerDocument->importNode($val, true); $node->appendChild($child); } elseif (\is_object($val)) { - if (null === $this->serializer) { + if (null === $this->normalizer) { throw new BadMethodCallException(sprintf('The serializer needs to be set to allow "%s()" to be used with object data.', __METHOD__)); } - return $this->selectNodeType($node, $this->serializer->normalize($val, $format, $context), $format, $context); + return $this->selectNodeType($node, $this->normalizer->normalize($val, $format, $context), $format, $context); } elseif (is_numeric($val)) { return $this->appendText($node, (string) $val); } elseif (\is_string($val) && $this->needsCdataWrapping($val, $context)) { diff --git a/src/Symfony/Component/Serializer/Normalizer/ChainDenormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ChainDenormalizer.php new file mode 100644 index 0000000000000..dd9554a4857c0 --- /dev/null +++ b/src/Symfony/Component/Serializer/Normalizer/ChainDenormalizer.php @@ -0,0 +1,207 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Normalizer; + +use Symfony\Component\Serializer\Exception\LogicException; +use Symfony\Component\Serializer\Exception\NotNormalizableValueException; +use Symfony\Component\Serializer\Exception\PartialDenormalizationException; +use Symfony\Component\Serializer\SerializerAwareInterface; +use Symfony\Component\Serializer\SerializerInterface; + +/** + * This holds a collection of denormalizers. It tries to be smart how it selects + * the correct Denormalizer. + * + * @author Tobias Nyholm + */ +final class ChainDenormalizer implements DenormalizerInterface, SerializerAwareInterface +{ + private const SCALAR_TYPES = [ + 'int' => true, + 'bool' => true, + 'float' => true, + 'string' => true, + ]; + + /** + * @deprecated since Symfony 7.1 + */ + private ?SerializerInterface $serializer = null; + + /** + * @var DenormalizerInterface[] + */ + private array $denormalizers = []; + + /** + * @var array>> + */ + private array $denormalizerCache = []; + + /** + * @var array> + */ + private array $supportedCache = []; + + /** + * @param DenormalizerInterface[] $denormalizers + */ + public function __construct(array $denormalizers = []) + { + foreach ($denormalizers as $denormalizer) { + $this->addDenormalizer($denormalizer); + } + } + + public function addDenormalizer(DenormalizerInterface $denormalizer): void + { + if ($denormalizer instanceof DenormalizerAwareInterface) { + $denormalizer->setDenormalizer($this); + } + + if (null !== $this->serializer && $denormalizer instanceof SerializerAwareInterface) { + $denormalizer->setSerializer($this->serializer); + } + + $this->denormalizers[] = $denormalizer; + $this->denormalizerCache = []; + $this->supportedCache = []; + } + + /** + * @throws NotNormalizableValueException + */ + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed + { + if (isset($context[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS], $context['not_normalizable_value_exceptions'])) { + throw new LogicException('Passing a value for "not_normalizable_value_exceptions" context key is not allowed.'); + } + + $denormalizer = $this->getDenormalizer($data, $type, $format, $context); + + // Check for a denormalizer first, e.g. the data is wrapped + if (!$denormalizer && isset(self::SCALAR_TYPES[$type])) { + if (!('is_'.$type)($data)) { + throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Data expected to be of type "%s" ("%s" given).', $type, get_debug_type($data)), $data, [$type], $context['deserialization_path'] ?? null, true); + } + + return $data; + } + + if ([] === $this->denormalizers) { + throw new LogicException('You must register at least one normalizer to be able to denormalize objects.'); + } + + if (!$denormalizer) { + throw new NotNormalizableValueException(sprintf('Could not denormalize object of type "%s", no supporting normalizer found.', $type)); + } + + return $denormalizer->denormalize($data, $type, $format, $context); + } + + /** + * Returns a matching denormalizer. + * + * @param mixed $data Data to restore + * @param string $class The expected class to instantiate + * @param string|null $format Format name, present to give the option to normalizers to act differently based on formats + * @param array $context Options available to the denormalizer + */ + private function getDenormalizer(mixed $data, string $class, ?string $format, array $context): ?DenormalizerInterface + { + if (!isset($this->denormalizerCache[$format][$class])) { + $this->denormalizerCache[$format][$class] = []; + $genericType = class_exists($class) || interface_exists($class, false) ? 'object' : '*'; + + foreach ($this->denormalizers as $k => $denormalizer) { + if (!$denormalizer instanceof DenormalizerInterface) { + continue; + } + + $supportedTypes = $denormalizer->getSupportedTypes($format); + + $doesClassRepresentCollection = str_ends_with($class, '[]'); + + foreach ($supportedTypes as $supportedType => $isCacheable) { + if (\in_array($supportedType, ['*', 'object'], true) + || $class !== $supportedType && ('object' !== $genericType || !is_subclass_of($class, $supportedType)) + && !($doesClassRepresentCollection && str_ends_with($supportedType, '[]') && is_subclass_of(strstr($class, '[]', true), strstr($supportedType, '[]', true))) + ) { + continue; + } + + if (null === $isCacheable) { + unset($supportedTypes['*'], $supportedTypes['object']); + } elseif ($this->denormalizerCache[$format][$class][$k] = $isCacheable && $denormalizer->supportsDenormalization(null, $class, $format, $context)) { + break 2; + } + + break; + } + + if (null === $isCacheable = $supportedTypes[\array_key_exists($genericType, $supportedTypes) ? $genericType : '*'] ?? null) { + continue; + } + + if ($this->denormalizerCache[$format][$class][$k] ??= $isCacheable && $denormalizer->supportsDenormalization(null, $class, $format, $context)) { + break; + } + } + } + + foreach ($this->denormalizerCache[$format][$class] as $k => $cached) { + $denormalizer = $this->denormalizers[$k]; + if ($cached || $denormalizer->supportsDenormalization($data, $class, $format, $context)) { + return $denormalizer; + } + } + + return null; + } + + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool + { + return isset(self::SCALAR_TYPES[$type]) || null !== $this->getDenormalizer($data, $type, $format, $context); + } + + public function getSupportedTypes(?string $format): array + { + if (null === $format) { + $format = '__null__'; + } + + if (!isset($this->supportedCache[$format])) { + foreach ($this->denormalizers as $denormalizer) { + $this->supportedCache + $denormalizer->getSupportedTypes($format); + } + } + + return $this->supportedCache[$format]; + } + + /** + * This method exists only for BC reasons. Will be removed in 8.0. + * + * @internal + * + * @deprecated + */ + public function setSerializer(SerializerInterface $serializer): void + { + $this->serializer = $serializer; + foreach ($this->denormalizers as $denormalizer) { + if ($denormalizer instanceof SerializerAwareInterface) { + $denormalizer->setSerializer($serializer); + } + } + } +} diff --git a/src/Symfony/Component/Serializer/Normalizer/ChainNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ChainNormalizer.php new file mode 100644 index 0000000000000..f1cd98d5a77c2 --- /dev/null +++ b/src/Symfony/Component/Serializer/Normalizer/ChainNormalizer.php @@ -0,0 +1,213 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Normalizer; + +use Symfony\Component\Serializer\Exception\LogicException; +use Symfony\Component\Serializer\Exception\NotNormalizableValueException; +use Symfony\Component\Serializer\Serializer; +use Symfony\Component\Serializer\SerializerAwareInterface; +use Symfony\Component\Serializer\SerializerInterface; + +/** + * This holds a collection of normalizers. It tries to be smart how it selects + * the correct Normalizer. + * + * @author Tobias Nyholm + */ +final class ChainNormalizer implements NormalizerInterface, SerializerAwareInterface +{ + /** + * @deprecated since Symfony 7.1 + */ + private ?SerializerInterface $serializer = null; + + /** + * @var NormalizerInterface[] + */ + private array $normalizers = []; + + /** + * @var array>> + */ + private array $normalizerCache = []; + + /** + * @var array> + */ + private array $supportedCache = []; + + /** + * @param NormalizerInterface[] $normalizers + */ + public function __construct(array $normalizers = []) + { + foreach ($normalizers as $normalizer) { + $this->addNormalizer($normalizer); + } + } + + /** + * Add Normalizer last in the line. + */ + public function addNormalizer(NormalizerInterface $normalizer): void + { + if ($normalizer instanceof NormalizerAwareInterface) { + $normalizer->setNormalizer($this); + } + if (null !== $this->serializer && $normalizer instanceof SerializerAwareInterface) { + $normalizer->setSerializer($this->serializer); + } + + $this->normalizers[] = $normalizer; + $this->normalizerCache = []; + $this->supportedCache = []; + } + + public function normalize(mixed $object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null + { + // If a normalizer supports the given data, use it + if ($normalizer = $this->getNormalizer($object, $format, $context)) { + return $normalizer->normalize($object, $format, $context); + } + + if (null === $object || \is_scalar($object)) { + return $object; + } + + if (\is_array($object) && !$object && ($context[Serializer::EMPTY_ARRAY_AS_OBJECT] ?? false)) { + return new \ArrayObject(); + } + + if (is_iterable($object)) { + if ($object instanceof \Countable && ($context[AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS] ?? false) && !\count($object)) { + return new \ArrayObject(); + } + + $normalized = []; + foreach ($object as $key => $val) { + $normalized[$key] = $this->normalize($val, $format, $context); + } + + return $normalized; + } + + if (\is_object($object)) { + if ([] === $this->normalizers) { + throw new LogicException('You must register at least one normalizer to be able to normalize objects.'); + } + + throw new NotNormalizableValueException(sprintf('Could not normalize object of type "%s", no supporting normalizer found.', get_debug_type($object))); + } + + throw new NotNormalizableValueException('An unexpected value could not be normalized: '.(!\is_resource($object) ? var_export($object, true) : sprintf('"%s" resource', get_resource_type($object)))); + } + + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool + { + return null !== $this->getNormalizer($data, $format, $context); + } + + public function getSupportedTypes(?string $format): array + { + if (null === $format) { + $format = '__null__'; + } + + if (!isset($this->supportedCache[$format])) { + foreach ($this->normalizers as $normalizer) { + $this->supportedCache + $normalizer->getSupportedTypes($format); + } + } + + return $this->supportedCache[$format]; + } + + /** + * Returns a matching normalizer. + * + * @param mixed $data Data to get the serializer for + * @param string|null $format Format name, present to give the option to normalizers to act differently based on formats + * @param array $context Options available to the normalizer + */ + private function getNormalizer(mixed $data, ?string $format, array $context): ?NormalizerInterface + { + if (\is_object($data)) { + $type = $data::class; + $genericType = 'object'; + } else { + $type = 'native-'.\gettype($data); + $genericType = '*'; + } + + if (!isset($this->normalizerCache[$format][$type])) { + $this->normalizerCache[$format][$type] = []; + + foreach ($this->normalizers as $k => $normalizer) { + if (!$normalizer instanceof NormalizerInterface) { + continue; + } + + $supportedTypes = $normalizer->getSupportedTypes($format); + + foreach ($supportedTypes as $supportedType => $isCacheable) { + if (\in_array($supportedType, ['*', 'object'], true) + || $type !== $supportedType && ('object' !== $genericType || !is_subclass_of($type, $supportedType)) + ) { + continue; + } + + if (null === $isCacheable) { + unset($supportedTypes['*'], $supportedTypes['object']); + } elseif ($this->normalizerCache[$format][$type][$k] = $isCacheable && $normalizer->supportsNormalization($data, $format, $context)) { + break 2; + } + + break; + } + + if (null === $isCacheable = $supportedTypes[\array_key_exists($genericType, $supportedTypes) ? $genericType : '*'] ?? null) { + continue; + } + + if ($this->normalizerCache[$format][$type][$k] ??= $isCacheable && $normalizer->supportsNormalization($data, $format, $context)) { + break; + } + } + } + + foreach ($this->normalizerCache[$format][$type] as $k => $cached) { + $normalizer = $this->normalizers[$k]; + if ($cached || $normalizer->supportsNormalization($data, $format, $context)) { + return $normalizer; + } + } + + return null; + } + + /** + * This method exists only for BC reasons. Will be removed in 8.0. + * + * @internal + * + * @deprecated + */ + public function setSerializer(SerializerInterface $serializer): void + { + $this->serializer = $serializer; + foreach ($this->normalizers as $normalizer) { + if ($normalizer instanceof SerializerAwareInterface) { + $normalizer->setSerializer($serializer); + } + } + } +} diff --git a/src/Symfony/Component/Serializer/Serializer.php b/src/Symfony/Component/Serializer/Serializer.php index 9fba20f144f4c..d324fd635bd12 100644 --- a/src/Symfony/Component/Serializer/Serializer.php +++ b/src/Symfony/Component/Serializer/Serializer.php @@ -18,12 +18,11 @@ use Symfony\Component\Serializer\Encoder\DecoderInterface; use Symfony\Component\Serializer\Encoder\EncoderInterface; use Symfony\Component\Serializer\Exception\InvalidArgumentException; -use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; use Symfony\Component\Serializer\Exception\PartialDenormalizationException; use Symfony\Component\Serializer\Exception\UnsupportedFormatException; -use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; -use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; +use Symfony\Component\Serializer\Normalizer\ChainDenormalizer; +use Symfony\Component\Serializer\Normalizer\ChainNormalizer; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; @@ -51,50 +50,54 @@ class Serializer implements SerializerInterface, NormalizerInterface, Denormaliz */ public const EMPTY_ARRAY_AS_OBJECT = 'empty_array_as_object'; - private const SCALAR_TYPES = [ - 'int' => true, - 'bool' => true, - 'float' => true, - 'string' => true, - ]; + private NormalizerInterface $normalizer; + + private DenormalizerInterface $denormalizer; protected ChainEncoder $encoder; protected ChainDecoder $decoder; - /** - * @var array>> - */ - private array $denormalizerCache = []; - - /** - * @var array>> - */ - private array $normalizerCache = []; - /** * @param array $normalizers * @param array $encoders */ public function __construct( - private array $normalizers = [], + array $normalizers = [], array $encoders = [], + ?NormalizerInterface $normalizer = null, + ?DenormalizerInterface $denormalizer = null, ) { - foreach ($normalizers as $normalizer) { - if ($normalizer instanceof SerializerAwareInterface) { - $normalizer->setSerializer($this); + if ([] !== $normalizers && (null !== $normalizer || null !== $denormalizer)) { + throw new InvalidArgumentException('You cannot use an array of $normalizers with a $normalizer/$denormalizer.'); + } + + $this->normalizer = $normalizer ?? new ChainNormalizer(); + $this->denormalizer = $denormalizer ?? new ChainDenormalizer(); + + if ([] !== $normalizers) { + trigger_deprecation('symfony/serializer', '7.1', 'Passing normalizers as first argument to "%s" is deprecated, use a chain normalizer/denormalizer instead.', __METHOD__); + } + + foreach ($normalizers as $item) { + if ($item instanceof SerializerAwareInterface) { + if (!$item instanceof NormalizerAwareInterface) { + // We assume they have started to migrate + trigger_deprecation('symfony/serializer', '7.1', 'Interface %s is deprecated, maybe you are interested to use %s instead.', SerializerAwareInterface::class, NormalizerAwareInterface::class); + } + $item->setSerializer($this); } - if ($normalizer instanceof DenormalizerAwareInterface) { - $normalizer->setDenormalizer($this); + if ($item instanceof DenormalizerInterface) { + $this->denormalizer->addDenormalizer($item); } - if ($normalizer instanceof NormalizerAwareInterface) { - $normalizer->setNormalizer($this); + if ($item instanceof NormalizerInterface) { + $this->normalizer->addNormalizer($item); } - if (!($normalizer instanceof NormalizerInterface || $normalizer instanceof DenormalizerInterface)) { - throw new InvalidArgumentException(sprintf('The class "%s" neither implements "%s" nor "%s".', get_debug_type($normalizer), NormalizerInterface::class, DenormalizerInterface::class)); + if (!($item instanceof NormalizerInterface || $item instanceof DenormalizerInterface)) { + throw new InvalidArgumentException(sprintf('The class "%s" neither implements "%s" nor "%s".', get_debug_type($item), NormalizerInterface::class, DenormalizerInterface::class)); } } @@ -102,8 +105,15 @@ public function __construct( $realEncoders = []; foreach ($encoders as $encoder) { if ($encoder instanceof SerializerAwareInterface) { + if (!$encoder instanceof NormalizerAwareInterface) { + // We assume they have started to migrate + trigger_deprecation('symfony/serializer', '7.1', 'Interface %s is deprecated, maybe you are interested to use %s instead.', SerializerAwareInterface::class, NormalizerAwareInterface::class); + } $encoder->setSerializer($this); } + if ($encoder instanceof NormalizerAwareInterface) { + $encoder->setNormalizer($this->normalizer); + } if ($encoder instanceof DecoderInterface) { $decoders[] = $encoder; } @@ -117,6 +127,14 @@ public function __construct( } $this->encoder = new ChainEncoder($realEncoders); $this->decoder = new ChainDecoder($decoders); + + // This code must exist as long as we support SerializerAwareInterface + if ($this->normalizer instanceof SerializerAwareInterface) { + $this->normalizer->setSerializer($this); + } + if ($this->denormalizer instanceof SerializerAwareInterface) { + $this->denormalizer->setSerializer($this); + } } final public function serialize(mixed $data, string $format, array $context = []): string @@ -145,41 +163,7 @@ final public function deserialize(mixed $data, string $type, string $format, arr public function normalize(mixed $data, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { - // If a normalizer supports the given data, use it - if ($normalizer = $this->getNormalizer($data, $format, $context)) { - return $normalizer->normalize($data, $format, $context); - } - - if (null === $data || \is_scalar($data)) { - return $data; - } - - if (\is_array($data) && !$data && ($context[self::EMPTY_ARRAY_AS_OBJECT] ?? false)) { - return new \ArrayObject(); - } - - if (is_iterable($data)) { - if ($data instanceof \Countable && ($context[AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS] ?? false) && !\count($data)) { - return new \ArrayObject(); - } - - $normalized = []; - foreach ($data as $key => $val) { - $normalized[$key] = $this->normalize($val, $format, $context); - } - - return $normalized; - } - - if (\is_object($data)) { - if (!$this->normalizers) { - throw new LogicException('You must register at least one normalizer to be able to normalize objects.'); - } - - throw new NotNormalizableValueException(sprintf('Could not normalize object of type "%s", no supporting normalizer found.', get_debug_type($data))); - } - - throw new NotNormalizableValueException('An unexpected value could not be normalized: '.(!\is_resource($data) ? var_export($data, true) : sprintf('"%s" resource', get_resource_type($data)))); + return $this->normalizer->normalize($data, $format, $context); } /** @@ -188,34 +172,11 @@ public function normalize(mixed $data, ?string $format = null, array $context = */ public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { - if (isset($context[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS], $context['not_normalizable_value_exceptions'])) { - throw new LogicException('Passing a value for "not_normalizable_value_exceptions" context key is not allowed.'); - } - - $normalizer = $this->getDenormalizer($data, $type, $format, $context); - - // Check for a denormalizer first, e.g. the data is wrapped - if (!$normalizer && isset(self::SCALAR_TYPES[$type])) { - if (!('is_'.$type)($data)) { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Data expected to be of type "%s" ("%s" given).', $type, get_debug_type($data)), $data, [$type], $context['deserialization_path'] ?? null, true); - } - - return $data; - } - - if (!$this->normalizers) { - throw new LogicException('You must register at least one normalizer to be able to denormalize objects.'); - } - - if (!$normalizer) { - throw new NotNormalizableValueException(sprintf('Could not denormalize object of type "%s", no supporting normalizer found.', $type)); - } - if (isset($context[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS])) { unset($context[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS]); $context['not_normalizable_value_exceptions'] = []; $errors = &$context['not_normalizable_value_exceptions']; - $denormalized = $normalizer->denormalize($data, $type, $format, $context); + $denormalized = $this->denormalizer->denormalize($data, $type, $format, $context); if ($errors) { // merge errors so that one path has only one error @@ -235,7 +196,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a return $denormalized; } - return $normalizer->denormalize($data, $type, $format, $context); + return $this->denormalizer->denormalize($data, $type, $format, $context); } public function getSupportedTypes(?string $format): array @@ -245,135 +206,12 @@ public function getSupportedTypes(?string $format): array public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool { - return null !== $this->getNormalizer($data, $format, $context); + return $this->normalizer->supportsNormalization($data, $format, $context); } public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { - return isset(self::SCALAR_TYPES[$type]) || null !== $this->getDenormalizer($data, $type, $format, $context); - } - - /** - * Returns a matching normalizer. - * - * @param mixed $data Data to get the serializer for - * @param string|null $format Format name, present to give the option to normalizers to act differently based on formats - * @param array $context Options available to the normalizer - */ - private function getNormalizer(mixed $data, ?string $format, array $context): ?NormalizerInterface - { - if (\is_object($data)) { - $type = $data::class; - $genericType = 'object'; - } else { - $type = 'native-'.\gettype($data); - $genericType = '*'; - } - - if (!isset($this->normalizerCache[$format][$type])) { - $this->normalizerCache[$format][$type] = []; - - foreach ($this->normalizers as $k => $normalizer) { - if (!$normalizer instanceof NormalizerInterface) { - continue; - } - - $supportedTypes = $normalizer->getSupportedTypes($format); - - foreach ($supportedTypes as $supportedType => $isCacheable) { - if (\in_array($supportedType, ['*', 'object'], true) - || $type !== $supportedType && ('object' !== $genericType || !is_subclass_of($type, $supportedType)) - ) { - continue; - } - - if (null === $isCacheable) { - unset($supportedTypes['*'], $supportedTypes['object']); - } elseif ($this->normalizerCache[$format][$type][$k] = $isCacheable && $normalizer->supportsNormalization($data, $format, $context)) { - break 2; - } - - break; - } - - if (null === $isCacheable = $supportedTypes[\array_key_exists($genericType, $supportedTypes) ? $genericType : '*'] ?? null) { - continue; - } - - if ($this->normalizerCache[$format][$type][$k] ??= $isCacheable && $normalizer->supportsNormalization($data, $format, $context)) { - break; - } - } - } - - foreach ($this->normalizerCache[$format][$type] as $k => $cached) { - $normalizer = $this->normalizers[$k]; - if ($cached || $normalizer->supportsNormalization($data, $format, $context)) { - return $normalizer; - } - } - - return null; - } - - /** - * Returns a matching denormalizer. - * - * @param mixed $data Data to restore - * @param string $class The expected class to instantiate - * @param string|null $format Format name, present to give the option to normalizers to act differently based on formats - * @param array $context Options available to the denormalizer - */ - private function getDenormalizer(mixed $data, string $class, ?string $format, array $context): ?DenormalizerInterface - { - if (!isset($this->denormalizerCache[$format][$class])) { - $this->denormalizerCache[$format][$class] = []; - $genericType = class_exists($class) || interface_exists($class, false) ? 'object' : '*'; - - foreach ($this->normalizers as $k => $normalizer) { - if (!$normalizer instanceof DenormalizerInterface) { - continue; - } - - $supportedTypes = $normalizer->getSupportedTypes($format); - - $doesClassRepresentCollection = str_ends_with($class, '[]'); - - foreach ($supportedTypes as $supportedType => $isCacheable) { - if (\in_array($supportedType, ['*', 'object'], true) - || $class !== $supportedType && ('object' !== $genericType || !is_subclass_of($class, $supportedType)) - && !($doesClassRepresentCollection && str_ends_with($supportedType, '[]') && is_subclass_of(strstr($class, '[]', true), strstr($supportedType, '[]', true))) - ) { - continue; - } - - if (null === $isCacheable) { - unset($supportedTypes['*'], $supportedTypes['object']); - } elseif ($this->denormalizerCache[$format][$class][$k] = $isCacheable && $normalizer->supportsDenormalization(null, $class, $format, $context)) { - break 2; - } - - break; - } - - if (null === $isCacheable = $supportedTypes[\array_key_exists($genericType, $supportedTypes) ? $genericType : '*'] ?? null) { - continue; - } - - if ($this->denormalizerCache[$format][$class][$k] ??= $isCacheable && $normalizer->supportsDenormalization(null, $class, $format, $context)) { - break; - } - } - } - - foreach ($this->denormalizerCache[$format][$class] as $k => $cached) { - $normalizer = $this->normalizers[$k]; - if ($cached || $normalizer->supportsDenormalization($data, $class, $format, $context)) { - return $normalizer; - } - } - - return null; + return $this->denormalizer->supportsDenormalization($data, $type, $format, $context); } final public function encode(mixed $data, string $format, array $context = []): string diff --git a/src/Symfony/Component/Serializer/SerializerAwareInterface.php b/src/Symfony/Component/Serializer/SerializerAwareInterface.php index 0b4db4c7768c4..fb363555f45ec 100644 --- a/src/Symfony/Component/Serializer/SerializerAwareInterface.php +++ b/src/Symfony/Component/Serializer/SerializerAwareInterface.php @@ -12,6 +12,8 @@ namespace Symfony\Component\Serializer; /** + * @deprecated since Symfony 7.2, use {@see NormalizerAwareInterface} instead + * * @author Jordi Boggiano */ interface SerializerAwareInterface diff --git a/src/Symfony/Component/Serializer/SerializerAwareTrait.php b/src/Symfony/Component/Serializer/SerializerAwareTrait.php index 495e5889ce818..72001a655b458 100644 --- a/src/Symfony/Component/Serializer/SerializerAwareTrait.php +++ b/src/Symfony/Component/Serializer/SerializerAwareTrait.php @@ -12,6 +12,8 @@ namespace Symfony\Component\Serializer; /** + * @deprecated since Symfony 7.2 + * * @author Joel Wurtz */ trait SerializerAwareTrait diff --git a/src/Symfony/Component/Serializer/Tests/DeserializeNestedArrayOfObjectsTest.php b/src/Symfony/Component/Serializer/Tests/DeserializeNestedArrayOfObjectsTest.php index 8da1b471bd567..802f345717c74 100644 --- a/src/Symfony/Component/Serializer/Tests/DeserializeNestedArrayOfObjectsTest.php +++ b/src/Symfony/Component/Serializer/Tests/DeserializeNestedArrayOfObjectsTest.php @@ -15,6 +15,7 @@ use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; +use Symfony\Component\Serializer\Normalizer\ChainDenormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Serializer; @@ -42,10 +43,11 @@ public function testPropertyPhpDoc($class) ] } EOF; - $serializer = new Serializer([ + $denormalizer = new ChainDenormalizer([ new ObjectNormalizer(null, null, null, new PhpDocExtractor()), new ArrayDenormalizer(), - ], ['json' => new JsonEncoder()]); + ]); + $serializer = new Serializer([], ['json' => new JsonEncoder()], null, $denormalizer); /** @var Zoo|ZooImmutable $zoo */ $zoo = $serializer->deserialize($json, $class, 'json'); @@ -74,10 +76,11 @@ public function testPropertyPhpDocWithKeyTypes() } } EOF; - $serializer = new Serializer([ + $denormalizer = new ChainDenormalizer([ new ObjectNormalizer(null, null, null, new PhpDocExtractor()), new ArrayDenormalizer(), - ], ['json' => new JsonEncoder()]); + ]); + $serializer = new Serializer([], ['json' => new JsonEncoder()], null, $denormalizer); /** @var ZooWithKeyTypes $zoo */ $zoo = $serializer->deserialize($json, ZooWithKeyTypes::class, 'json'); diff --git a/src/Symfony/Component/Serializer/Tests/Encoder/JsonEncoderTest.php b/src/Symfony/Component/Serializer/Tests/Encoder/JsonEncoderTest.php index a34e82c6b09a5..d4224a5d12105 100644 --- a/src/Symfony/Component/Serializer/Tests/Encoder/JsonEncoderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Encoder/JsonEncoderTest.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Exception\UnexpectedValueException; +use Symfony\Component\Serializer\Normalizer\ChainNormalizer; use Symfony\Component\Serializer\Normalizer\CustomNormalizer; use Symfony\Component\Serializer\Serializer; @@ -24,8 +25,9 @@ class JsonEncoderTest extends TestCase protected function setUp(): void { + $normalizer = new ChainNormalizer([new CustomNormalizer()]); $this->encoder = new JsonEncoder(); - $this->serializer = new Serializer([new CustomNormalizer()], ['json' => new JsonEncoder()]); + $this->serializer = new Serializer([], ['json' => new JsonEncoder()], $normalizer); } public function testEncodeScalar() diff --git a/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php b/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php index 5be6be232dc65..8bb2b09618f52 100644 --- a/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php @@ -16,6 +16,7 @@ use Symfony\Component\Serializer\Encoder\XmlEncoder; use Symfony\Component\Serializer\Exception\NotEncodableValueException; use Symfony\Component\Serializer\Exception\UnexpectedValueException; +use Symfony\Component\Serializer\Normalizer\ChainNormalizer; use Symfony\Component\Serializer\Normalizer\CustomNormalizer; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Serializer; @@ -35,8 +36,8 @@ class XmlEncoderTest extends TestCase protected function setUp(): void { $this->encoder = new XmlEncoder(); - $serializer = new Serializer([new CustomNormalizer()], ['xml' => new XmlEncoder()]); - $this->encoder->setSerializer($serializer); + $normalizer = new ChainNormalizer([new CustomNormalizer()]); + $serializer = new Serializer([], ['xml' => $this->encoder], $normalizer); } public function testEncodeScalar() @@ -461,8 +462,8 @@ public function testEncodeSerializerXmlRootNodeNameOption() public function testEncodeTraversableWhenNormalizable() { $this->encoder = new XmlEncoder(); - $serializer = new Serializer([new CustomNormalizer()], ['xml' => new XmlEncoder()]); - $this->encoder->setSerializer($serializer); + $normalizer = new CustomNormalizer(); + $serializer = new Serializer([], ['xml' => $this->encoder], $normalizer); $expected = <<<'XML' @@ -726,8 +727,8 @@ public function testDecodePreserveComments() XmlEncoder::ROOT_NODE_NAME => 'people', XmlEncoder::DECODER_IGNORED_NODE_TYPES => [\XML_PI_NODE], ]); - $serializer = new Serializer([new CustomNormalizer()], ['xml' => new XmlEncoder()]); - $this->encoder->setSerializer($serializer); + $normalizer = new CustomNormalizer(); + $serializer = new Serializer([], ['xml' => $this->encoder], $normalizer); $expected = ['person' => [ ['firstname' => 'Benjamin', 'lastname' => 'Alexandre', '#comment' => ' This comment should be decoded. '], @@ -740,8 +741,8 @@ public function testDecodePreserveComments() public function testDecodeAlwaysAsCollection() { $this->encoder = new XmlEncoder([XmlEncoder::ROOT_NODE_NAME => 'response']); - $serializer = new Serializer([new CustomNormalizer()], ['xml' => new XmlEncoder()]); - $this->encoder->setSerializer($serializer); + $normalizer = new CustomNormalizer(); + $serializer = new Serializer([], ['xml' => $this->encoder], $normalizer); $source = <<<'XML' @@ -1002,14 +1003,13 @@ public function testEncodeWithoutComment() private function createXmlEncoderWithEnvelopeNormalizer(): XmlEncoder { - $normalizers = [ + $normalizer = new ChainNormalizer([ $envelopeNormalizer = new EnvelopeNormalizer(), new EnvelopedMessageNormalizer(), - ]; + ]); $encoder = new XmlEncoder(); - $serializer = new Serializer($normalizers, ['xml' => $encoder]); - $encoder->setSerializer($serializer); + $serializer = new Serializer([], ['xml' => $encoder], $normalizer); $envelopeNormalizer->setSerializer($serializer); return $encoder; @@ -1018,8 +1018,9 @@ private function createXmlEncoderWithEnvelopeNormalizer(): XmlEncoder private function createXmlEncoderWithDateTimeNormalizer(): XmlEncoder { $encoder = new XmlEncoder(); - $serializer = new Serializer([$this->createMockDateTimeNormalizer()], ['xml' => new XmlEncoder()]); - $encoder->setSerializer($serializer); + $normalizer = new ChainNormalizer([$this->createMockDateTimeNormalizer()]); + $serializer = new Serializer([], ['xml' => $encoder], $normalizer); + $encoder->setNormalizer($normalizer); return $encoder; } diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractNormalizerTest.php index 3108fe3c6ae3c..0d8c9b756790f 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractNormalizerTest.php @@ -24,6 +24,8 @@ use Symfony\Component\Serializer\Mapping\Loader\LoaderChain; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; +use Symfony\Component\Serializer\Normalizer\ChainDenormalizer; +use Symfony\Component\Serializer\Normalizer\ChainNormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Normalizer\PropertyNormalizer; use Symfony\Component\Serializer\Serializer; @@ -191,8 +193,7 @@ public function testObjectWithVariadicConstructorTypedArguments(AbstractNormaliz $d2->qux = 'QUZ'; $obj = new VariadicConstructorTypedArgsDummy($d1, $d2); - $serializer = new Serializer([$normalizer], [new JsonEncoder()]); - $normalizer->setSerializer($serializer); + $serializer = new Serializer([], [new JsonEncoder()], new ChainNormalizer([$normalizer]), new ChainDenormalizer([$normalizer])); $data = $serializer->serialize($obj, 'json'); $dummy = $normalizer->denormalize(json_decode($data, true), VariadicConstructorTypedArgsDummy::class); $this->assertInstanceOf(VariadicConstructorTypedArgsDummy::class, $dummy); @@ -227,8 +228,8 @@ public function testVariadicSerializationWithPreservingKeys(AbstractNormalizer $ $arr = ['d1' => $d1, 'd2' => $d2]; $obj = new VariadicConstructorTypedArgsDummy(...$arr); - $serializer = new Serializer([$normalizer], [new JsonEncoder()]); - $normalizer->setSerializer($serializer); + $serializer = new Serializer([], [new JsonEncoder()], new ChainNormalizer([$normalizer]), new ChainDenormalizer([$normalizer])); + $this->assertEquals( '{"foo":{"d1":{"foo":"Foo","bar":"Bar","baz":"Baz","qux":"Quz"},"d2":{"foo":"FOO","bar":"BAR","baz":"BAZ","qux":"QUZ"}}}', $data = $serializer->serialize($obj, 'json') @@ -260,7 +261,7 @@ public function testVariadicConstructorDenormalization() ]; $normalizer = new ObjectNormalizer(); - $normalizer->setSerializer(new Serializer([$normalizer])); + new Serializer([], [], $normalizer, $normalizer); $expected = new DummyWithWithVariadicParameterConstructor('woo', 1, new Dummy(), new Dummy()); $actual = $normalizer->denormalize($data, DummyWithWithVariadicParameterConstructor::class); diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php index f41c0fdf3956b..ce1317722dd6c 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -38,6 +38,8 @@ use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; use Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer; +use Symfony\Component\Serializer\Normalizer\ChainDenormalizer; +use Symfony\Component\Serializer\Normalizer\ChainNormalizer; use Symfony\Component\Serializer\Normalizer\CustomNormalizer; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; @@ -550,8 +552,7 @@ public function hasMetadataFor($value): bool $discriminatorResolver = new ClassDiscriminatorFromClassMetadata($loaderMock); $normalizer = new AbstractObjectNormalizerDummy($factory, null, new PhpDocExtractor(), $discriminatorResolver); - $serializer = new Serializer([$normalizer]); - $normalizer->setSerializer($serializer); + $serializer = new Serializer([], [], null, $normalizer); $normalizedData = $normalizer->denormalize(['foo' => 'foo', 'baz' => 'baz', 'quux' => ['value' => 'quux'], 'type' => 'second'], AbstractDummy::class); $this->assertInstanceOf(DummySecondChildQuux::class, $normalizedData->quux); @@ -585,8 +586,7 @@ public function hasMetadataFor($value): bool $discriminatorResolver = new ClassDiscriminatorFromClassMetadata($loaderMock); $normalizer = new AbstractObjectNormalizerDummy($factory, null, new PhpDocExtractor(), $discriminatorResolver); - $serializer = new Serializer([$normalizer]); - $normalizer->setSerializer($serializer); + $serializer = new Serializer([], [], null, $normalizer); $data = [ 'foo' => 'foo', @@ -769,7 +769,7 @@ public function testDenormalizeRecursiveWithObjectAttributeWithStringValue() { $extractor = new ReflectionExtractor(); $normalizer = new ObjectNormalizer(null, null, null, $extractor); - $serializer = new Serializer([$normalizer]); + $serializer = new Serializer([], [], null, $normalizer); $obj = $serializer->denormalize(['inner' => 'foo'], ObjectOuter::class); @@ -782,7 +782,7 @@ public function testDenormalizeUsesContextAttributeForPropertiesInConstructorWit $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory), null, $extractor); - $serializer = new Serializer([new DateTimeNormalizer([DateTimeNormalizer::FORMAT_KEY => 'd-m-Y']), $normalizer]); + $serializer = new Serializer([], [], null, new ChainDenormalizer([new DateTimeNormalizer([DateTimeNormalizer::FORMAT_KEY => 'd-m-Y']), $normalizer])); /** @var ObjectDummyWithContextAttribute $obj */ $obj = $serializer->denormalize(['property_with_serialized_name' => '01-02-2022', 'propertyWithoutSerializedName' => '01-02-2022'], ObjectDummyWithContextAttribute::class); @@ -796,7 +796,7 @@ public function testNormalizeUsesContextAttributeForPropertiesInConstructorWithS $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory), null, $extractor); - $serializer = new Serializer([new DateTimeNormalizer(), $normalizer]); + $serializer = new Serializer([], [], new ChainNormalizer([new DateTimeNormalizer(), $normalizer])); $obj = new ObjectDummyWithContextAttributeAndSerializedPath(new \DateTimeImmutable('22-02-2023')); @@ -811,7 +811,7 @@ public function testNormalizeUsesContextAttributeForProperties() $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory), null, $extractor); - $serializer = new Serializer([$normalizer]); + $serializer = new Serializer([], [], $normalizer); $obj = new ObjectDummyWithContextAttributeSkipNullValues(); @@ -835,7 +835,7 @@ public function supportsNormalization(mixed $data, ?string $format = null, array } }; - $serializer = new Serializer([$normalizer]); + $serializer = new Serializer([], [], new ChainNormalizer([$normalizer])); $serializer->normalize($object); $this->assertSame('called', $object->bar); @@ -843,13 +843,15 @@ public function supportsNormalization(mixed $data, ?string $format = null, array public function testDenormalizeUnionOfEnums() { - $serializer = new Serializer([ + $normalizers = [ new BackedEnumNormalizer(), new ObjectNormalizer( classMetadataFactory: new ClassMetadataFactory(new AttributeLoader()), propertyTypeExtractor: new PropertyInfoExtractor([], [new ReflectionExtractor()]), ), - ]); + ]; + + $serializer = new Serializer([], [], new ChainNormalizer($normalizers), new ChainDenormalizer($normalizers)); $normalized = $serializer->normalize(new DummyWithEnumUnion(EnumA::A)); $this->assertEquals(new DummyWithEnumUnion(EnumA::A), $serializer->denormalize($normalized, DummyWithEnumUnion::class)); @@ -937,7 +939,8 @@ protected function setAttributeValue(object $object, string $attribute, $value, public function testDenormalizeUntypedFormat() { - $serializer = new Serializer([new ObjectNormalizer(null, null, null, new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]))]); + $denormalizer = new ChainDenormalizer([new ObjectNormalizer(null, null, null, new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]))]); + $serializer = new Serializer([], [], null, $denormalizer); $actual = $serializer->denormalize(['value' => ''], DummyWithObjectOrNull::class, 'xml'); $this->assertEquals(new DummyWithObjectOrNull(null), $actual); @@ -945,21 +948,26 @@ public function testDenormalizeUntypedFormat() public function testDenormalizeUntypedFormatNotNormalizable() { + $denormalizer = new ChainDenormalizer([new CustomNormalizer(), new ObjectNormalizer(null, null, null, new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]))]); + $serializer = new Serializer([], [], null, $denormalizer); + $this->expectException(NotNormalizableValueException::class); - $serializer = new Serializer([new CustomNormalizer(), new ObjectNormalizer(null, null, null, new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]))]); $serializer->denormalize(['value' => 'test'], DummyWithNotNormalizable::class, 'xml'); } public function testDenormalizeUntypedFormatMissingArg() { + $denormalizer = new ChainDenormalizer([new ObjectNormalizer(null, null, null, new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]))]); + $serializer = new Serializer([], [], null, $denormalizer); + $this->expectException(MissingConstructorArgumentsException::class); - $serializer = new Serializer([new ObjectNormalizer(null, null, null, new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]))]); $serializer->denormalize(['value' => 'invalid'], DummyWithObjectOrNull::class, 'xml'); } public function testDenormalizeUntypedFormatScalar() { - $serializer = new Serializer([new ObjectNormalizer(null, null, null, new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]))]); + $denormalizer = new ChainDenormalizer([new ObjectNormalizer(null, null, null, new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]))]); + $serializer = new Serializer([], [], null, $denormalizer); $actual = $serializer->denormalize(['value' => 'false'], DummyWithObjectOrBool::class, 'xml'); $this->assertEquals(new DummyWithObjectOrBool(false), $actual); @@ -967,7 +975,8 @@ public function testDenormalizeUntypedFormatScalar() public function testDenormalizeUntypedStringObject() { - $serializer = new Serializer([new CustomNormalizer(), new ObjectNormalizer(null, null, null, new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]))]); + $denormalizer = new ChainDenormalizer([new CustomNormalizer(), new ObjectNormalizer(null, null, null, new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]))]); + $serializer = new Serializer([], [], null, $denormalizer); $actual = $serializer->denormalize(['value' => ''], DummyWithStringObject::class, 'xml'); $this->assertEquals(new DummyWithStringObject(new DummyString()), $actual); @@ -1003,7 +1012,7 @@ protected function createChildContext(array $parentContext, string $attribute, ? } }; - $serializer = new Serializer([$normalizer]); + $serializer = new Serializer([], [], $normalizer, $normalizer); $serializer->normalize($foobar, null, ['cache_key' => 'hardcoded', 'iri' => '/dummy/1']); $firstChildContextCacheKey = $normalizer->childContextCacheKey; @@ -1043,7 +1052,7 @@ protected function createChildContext(array $parentContext, string $attribute, ? } }; - $serializer = new Serializer([$normalizer]); + $serializer = new Serializer([], [], $normalizer, $normalizer); $serializer->normalize($foobar, null, ['cache_key' => 'hardcoded', 'iri' => '/dummy/1']); $this->assertSame('hardcoded-foo', $normalizer->childContextCacheKey); @@ -1078,7 +1087,7 @@ protected function createChildContext(array $parentContext, string $attribute, ? } }; - $serializer = new Serializer([$normalizer]); + $serializer = new Serializer([], [], $normalizer, $normalizer); $serializer->normalize($foobar, null, ['cache_key' => false]); $this->assertFalse($normalizer->childContextCacheKey); diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ContextMetadataTestTrait.php b/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ContextMetadataTestTrait.php index 10f5a003017b0..97699aca92c90 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ContextMetadataTestTrait.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ContextMetadataTestTrait.php @@ -17,6 +17,8 @@ use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; +use Symfony\Component\Serializer\Normalizer\ChainDenormalizer; +use Symfony\Component\Serializer\Normalizer\ChainNormalizer; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Serializer; @@ -35,7 +37,7 @@ public function testContextMetadataNormalize(string $contextMetadataDummyClass) { $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory, null, null, new PhpDocExtractor()); - new Serializer([new DateTimeNormalizer(), $normalizer]); + new Serializer([], [], new ChainNormalizer([new DateTimeNormalizer(), $normalizer])); $dummy = new $contextMetadataDummyClass(); $dummy->date = new \DateTimeImmutable('2011-07-28T08:44:00.123+00:00'); @@ -58,7 +60,7 @@ public function testContextMetadataContextDenormalize(string $contextMetadataDum { $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory, null, null, new PhpDocExtractor()); - new Serializer([new DateTimeNormalizer(), $normalizer]); + new Serializer([], [], new ChainNormalizer([new DateTimeNormalizer(), $normalizer]), new ChainDenormalizer([new DateTimeNormalizer(), $normalizer])); /** @var ContextMetadataDummy|ContextChildMetadataDummy $dummy */ $dummy = $normalizer->denormalize(['date' => '2011-07-28T08:44:00+00:00'], $contextMetadataDummyClass); @@ -90,7 +92,7 @@ public function testContextDenormalizeWithNameConverter() { $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory, new CamelCaseToSnakeCaseNameConverter(), null, new PhpDocExtractor()); - new Serializer([new DateTimeNormalizer(), $normalizer]); + new Serializer([], [], null, new ChainDenormalizer([new DateTimeNormalizer(), $normalizer])); /** @var ContextMetadataNamingDummy $dummy */ $dummy = $normalizer->denormalize(['created_at' => '28/07/2011'], ContextMetadataNamingDummy::class); diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php index f846a0d972c8d..e79160c0e7fe1 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php @@ -25,6 +25,8 @@ use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; +use Symfony\Component\Serializer\Normalizer\ChainDenormalizer; +use Symfony\Component\Serializer\Normalizer\ChainNormalizer; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; @@ -248,7 +250,7 @@ protected function getNormalizerForCircularReference(array $defaultContext): Get { $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new GetSetMethodNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory), null, null, null, $defaultContext); - new Serializer([$normalizer]); + new Serializer([], [], $normalizer); return $normalizer; } @@ -262,7 +264,7 @@ protected function getDenormalizerForConstructArguments(): GetSetMethodNormalize { $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $denormalizer = new GetSetMethodNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); - new Serializer([$denormalizer]); + new Serializer([], [], null, new ChainDenormalizer([$denormalizer])); return $denormalizer; } @@ -334,7 +336,7 @@ protected function getNormalizerForMaxDepth(): NormalizerInterface { $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new GetSetMethodNormalizer($classMetadataFactory); - $serializer = new Serializer([$normalizer]); + $serializer = new Serializer([], [], $normalizer); $normalizer->setSerializer($serializer); return $normalizer; @@ -344,7 +346,7 @@ protected function getDenormalizerForObjectToPopulate(): DenormalizerInterface { $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new GetSetMethodNormalizer($classMetadataFactory, null, new PhpDocExtractor()); - new Serializer([$normalizer]); + new Serializer([], [], null, $normalizer); return $normalizer; } @@ -353,8 +355,7 @@ protected function getDenormalizerForTypeEnforcement(): DenormalizerInterface { $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); $normalizer = new GetSetMethodNormalizer(null, null, $extractor); - $serializer = new Serializer([new ArrayDenormalizer(), $normalizer]); - $normalizer->setSerializer($serializer); + $serializer = new Serializer([], [], null, new ChainDenormalizer([new ArrayDenormalizer(), $normalizer])); return $normalizer; } @@ -368,7 +369,7 @@ protected function getNormalizerForIgnoredAttributes(): GetSetMethodNormalizer { $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new GetSetMethodNormalizer($classMetadataFactory, null, new PhpDocExtractor()); - new Serializer([$normalizer]); + new Serializer([], [], $normalizer); return $normalizer; } @@ -377,7 +378,7 @@ protected function getDenormalizerForIgnoredAttributes(): GetSetMethodNormalizer { $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new GetSetMethodNormalizer($classMetadataFactory, null, new PhpDocExtractor()); - new Serializer([$normalizer]); + new Serializer([], [], $normalizer, $normalizer); return $normalizer; } @@ -399,7 +400,7 @@ public function testUnableToNormalizeObjectAttribute() public function testSiblingReference() { - $serializer = new Serializer([$this->normalizer]); + $serializer = new Serializer([], [], $this->normalizer); $this->normalizer->setSerializer($serializer); $expected = [ diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/JsonSerializableNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/JsonSerializableNormalizerTest.php index f8f8546d7cb39..c083281dcc18d 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/JsonSerializableNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/JsonSerializableNormalizerTest.php @@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Exception\CircularReferenceException; use Symfony\Component\Serializer\Exception\InvalidArgumentException; +use Symfony\Component\Serializer\Normalizer\ChainNormalizer; use Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Serializer; @@ -88,7 +89,7 @@ public function testCircularNormalize() protected function getNormalizerForCircularReference(array $defaultContext): JsonSerializableNormalizer { $normalizer = new JsonSerializableNormalizer(null, null, $defaultContext); - new Serializer([$normalizer]); + new Serializer([], [], new ChainNormalizer([$normalizer])); return $normalizer; } diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/MapDenormalizationTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/MapDenormalizationTest.php index ea45159557206..04996da9b1bef 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/MapDenormalizationTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/MapDenormalizationTest.php @@ -23,6 +23,8 @@ use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; +use Symfony\Component\Serializer\Normalizer\ChainDenormalizer; +use Symfony\Component\Serializer\Normalizer\ChainNormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Serializer; @@ -211,7 +213,7 @@ public function hasMetadataFor($value): bool $factory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new ObjectNormalizer($factory, null, null, new PhpDocExtractor(), new ClassDiscriminatorFromClassMetadata($loaderMock)); - $serializer = new Serializer([$normalizer, new ArrayDenormalizer()]); + $serializer = new Serializer([], [], $normalizer, new ChainDenormalizer([$normalizer, new ArrayDenormalizer()])); $normalizer->setSerializer($serializer); return $serializer; diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php index 19bbcde2c710e..cf9fb61461757 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php @@ -32,6 +32,8 @@ use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; +use Symfony\Component\Serializer\Normalizer\ChainDenormalizer; +use Symfony\Component\Serializer\Normalizer\ChainNormalizer; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; @@ -296,7 +298,7 @@ public function testConstructorWithObjectTypeHintDenormalize() ]; $normalizer = new ObjectNormalizer(); - $serializer = new Serializer([$normalizer]); + $serializer = new Serializer([], [], null, $normalizer); $normalizer->setSerializer($serializer); $obj = $normalizer->denormalize($data, DummyWithConstructorObject::class); @@ -315,7 +317,7 @@ public function testConstructorWithUnconstructableNullableObjectTypeHintDenormal ]; $normalizer = new ObjectNormalizer(); - $serializer = new Serializer([$normalizer]); + $serializer = new Serializer([], [], null, $normalizer); $normalizer->setSerializer($serializer); $obj = $normalizer->denormalize($data, DummyWithNullableConstructorObject::class); @@ -335,7 +337,7 @@ public function testConstructorWithUnknownObjectTypeHintDenormalize() ]; $normalizer = new ObjectNormalizer(); - $serializer = new Serializer([$normalizer]); + $serializer = new Serializer([], [], null, $normalizer); $normalizer->setSerializer($serializer); $this->expectException(RuntimeException::class); @@ -350,7 +352,7 @@ protected function getNormalizerForAttributes(): ObjectNormalizer { $normalizer = new ObjectNormalizer(); // instantiate a serializer with the normalizer to handle normalizing recursive structures - new Serializer([$normalizer]); + new Serializer([], [], $normalizer); return $normalizer; } @@ -359,7 +361,7 @@ protected function getDenormalizerForAttributes(): ObjectNormalizer { $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory, null, null, new ReflectionExtractor()); - new Serializer([$normalizer]); + new Serializer([], [], null, $normalizer); return $normalizer; } @@ -372,7 +374,7 @@ protected function getNormalizerForFilterBool(): ObjectNormalizer public function testAttributesContextDenormalizeConstructor() { $normalizer = new ObjectNormalizer(null, null, null, new ReflectionExtractor()); - $serializer = new Serializer([$normalizer]); + $serializer = new Serializer([], [], null, $normalizer); $objectInner = new ObjectInner(); $objectInner->bar = 'bar'; @@ -390,8 +392,7 @@ public function testNormalizeSameObjectWithDifferentAttributes() { $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $this->normalizer = new ObjectNormalizer($classMetadataFactory); - $serializer = new Serializer([$this->normalizer]); - $this->normalizer->setSerializer($serializer); + $serializer = new Serializer([], [], $this->normalizer); $dummy = new ObjectOuter(); $dummy->foo = new ObjectInner(); @@ -434,7 +435,7 @@ protected function getNormalizerForCallbacksWithPropertyTypeExtractor(): ObjectN protected function getNormalizerForCircularReference(array $defaultContext): ObjectNormalizer { $normalizer = new ObjectNormalizer(null, null, null, null, null, null, $defaultContext); - new Serializer([$normalizer]); + new Serializer([], [], $normalizer); return $normalizer; } @@ -446,9 +447,7 @@ protected function getSelfReferencingModel() public function testSiblingReference() { - $serializer = new Serializer([$this->normalizer]); - $this->normalizer->setSerializer($serializer); - + $serializer = new Serializer([], [], $this->normalizer); $siblingHolder = new SiblingHolder(); $expected = [ @@ -465,8 +464,7 @@ protected function getDenormalizerForConstructArguments(): ObjectNormalizer { $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $denormalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); - $serializer = new Serializer([$denormalizer]); - $denormalizer->setSerializer($serializer); + $serializer = new Serializer([], [], null, new ChainDenormalizer([$denormalizer])); return $denormalizer; } @@ -478,7 +476,7 @@ protected function getNormalizerForGroups(): ObjectNormalizer $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory); // instantiate a serializer with the normalizer to handle normalizing recursive structures - new Serializer([$normalizer]); + new Serializer([], [], $normalizer); return $normalizer; } @@ -557,7 +555,7 @@ protected function getNormalizerForIgnoredAttributes(): ObjectNormalizer { $normalizer = new ObjectNormalizer(); // instantiate a serializer with the normalizer to handle normalizing recursive structures - new Serializer([$normalizer]); + new Serializer([], [], $normalizer); return $normalizer; } @@ -566,7 +564,7 @@ protected function getDenormalizerForIgnoredAttributes(): ObjectNormalizer { $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory, null, null, new ReflectionExtractor()); - new Serializer([$normalizer]); + new Serializer([], [], null, $normalizer); return $normalizer; } @@ -577,7 +575,7 @@ protected function getNormalizerForMaxDepth(): ObjectNormalizer { $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory); - $serializer = new Serializer([$normalizer]); + $serializer = new Serializer([], [], $normalizer); $normalizer->setSerializer($serializer); return $normalizer; @@ -589,7 +587,7 @@ protected function getDenormalizerForObjectToPopulate(): ObjectNormalizer { $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory, null, null, new PhpDocExtractor()); - new Serializer([$normalizer]); + new Serializer([], [], $normalizer, $normalizer); return $normalizer; } @@ -643,7 +641,7 @@ protected function getDenormalizerForTypeEnforcement(): ObjectNormalizer { $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); $normalizer = new ObjectNormalizer(null, null, null, $extractor); - new Serializer([new ArrayDenormalizer(), $normalizer]); + new Serializer([], [], null, new ChainDenormalizer([new ArrayDenormalizer(), $normalizer])); return $normalizer; } @@ -713,7 +711,8 @@ public function testDenomalizeRecursive() { $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); $normalizer = new ObjectNormalizer(null, null, null, $extractor); - $serializer = new Serializer([new ArrayDenormalizer(), new DateTimeNormalizer(), $normalizer]); + $chainDenormalizer = new ChainDenormalizer([new ArrayDenormalizer(), new DateTimeNormalizer(), $normalizer]); + $serializer = new Serializer([], [], null, $chainDenormalizer); $obj = $serializer->denormalize([ 'inner' => ['foo' => 'foo', 'bar' => 'bar'], @@ -732,7 +731,7 @@ public function testAcceptJsonNumber() { $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); $normalizer = new ObjectNormalizer(null, null, null, $extractor); - $serializer = new Serializer([new ArrayDenormalizer(), new DateTimeNormalizer(), $normalizer]); + $serializer = new Serializer([], [], null, new ChainDenormalizer([new ArrayDenormalizer(), new DateTimeNormalizer(), $normalizer])); $this->assertSame(10.0, $serializer->denormalize(['number' => 10], JsonNumber::class, 'json')->number); $this->assertSame(10.0, $serializer->denormalize(['number' => 10], JsonNumber::class, 'jsonld')->number); @@ -746,7 +745,7 @@ public function testDoesntHaveIssuesWithUnionConstTypes() $extractor = new PropertyInfoExtractor([], [new PhpStanExtractor(), new PhpDocExtractor(), new ReflectionExtractor()]); $normalizer = new ObjectNormalizer(null, null, null, $extractor); - $serializer = new Serializer([new ArrayDenormalizer(), new DateTimeNormalizer(), $normalizer]); + $serializer = new Serializer([], [], null, new ChainDenormalizer([new ArrayDenormalizer(), new DateTimeNormalizer(), $normalizer])); $this->assertSame('bar', $serializer->denormalize(['foo' => 'bar'], (new class() { /** @var self::*|null */ @@ -771,7 +770,7 @@ public function testDenormalizeFalsePseudoType() $propertyTypeExtractor = new PropertyInfoExtractor([], [new ReflectionExtractor()], [], [], []); $objectNormalizer = new ObjectNormalizer(null, null, null, $propertyTypeExtractor); - $serializer = new Serializer([$objectNormalizer]); + $serializer = new Serializer([], [], null, new ChainDenormalizer([$objectNormalizer])); // when denormalizing some data into an object where an attribute uses the false pseudo type /** @var Php80Dummy $object */ diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ProblemNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ProblemNormalizerTest.php index e0a90229a3f20..59d14ee1c5d98 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/ProblemNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ProblemNormalizerTest.php @@ -17,6 +17,7 @@ use Symfony\Component\Messenger\Exception\ValidationFailedException as MessageValidationFailedException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; use Symfony\Component\Serializer\Exception\PartialDenormalizationException; +use Symfony\Component\Serializer\Normalizer\ChainNormalizer; use Symfony\Component\Serializer\Normalizer\ConstraintViolationListNormalizer; use Symfony\Component\Serializer\Normalizer\ProblemNormalizer; use Symfony\Component\Serializer\Serializer; @@ -82,7 +83,8 @@ public function testNormalizePartialDenormalizationException() public function testNormalizeValidationFailedException() { - $this->normalizer->setSerializer(new Serializer([new ConstraintViolationListNormalizer()])); + $serializer = new Serializer([], [], new ConstraintViolationListNormalizer()); + $this->normalizer->setSerializer($serializer); $expected = [ 'type' => 'https://symfony.com/errors/validation', @@ -106,7 +108,8 @@ public function testNormalizeValidationFailedException() public function testNormalizeMessageValidationFailedException() { - $this->normalizer->setSerializer(new Serializer([new ConstraintViolationListNormalizer()])); + $serializer = new Serializer([], [], new ConstraintViolationListNormalizer()); + $this->normalizer->setSerializer($serializer); $expected = [ 'type' => 'https://symfony.com/errors/validation', diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php index b93a7bb9fd1cd..6a8b9ddb9eac7 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php @@ -24,6 +24,8 @@ use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; +use Symfony\Component\Serializer\Normalizer\ChainDenormalizer; +use Symfony\Component\Serializer\Normalizer\ChainNormalizer; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\PropertyNormalizer; use Symfony\Component\Serializer\Serializer; @@ -251,7 +253,7 @@ protected function getNormalizerForCallbacksWithPropertyTypeExtractor(): Propert protected function getNormalizerForCircularReference(array $defaultContext): PropertyNormalizer { $normalizer = new PropertyNormalizer(null, null, null, null, null, $defaultContext); - new Serializer([$normalizer]); + new Serializer([], [], $normalizer); return $normalizer; } @@ -268,7 +270,7 @@ protected function getNormalizerForFilterBool(): PropertyNormalizer public function testSiblingReference() { - $serializer = new Serializer([$this->normalizer]); + $serializer = new Serializer([], [], $this->normalizer); $this->normalizer->setSerializer($serializer); $siblingHolder = new PropertySiblingHolder(); @@ -285,7 +287,7 @@ protected function getDenormalizerForConstructArguments(): PropertyNormalizer { $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $denormalizer = new PropertyNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); - $serializer = new Serializer([$denormalizer]); + $serializer = new Serializer([], [], null, new ChainDenormalizer([$denormalizer])); $denormalizer->setSerializer($serializer); return $denormalizer; @@ -353,7 +355,7 @@ protected function getDenormalizerForIgnoredAttributes(): PropertyNormalizer { $normalizer = new PropertyNormalizer(); // instantiate a serializer with the normalizer to handle normalizing recursive structures - new Serializer([$normalizer]); + new Serializer([], [], null, $normalizer); return $normalizer; } @@ -362,7 +364,7 @@ protected function getNormalizerForIgnoredAttributes(): PropertyNormalizer { $normalizer = new PropertyNormalizer(); // instantiate a serializer with the normalizer to handle normalizing recursive structures - new Serializer([$normalizer]); + new Serializer([], [], $normalizer); return $normalizer; } @@ -376,7 +378,7 @@ protected function getNormalizerForMaxDepth(): PropertyNormalizer { $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new PropertyNormalizer($classMetadataFactory); - $serializer = new Serializer([$normalizer]); + $serializer = new Serializer([], [], $normalizer); $normalizer->setSerializer($serializer); return $normalizer; @@ -386,7 +388,7 @@ protected function getDenormalizerForObjectToPopulate(): PropertyNormalizer { $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new PropertyNormalizer($classMetadataFactory, null, new PhpDocExtractor()); - new Serializer([$normalizer]); + new Serializer([], [], null, $normalizer); return $normalizer; } @@ -395,7 +397,7 @@ protected function getDenormalizerForTypeEnforcement(): DenormalizerInterface { $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); $normalizer = new PropertyNormalizer(null, null, $extractor); - $serializer = new Serializer([new ArrayDenormalizer(), $normalizer]); + $serializer = new Serializer([], [], null, new ChainDenormalizer([new ArrayDenormalizer(), $normalizer])); $normalizer->setSerializer($serializer); return $normalizer; diff --git a/src/Symfony/Component/Serializer/Tests/SerializerTest.php b/src/Symfony/Component/Serializer/Tests/SerializerTest.php index 1bf5905adf971..1a0d1af70178d 100644 --- a/src/Symfony/Component/Serializer/Tests/SerializerTest.php +++ b/src/Symfony/Component/Serializer/Tests/SerializerTest.php @@ -37,6 +37,8 @@ use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; use Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer; +use Symfony\Component\Serializer\Normalizer\ChainDenormalizer; +use Symfony\Component\Serializer\Normalizer\ChainNormalizer; use Symfony\Component\Serializer\Normalizer\CustomNormalizer; use Symfony\Component\Serializer\Normalizer\DataUriNormalizer; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; @@ -80,6 +82,9 @@ class SerializerTest extends TestCase { + /** + * @group legacy + */ public function testItThrowsExceptionOnInvalidNormalizer() { $this->expectException(InvalidArgumentException::class); @@ -98,10 +103,8 @@ public function testItThrowsExceptionOnInvalidEncoder() public function testNormalizeNoMatch() { - $serializer = new Serializer([$this->createMock(NormalizerInterface::class)]); - + $serializer = new Serializer([], [], new ChainNormalizer([$this->createMock(NormalizerInterface::class)])); $this->expectException(UnexpectedValueException::class); - $serializer->normalize(new \stdClass(), 'xml'); } @@ -114,32 +117,28 @@ public function testNormalizeTraversable() public function testNormalizeGivesPriorityToInterfaceOverTraversable() { - $serializer = new Serializer([new CustomNormalizer()], ['json' => new JsonEncoder()]); + $serializer = new Serializer([], ['json' => new JsonEncoder()], new ChainNormalizer([new CustomNormalizer()])); $result = $serializer->serialize(new NormalizableTraversableDummy(), 'json'); $this->assertEquals('{"foo":"normalizedFoo","bar":"normalizedBar"}', $result); } public function testNormalizeOnDenormalizer() { - $serializer = new Serializer([new TestDenormalizer()], []); - - $this->expectException(UnexpectedValueException::class); - - $this->assertTrue($serializer->normalize(new \stdClass(), 'json')); + $serializer = new Serializer([], [], null, new ChainDenormalizer([new TestDenormalizer()])); + $this->expectException(LogicException::class); + $serializer->normalize(new \stdClass(), 'json'); } public function testDenormalizeNoMatch() { - $serializer = new Serializer([$this->createMock(NormalizerInterface::class)]); - + $serializer = new Serializer([], [], null, new ChainDenormalizer([$this->createMock(DenormalizerInterface::class)])); $this->expectException(UnexpectedValueException::class); - $serializer->denormalize('foo', 'stdClass'); } public function testDenormalizeOnObjectThatOnlySupportsDenormalization() { - $serializer = new Serializer([new CustomNormalizer()]); + $serializer = new Serializer([], [], null, new ChainDenormalizer([new CustomNormalizer()])); $obj = $serializer->denormalize('foo', (new DenormalizableDummy())::class, 'xml'); $this->assertInstanceOf(DenormalizableDummy::class, $obj); @@ -147,17 +146,16 @@ public function testDenormalizeOnObjectThatOnlySupportsDenormalization() public function testDenormalizeOnNormalizer() { - $serializer = new Serializer([new TestNormalizer()], []); + $serializer = new Serializer([], [], new TestNormalizer()); $data = ['title' => 'foo', 'numbers' => [5, 3]]; - $this->expectException(UnexpectedValueException::class); - - $this->assertTrue($serializer->denormalize(json_encode($data), 'stdClass', 'json')); + $this->expectException(LogicException::class); + $serializer->denormalize(json_encode($data), 'stdClass', 'json'); } public function testCustomNormalizerCanNormalizeCollectionsAndScalar() { - $serializer = new Serializer([new TestNormalizer()], []); + $serializer = new Serializer([], [], new TestNormalizer()); $this->assertNull($serializer->normalize(['a', 'b'])); $this->assertNull($serializer->normalize(new \ArrayObject(['c', 'd']))); $this->assertNull($serializer->normalize([])); @@ -178,7 +176,7 @@ public function testNormalizeWithSupportOnData() ->willReturn(true); $normalizer2->method('normalize')->willReturn('test2'); - $serializer = new Serializer([$normalizer1, $normalizer2]); + $serializer = new Serializer([], [], new ChainNormalizer([$normalizer1, $normalizer2])); $data = new \stdClass(); $data->test = true; @@ -201,7 +199,7 @@ public function testDenormalizeWithSupportOnData() ->willReturn(true); $denormalizer2->method('denormalize')->willReturn('test2'); - $serializer = new Serializer([$denormalizer1, $denormalizer2]); + $serializer = new Serializer([], [], null, new ChainDenormalizer([$denormalizer1, $denormalizer2])); $this->assertEquals('test1', $serializer->denormalize(['test1' => true], 'test')); @@ -210,7 +208,7 @@ public function testDenormalizeWithSupportOnData() public function testSerialize() { - $serializer = new Serializer([new GetSetMethodNormalizer()], ['json' => new JsonEncoder()]); + $serializer = new Serializer([], ['json' => new JsonEncoder()], new ChainNormalizer([new GetSetMethodNormalizer()])); $data = ['title' => 'foo', 'numbers' => [5, 3]]; $result = $serializer->serialize(Model::fromArray($data), 'json'); $this->assertEquals(json_encode($data), $result); @@ -233,7 +231,7 @@ public function testSerializeArrayOfScalars() public function testSerializeEmpty() { - $serializer = new Serializer([new ObjectNormalizer()], ['json' => new JsonEncoder()]); + $serializer = new Serializer([], ['json' => new JsonEncoder()], new ChainNormalizer([new ObjectNormalizer()])); $data = ['foo' => new \stdClass()]; // Old buggy behaviour @@ -266,7 +264,7 @@ public function testSerializeNoNormalizer() public function testDeserialize() { - $serializer = new Serializer([new GetSetMethodNormalizer()], ['json' => new JsonEncoder()]); + $serializer = new Serializer([], ['json' => new JsonEncoder()], null, new ChainDenormalizer([new GetSetMethodNormalizer()])); $data = ['title' => 'foo', 'numbers' => [5, 3]]; $result = $serializer->deserialize(json_encode($data), Model::class, 'json'); $this->assertEquals($data, $result->toArray()); @@ -274,7 +272,7 @@ public function testDeserialize() public function testDeserializeUseCache() { - $serializer = new Serializer([new GetSetMethodNormalizer()], ['json' => new JsonEncoder()]); + $serializer = new Serializer([], ['json' => new JsonEncoder()], null, new ChainDenormalizer([new GetSetMethodNormalizer()])); $data = ['title' => 'foo', 'numbers' => [5, 3]]; $serializer->deserialize(json_encode($data), Model::class, 'json'); $data = ['title' => 'bar', 'numbers' => [2, 8]]; @@ -294,11 +292,10 @@ public function testDeserializeNoNormalizer() public function testDeserializeWrongNormalizer() { - $serializer = new Serializer([new CustomNormalizer()], ['json' => new JsonEncoder()]); + $serializer = new Serializer([], ['json' => new JsonEncoder()], null, new ChainDenormalizer([new CustomNormalizer()])); $data = ['title' => 'foo', 'numbers' => [5, 3]]; $this->expectException(UnexpectedValueException::class); - $serializer->deserialize(json_encode($data), Model::class, 'json'); } @@ -314,14 +311,14 @@ public function testDeserializeNoEncoder() public function testDeserializeSupported() { - $serializer = new Serializer([new GetSetMethodNormalizer()], []); + $serializer = new Serializer([], [], null, new ChainDenormalizer([new GetSetMethodNormalizer()])); $data = ['title' => 'foo', 'numbers' => [5, 3]]; $this->assertTrue($serializer->supportsDenormalization(json_encode($data), Model::class, 'json')); } public function testDeserializeNotSupported() { - $serializer = new Serializer([new GetSetMethodNormalizer()], []); + $serializer = new Serializer([], [], null, new ChainDenormalizer([new GetSetMethodNormalizer()])); $data = ['title' => 'foo', 'numbers' => [5, 3]]; $this->assertFalse($serializer->supportsDenormalization(json_encode($data), 'stdClass', 'json')); } @@ -351,18 +348,13 @@ public function testDecode() public function testSupportsArrayDeserialization() { - $serializer = new Serializer( - [ - new GetSetMethodNormalizer(), - new PropertyNormalizer(), - new ObjectNormalizer(), - new CustomNormalizer(), - new ArrayDenormalizer(), - ], - [ - 'json' => new JsonEncoder(), - ] - ); + $serializer = new Serializer([], ['json' => new JsonEncoder()], null, new ChainDenormalizer([ + new GetSetMethodNormalizer(), + new PropertyNormalizer(), + new ObjectNormalizer(), + new CustomNormalizer(), + new ArrayDenormalizer(), + ])); $this->assertTrue( $serializer->supportsDenormalization([], __NAMESPACE__.'\Model[]', 'json') @@ -378,15 +370,7 @@ public function testDeserializeArray() Model::fromArray(['title' => 'bar', 'numbers' => [2, 8]]), ]; - $serializer = new Serializer( - [ - new GetSetMethodNormalizer(), - new ArrayDenormalizer(), - ], - [ - 'json' => new JsonEncoder(), - ] - ); + $serializer = new Serializer([], ['json' => new JsonEncoder()], null, new ChainDenormalizer([new GetSetMethodNormalizer(), new ArrayDenormalizer()])); $this->assertEquals( $expectedData, @@ -400,7 +384,7 @@ public function testNormalizerAware() $normalizerAware->expects($this->once()) ->method('setNormalizer'); - new Serializer([$normalizerAware]); + new Serializer([], [], new ChainNormalizer([$normalizerAware])); } public function testDenormalizerAware() @@ -409,14 +393,14 @@ public function testDenormalizerAware() $denormalizerAware->expects($this->once()) ->method('setDenormalizer'); - new Serializer([$denormalizerAware]); + new Serializer([], [], null, new ChainDenormalizer([$denormalizerAware])); } public function testDeserializeObjectConstructorWithObjectTypeHint() { $jsonData = '{"bar":{"value":"baz"}}'; - $serializer = new Serializer([new ObjectNormalizer()], ['json' => new JsonEncoder()]); + $serializer = new Serializer([], ['json' => new JsonEncoder()], null, new ChainDenormalizer([new ObjectNormalizer()])); $this->assertEquals(new Foo(new Bar('baz')), $serializer->deserialize($jsonData, Foo::class, 'json')); } @@ -449,7 +433,8 @@ public function hasMetadataFor($value): bool }; $discriminatorResolver = new ClassDiscriminatorFromClassMetadata($loaderMock); - $serializer = new Serializer([new ObjectNormalizer(null, null, null, new PhpDocExtractor(), $discriminatorResolver)], ['json' => new JsonEncoder()]); + $normalizer = new ObjectNormalizer(null, null, null, new PhpDocExtractor(), $discriminatorResolver); + $serializer = new Serializer([], ['json' => new JsonEncoder()], $normalizer, $normalizer); $jsonData = '{"type":"first","quux":{"value":"quux"},"bar":"bar-value","foo":"foo-value"}'; @@ -565,14 +550,17 @@ public function testNotNormalizableValueExceptionMessageForAResource() public function testNormalizeTransformEmptyArrayObjectToArray() { $serializer = new Serializer( - [ + [], + ['json' => new JsonEncoder()], + new ChainNormalizer([ + new PropertyNormalizer(), + new ObjectNormalizer(), + ]), + new ChainDenormalizer([ new PropertyNormalizer(), new ObjectNormalizer(), new ArrayDenormalizer(), - ], - [ - 'json' => new JsonEncoder(), - ] + ]) ); $object = []; @@ -587,16 +575,12 @@ public function testNormalizeTransformEmptyArrayObjectToArray() public static function provideObjectOrCollectionTests() { - $serializer = new Serializer( - [ - new PropertyNormalizer(), - new ObjectNormalizer(), - new ArrayDenormalizer(), - ], - [ - 'json' => new JsonEncoder(), - ] - ); + $normalizer = new ChainNormalizer([ + new PropertyNormalizer(), + new ObjectNormalizer(), + ]); + $denormalizer = new ChainDenormalizer([new ArrayDenormalizer()]); + $serializer = new Serializer([], ['json' => new JsonEncoder()], $normalizer, $denormalizer); $data = []; $data['a1'] = new \ArrayObject(); @@ -744,7 +728,7 @@ public function testDeserializeInconsistentScalarType() public function testDeserializeScalarArray() { - $serializer = new Serializer([new ArrayDenormalizer()], ['json' => new JsonEncoder()]); + $serializer = new Serializer([], ['json' => new JsonEncoder()], null, new ChainDenormalizer([new ArrayDenormalizer()])); $this->assertSame([42], $serializer->deserialize('[42]', 'int[]', 'json')); $this->assertSame([true, false], $serializer->deserialize('[true,false]', 'bool[]', 'json')); @@ -754,16 +738,14 @@ public function testDeserializeScalarArray() public function testDeserializeInconsistentScalarArray() { - $serializer = new Serializer([new ArrayDenormalizer()], ['json' => new JsonEncoder()]); - + $serializer = new Serializer([], ['json' => new JsonEncoder()], null, new ChainDenormalizer([new ArrayDenormalizer()])); $this->expectException(NotNormalizableValueException::class); - $serializer->deserialize('["42"]', 'int[]', 'json'); } public function testDeserializeOnObjectWithObjectCollectionProperty() { - $serializer = new Serializer([new FooInterfaceDummyDenormalizer(), new ObjectNormalizer(null, null, null, new PhpDocExtractor())], [new JsonEncoder()]); + $serializer = new Serializer([], [new JsonEncoder()], null, new ChainDenormalizer([new FooInterfaceDummyDenormalizer(), new ObjectNormalizer(null, null, null, new PhpDocExtractor())])); $obj = $serializer->deserialize('{"foo":[{"name":"bar"}]}', ObjectCollectionPropertyDummy::class, 'json'); $this->assertInstanceOf(ObjectCollectionPropertyDummy::class, $obj); @@ -778,7 +760,7 @@ public function testDeserializeOnObjectWithObjectCollectionProperty() public function testDeserializeWrappedScalar() { - $serializer = new Serializer([new UnwrappingDenormalizer()], ['json' => new JsonEncoder()]); + $serializer = new Serializer([], ['json' => new JsonEncoder()], null, new ChainDenormalizer([new UnwrappingDenormalizer()])); $this->assertSame(42, $serializer->deserialize('{"wrapper": 42}', 'int', 'json', [UnwrappingDenormalizer::UNWRAP_PATH => '[wrapper]'])); } @@ -786,7 +768,8 @@ public function testDeserializeWrappedScalar() public function testDeserializeNullableIntInXml() { $extractor = new PropertyInfoExtractor([], [new ReflectionExtractor()]); - $serializer = new Serializer([new ObjectNormalizer(null, null, null, $extractor)], ['xml' => new XmlEncoder()]); + $normalizer = new ObjectNormalizer(null, null, null, $extractor); + $serializer = new Serializer([], ['xml' => new XmlEncoder()], $normalizer, $normalizer); $obj = $serializer->deserialize('', DummyNullableInt::class, 'xml'); $this->assertInstanceOf(DummyNullableInt::class, $obj); @@ -798,11 +781,13 @@ public function testUnionTypeDeserializable() $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); $serializer = new Serializer( - [ + [], + ['json' => new JsonEncoder()], + null, + new ChainDenormalizer([ new DateTimeNormalizer(), new ObjectNormalizer($classMetadataFactory, null, null, $extractor, new ClassDiscriminatorFromClassMetadata($classMetadataFactory)), - ], - ['json' => new JsonEncoder()] + ]) ); $actual = $serializer->deserialize('{ "changed": null }', DummyUnionType::class, 'json', [ @@ -830,10 +815,12 @@ public function testUnionTypeDeserializableWithoutAllowedExtraAttributes() $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); $serializer = new Serializer( - [ + [], + ['json' => new JsonEncoder()], + null, + new ChainDenormalizer([ new ObjectNormalizer($classMetadataFactory, null, null, $extractor, new ClassDiscriminatorFromClassMetadata($classMetadataFactory)), - ], - ['json' => new JsonEncoder()] + ]) ); $actual = $serializer->deserialize('{ "v": { "a": 0 }}', DummyUnionWithAAndCAndB::class, 'json', [ @@ -863,7 +850,7 @@ public function testUnionTypeDeserializableWithoutAllowedExtraAttributes() public function testFalseBuiltInTypes() { $extractor = new PropertyInfoExtractor([], [new ReflectionExtractor()]); - $serializer = new Serializer([new ObjectNormalizer(null, null, null, $extractor)], ['json' => new JsonEncoder()]); + $serializer = new Serializer([], ['json' => new JsonEncoder()], null, new ChainDenormalizer([new ObjectNormalizer(null, null, null, $extractor)])); $actual = $serializer->deserialize('{"false":false}', FalseBuiltInDummy::class, 'json'); @@ -873,7 +860,7 @@ public function testFalseBuiltInTypes() public function testTrueBuiltInTypes() { $extractor = new PropertyInfoExtractor([], [new ReflectionExtractor()]); - $serializer = new Serializer([new ObjectNormalizer(null, null, null, $extractor)], ['json' => new JsonEncoder()]); + $serializer = new Serializer([], ['json' => new JsonEncoder()], null, new ChainDenormalizer([new ObjectNormalizer(null, null, null, $extractor)])); $actual = $serializer->deserialize('{"true":true}', TrueBuiltInDummy::class, 'json'); @@ -882,7 +869,7 @@ public function testTrueBuiltInTypes() public function testDeserializeUntypedFormat() { - $serializer = new Serializer([new ObjectNormalizer(null, null, null, new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]))], ['csv' => new CsvEncoder()]); + $serializer = new Serializer([], ['csv' => new CsvEncoder()], null, new ChainDenormalizer([new ObjectNormalizer(null, null, null, new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]))])); $actual = $serializer->deserialize('value'.\PHP_EOL.',', DummyWithObjectOrNull::class, 'csv', [CsvEncoder::AS_COLLECTION_KEY => false]); $this->assertEquals(new DummyWithObjectOrNull(null), $actual); @@ -892,7 +879,9 @@ private function serializerWithClassDiscriminator() { $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); - return new Serializer([new ObjectNormalizer($classMetadataFactory, null, null, new ReflectionExtractor(), new ClassDiscriminatorFromClassMetadata($classMetadataFactory))], ['json' => new JsonEncoder()]); + $normalizers = new ObjectNormalizer($classMetadataFactory, null, null, new ReflectionExtractor(), new ClassDiscriminatorFromClassMetadata($classMetadataFactory)); + + return new Serializer([], ['json' => new JsonEncoder()], $normalizers, $normalizers); } public function testDeserializeAndUnwrap() @@ -901,7 +890,7 @@ public function testDeserializeAndUnwrap() $expectedData = Model::fromArray(['title' => 'value', 'numbers' => [5, 3]]); - $serializer = new Serializer([new UnwrappingDenormalizer(new PropertyAccessor()), new ObjectNormalizer()], ['json' => new JsonEncoder()]); + $serializer = new Serializer([], ['json' => new JsonEncoder()], null, new ChainDenormalizer([new UnwrappingDenormalizer(new PropertyAccessor()), new ObjectNormalizer()])); $this->assertEquals( $expectedData, @@ -950,15 +939,17 @@ public function testCollectDenormalizationErrors(?ClassMetadataFactory $classMet $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); $serializer = new Serializer( - [ + [], + ['json' => new JsonEncoder()], + null, + new ChainDenormalizer([ new ArrayDenormalizer(), new DateTimeNormalizer(), new DateTimeZoneNormalizer(), new DataUriNormalizer(), new UidNormalizer(), new ObjectNormalizer($classMetadataFactory, null, null, $extractor, $classMetadataFactory ? new ClassDiscriminatorFromClassMetadata($classMetadataFactory) : null), - ], - ['json' => new JsonEncoder()] + ]) ); try { @@ -1166,11 +1157,13 @@ public function testCollectDenormalizationErrors2(?ClassMetadataFactory $classMe $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); $serializer = new Serializer( - [ + [], + ['json' => new JsonEncoder()], + null, + new ChainDenormalizer([ new ArrayDenormalizer(), new ObjectNormalizer($classMetadataFactory, null, null, $extractor, $classMetadataFactory ? new ClassDiscriminatorFromClassMetadata($classMetadataFactory) : null), - ], - ['json' => new JsonEncoder()] + ]) ); try { @@ -1228,7 +1221,7 @@ public function testCollectDenormalizationErrorsWithoutTypeExtractor() "float": [] }'; - $serializer = new Serializer([new ObjectNormalizer()], ['json' => new JsonEncoder()]); + $serializer = new Serializer([], ['json' => new JsonEncoder()], new ObjectNormalizer(), new ObjectNormalizer()); try { $serializer->deserialize($json, Php74Full::class, 'json', [ @@ -1293,10 +1286,12 @@ public function testCollectDenormalizationErrorsWithConstructor(?ClassMetadataFa $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); $serializer = new Serializer( - [ + [], + ['json' => new JsonEncoder()], + null, + new ChainDenormalizer([ new ObjectNormalizer($classMetadataFactory, null, null, $extractor, $classMetadataFactory ? new ClassDiscriminatorFromClassMetadata($classMetadataFactory) : null), - ], - ['json' => new JsonEncoder()] + ]) ); try { @@ -1352,6 +1347,58 @@ public function testCollectDenormalizationErrorsWithConstructor(?ClassMetadataFa $this->assertSame($expected, $exceptionsAsArray); } + /** + * @group legacy + * + * If we use the Symfony 7.0 way to create a Serializer, will we still get a PartialDenormalizationException? + */ + public function testDenormalizationCollectErrorsWithLegacy() + { + $json = '{"string": "some string", "bool": "bool", "int": true}'; + + $extractor = new PropertyInfoExtractor([], [new ReflectionExtractor()]); + $serializer = new Serializer( + [new ObjectNormalizer(null, null, null, $extractor)], + ['json' => new JsonEncoder()], + ); + + try { + $serializer->deserialize($json, WithTypedConstructor::class, 'json', [ + DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true, + ]); + + $this->fail(); + } catch (\Throwable $th) { + $this->assertInstanceOf(PartialDenormalizationException::class, $th); + } + } + + /** + * Make sure throwing PartialDenormalizationException is a feature of the serializer, not the ChainNormalizer. + */ + public function testDenormalizationCollectErrorsNoChainDenormalizer() + { + $json = '{"string": "some string", "bool": "bool", "int": true}'; + + $extractor = new PropertyInfoExtractor([], [new ReflectionExtractor()]); + $serializer = new Serializer( + [], + ['json' => new JsonEncoder()], + new ObjectNormalizer(null, null, null, $extractor), + new ObjectNormalizer(null, null, null, $extractor) + ); + + try { + $serializer->deserialize($json, WithTypedConstructor::class, 'json', [ + DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true, + ]); + + $this->fail(); + } catch (\Throwable $th) { + $this->assertInstanceOf(PartialDenormalizationException::class, $th); + } + } + public function testCollectDenormalizationErrorsWithInvalidConstructorTypes() { $json = '{"string": "some string", "bool": "bool", "int": true}'; @@ -1359,8 +1406,10 @@ public function testCollectDenormalizationErrorsWithInvalidConstructorTypes() $extractor = new PropertyInfoExtractor([], [new ReflectionExtractor()]); $serializer = new Serializer( - [new ObjectNormalizer(null, null, null, $extractor)], - ['json' => new JsonEncoder()] + [], + ['json' => new JsonEncoder()], + null, + new ChainDenormalizer([new ObjectNormalizer(null, null, null, $extractor)]) ); try { @@ -1416,11 +1465,13 @@ public function testCollectDenormalizationErrorsWithInvalidConstructorTypes() public function testCollectDenormalizationErrorsWithEnumConstructor() { $serializer = new Serializer( - [ + [], + ['json' => new JsonEncoder()], + null, + new ChainDenormalizer([ new BackedEnumNormalizer(), new ObjectNormalizer(), - ], - ['json' => new JsonEncoder()] + ]) ); try { @@ -1455,11 +1506,13 @@ public function testCollectDenormalizationErrorsWithWrongPropertyWithoutConstruc $propertyInfoExtractor = new PropertyInfoExtractor([], [$reflectionExtractor], [], [], []); $serializer = new Serializer( - [ + [], + ['json' => new JsonEncoder()], + null, + new ChainDenormalizer([ new BackedEnumNormalizer(), new ObjectNormalizer($classMetadataFactory, null, null, $propertyInfoExtractor), - ], - ['json' => new JsonEncoder()] + ]) ); try { @@ -1492,11 +1545,13 @@ public function testCollectDenormalizationErrorsWithWrongPropertyWithoutConstruc public function testNoCollectDenormalizationErrorsWithWrongEnumOnConstructor() { $serializer = new Serializer( - [ + [], + ['json' => new JsonEncoder()], + null, + new ChainDenormalizer([ new BackedEnumNormalizer(), new ObjectNormalizer(), - ], - ['json' => new JsonEncoder()] + ]) ); try { @@ -1517,12 +1572,9 @@ public function testGroupsOnClassSerialization() $obj->setBaz('baz'); $serializer = new Serializer( - [ - new ObjectNormalizer(), - ], - [ - 'json' => new JsonEncoder(), - ] + [], + ['json' => new JsonEncoder()], + new ChainNormalizer([new ObjectNormalizer()]) ); $this->assertSame( @@ -1560,13 +1612,16 @@ public function testSerializerUsesSupportedTypesMethod() Model::class => true, ]); + $normalizers = [ + $neverCalledNormalizer, + $supportedAndCachedNormalizer, + new ObjectNormalizer(), + ]; $serializer = new Serializer( - [ - $neverCalledNormalizer, - $supportedAndCachedNormalizer, - new ObjectNormalizer(), - ], - ['json' => new JsonEncoder()] + [], + ['json' => new JsonEncoder()], + new ChainNormalizer($normalizers), + new ChainDenormalizer($normalizers), ); // Normalization process @@ -1615,9 +1670,12 @@ public function testPartialDenormalizationWithMissingConstructorTypes() $extractor = new PropertyInfoExtractor([], [new ReflectionExtractor()]); + $objectNormalizer = new ObjectNormalizer(null, null, null, $extractor); $serializer = new Serializer( - [new ObjectNormalizer(null, null, null, $extractor)], - ['json' => new JsonEncoder()] + [], + ['json' => new JsonEncoder()], + $objectNormalizer, + $objectNormalizer, ); try { From fc9c9ff92c5eb288dd7420466343fde51e676719 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Wed, 19 Jun 2024 18:22:07 +0200 Subject: [PATCH 02/10] allow sf 6.4 on Mime and Messenger --- .../Tests/Stamp/ErrorDetailsStampTest.php | 18 ++++++++--- .../Tests/Stamp/TransportNamesStampTest.php | 17 +++++++---- .../Transport/Serialization/Serializer.php | 12 ++++++-- src/Symfony/Component/Messenger/composer.json | 4 +-- .../Component/Mime/Tests/EmailTest.php | 30 +++++++++++-------- .../Component/Mime/Tests/MessageTest.php | 30 +++++++++++-------- src/Symfony/Component/Mime/composer.json | 2 +- 7 files changed, 72 insertions(+), 41 deletions(-) diff --git a/src/Symfony/Component/Messenger/Tests/Stamp/ErrorDetailsStampTest.php b/src/Symfony/Component/Messenger/Tests/Stamp/ErrorDetailsStampTest.php index 8eeb63ddfd43c..9450d58842f13 100644 --- a/src/Symfony/Component/Messenger/Tests/Stamp/ErrorDetailsStampTest.php +++ b/src/Symfony/Component/Messenger/Tests/Stamp/ErrorDetailsStampTest.php @@ -58,14 +58,24 @@ public function testDeserialization() { $exception = new \Exception('exception message'); $stamp = ErrorDetailsStamp::create($exception); - $serializer = new Serializer( - new SymfonySerializer( + + // if Symfony 7.2 + if (class_exists(ChainNormalizer::class)) { + $symfonySerializer = new SymfonySerializer( [], [new JsonEncoder()], new ChainNormalizer([new FlattenExceptionNormalizer(), new ObjectNormalizer()]), new ChainDenormalizer([new ArrayDenormalizer(), new FlattenExceptionNormalizer(), new ObjectNormalizer()]) - ) - ); + ); + } else { + $symfonySerializer = new SymfonySerializer([ + new ArrayDenormalizer(), + new FlattenExceptionNormalizer(), + new ObjectNormalizer(), + ], [new JsonEncoder()]); + } + + $serializer = new Serializer($symfonySerializer); $deserializedEnvelope = $serializer->decode($serializer->encode(new Envelope(new \stdClass(), [$stamp]))); diff --git a/src/Symfony/Component/Messenger/Tests/Stamp/TransportNamesStampTest.php b/src/Symfony/Component/Messenger/Tests/Stamp/TransportNamesStampTest.php index dc5567149488f..4d61d1462d6bb 100644 --- a/src/Symfony/Component/Messenger/Tests/Stamp/TransportNamesStampTest.php +++ b/src/Symfony/Component/Messenger/Tests/Stamp/TransportNamesStampTest.php @@ -38,16 +38,23 @@ public function testGetSenders() public function testDeserialization() { - $stamp = new TransportNamesStamp(['foo']); - $serializer = new Serializer( - new SymfonySerializer( + // if Symfony 7.2 + if (class_exists(ChainNormalizer::class)) { + $symfonySerializer = new SymfonySerializer( [], [new JsonEncoder()], new ChainNormalizer([new ObjectNormalizer()]), new ChainDenormalizer([new ArrayDenormalizer(), new ObjectNormalizer()]), - ) - ); + ); + } else { + $symfonySerializer = new SymfonySerializer( + [new ArrayDenormalizer(), new ObjectNormalizer()], + [new JsonEncoder()] + ); + } + $stamp = new TransportNamesStamp(['foo']); + $serializer = new Serializer($symfonySerializer); $deserializedEnvelope = $serializer->decode($serializer->encode(new Envelope(new \stdClass(), [$stamp]))); $deserializedStamp = $deserializedEnvelope->last(TransportNamesStamp::class); diff --git a/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php b/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php index ba7543db4c432..2cbd8170cc283 100644 --- a/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php +++ b/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php @@ -55,9 +55,15 @@ public static function create(): self } $encoders = [new XmlEncoder(), new JsonEncoder()]; - $normalizers = [new DateTimeNormalizer(), new ObjectNormalizer()]; - $denormalizers = [new DateTimeNormalizer(), new ArrayDenormalizer(), new ObjectNormalizer()]; - $serializer = new SymfonySerializer([], $encoders, new ChainNormalizer($normalizers), new ChainDenormalizer($denormalizers)); + // if Symfony 7.2 + if (class_exists(ChainNormalizer::class)) { + $normalizers = [new DateTimeNormalizer(), new ObjectNormalizer()]; + $denormalizers = [new DateTimeNormalizer(), new ArrayDenormalizer(), new ObjectNormalizer()]; + $serializer = new SymfonySerializer([], $encoders, new ChainNormalizer($normalizers), new ChainDenormalizer($denormalizers)); + } else { + $normalizers = [new DateTimeNormalizer(), new ArrayDenormalizer(), new ObjectNormalizer()]; + $serializer = new SymfonySerializer($normalizers, $encoders); + } return new self($serializer); } diff --git a/src/Symfony/Component/Messenger/composer.json b/src/Symfony/Component/Messenger/composer.json index d8049be57add8..c51fdbfb58161 100644 --- a/src/Symfony/Component/Messenger/composer.json +++ b/src/Symfony/Component/Messenger/composer.json @@ -30,7 +30,7 @@ "symfony/property-access": "^6.4|^7.0", "symfony/rate-limiter": "^6.4|^7.0", "symfony/routing": "^6.4|^7.0", - "symfony/serializer": "^7.2", + "symfony/serializer": "^6.4|^7.0", "symfony/service-contracts": "^2.5|^3", "symfony/stopwatch": "^6.4|^7.0", "symfony/validator": "^6.4|^7.0" @@ -41,7 +41,7 @@ "symfony/event-dispatcher-contracts": "<2.5", "symfony/framework-bundle": "<6.4", "symfony/http-kernel": "<6.4", - "symfony/serializer": "<7.1" + "symfony/serializer": "<6.4" }, "autoload": { "psr-4": { "Symfony\\Component\\Messenger\\": "" }, diff --git a/src/Symfony/Component/Mime/Tests/EmailTest.php b/src/Symfony/Component/Mime/Tests/EmailTest.php index f8a886d23d87c..961b512c3574f 100644 --- a/src/Symfony/Component/Mime/Tests/EmailTest.php +++ b/src/Symfony/Component/Mime/Tests/EmailTest.php @@ -572,21 +572,25 @@ public function testSymfonySerialize() $extractor = new PhpDocExtractor(); $propertyNormalizer = new PropertyNormalizer(null, null, $extractor); - $serializer = new Serializer( - [], - [new JsonEncoder()], - new ChainNormalizer([ - new MimeMessageNormalizer($propertyNormalizer), - new ObjectNormalizer(null, null, null, $extractor), - $propertyNormalizer, - ]), - new ChainDenormalizer([ + $mimeMessageNormalizer = new MimeMessageNormalizer($propertyNormalizer); + $objectNormalizer = new ObjectNormalizer(null, null, null, $extractor); + + // if Symfony 7.2 + if (class_exists(ChainNormalizer::class)) { + $serializer = new Serializer( + [], + [new JsonEncoder()], + new ChainNormalizer([$mimeMessageNormalizer, $objectNormalizer, $propertyNormalizer,]), + new ChainDenormalizer([new ArrayDenormalizer(), $mimeMessageNormalizer, $objectNormalizer, $propertyNormalizer,]) + ); + } else { + $serializer = new Serializer([ new ArrayDenormalizer(), - new MimeMessageNormalizer($propertyNormalizer), - new ObjectNormalizer(null, null, null, $extractor), + $mimeMessageNormalizer, + $objectNormalizer, $propertyNormalizer, - ]) - ); + ], [new JsonEncoder()]); + } $serialized = $serializer->serialize($e, 'json', [ObjectNormalizer::IGNORED_ATTRIBUTES => ['cachedBody']]); $this->assertSame($expectedJson, json_encode(json_decode($serialized), \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); diff --git a/src/Symfony/Component/Mime/Tests/MessageTest.php b/src/Symfony/Component/Mime/Tests/MessageTest.php index d1714db2fc629..9cd56f95d291a 100644 --- a/src/Symfony/Component/Mime/Tests/MessageTest.php +++ b/src/Symfony/Component/Mime/Tests/MessageTest.php @@ -262,21 +262,25 @@ public function testSymfonySerialize() $extractor = new PhpDocExtractor(); $propertyNormalizer = new PropertyNormalizer(null, null, $extractor); - $serializer = new Serializer( - [], - [new JsonEncoder()], - new ChainNormalizer([ - new MimeMessageNormalizer($propertyNormalizer), - new ObjectNormalizer(null, null, null, $extractor), - $propertyNormalizer, - ]), - new ChainDenormalizer([ + $mimeMessageNormalizer = new MimeMessageNormalizer($propertyNormalizer); + $objectNormalizer = new ObjectNormalizer(null, null, null, $extractor); + + // if Symfony 7.2 + if (class_exists(ChainNormalizer::class)) { + $serializer = new Serializer( + [], + [new JsonEncoder()], + new ChainNormalizer([$mimeMessageNormalizer, $objectNormalizer, $propertyNormalizer,]), + new ChainDenormalizer([new ArrayDenormalizer(), $mimeMessageNormalizer, $objectNormalizer, $propertyNormalizer,]) + ); + } else { + $serializer = new Serializer([ new ArrayDenormalizer(), - new MimeMessageNormalizer($propertyNormalizer), - new ObjectNormalizer(null, null, null, $extractor), + $mimeMessageNormalizer, + $objectNormalizer, $propertyNormalizer, - ]) - ); + ], [new JsonEncoder()]); + } $serialized = $serializer->serialize($e, 'json'); $this->assertStringMatchesFormat($expectedJson, json_encode(json_decode($serialized), \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); diff --git a/src/Symfony/Component/Mime/composer.json b/src/Symfony/Component/Mime/composer.json index 31cc7ab4d15c9..5304bdf36d90b 100644 --- a/src/Symfony/Component/Mime/composer.json +++ b/src/Symfony/Component/Mime/composer.json @@ -28,7 +28,7 @@ "symfony/process": "^6.4|^7.0", "symfony/property-access": "^6.4|^7.0", "symfony/property-info": "^6.4|^7.0", - "symfony/serializer": "^7.2" + "symfony/serializer": "^6.4.3|^7.0.3" }, "conflict": { "egulias/email-validator": "~3.0.0", From 07c75ad4622132f6a819fd7c7bf55dfca4d6f5ac Mon Sep 17 00:00:00 2001 From: Nyholm Date: Wed, 19 Jun 2024 18:33:36 +0200 Subject: [PATCH 03/10] avoid updating composer.json in Messenger bridges --- .../Tests/Transport/AmazonSqsReceiverTest.php | 12 ++++++++---- .../Messenger/Bridge/AmazonSqs/composer.json | 2 +- .../Bridge/Amqp/Tests/Fixtures/long_receiver.php | 11 ++++++++--- .../Tests/Transport/AmqpExtIntegrationTest.php | 12 +++++++++--- .../Amqp/Tests/Transport/AmqpReceiverTest.php | 12 +++++++++--- .../Component/Messenger/Bridge/Amqp/composer.json | 2 +- .../Tests/Transport/BeanstalkdReceiverTest.php | 14 +++++++++----- .../Messenger/Bridge/Beanstalkd/composer.json | 2 +- .../Tests/Transport/DoctrineReceiverTest.php | 12 ++++++++---- .../Messenger/Bridge/Doctrine/composer.json | 2 +- .../Redis/Tests/Transport/RedisReceiverTest.php | 12 +++++++++--- .../Component/Messenger/Bridge/Redis/composer.json | 2 +- 12 files changed, 65 insertions(+), 30 deletions(-) diff --git a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Tests/Transport/AmazonSqsReceiverTest.php b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Tests/Transport/AmazonSqsReceiverTest.php index 58153a0f4e822..f455279124f5b 100644 --- a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Tests/Transport/AmazonSqsReceiverTest.php +++ b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Tests/Transport/AmazonSqsReceiverTest.php @@ -20,6 +20,7 @@ use Symfony\Component\Messenger\Transport\Serialization\Serializer; use Symfony\Component\Serializer as SerializerComponent; use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Normalizer\ChainNormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; class AmazonSqsReceiverTest extends TestCase @@ -67,10 +68,13 @@ private function createSqsEnvelope() private function createSerializer(): Serializer { - $serializer = new Serializer( - new SerializerComponent\Serializer([], ['json' => new JsonEncoder()], new ObjectNormalizer(), new ObjectNormalizer()) - ); + // if Symfony 7.2 + if (class_exists(ChainNormalizer::class)) { + $symfonySerializer = new SerializerComponent\Serializer([], ['json' => new JsonEncoder()], new ObjectNormalizer(), new ObjectNormalizer()); + } else { + $symfonySerializer = new SerializerComponent\Serializer([new ObjectNormalizer()], ['json' => new JsonEncoder()]); + } - return $serializer; + return new Serializer($symfonySerializer); } } diff --git a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/composer.json b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/composer.json index e1537ce540bef..18b24c66dcf25 100644 --- a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/composer.json +++ b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/composer.json @@ -26,7 +26,7 @@ "require-dev": { "symfony/http-client-contracts": "^2.5|^3", "symfony/property-access": "^6.4|^7.0", - "symfony/serializer": "^7.2" + "symfony/serializer": "^6.4|^7.0" }, "conflict": { "symfony/http-client-contracts": "<2.5" diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Fixtures/long_receiver.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Fixtures/long_receiver.php index f7ac37b963c3a..f9a9040335bc6 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Fixtures/long_receiver.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Fixtures/long_receiver.php @@ -23,11 +23,16 @@ use Symfony\Component\Serializer as SerializerComponent; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; +use Symfony\Component\Serializer\Normalizer\ChainNormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; -$serializer = new Serializer( - new SerializerComponent\Serializer([], ['json' => new JsonEncoder()], new ObjectNormalizer(), new SerializerComponent\Normalizer\ChainDenormalizer([new ObjectNormalizer(), new ArrayDenormalizer()])) -); +// if Symfony 7.2 +if (class_exists(ChainNormalizer::class)) { + $symfonySerializer = new SerializerComponent\Serializer([], ['json' => new JsonEncoder()], new ObjectNormalizer(), new SerializerComponent\Normalizer\ChainDenormalizer([new ObjectNormalizer(), new ArrayDenormalizer()])); +} else { + $symfonySerializer = new SerializerComponent\Serializer([new ObjectNormalizer(), new ArrayDenormalizer()], ['json' => new JsonEncoder()]); +} +$serializer = new Serializer($symfonySerializer); $connection = Connection::fromDsn(getenv('DSN')); $receiver = new AmqpReceiver($connection, $serializer); diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/AmqpExtIntegrationTest.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/AmqpExtIntegrationTest.php index e6eff726cab96..3cb8481e9d8bf 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/AmqpExtIntegrationTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/AmqpExtIntegrationTest.php @@ -29,6 +29,7 @@ use Symfony\Component\Serializer as SerializerComponent; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; +use Symfony\Component\Serializer\Normalizer\ChainNormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; /** @@ -261,9 +262,14 @@ private function waitForOutput(Process $process, string $output, $timeoutInSecon private function createSerializer(): SerializerInterface { - return new Serializer( - new SerializerComponent\Serializer([], ['json' => new JsonEncoder()], new ObjectNormalizer(), new SerializerComponent\Normalizer\ChainDenormalizer([new ObjectNormalizer(), new ArrayDenormalizer()])) - ); + // if Symfony 7.2 + if (class_exists(ChainNormalizer::class)) { + $symfonySerializer = new SerializerComponent\Serializer([], ['json' => new JsonEncoder()], new ObjectNormalizer(), new SerializerComponent\Normalizer\ChainDenormalizer([new ObjectNormalizer(), new ArrayDenormalizer()])); + } else { + $symfonySerializer = new SerializerComponent\Serializer([new ObjectNormalizer(), new ArrayDenormalizer()], ['json' => new JsonEncoder()]); + } + + return new Serializer($symfonySerializer); } private function assertApproximateDuration($startTime, int $expectedDuration) diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/AmqpReceiverTest.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/AmqpReceiverTest.php index bf9a2918d6d6c..95304b5fbc282 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/AmqpReceiverTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/AmqpReceiverTest.php @@ -22,6 +22,7 @@ use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface; use Symfony\Component\Serializer as SerializerComponent; use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Normalizer\ChainNormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; /** @@ -31,9 +32,14 @@ class AmqpReceiverTest extends TestCase { public function testItReturnsTheDecodedMessageToTheHandler() { - $serializer = new Serializer( - new SerializerComponent\Serializer([], ['json' => new JsonEncoder()], new ObjectNormalizer(), new ObjectNormalizer()) - ); + // if Symfony 7.2 + if (class_exists(ChainNormalizer::class)) { + $symfonySerializer = new SerializerComponent\Serializer([], ['json' => new JsonEncoder()], new ObjectNormalizer(), new ObjectNormalizer()); + } else { + $symfonySerializer = new SerializerComponent\Serializer([new ObjectNormalizer()], ['json' => new JsonEncoder()]); + } + + $serializer = new Serializer($symfonySerializer); $amqpEnvelope = $this->createAMQPEnvelope(); $connection = $this->createMock(Connection::class); diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/composer.json b/src/Symfony/Component/Messenger/Bridge/Amqp/composer.json index ed86243602c8b..9bcbe024eb918 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/composer.json +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/composer.json @@ -24,7 +24,7 @@ "symfony/event-dispatcher": "^6.4|^7.0", "symfony/process": "^6.4|^7.0", "symfony/property-access": "^6.4|^7.0", - "symfony/serializer": "^7.2" + "symfony/serializer": "^6.4|^7.0" }, "autoload": { "psr-4": { "Symfony\\Component\\Messenger\\Bridge\\Amqp\\": "" }, diff --git a/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Tests/Transport/BeanstalkdReceiverTest.php b/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Tests/Transport/BeanstalkdReceiverTest.php index 2a01837ea573f..b73eac7548125 100644 --- a/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Tests/Transport/BeanstalkdReceiverTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Tests/Transport/BeanstalkdReceiverTest.php @@ -21,6 +21,7 @@ use Symfony\Component\Messenger\Transport\Serialization\Serializer; use Symfony\Component\Serializer as SerializerComponent; use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Normalizer\ChainNormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; final class BeanstalkdReceiverTest extends TestCase @@ -91,10 +92,13 @@ private function createBeanstalkdEnvelope(): array private function createSerializer(): Serializer { - $serializer = new Serializer( - new SerializerComponent\Serializer([], ['json' => new JsonEncoder()], new ObjectNormalizer(), new ObjectNormalizer()) - ); - - return $serializer; + // if Symfony 7.2 + if (class_exists(ChainNormalizer::class)) { + $symfonySerializer = new SerializerComponent\Serializer([], ['json' => new JsonEncoder()], new ObjectNormalizer(), new ObjectNormalizer()); + } else { + $symfonySerializer = new SerializerComponent\Serializer([new ObjectNormalizer()], ['json' => new JsonEncoder()]); + } + + return new Serializer($symfonySerializer); } } diff --git a/src/Symfony/Component/Messenger/Bridge/Beanstalkd/composer.json b/src/Symfony/Component/Messenger/Bridge/Beanstalkd/composer.json index 023683747ee83..e0b0eedbf933e 100644 --- a/src/Symfony/Component/Messenger/Bridge/Beanstalkd/composer.json +++ b/src/Symfony/Component/Messenger/Bridge/Beanstalkd/composer.json @@ -18,7 +18,7 @@ }, "require-dev": { "symfony/property-access": "^6.4|^7.0", - "symfony/serializer": "^7.2" + "symfony/serializer": "^6.4|^7.0" }, "autoload": { "psr-4": { "Symfony\\Component\\Messenger\\Bridge\\Beanstalkd\\": "" }, diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineReceiverTest.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineReceiverTest.php index f3bc42b37d475..be4433232cd30 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineReceiverTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineReceiverTest.php @@ -26,6 +26,7 @@ use Symfony\Component\Messenger\Transport\Serialization\Serializer; use Symfony\Component\Serializer as SerializerComponent; use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Normalizer\ChainNormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; class DoctrineReceiverTest extends TestCase @@ -314,10 +315,13 @@ private function createDoctrineEnvelope(): array private function createSerializer(): Serializer { - $serializer = new Serializer( - new SerializerComponent\Serializer([], ['json' => new JsonEncoder()], new ObjectNormalizer(), new ObjectNormalizer()) - ); + // if Symfony 7.2 + if (class_exists(ChainNormalizer::class)) { + $symfonySerializer = new SerializerComponent\Serializer([], ['json' => new JsonEncoder()], new ObjectNormalizer(), new ObjectNormalizer()); + } else { + $symfonySerializer = new SerializerComponent\Serializer([new ObjectNormalizer()], ['json' => new JsonEncoder()]); + } - return $serializer; + return new Serializer($symfonySerializer); } } diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/composer.json b/src/Symfony/Component/Messenger/Bridge/Doctrine/composer.json index 7af223980cc9c..23be7241e04ed 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/composer.json +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/composer.json @@ -24,7 +24,7 @@ "require-dev": { "doctrine/persistence": "^1.3|^2|^3", "symfony/property-access": "^6.4|^7.0", - "symfony/serializer": "^7.2" + "symfony/serializer": "^6.4|^7.0" }, "conflict": { "doctrine/persistence": "<1.3" diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisReceiverTest.php b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisReceiverTest.php index fa26f3ad8f7c2..8a11b5eace52a 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisReceiverTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisReceiverTest.php @@ -23,6 +23,7 @@ use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface; use Symfony\Component\Serializer as SerializerComponent; use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Normalizer\ChainNormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; class RedisReceiverTest extends TestCase @@ -61,6 +62,13 @@ public function testItRejectTheMessageIfThereIsAMessageDecodingFailedException(a public static function redisEnvelopeProvider(): \Generator { + // if Symfony 7.2 + if (class_exists(ChainNormalizer::class)) { + $symfonySerializer = new SerializerComponent\Serializer([], ['json' => new JsonEncoder()], new ObjectNormalizer(), new ObjectNormalizer()); + } else { + $symfonySerializer = new SerializerComponent\Serializer([new ObjectNormalizer()], ['json' => new JsonEncoder()]); + } + yield [ [ 'id' => 1, @@ -74,9 +82,7 @@ public static function redisEnvelopeProvider(): \Generator ], ], new DummyMessage('Hi'), - new Serializer( - new SerializerComponent\Serializer([], ['json' => new JsonEncoder()], new ObjectNormalizer(), new ObjectNormalizer()) - ), + new Serializer($symfonySerializer), ]; yield [ diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/composer.json b/src/Symfony/Component/Messenger/Bridge/Redis/composer.json index a1ecf02036fcd..f322f27c2107d 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/composer.json +++ b/src/Symfony/Component/Messenger/Bridge/Redis/composer.json @@ -22,7 +22,7 @@ }, "require-dev": { "symfony/property-access": "^6.4|^7.0", - "symfony/serializer": "^7.2" + "symfony/serializer": "^6.4|^7.0" }, "autoload": { "psr-4": { "Symfony\\Component\\Messenger\\Bridge\\Redis\\": "" }, From 70896a41cd3a1358e7ffbd76af4056a3cd02ea08 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Wed, 19 Jun 2024 23:02:13 +0200 Subject: [PATCH 04/10] updates from review --- .../Component/Serializer/Normalizer/ChainDenormalizer.php | 2 +- src/Symfony/Component/Serializer/Serializer.php | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/Serializer/Normalizer/ChainDenormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ChainDenormalizer.php index dd9554a4857c0..d12f9f178cd93 100644 --- a/src/Symfony/Component/Serializer/Normalizer/ChainDenormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/ChainDenormalizer.php @@ -33,7 +33,7 @@ final class ChainDenormalizer implements DenormalizerInterface, SerializerAwareI ]; /** - * @deprecated since Symfony 7.1 + * @deprecated since Symfony 7.2 */ private ?SerializerInterface $serializer = null; diff --git a/src/Symfony/Component/Serializer/Serializer.php b/src/Symfony/Component/Serializer/Serializer.php index d324fd635bd12..f87096c0ed5da 100644 --- a/src/Symfony/Component/Serializer/Serializer.php +++ b/src/Symfony/Component/Serializer/Serializer.php @@ -69,21 +69,21 @@ public function __construct( ?DenormalizerInterface $denormalizer = null, ) { if ([] !== $normalizers && (null !== $normalizer || null !== $denormalizer)) { - throw new InvalidArgumentException('You cannot use an array of $normalizers with a $normalizer/$denormalizer.'); + throw new InvalidArgumentException('You cannot use an array of $normalizers with a $normalizer/$denormalizer. Please use the $normalizer/$denormalizer arguments only instead.'); } $this->normalizer = $normalizer ?? new ChainNormalizer(); $this->denormalizer = $denormalizer ?? new ChainDenormalizer(); if ([] !== $normalizers) { - trigger_deprecation('symfony/serializer', '7.1', 'Passing normalizers as first argument to "%s" is deprecated, use a chain normalizer/denormalizer instead.', __METHOD__); + trigger_deprecation('symfony/serializer', '7.2', 'Passing normalizers as first argument to "%s" is deprecated, use a chain normalizer/denormalizer instead.', __METHOD__); } foreach ($normalizers as $item) { if ($item instanceof SerializerAwareInterface) { if (!$item instanceof NormalizerAwareInterface) { // We assume they have started to migrate - trigger_deprecation('symfony/serializer', '7.1', 'Interface %s is deprecated, maybe you are interested to use %s instead.', SerializerAwareInterface::class, NormalizerAwareInterface::class); + trigger_deprecation('symfony/serializer', '7.2', 'Interface %s is deprecated, maybe you are interested to use %s instead.', SerializerAwareInterface::class, NormalizerAwareInterface::class); } $item->setSerializer($this); } @@ -107,7 +107,7 @@ public function __construct( if ($encoder instanceof SerializerAwareInterface) { if (!$encoder instanceof NormalizerAwareInterface) { // We assume they have started to migrate - trigger_deprecation('symfony/serializer', '7.1', 'Interface %s is deprecated, maybe you are interested to use %s instead.', SerializerAwareInterface::class, NormalizerAwareInterface::class); + trigger_deprecation('symfony/serializer', '7.2', 'Interface %s is deprecated, maybe you are interested to use %s instead.', SerializerAwareInterface::class, NormalizerAwareInterface::class); } $encoder->setSerializer($this); } From 3ef1e1c33e8aab4a5415366fe1be99dd95971cc9 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Wed, 19 Jun 2024 23:05:57 +0200 Subject: [PATCH 05/10] make TwigBridge support sf 6.4 --- .../Extension/SerializerExtensionTest.php | 10 ++++- .../Twig/Tests/Mime/TemplatedEmailTest.php | 44 ++++++++++++------- src/Symfony/Bridge/Twig/composer.json | 2 +- 3 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/SerializerExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/SerializerExtensionTest.php index 0d4f5765d7dc7..e580814b55e71 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/SerializerExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/SerializerExtensionTest.php @@ -19,6 +19,7 @@ use Symfony\Component\Serializer\Encoder\YamlEncoder; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; +use Symfony\Component\Serializer\Normalizer\ChainNormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Serializer; use Twig\Environment; @@ -50,7 +51,14 @@ public static function serializerDataProvider(): \Generator private function getTwig(string $template): Environment { $meta = new ClassMetadataFactory(new AttributeLoader()); - $runtime = new SerializerRuntime(new Serializer([], [new JsonEncoder(), new YamlEncoder()], new ObjectNormalizer($meta), new ObjectNormalizer($meta))); + + // if Symfony 7.2 + if (class_exists(ChainNormalizer::class)) { + $serializer = new Serializer([], [new JsonEncoder(), new YamlEncoder()], new ObjectNormalizer($meta), new ObjectNormalizer($meta)); + } else { + $serializer = new Serializer([new ObjectNormalizer($meta)], [new JsonEncoder(), new YamlEncoder()]); + } + $runtime = new SerializerRuntime($serializer); $mockRuntimeLoader = $this->createMock(RuntimeLoaderInterface::class); $mockRuntimeLoader diff --git a/src/Symfony/Bridge/Twig/Tests/Mime/TemplatedEmailTest.php b/src/Symfony/Bridge/Twig/Tests/Mime/TemplatedEmailTest.php index 300b1f6702bef..1db130e0cedc9 100644 --- a/src/Symfony/Bridge/Twig/Tests/Mime/TemplatedEmailTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Mime/TemplatedEmailTest.php @@ -107,23 +107,33 @@ public function testSymfonySerialize() $extractor = new PhpDocExtractor(); $propertyNormalizer = new PropertyNormalizer(null, null, $extractor); - $normalizers = [ - new MimeMessageNormalizer($propertyNormalizer), - new ObjectNormalizer(null, null, null, $extractor), - $propertyNormalizer, - ]; - $denormalizers = [ - new ArrayDenormalizer(), - new MimeMessageNormalizer($propertyNormalizer), - new ObjectNormalizer(null, null, null, $extractor), - $propertyNormalizer, - ]; - $serializer = new Serializer( - [], - [new JsonEncoder()], - new ChainNormalizer($normalizers), - new ChainDenormalizer($denormalizers), - ); + // if Symfony 7.2 + if (class_exists(ChainNormalizer::class)) { + $normalizers = [ + new MimeMessageNormalizer($propertyNormalizer), + new ObjectNormalizer(null, null, null, $extractor), + $propertyNormalizer, + ]; + $denormalizers = [ + new ArrayDenormalizer(), + new MimeMessageNormalizer($propertyNormalizer), + new ObjectNormalizer(null, null, null, $extractor), + $propertyNormalizer, + ]; + $serializer = new Serializer( + [], + [new JsonEncoder()], + new ChainNormalizer($normalizers), + new ChainDenormalizer($denormalizers), + ); + } else { + $serializer = new Serializer([ + new ArrayDenormalizer(), + new MimeMessageNormalizer($propertyNormalizer), + new ObjectNormalizer(null, null, null, $extractor), + $propertyNormalizer, + ], [new JsonEncoder()]); + } $serialized = $serializer->serialize($e, 'json', [ObjectNormalizer::IGNORED_ATTRIBUTES => ['cachedBody']]); $this->assertStringMatchesFormat($expectedJson, json_encode(json_decode($serialized), \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index 3a0206cb60d29..f7f8d32d620ea 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -44,7 +44,7 @@ "symfony/security-core": "^6.4|^7.0", "symfony/security-csrf": "^6.4|^7.0", "symfony/security-http": "^6.4|^7.0", - "symfony/serializer": "^7.2", + "symfony/serializer": "^6.4.3|^7.0.3", "symfony/stopwatch": "^6.4|^7.0", "symfony/console": "^6.4|^7.0", "symfony/expression-language": "^6.4|^7.0", From 0e9704adc0e8c0bbdcfa41632306fe6bceb94a38 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Wed, 19 Jun 2024 23:10:45 +0200 Subject: [PATCH 06/10] make error handler support sf 6.4 --- .../Tests/ErrorRenderer/SerializerErrorRendererTest.php | 9 ++++++++- src/Symfony/Component/ErrorHandler/composer.json | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/SerializerErrorRendererTest.php b/src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/SerializerErrorRendererTest.php index 86ff26923ca4b..35e4aa0ff6657 100644 --- a/src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/SerializerErrorRendererTest.php +++ b/src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/SerializerErrorRendererTest.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\ErrorHandler\ErrorRenderer\SerializerErrorRenderer; use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Normalizer\ChainNormalizer; use Symfony\Component\Serializer\Normalizer\ProblemNormalizer; use Symfony\Component\Serializer\Serializer; @@ -28,9 +29,15 @@ public function testDefaultContent() public function testSerializerContent() { + // if Symfony 7.2 + if (class_exists(ChainNormalizer::class)) { + $serializer = new Serializer([], [new JsonEncoder()], new ProblemNormalizer()); + } else { + $serializer = new Serializer([new ProblemNormalizer()], [new JsonEncoder()]); + } $exception = new \RuntimeException('Foo'); $errorRenderer = new SerializerErrorRenderer( - new Serializer([], [new JsonEncoder()], new ProblemNormalizer()), + $serializer, fn () => 'json' ); diff --git a/src/Symfony/Component/ErrorHandler/composer.json b/src/Symfony/Component/ErrorHandler/composer.json index 70e4856c87594..987a68f641448 100644 --- a/src/Symfony/Component/ErrorHandler/composer.json +++ b/src/Symfony/Component/ErrorHandler/composer.json @@ -22,7 +22,7 @@ }, "require-dev": { "symfony/http-kernel": "^6.4|^7.0", - "symfony/serializer": "^7.1", + "symfony/serializer": "^6.4|^7.0", "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { From 1271e493ee070f9ae309ebc0cb79845067445e3c Mon Sep 17 00:00:00 2001 From: Nyholm Date: Wed, 19 Jun 2024 23:17:30 +0200 Subject: [PATCH 07/10] cs --- .../ArgumentResolver/RequestPayloadValueResolverTest.php | 1 - src/Symfony/Component/Mime/Tests/EmailTest.php | 4 ++-- src/Symfony/Component/Mime/Tests/MessageTest.php | 4 ++-- .../Component/Serializer/Normalizer/ChainDenormalizer.php | 1 - src/Symfony/Component/Serializer/Serializer.php | 4 ++-- 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php index 3b7385bb8eee7..92f93cdf5c3c5 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php @@ -28,7 +28,6 @@ use Symfony\Component\Serializer\Exception\PartialDenormalizationException; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; use Symfony\Component\Serializer\Normalizer\ChainDenormalizer; -use Symfony\Component\Serializer\Normalizer\ChainNormalizer; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Serializer; diff --git a/src/Symfony/Component/Mime/Tests/EmailTest.php b/src/Symfony/Component/Mime/Tests/EmailTest.php index 961b512c3574f..59a322bf1eaeb 100644 --- a/src/Symfony/Component/Mime/Tests/EmailTest.php +++ b/src/Symfony/Component/Mime/Tests/EmailTest.php @@ -580,8 +580,8 @@ public function testSymfonySerialize() $serializer = new Serializer( [], [new JsonEncoder()], - new ChainNormalizer([$mimeMessageNormalizer, $objectNormalizer, $propertyNormalizer,]), - new ChainDenormalizer([new ArrayDenormalizer(), $mimeMessageNormalizer, $objectNormalizer, $propertyNormalizer,]) + new ChainNormalizer([$mimeMessageNormalizer, $objectNormalizer, $propertyNormalizer]), + new ChainDenormalizer([new ArrayDenormalizer(), $mimeMessageNormalizer, $objectNormalizer, $propertyNormalizer]) ); } else { $serializer = new Serializer([ diff --git a/src/Symfony/Component/Mime/Tests/MessageTest.php b/src/Symfony/Component/Mime/Tests/MessageTest.php index 9cd56f95d291a..c984833d6c739 100644 --- a/src/Symfony/Component/Mime/Tests/MessageTest.php +++ b/src/Symfony/Component/Mime/Tests/MessageTest.php @@ -270,8 +270,8 @@ public function testSymfonySerialize() $serializer = new Serializer( [], [new JsonEncoder()], - new ChainNormalizer([$mimeMessageNormalizer, $objectNormalizer, $propertyNormalizer,]), - new ChainDenormalizer([new ArrayDenormalizer(), $mimeMessageNormalizer, $objectNormalizer, $propertyNormalizer,]) + new ChainNormalizer([$mimeMessageNormalizer, $objectNormalizer, $propertyNormalizer]), + new ChainDenormalizer([new ArrayDenormalizer(), $mimeMessageNormalizer, $objectNormalizer, $propertyNormalizer]) ); } else { $serializer = new Serializer([ diff --git a/src/Symfony/Component/Serializer/Normalizer/ChainDenormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ChainDenormalizer.php index d12f9f178cd93..321a4ecfe2fec 100644 --- a/src/Symfony/Component/Serializer/Normalizer/ChainDenormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/ChainDenormalizer.php @@ -13,7 +13,6 @@ use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; -use Symfony\Component\Serializer\Exception\PartialDenormalizationException; use Symfony\Component\Serializer\SerializerAwareInterface; use Symfony\Component\Serializer\SerializerInterface; diff --git a/src/Symfony/Component/Serializer/Serializer.php b/src/Symfony/Component/Serializer/Serializer.php index f87096c0ed5da..c0da8b932689b 100644 --- a/src/Symfony/Component/Serializer/Serializer.php +++ b/src/Symfony/Component/Serializer/Serializer.php @@ -83,7 +83,7 @@ public function __construct( if ($item instanceof SerializerAwareInterface) { if (!$item instanceof NormalizerAwareInterface) { // We assume they have started to migrate - trigger_deprecation('symfony/serializer', '7.2', 'Interface %s is deprecated, maybe you are interested to use %s instead.', SerializerAwareInterface::class, NormalizerAwareInterface::class); + trigger_deprecation('symfony/serializer', '7.2', 'Interface %s is deprecated, use %s instead.', SerializerAwareInterface::class, NormalizerAwareInterface::class); } $item->setSerializer($this); } @@ -107,7 +107,7 @@ public function __construct( if ($encoder instanceof SerializerAwareInterface) { if (!$encoder instanceof NormalizerAwareInterface) { // We assume they have started to migrate - trigger_deprecation('symfony/serializer', '7.2', 'Interface %s is deprecated, maybe you are interested to use %s instead.', SerializerAwareInterface::class, NormalizerAwareInterface::class); + trigger_deprecation('symfony/serializer', '7.2', 'Interface %s is deprecated, use %s instead.', SerializerAwareInterface::class, NormalizerAwareInterface::class); } $encoder->setSerializer($this); } From 2ebb160a0ffb47572ba6e5f9f823502f2fe1fa87 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Wed, 19 Jun 2024 23:28:16 +0200 Subject: [PATCH 08/10] minors --- src/Symfony/Bundle/FrameworkBundle/composer.json | 2 +- .../Component/Serializer/Normalizer/ChainDenormalizer.php | 2 +- src/Symfony/Component/Serializer/Normalizer/ChainNormalizer.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 80b2c35e98eec..e6450b201b239 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -93,7 +93,7 @@ "symfony/property-info": "<6.4", "symfony/property-access": "<6.4", "symfony/scheduler": "<6.4.4|>=7.0.0,<7.0.4", - "symfony/serializer": "<6.4", + "symfony/serializer": "<7.2", "symfony/security-csrf": "<6.4", "symfony/security-core": "<6.4", "symfony/stopwatch": "<6.4", diff --git a/src/Symfony/Component/Serializer/Normalizer/ChainDenormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ChainDenormalizer.php index 321a4ecfe2fec..2c6f3bc6df347 100644 --- a/src/Symfony/Component/Serializer/Normalizer/ChainDenormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/ChainDenormalizer.php @@ -111,7 +111,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a * Returns a matching denormalizer. * * @param mixed $data Data to restore - * @param string $class The expected class to instantiate + * @param string $class The expected class to instantiate or type to convert to * @param string|null $format Format name, present to give the option to normalizers to act differently based on formats * @param array $context Options available to the denormalizer */ diff --git a/src/Symfony/Component/Serializer/Normalizer/ChainNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ChainNormalizer.php index f1cd98d5a77c2..46647c9bce7ee 100644 --- a/src/Symfony/Component/Serializer/Normalizer/ChainNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/ChainNormalizer.php @@ -26,7 +26,7 @@ final class ChainNormalizer implements NormalizerInterface, SerializerAwareInterface { /** - * @deprecated since Symfony 7.1 + * @deprecated since Symfony 7.2 */ private ?SerializerInterface $serializer = null; From 1d9d0c22900fd8ae4cb0daaf1d81fc0f51269586 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Thu, 20 Jun 2024 10:03:47 +0200 Subject: [PATCH 09/10] updating from feedback --- .../Serializer/Debug/TraceableEncoder.php | 14 ++++- .../Normalizer/ChainDenormalizer.php | 12 ++-- .../Serializer/Normalizer/ChainNormalizer.php | 12 ++-- .../Component/Serializer/Serializer.php | 21 ++++--- .../Serializer/SerializerAwareInterface.php | 4 +- .../Normalizer/ChainDenormalizerTest.php | 57 +++++++++++++++++++ .../Tests/Normalizer/ChainNormalizerTest.php | 57 +++++++++++++++++++ 7 files changed, 153 insertions(+), 24 deletions(-) create mode 100644 src/Symfony/Component/Serializer/Tests/Normalizer/ChainDenormalizerTest.php create mode 100644 src/Symfony/Component/Serializer/Tests/Normalizer/ChainNormalizerTest.php diff --git a/src/Symfony/Component/Serializer/Debug/TraceableEncoder.php b/src/Symfony/Component/Serializer/Debug/TraceableEncoder.php index d6e9b4d0ec41e..5ab126f6be5a7 100644 --- a/src/Symfony/Component/Serializer/Debug/TraceableEncoder.php +++ b/src/Symfony/Component/Serializer/Debug/TraceableEncoder.php @@ -15,6 +15,8 @@ use Symfony\Component\Serializer\Encoder\DecoderInterface; use Symfony\Component\Serializer\Encoder\EncoderInterface; use Symfony\Component\Serializer\Encoder\NormalizationAwareInterface; +use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\SerializerAwareInterface; @@ -27,7 +29,7 @@ * * @internal */ -class TraceableEncoder implements EncoderInterface, DecoderInterface, SerializerAwareInterface, NormalizerAwareInterface +class TraceableEncoder implements EncoderInterface, DecoderInterface, SerializerAwareInterface, NormalizerAwareInterface, DenormalizerAwareInterface { public function __construct( private EncoderInterface|DecoderInterface $encoder, @@ -105,6 +107,16 @@ public function setNormalizer(NormalizerInterface $normalizer): void $this->encoder->setNormalizer($normalizer); } + + public function setDenormalizer(DenormalizerInterface $denormalizer): void + { + if (!$this->encoder instanceof DenormalizerInterface) { + return; + } + + $this->encoder->setDenormalizer($denormalizer); + } + public function needsNormalization(): bool { return !$this->encoder instanceof NormalizationAwareInterface; diff --git a/src/Symfony/Component/Serializer/Normalizer/ChainDenormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ChainDenormalizer.php index 2c6f3bc6df347..818e72e5bd117 100644 --- a/src/Symfony/Component/Serializer/Normalizer/ChainDenormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/ChainDenormalizer.php @@ -61,7 +61,7 @@ public function __construct(array $denormalizers = []) } } - public function addDenormalizer(DenormalizerInterface $denormalizer): void + private function addDenormalizer(DenormalizerInterface $denormalizer): void { if ($denormalizer instanceof DenormalizerAwareInterface) { $denormalizer->setDenormalizer($this); @@ -122,10 +122,6 @@ private function getDenormalizer(mixed $data, string $class, ?string $format, ar $genericType = class_exists($class) || interface_exists($class, false) ? 'object' : '*'; foreach ($this->denormalizers as $k => $denormalizer) { - if (!$denormalizer instanceof DenormalizerInterface) { - continue; - } - $supportedTypes = $denormalizer->getSupportedTypes($format); $doesClassRepresentCollection = str_ends_with($class, '[]'); @@ -180,11 +176,13 @@ public function getSupportedTypes(?string $format): array if (!isset($this->supportedCache[$format])) { foreach ($this->denormalizers as $denormalizer) { - $this->supportedCache + $denormalizer->getSupportedTypes($format); + foreach($denormalizer->getSupportedTypes($format) as $type => $supported) { + $this->supportedCache[$format][$type] = $supported || ($this->supportedCache[$format][$type] ?? false); + } } } - return $this->supportedCache[$format]; + return $this->supportedCache[$format] ?? []; } /** diff --git a/src/Symfony/Component/Serializer/Normalizer/ChainNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ChainNormalizer.php index 46647c9bce7ee..5271114266a8b 100644 --- a/src/Symfony/Component/Serializer/Normalizer/ChainNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/ChainNormalizer.php @@ -58,7 +58,7 @@ public function __construct(array $normalizers = []) /** * Add Normalizer last in the line. */ - public function addNormalizer(NormalizerInterface $normalizer): void + private function addNormalizer(NormalizerInterface $normalizer): void { if ($normalizer instanceof NormalizerAwareInterface) { $normalizer->setNormalizer($this); @@ -124,11 +124,13 @@ public function getSupportedTypes(?string $format): array if (!isset($this->supportedCache[$format])) { foreach ($this->normalizers as $normalizer) { - $this->supportedCache + $normalizer->getSupportedTypes($format); + foreach($normalizer->getSupportedTypes($format) as $type => $supported) { + $this->supportedCache[$format][$type] = $supported || ($this->supportedCache[$format][$type] ?? false); + } } } - return $this->supportedCache[$format]; + return $this->supportedCache[$format] ?? []; } /** @@ -152,10 +154,6 @@ private function getNormalizer(mixed $data, ?string $format, array $context): ?N $this->normalizerCache[$format][$type] = []; foreach ($this->normalizers as $k => $normalizer) { - if (!$normalizer instanceof NormalizerInterface) { - continue; - } - $supportedTypes = $normalizer->getSupportedTypes($format); foreach ($supportedTypes as $supportedType => $isCacheable) { diff --git a/src/Symfony/Component/Serializer/Serializer.php b/src/Symfony/Component/Serializer/Serializer.php index c0da8b932689b..7ff58743851f5 100644 --- a/src/Symfony/Component/Serializer/Serializer.php +++ b/src/Symfony/Component/Serializer/Serializer.php @@ -23,6 +23,7 @@ use Symfony\Component\Serializer\Exception\UnsupportedFormatException; use Symfony\Component\Serializer\Normalizer\ChainDenormalizer; use Symfony\Component\Serializer\Normalizer\ChainNormalizer; +use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; @@ -72,28 +73,27 @@ public function __construct( throw new InvalidArgumentException('You cannot use an array of $normalizers with a $normalizer/$denormalizer. Please use the $normalizer/$denormalizer arguments only instead.'); } - $this->normalizer = $normalizer ?? new ChainNormalizer(); - $this->denormalizer = $denormalizer ?? new ChainDenormalizer(); - if ([] !== $normalizers) { trigger_deprecation('symfony/serializer', '7.2', 'Passing normalizers as first argument to "%s" is deprecated, use a chain normalizer/denormalizer instead.', __METHOD__); } + $localNormalizers = []; + $localDenormalizers = []; + foreach ($normalizers as $item) { if ($item instanceof SerializerAwareInterface) { if (!$item instanceof NormalizerAwareInterface) { - // We assume they have started to migrate trigger_deprecation('symfony/serializer', '7.2', 'Interface %s is deprecated, use %s instead.', SerializerAwareInterface::class, NormalizerAwareInterface::class); } $item->setSerializer($this); } if ($item instanceof DenormalizerInterface) { - $this->denormalizer->addDenormalizer($item); + $localNormalizers[] = $item; } if ($item instanceof NormalizerInterface) { - $this->normalizer->addNormalizer($item); + $localDenormalizers[] = $item; } if (!($item instanceof NormalizerInterface || $item instanceof DenormalizerInterface)) { @@ -101,19 +101,24 @@ public function __construct( } } + $this->normalizer = $normalizer ?? new ChainNormalizer($localNormalizers); + $this->denormalizer = $denormalizer ?? new ChainDenormalizer($localDenormalizers); + $decoders = []; $realEncoders = []; foreach ($encoders as $encoder) { if ($encoder instanceof SerializerAwareInterface) { if (!$encoder instanceof NormalizerAwareInterface) { - // We assume they have started to migrate - trigger_deprecation('symfony/serializer', '7.2', 'Interface %s is deprecated, use %s instead.', SerializerAwareInterface::class, NormalizerAwareInterface::class); + trigger_deprecation('symfony/serializer', '7.2', 'Interface %s is deprecated, use %s and/or %s instead.', SerializerAwareInterface::class, NormalizerAwareInterface::class, DenormalizerAwareInterface::class); } $encoder->setSerializer($this); } if ($encoder instanceof NormalizerAwareInterface) { $encoder->setNormalizer($this->normalizer); } + if ($encoder instanceof DenormalizerAwareInterface) { + $encoder->setDenormalizer($this->denormalizer); + } if ($encoder instanceof DecoderInterface) { $decoders[] = $encoder; } diff --git a/src/Symfony/Component/Serializer/SerializerAwareInterface.php b/src/Symfony/Component/Serializer/SerializerAwareInterface.php index fb363555f45ec..77feb12fcf0ea 100644 --- a/src/Symfony/Component/Serializer/SerializerAwareInterface.php +++ b/src/Symfony/Component/Serializer/SerializerAwareInterface.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Serializer; /** - * @deprecated since Symfony 7.2, use {@see NormalizerAwareInterface} instead + * @deprecated since Symfony 7.2, use {@see NormalizerAwareInterface} or {@see DenormalizerAwareInterface} instead * * @author Jordi Boggiano */ @@ -20,6 +20,8 @@ interface SerializerAwareInterface { /** * Sets the owning Serializer object. + * + * @deprecated since Symfony 7.2, use {@see NormalizerAwareInterface} or {@see DenormalizerAwareInterface} instead */ public function setSerializer(SerializerInterface $serializer): void; } diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ChainDenormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ChainDenormalizerTest.php new file mode 100644 index 0000000000000..883a81caf52b2 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ChainDenormalizerTest.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Normalizer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Normalizer\ChainDenormalizer; +use Symfony\Component\Serializer\Normalizer\CustomNormalizer; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; +use Symfony\Component\Serializer\Serializer; +use Symfony\Component\Serializer\SerializerAwareInterface; +use Symfony\Component\Serializer\Tests\Fixtures\ScalarDummy; + +class ChainDenormalizerTest extends TestCase +{ + public function testGetSupportedTypesCache() + { + $normalizer = $this->getMockBuilder(DenormalizerInterface::class) + ->disableOriginalConstructor() + ->onlyMethods(['denormalize', 'supportsDenormalization', 'getSupportedTypes']) + ->getMock(); + $normalizer->expects($this->once())->method('getSupportedTypes')->willReturn(['*'=>true]); + + $chain = new ChainDenormalizer([$normalizer]); + $this->assertEquals(['*'=>true], $chain->getSupportedTypes('format')); + $this->assertEquals(['*'=>true], $chain->getSupportedTypes('format')); + } + + public function testGetSupportedTypesOrder() + { + $normalizerA = $this->getMockBuilder(DenormalizerInterface::class) + ->disableOriginalConstructor() + ->onlyMethods(['denormalize', 'supportsDenormalization', 'getSupportedTypes']) + ->getMock(); + $normalizerA->expects($this->any())->method('getSupportedTypes')->willReturn(['foo'=>true]); + $normalizerB = $this->getMockBuilder(DenormalizerInterface::class) + ->disableOriginalConstructor() + ->onlyMethods(['denormalize', 'supportsDenormalization', 'getSupportedTypes']) + ->getMock(); + $normalizerB->expects($this->any())->method('getSupportedTypes')->willReturn(['foo'=>false]); + + $chain = new ChainDenormalizer([$normalizerA, $normalizerB]); + $this->assertEquals(['foo'=>true], $chain->getSupportedTypes('format')); + + $chain = new ChainDenormalizer([$normalizerB, $normalizerA]); + $this->assertEquals(['foo'=>true], $chain->getSupportedTypes('format')); + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ChainNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ChainNormalizerTest.php new file mode 100644 index 0000000000000..22fa27309eed3 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ChainNormalizerTest.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Normalizer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Normalizer\ChainNormalizer; +use Symfony\Component\Serializer\Normalizer\CustomNormalizer; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; +use Symfony\Component\Serializer\Serializer; +use Symfony\Component\Serializer\SerializerAwareInterface; +use Symfony\Component\Serializer\Tests\Fixtures\ScalarDummy; + +class ChainNormalizerTest extends TestCase +{ + public function testGetSupportedTypesCache() + { + $normalizer = $this->getMockBuilder(NormalizerInterface::class) + ->disableOriginalConstructor() + ->onlyMethods(['normalize', 'supportsNormalization', 'getSupportedTypes']) + ->getMock(); + $normalizer->expects($this->once())->method('getSupportedTypes')->willReturn(['*'=>true]); + + $chain = new ChainNormalizer([$normalizer]); + $this->assertEquals(['*'=>true], $chain->getSupportedTypes('format')); + $this->assertEquals(['*'=>true], $chain->getSupportedTypes('format')); + } + + public function testGetSupportedTypesOrder() + { + $normalizerA = $this->getMockBuilder(NormalizerInterface::class) + ->disableOriginalConstructor() + ->onlyMethods(['normalize', 'supportsNormalization', 'getSupportedTypes']) + ->getMock(); + $normalizerA->expects($this->any())->method('getSupportedTypes')->willReturn(['foo'=>true]); + $normalizerB = $this->getMockBuilder(NormalizerInterface::class) + ->disableOriginalConstructor() + ->onlyMethods(['normalize', 'supportsNormalization', 'getSupportedTypes']) + ->getMock(); + $normalizerB->expects($this->any())->method('getSupportedTypes')->willReturn(['foo'=>false]); + + $chain = new ChainNormalizer([$normalizerA, $normalizerB]); + $this->assertEquals(['foo'=>true], $chain->getSupportedTypes('format')); + + $chain = new ChainNormalizer([$normalizerB, $normalizerA]); + $this->assertEquals(['foo'=>true], $chain->getSupportedTypes('format')); + } +} From 4f523889f14195300dddac4891f9bda860d0429f Mon Sep 17 00:00:00 2001 From: Nyholm Date: Thu, 20 Jun 2024 10:19:46 +0200 Subject: [PATCH 10/10] introduce serializer.denormalizer tag --- .../DependencyInjection/SerializerPass.php | 43 ++++++------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/src/Symfony/Component/Serializer/DependencyInjection/SerializerPass.php b/src/Symfony/Component/Serializer/DependencyInjection/SerializerPass.php index 6d68bf6f53b3a..b1b671bccae5f 100644 --- a/src/Symfony/Component/Serializer/DependencyInjection/SerializerPass.php +++ b/src/Symfony/Component/Serializer/DependencyInjection/SerializerPass.php @@ -40,17 +40,16 @@ public function process(ContainerBuilder $container): void return; } - if (!$normalizers = $this->findAndSortTaggedServices('serializer.normalizer', $container)) { - throw new RuntimeException('You must tag at least one service as "serializer.normalizer" to use the "serializer" service.'); - } - if (!$encoders = $this->findAndSortTaggedServices('serializer.encoder', $container)) { throw new RuntimeException('You must tag at least one service as "serializer.encoder" to use the "serializer" service.'); } + $normalizers = $this->findAndSortTaggedServices('serializer.normalizer', $container); + $denormalizers = $this->findAndSortTaggedServices('serializer.denormalizer', $container); + if ($container->hasParameter('serializer.default_context')) { $defaultContext = $container->getParameter('serializer.default_context'); - foreach (array_merge($normalizers, $encoders) as $service) { + foreach (array_merge($normalizers, $denormalizers, $encoders) as $service) { $definition = $container->getDefinition($service); $definition->setBindings(['array $defaultContext' => new BoundArgument($defaultContext, false)] + $definition->getBindings()); } @@ -64,42 +63,28 @@ public function process(ContainerBuilder $container): void ->setArguments([$normalizer, new Reference('serializer.data_collector')]); } + foreach ($denormalizers as $i => $denormalizer ) { + $normalizers[$i] = $container->register('.debug.serializer.denormalizer.'.$denormalizer, TraceableNormalizer::class) + ->setArguments([$denormalizer, new Reference('serializer.data_collector')]); + } + foreach ($encoders as $i => $encoder) { $encoders[$i] = $container->register('.debug.serializer.encoder.'.$encoder, TraceableEncoder::class) ->setArguments([$encoder, new Reference('serializer.data_collector')]); } } - $denormalizers = []; - $trueNormalizers = []; - /** @var Reference|Definition $definition */ - foreach ($normalizers as $definition) { - $reference = null; - if ($definition instanceof Reference) { - $reference = $definition; - $definition = $container->getDefinition($definition); - } - - $class = $definition->getClass(); - if (null === $class) { - continue; - } - $interfaces = class_implements($class); - if (isset($interfaces[NormalizerInterface::class])) { - $trueNormalizers[] = $reference ?? $definition; - } - if (isset($interfaces[DenormalizerInterface::class])) { - $denormalizers[] = $reference ?? $definition; - } - } - $serializerDefinition = $container->getDefinition('serializer'); $serializerDefinition->replaceArgument(1, $encoders); + // if FrameworkBundle 7.2 or above if ($container->hasDefinition('serializer.normalizer') && $container->hasDefinition('serializer.denormalizer')) { - $container->getDefinition('serializer.normalizer')->replaceArgument(0, $trueNormalizers); + $container->getDefinition('serializer.normalizer')->replaceArgument(0, $normalizers); $container->getDefinition('serializer.denormalizer')->replaceArgument(0, $denormalizers); } else { + if (!$normalizers) { + throw new RuntimeException('You must tag at least one service as "serializer.normalizer" to use the "serializer" service.'); + } $serializerDefinition->replaceArgument(0, $normalizers); } }