Skip to content

Commit 80d993f

Browse files
committed
[Serializer] Fix ObjectNormalizer default context with named serializers
1 parent 4471969 commit 80d993f

File tree

6 files changed

+110
-28
lines changed

6 files changed

+110
-28
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1946,24 +1946,14 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder
19461946
$container->setParameter('serializer.default_context', $defaultContext);
19471947
}
19481948

1949-
if (!$container->hasDefinition('serializer.normalizer.object')) {
1950-
return;
1951-
}
1952-
1953-
$arguments = $container->getDefinition('serializer.normalizer.object')->getArguments();
1954-
$context = $arguments[6] ?? $defaultContext;
1955-
1956-
if (isset($config['circular_reference_handler']) && $config['circular_reference_handler']) {
1957-
$context += ['circular_reference_handler' => new Reference($config['circular_reference_handler'])];
1958-
$container->getDefinition('serializer.normalizer.object')->setArgument(5, null);
1949+
if ($config['circular_reference_handler'] ?? false) {
1950+
$container->setParameter('.serializer.circular_reference_handler', $config['circular_reference_handler']);
19591951
}
19601952

19611953
if ($config['max_depth_handler'] ?? false) {
1962-
$context += ['max_depth_handler' => new Reference($config['max_depth_handler'])];
1954+
$container->setParameter('.serializer.max_depth_handler', $config['max_depth_handler']);
19631955
}
19641956

1965-
$container->getDefinition('serializer.normalizer.object')->setArgument(6, $context);
1966-
19671957
$container->getDefinition('serializer.normalizer.property')->setArgument(5, $defaultContext);
19681958

19691959
$container->setParameter('.serializer.named_serializers', $config['named_serializers'] ?? []);

src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@
129129
service('property_info')->ignoreOnInvalid(),
130130
service('serializer.mapping.class_discriminator_resolver')->ignoreOnInvalid(),
131131
null,
132-
null,
132+
abstract_arg('default context, set in the SerializerPass'),
133133
service('property_info')->ignoreOnInvalid(),
134134
])
135135
->tag('serializer.normalizer', ['built_in' => true, 'priority' => -1000])

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
3434
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
3535
use Symfony\Component\DependencyInjection\ChildDefinition;
36+
use Symfony\Component\DependencyInjection\Compiler\ResolveBindingsPass;
3637
use Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass;
3738
use Symfony\Component\DependencyInjection\Compiler\ResolveInstanceofConditionalsPass;
3839
use Symfony\Component\DependencyInjection\Compiler\ResolveTaggedIteratorArgumentPass;
@@ -67,6 +68,7 @@
6768
use Symfony\Component\Notifier\TexterInterface;
6869
use Symfony\Component\PropertyAccess\PropertyAccessor;
6970
use Symfony\Component\Security\Core\AuthenticationEvents;
71+
use Symfony\Component\Serializer\DependencyInjection\SerializerPass;
7072
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
7173
use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader;
7274
use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader;
@@ -1447,9 +1449,6 @@ public function testSerializerEnabled()
14471449
$this->assertEquals(AttributeLoader::class, $argument[0]->getClass());
14481450
$this->assertEquals(new Reference('serializer.name_converter.camel_case_to_snake_case'), $container->getDefinition('serializer.name_converter.metadata_aware')->getArgument(1));
14491451
$this->assertEquals(new Reference('property_info', ContainerBuilder::IGNORE_ON_INVALID_REFERENCE), $container->getDefinition('serializer.normalizer.object')->getArgument(3));
1450-
$this->assertArrayHasKey('circular_reference_handler', $container->getDefinition('serializer.normalizer.object')->getArgument(6));
1451-
$this->assertArrayHasKey('max_depth_handler', $container->getDefinition('serializer.normalizer.object')->getArgument(6));
1452-
$this->assertEquals($container->getDefinition('serializer.normalizer.object')->getArgument(6)['max_depth_handler'], new Reference('my.max.depth.handler'));
14531452
}
14541453

