diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index bd27c27a6948c..8d64adeca341d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -1946,24 +1946,14 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder $container->setParameter('serializer.default_context', $defaultContext); } - if (!$container->hasDefinition('serializer.normalizer.object')) { - return; - } - - $arguments = $container->getDefinition('serializer.normalizer.object')->getArguments(); - $context = $arguments[6] ?? $defaultContext; - - if (isset($config['circular_reference_handler']) && $config['circular_reference_handler']) { - $context += ['circular_reference_handler' => new Reference($config['circular_reference_handler'])]; - $container->getDefinition('serializer.normalizer.object')->setArgument(5, null); + if ($config['circular_reference_handler'] ?? false) { + $container->setParameter('.serializer.circular_reference_handler', $config['circular_reference_handler']); } if ($config['max_depth_handler'] ?? false) { - $context += ['max_depth_handler' => new Reference($config['max_depth_handler'])]; + $container->setParameter('.serializer.max_depth_handler', $config['max_depth_handler']); } - $container->getDefinition('serializer.normalizer.object')->setArgument(6, $context); - $container->getDefinition('serializer.normalizer.property')->setArgument(5, $defaultContext); $container->setParameter('.serializer.named_serializers', $config['named_serializers'] ?? []); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php index 4686a88f662d6..b291f51ac8546 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php @@ -129,7 +129,7 @@ service('property_info')->ignoreOnInvalid(), service('serializer.mapping.class_discriminator_resolver')->ignoreOnInvalid(), null, - null, + abstract_arg('default context, set in the SerializerPass'), service('property_info')->ignoreOnInvalid(), ]) ->tag('serializer.normalizer', ['built_in' => true, 'priority' => -1000]) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php index 5f5f418010663..7bf66512d2b2b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php @@ -33,6 +33,7 @@ use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Compiler\ResolveBindingsPass; use Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass; use Symfony\Component\DependencyInjection\Compiler\ResolveInstanceofConditionalsPass; use Symfony\Component\DependencyInjection\Compiler\ResolveTaggedIteratorArgumentPass; @@ -67,6 +68,7 @@ use Symfony\Component\Notifier\TexterInterface; use Symfony\Component\PropertyAccess\PropertyAccessor; use Symfony\Component\Security\Core\AuthenticationEvents; +use Symfony\Component\Serializer\DependencyInjection\SerializerPass; use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader; use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader; @@ -1447,9 +1449,6 @@ public function testSerializerEnabled() $this->assertEquals(AttributeLoader::class, $argument[0]->getClass()); $this->assertEquals(new Reference('serializer.name_converter.camel_case_to_snake_case'), $container->getDefinition('serializer.name_converter.metadata_aware')->getArgument(1)); $this->assertEquals(new Reference('property_info', ContainerBuilder::IGNORE_ON_INVALID_REFERENCE), $container->getDefinition('serializer.normalizer.object')->getArgument(3)); - $this->assertArrayHasKey('circular_reference_handler', $container->getDefinition('serializer.normalizer.object')->getArgument(6)); - $this->assertArrayHasKey('max_depth_handler', $container->getDefinition('serializer.normalizer.object')->getArgument(6)); - $this->assertEquals($container->getDefinition('serializer.normalizer.object')->getArgument(6)['max_depth_handler'], new Reference('my.max.depth.handler')); } public function testSerializerWithoutTranslator() @@ -1547,13 +1546,22 @@ public function testJsonSerializableNormalizerRegistered() public function testObjectNormalizerRegistered() { - $container = $this->createContainerFromFile('full'); + $container = $this->createContainerFromFile('full', compile: false); + $container->addCompilerPass(new SerializerPass()); + $container->addCompilerPass(new ResolveBindingsPass()); + $container->compile(); $definition = $container->getDefinition('serializer.normalizer.object'); $tag = $definition->getTag('serializer.normalizer'); $this->assertEquals(ObjectNormalizer::class, $definition->getClass()); $this->assertEquals(-1000, $tag[0]['priority']); + + $this->assertEquals([ + 'enable_max_depth' => true, + 'circular_reference_handler' => new Reference('my.circular.reference.handler'), + 'max_depth_handler' => new Reference('my.max.depth.handler'), + ], $definition->getArgument(6)); } public function testConstraintViolationListNormalizerRegistered() diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 9b3e7c86ea3ff..6689b61b05990 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": "^7.1", + "symfony/serializer": "^7.2.5", "symfony/stopwatch": "^6.4|^7.0", "symfony/string": "^6.4|^7.0", "symfony/translation": "^6.4|^7.0", @@ -97,7 +97,7 @@ "symfony/scheduler": "<6.4.4|>=7.0.0,<7.0.4", "symfony/security-csrf": "<7.2", "symfony/security-core": "<6.4", - "symfony/serializer": "<7.1", + "symfony/serializer": "<7.2.5", "symfony/stopwatch": "<6.4", "symfony/translation": "<6.4", "symfony/twig-bridge": "<6.4", diff --git a/src/Symfony/Component/Serializer/DependencyInjection/SerializerPass.php b/src/Symfony/Component/Serializer/DependencyInjection/SerializerPass.php index 7b7f6f1c2313b..179b7a3d92e9d 100644 --- a/src/Symfony/Component/Serializer/DependencyInjection/SerializerPass.php +++ b/src/Symfony/Component/Serializer/DependencyInjection/SerializerPass.php @@ -19,6 +19,7 @@ use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\Serializer\Debug\TraceableEncoder; use Symfony\Component\Serializer\Debug\TraceableNormalizer; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\SerializerInterface; /** @@ -54,17 +55,27 @@ public function process(ContainerBuilder $container): void throw new RuntimeException('You must tag at least one service as "serializer.encoder" to use the "serializer" service.'); } + $defaultContext = []; if ($container->hasParameter('serializer.default_context')) { $defaultContext = $container->getParameter('serializer.default_context'); - $this->bindDefaultContext($container, array_merge($normalizers, $encoders), $defaultContext); $container->getParameterBag()->remove('serializer.default_context'); $container->getDefinition('serializer')->setArgument('$defaultContext', $defaultContext); } + /** @var ?string $circularReferenceHandler */ + $circularReferenceHandler = $container->hasParameter('.serializer.circular_reference_handler') + ? $container->getParameter('.serializer.circular_reference_handler') : null; + + /** @var ?string $maxDepthHandler */ + $maxDepthHandler = $container->hasParameter('.serializer.max_depth_handler') + ? $container->getParameter('.serializer.max_depth_handler') : null; + + $this->bindDefaultContext($container, array_merge($normalizers, $encoders), $defaultContext, $circularReferenceHandler, $maxDepthHandler); + $this->configureSerializer($container, 'serializer', $normalizers, $encoders, 'default'); if ($namedSerializers) { - $this->configureNamedSerializers($container); + $this->configureNamedSerializers($container, $circularReferenceHandler, $maxDepthHandler); } } @@ -98,11 +109,22 @@ private function createNamedSerializerTags(ContainerBuilder $container, string $ } } - private function bindDefaultContext(ContainerBuilder $container, array $services, array $defaultContext): void + private function bindDefaultContext(ContainerBuilder $container, array $services, array $defaultContext, ?string $circularReferenceHandler, ?string $maxDepthHandler): void { foreach ($services as $id) { $definition = $container->getDefinition((string) $id); - $definition->setBindings(['array $defaultContext' => new BoundArgument($defaultContext, false)] + $definition->getBindings()); + + $context = $defaultContext; + if (is_a($definition->getClass(), ObjectNormalizer::class, true)) { + if (null !== $circularReferenceHandler) { + $context += ['circular_reference_handler' => new Reference($circularReferenceHandler)]; + } + if (null !== $maxDepthHandler) { + $context += ['max_depth_handler' => new Reference($maxDepthHandler)]; + } + } + + $definition->setBindings(['array $defaultContext' => new BoundArgument($context, false)] + $definition->getBindings()); } } @@ -125,7 +147,7 @@ private function configureSerializer(ContainerBuilder $container, string $id, ar $serializerDefinition->replaceArgument(1, $encoders); } - private function configureNamedSerializers(ContainerBuilder $container): void + private function configureNamedSerializers(ContainerBuilder $container, ?string $circularReferenceHandler, ?string $maxDepthHandler): void { $defaultSerializerNameConverter = $container->hasParameter('.serializer.name_converter') ? $container->getParameter('.serializer.name_converter') : null; @@ -149,7 +171,7 @@ private function configureNamedSerializers(ContainerBuilder $container): void $normalizers = $this->buildChildDefinitions($container, $serializerName, $normalizers, $config); $encoders = $this->buildChildDefinitions($container, $serializerName, $encoders, $config); - $this->bindDefaultContext($container, array_merge($normalizers, $encoders), $config['default_context']); + $this->bindDefaultContext($container, array_merge($normalizers, $encoders), $config['default_context'], $circularReferenceHandler, $maxDepthHandler); $container->registerChild($serializerId, 'serializer')->setArgument('$defaultContext', $config['default_context']); $container->registerAliasForArgument($serializerId, SerializerInterface::class, $serializerName.'.serializer'); @@ -184,7 +206,9 @@ private function buildChildDefinitions(ContainerBuilder $container, string $seri foreach ($services as &$id) { $childId = $id.'.'.$serializerName; - $definition = $container->registerChild($childId, (string) $id); + $definition = $container->registerChild($childId, (string) $id) + ->setClass($container->getDefinition((string) $id)->getClass()) + ; if (null !== $nameConverterIndex = $this->findNameConverterIndex($container, (string) $id)) { $definition->replaceArgument($nameConverterIndex, new Reference($config['name_converter'])); diff --git a/src/Symfony/Component/Serializer/Tests/DependencyInjection/SerializerPassTest.php b/src/Symfony/Component/Serializer/Tests/DependencyInjection/SerializerPassTest.php index 769243be25f88..88ec02b87c57d 100644 --- a/src/Symfony/Component/Serializer/Tests/DependencyInjection/SerializerPassTest.php +++ b/src/Symfony/Component/Serializer/Tests/DependencyInjection/SerializerPassTest.php @@ -19,6 +19,7 @@ use Symfony\Component\Serializer\Debug\TraceableNormalizer; use Symfony\Component\Serializer\Debug\TraceableSerializer; use Symfony\Component\Serializer\DependencyInjection\SerializerPass; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\SerializerInterface; /** @@ -99,6 +100,32 @@ public function testBindSerializerDefaultContext() $this->assertEquals($context, $container->getDefinition('serializer')->getArgument('$defaultContext')); } + /** + * @testWith [{}, {}] + * [{"serializer.default_context": {"enable_max_depth": true}}, {"enable_max_depth": true}] + * [{".serializer.circular_reference_handler": "foo"}, {"circular_reference_handler": "foo"}] + * [{".serializer.max_depth_handler": "bar"}, {"max_depth_handler": "bar"}] + * [{"serializer.default_context": {"enable_max_depth": true}, ".serializer.circular_reference_handler": "foo", ".serializer.max_depth_handler": "bar"}, {"enable_max_depth": true, "circular_reference_handler": "foo", "max_depth_handler": "bar"}] + */ + public function testBindObjectNormalizerDefaultContext(array $parameters, array $context) + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->register('serializer')->setArguments([null, null, []]); + $container->getParameterBag()->add($parameters); + $definition = $container->register('serializer.normalizer.object') + ->setClass(ObjectNormalizer::class) + ->addTag('serializer.normalizer') + ->addTag('serializer.encoder') + ; + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $bindings = $definition->getBindings(); + $this->assertEquals($bindings['array $defaultContext'], new BoundArgument($context, false)); + } + public function testNormalizersAndEncodersAreDecoratedAndOrderedWhenCollectingData() { $container = new ContainerBuilder(); @@ -565,7 +592,9 @@ public function testBindSerializerDefaultContextToNamedSerializers() $serializerPass = new SerializerPass(); $serializerPass->process($container); - $this->assertEmpty($definition->getBindings()); + $bindings = $definition->getBindings(); + $this->assertArrayHasKey('array $defaultContext', $bindings); + $this->assertEquals($bindings['array $defaultContext'], new BoundArgument([], false)); $bindings = $container->getDefinition('n1.api')->getBindings(); $this->assertArrayHasKey('array $defaultContext', $bindings); @@ -574,6 +603,37 @@ public function testBindSerializerDefaultContextToNamedSerializers() $this->assertEquals($defaultContext, $container->getDefinition('serializer.api')->getArgument('$defaultContext')); } + /** + * @testWith [{}, {}, {}] + * [{"enable_max_depth": true}, {}, {"enable_max_depth": true}] + * [{}, {".serializer.circular_reference_handler": "foo"}, {"circular_reference_handler": "foo"}] + * [{}, {".serializer.max_depth_handler": "bar"}, {"max_depth_handler": "bar"}] + * [{"enable_max_depth": true}, {".serializer.circular_reference_handler": "foo", ".serializer.max_depth_handler": "bar"}, {"enable_max_depth": true, "circular_reference_handler": "foo", "max_depth_handler": "bar"}] + */ + public function testBindNamedSerializerObjectNormalizerDefaultContext(array $defaultContext, array $parameters, array $context) + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.named_serializers', [ + 'api' => ['default_context' => $defaultContext], + ]); + + $container->register('serializer')->setArguments([null, null, []]); + $container->getParameterBag()->add($parameters); + $container->register('serializer.normalizer.object') + ->setClass(ObjectNormalizer::class) + ->addTag('serializer.normalizer', ['serializer' => '*']) + ->addTag('serializer.encoder', ['serializer' => '*']) + ; + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $bindings = $container->getDefinition('serializer.normalizer.object.api')->getBindings(); + $this->assertArrayHasKey('array $defaultContext', $bindings); + $this->assertEquals($bindings['array $defaultContext'], new BoundArgument($context, false)); + } + public function testNamedSerializersAreRegistered() { $container = new ContainerBuilder();