14551454
public function testSerializerWithoutTranslator()
@@ -1547,13 +1546,22 @@ public function testJsonSerializableNormalizerRegistered()
15471546

15481547
public function testObjectNormalizerRegistered()
15491548
{
1550-
$container = $this->createContainerFromFile('full');
1549+
$container = $this->createContainerFromFile('full', compile: false);
1550+
$container->addCompilerPass(new SerializerPass());
1551+
$container->addCompilerPass(new ResolveBindingsPass());
1552+
$container->compile();
15511553

15521554
$definition = $container->getDefinition('serializer.normalizer.object');
15531555
$tag = $definition->getTag('serializer.normalizer');
15541556

15551557
$this->assertEquals(ObjectNormalizer::class, $definition->getClass());
15561558
$this->assertEquals(-1000, $tag[0]['priority']);
1559+
1560+
$this->assertEquals([
1561+
'enable_max_depth' => true,
1562+
'circular_reference_handler' => new Reference('my.circular.reference.handler'),
1563+
'max_depth_handler' => new Reference('my.max.depth.handler'),
1564+
], $definition->getArgument(6));
15571565
}
15581566

15591567
public function testConstraintViolationListNormalizerRegistered()

src/Symfony/Bundle/FrameworkBundle/composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
"symfony/scheduler": "^6.4.4|^7.0.4",
6060
"symfony/security-bundle": "^6.4|^7.0",
6161
"symfony/semaphore": "^6.4|^7.0",
62-
"symfony/serializer": "^7.1",
62+
"symfony/serializer": "^7.2.5",
6363
"symfony/stopwatch": "^6.4|^7.0",
6464
"symfony/string": "^6.4|^7.0",
6565
"symfony/translation": "^6.4|^7.0",
@@ -97,7 +97,7 @@
9797
"symfony/scheduler": "<6.4.4|>=7.0.0,<7.0.4",
9898
"symfony/security-csrf": "<7.2",
9999
"symfony/security-core": "<6.4",
100-
"symfony/serializer": "<7.1",
100+
"symfony/serializer": "<7.2.5",
101101
"symfony/stopwatch": "<6.4",
102102
"symfony/translation": "<6.4",
103103
"symfony/twig-bridge": "<6.4",

src/Symfony/Component/Serializer/DependencyInjection/SerializerPass.php

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Symfony\Component\DependencyInjection\Reference;
2020
use Symfony\Component\Serializer\Debug\TraceableEncoder;
2121
use Symfony\Component\Serializer\Debug\TraceableNormalizer;
22+
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
2223
use Symfony\Component\Serializer\SerializerInterface;
2324

2425
/**
@@ -54,17 +55,27 @@ public function process(ContainerBuilder $container): void
5455
throw new RuntimeException('You must tag at least one service as "serializer.encoder" to use the "serializer" service.');
5556
}
5657

58+
$defaultContext = [];
5759
if ($container->hasParameter('serializer.default_context')) {
5860
$defaultContext = $container->getParameter('serializer.default_context');
59-
$this->bindDefaultContext($container, array_merge($normalizers, $encoders), $defaultContext);
6061
$container->getParameterBag()->remove('serializer.default_context');
6162
$container->getDefinition('serializer')->setArgument('$defaultContext', $defaultContext);
6263
}
6364

65+
/** @var ?string $circularReferenceHandler */
66+
$circularReferenceHandler = $container->hasParameter('.serializer.circular_reference_handler')
67+
? $container->getParameter('.serializer.circular_reference_handler') : null;
68+
69+
/** @var ?string $maxDepthHandler */
70+
$maxDepthHandler = $container->hasParameter('.serializer.max_depth_handler')
71+
? $container->getParameter('.serializer.max_depth_handler') : null;
72+
73+
$this->bindDefaultContext($container, array_merge($normalizers, $encoders), $defaultContext, $circularReferenceHandler, $maxDepthHandler);
74+
6475
$this->configureSerializer($container, 'serializer', $normalizers, $encoders, 'default');
6576

6677
if ($namedSerializers) {
67-
$this->configureNamedSerializers($container);
78+
$this->configureNamedSerializers($container, $circularReferenceHandler, $maxDepthHandler);
6879
}
6980
}
7081

@@ -98,11 +109,22 @@ private function createNamedSerializerTags(ContainerBuilder $container, string $
98109
}
99110
}
100111

101-
private function bindDefaultContext(ContainerBuilder $container, array $services, array $defaultContext): void
112+
private function bindDefaultContext(ContainerBuilder $container, array $services, array $defaultContext, ?string $circularReferenceHandler, ?string $maxDepthHandler): void
102113
{
103114
foreach ($services as $id) {
104115
$definition = $container->getDefinition((string) $id);
105-
$definition->setBindings(['array $defaultContext' => new BoundArgument($defaultContext, false)] + $definition->getBindings());
116+
117+
$context = $defaultContext;
118+
if (is_a($definition->getClass(), ObjectNormalizer::class, true)) {
119+
if (null !== $circularReferenceHandler) {
120+
$context += ['circular_reference_handler' => new Reference($circularReferenceHandler)];
121+
}
122+
if (null !== $maxDepthHandler) {
123+
$context += ['max_depth_handler' => new Reference($maxDepthHandler)];
124+
}
125+
}
126+
127+
$definition->setBindings(['array $defaultContext' => new BoundArgument($context, false)] + $definition->getBindings());
106128
}
107129
}
108130

@@ -125,7 +147,7 @@ private function configureSerializer(ContainerBuilder $container, string $id, ar
125147
$serializerDefinition->replaceArgument(1, $encoders);
126148
}
127149

128-
private function configureNamedSerializers(ContainerBuilder $container): void
150+
private function configureNamedSerializers(ContainerBuilder $container, ?string $circularReferenceHandler, ?string $maxDepthHandler): void
129151
{
130152
$defaultSerializerNameConverter = $container->hasParameter('.serializer.name_converter')
131153
? $container->getParameter('.serializer.name_converter') : null;
@@ -149,7 +171,7 @@ private function configureNamedSerializers(ContainerBuilder $container): void
149171
$normalizers = $this->buildChildDefinitions($container, $serializerName, $normalizers, $config);
150172
$encoders = $this->buildChildDefinitions($container, $serializerName, $encoders, $config);
151173

152-
$this->bindDefaultContext($container, array_merge($normalizers, $encoders), $config['default_context']);
174+
$this->bindDefaultContext($container, array_merge($normalizers, $encoders), $config['default_context'], $circularReferenceHandler, $maxDepthHandler);
153175

154176
$container->registerChild($serializerId, 'serializer')->setArgument('$defaultContext', $config['default_context']);
155177
$container->registerAliasForArgument($serializerId, SerializerInterface::class, $serializerName.'.serializer');
@@ -184,7 +206,9 @@ private function buildChildDefinitions(ContainerBuilder $container, string $seri
184206
foreach ($services as &$id) {
185207
$childId = $id.'.'.$serializerName;
186208

187-
$definition = $container->registerChild($childId, (string) $id);
209+
$definition = $container->registerChild($childId, (string) $id)
210+
->setClass($container->getDefinition((string) $id)->getClass())
211+
;
188212

189213
if (null !== $nameConverterIndex = $this->findNameConverterIndex($container, (string) $id)) {
190214
$definition->replaceArgument($nameConverterIndex, new Reference($config['name_converter']));

src/Symfony/Component/Serializer/Tests/DependencyInjection/SerializerPassTest.php

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Symfony\Component\Serializer\Debug\TraceableNormalizer;
2020
use Symfony\Component\Serializer\Debug\TraceableSerializer;
2121
use Symfony\Component\Serializer\DependencyInjection\SerializerPass;
22+
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
2223
use Symfony\Component\Serializer\SerializerInterface;
2324

2425
/**
@@ -99,6 +100,32 @@ public function testBindSerializerDefaultContext()
99100
$this->assertEquals($context, $container->getDefinition('serializer')->getArgument('$defaultContext'));
100101
}
101102

103+
/**
104+
* @testWith [{}, {}]
105+
* [{"serializer.default_context": {"enable_max_depth": true}}, {"enable_max_depth": true}]
106+
* [{".serializer.circular_reference_handler": "foo"}, {"circular_reference_handler": "foo"}]
107+
* [{".serializer.max_depth_handler": "bar"}, {"max_depth_handler": "bar"}]
108+
* [{"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"}]
109+
*/
110+
public function testBindObjectNormalizerDefaultContext(array $parameters, array $context)
111+
{
112+
$container = new ContainerBuilder();
113+
$container->setParameter('kernel.debug', false);
114+
$container->register('serializer')->setArguments([null, null, []]);
115+
$container->getParameterBag()->add($parameters);
116+
$definition = $container->register('serializer.normalizer.object')
117+
->setClass(ObjectNormalizer::class)
118+
->addTag('serializer.normalizer')
119+
->addTag('serializer.encoder')
120+
;
121+
122+
$serializerPass = new SerializerPass();
123+
$serializerPass->process($container);
124+
125+
$bindings = $definition->getBindings();
126+
$this->assertEquals($bindings['array $defaultContext'], new BoundArgument($context, false));
127+
}
128+
102129
public function testNormalizersAndEncodersAreDecoratedAndOrderedWhenCollectingData()
103130
{
104131
$container = new ContainerBuilder();
@@ -565,7 +592,9 @@ public function testBindSerializerDefaultContextToNamedSerializers()
565592
$serializerPass = new SerializerPass();
566593
$serializerPass->process($container);
567594

568-
$this->assertEmpty($definition->getBindings());
595+
$bindings = $definition->getBindings();
596+
$this->assertArrayHasKey('array $defaultContext', $bindings);
597+
$this->assertEquals($bindings['array $defaultContext'], new BoundArgument([], false));
569598

570599
$bindings = $container->getDefinition('n1.api')->getBindings();
571600
$this->assertArrayHasKey('array $defaultContext', $bindings);
@@ -574,6 +603,37 @@ public function testBindSerializerDefaultContextToNamedSerializers()
574603
$this->assertEquals($defaultContext, $container->getDefinition('serializer.api')->getArgument('$defaultContext'));
575604
}
576605

606+
/**
607+
* @testWith [{}, {}, {}]
608+
* [{"enable_max_depth": true}, {}, {"enable_max_depth": true}]
609+
* [{}, {".serializer.circular_reference_handler": "foo"}, {"circular_reference_handler": "foo"}]
610+
* [{}, {".serializer.max_depth_handler": "bar"}, {"max_depth_handler": "bar"}]
611+
* [{"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"}]
612+
*/
613+
public function testBindNamedSerializerObjectNormalizerDefaultContext(array $defaultContext, array $parameters, array $context)
614+
{
615+
$container = new ContainerBuilder();
616+
$container->setParameter('kernel.debug', false);
617+
$container->setParameter('.serializer.named_serializers', [
618+
'api' => ['default_context' => $defaultContext],
619+
]);
620+
621+
$container->register('serializer')->setArguments([null, null, []]);
622+
$container->getParameterBag()->add($parameters);
623+
$container->register('serializer.normalizer.object')
624+
->setClass(ObjectNormalizer::class)
625+
->addTag('serializer.normalizer', ['serializer' => '*'])
626+
->addTag('serializer.encoder', ['serializer' => '*'])
627+
;
628+
629+
$serializerPass = new SerializerPass();
630+
$serializerPass->process($container);
631+
632+
$bindings = $container->getDefinition('serializer.normalizer.object.api')->getBindings();
633+
$this->assertArrayHasKey('array $defaultContext', $bindings);
634+
$this->assertEquals($bindings['array $defaultContext'], new BoundArgument($context, false));
635+
}
636+
577637
public function testNamedSerializersAreRegistered()
578638
{
579639
$container = new ContainerBuilder();

0 commit comments

Comments
 (0)