8000 [Serializer] Handle default context in Serializer · symfony/symfony@f1bac8a · GitHub
[go: up one dir, main page]

Skip to content

Commit f1bac8a

Browse files
committed
[Serializer] Handle default context in Serializer
1 parent f6312d3 commit f1bac8a

File tree

6 files changed

+88
-207
lines changed

6 files changed

+88
-207
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060

6161
$container->services()
6262
->set('serializer', Serializer::class)
63-
->args([[], []])
63+
->args([[], [], []])
6464

6565
->alias(SerializerInterface::class, 'serializer')
6666
->alias(NormalizerInterface::class, 'serializer')

src/Symfony/Component/Serializer/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ CHANGELOG
55
---
66

77
* Deprecate the `CompiledClassMetadataFactory` and `CompiledClassMetadataCacheWarmer` classes
8+
* Handle default context in `Serializer` class
89

910
7.2
1011
---

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

Lines changed: 14 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
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\SerializerInterface;
2322

2423
/**
2524
* Adds all services with the tags "serializer.encoder" and "serializer.normalizer" as
@@ -32,177 +31,47 @@ class SerializerPass implements CompilerPassInterface
3231
{
3332
use PriorityTaggedServiceTrait;
3433

35-
private const NAME_CONVERTER_METADATA_AWARE_ID = 'serializer.name_converter.metadata_aware';
36-
37-
public function process(ContainerBuilder $container): void
34+
/**
35+
* @return void
36+
*/
37+
public function process(ContainerBuilder $container)
3838
{
3939
if (!$container->hasDefinition('serializer')) {
4040
return;
4141
}
4242

43-
$namedSerializers = $container->hasParameter('.serializer.named_serializers')
44-
? $container->getParameter('.serializer.named_serializers') : [];
45-
46-
$this->createNamedSerializerTags($container, 'serializer.normalizer', 'include_built_in_normalizers', $namedSerializers);
47-
$this->createNamedSerializerTags($container, 'serializer.encoder', 'include_built_in_encoders', $namedSerializers);
48-
49-
if (!$normalizers = $this->findAndSortTaggedServices('serializer.normalizer.default', $container)) {
43+
if (!$normalizers = $this->findAndSortTaggedServices('serializer.normalizer', $container)) {
5044
throw new RuntimeException('You must tag at least one service as "serializer.normalizer" to use the "serializer" service.');
5145
}
5246

53-
if (!$encoders = $this->findAndSortTaggedServices('serializer.encoder.default', $container)) {
47+
if (!$encoders = $this->findAndSortTaggedServices('serializer.encoder', $container)) {
5448
throw new RuntimeException('You must tag at least one service as "serializer.encoder" to use the "serializer" service.');
5549
}
5650

5751
if ($container->hasParameter('serializer.default_context')) {
5852
$defaultContext = $container->getParameter('serializer.default_context');
59-
$this->bindDefaultContext($container, array_merge($normalizers, $encoders), $defaultContext);
60-
$container->getParameterBag()->remove('serializer.default_context');
61-
}
62-
63-
$this->configureSerializer($container, 'serializer', $normalizers, $encoders, 'default');
64-
65-
if ($namedSerializers) {
66-
$this->configureNamedSerializers($container);
67-
}
68-
}
69-
70-
private function createNamedSerializerTags(ContainerBuilder $container, string $tagName, string $configName, array $namedSerializers): void
71-
{
72-
$serializerNames = array_keys($namedSerializers);
73-
$withBuiltIn = array_filter($serializerNames, fn (string $name) => $namedSerializers[$name][$configName] ?? false);
74-
75-
foreach ($container->findTaggedServiceIds($tagName) as $serviceId => $tags) {
76-
$definition = $container->getDefinition($serviceId);
77-
78-
foreach ($tags as $tag) {
79-
$names = (array) ($tag['serializer'] ?? []);
80-
81-
if (!$names) {
82-
$names = ['default'];
83-
} elseif (\in_array('*', $names, true)) {
84-
$names = array_unique(['default', ...$serializerNames]);
85-
}
86-
87-
if ($tag['built_in'] ?? false) {
88-
$names = array_unique(['default', ...$names, ...$withBuiltIn]);
89-
}
90-
91-
unset($tag['serializer'], $tag['built_in']);
92-
93-
foreach ($names as $name) {
94-
$definition->addTag($tagName.'.'.$name, $tag);
95-
}
53+
foreach (array_merge($normalizers, $encoders) as $service) {
54+
$definition = $container->getDefinition($service);
55+
$definition->setBindings(['array $defaultContext' => new BoundArgument($defaultContext, false)] + $definition->getBindings());
9656
}
57+
$container->getDefinition('serializer')->replaceArgument(2, $defaultContext);;
58+
$container->getParameterBag()->remove('serializer.default_context');
9759
}
98-
}
99-
100-
private function bindDefaultContext(ContainerBuilder $container, array $services, array $defaultContext): void
101-
{
102-
foreach ($services as $id) {
103-
$definition = $container->getDefinition((string) $id);
104-
$definition->setBindings(['array $defaultContext' => new BoundArgument($defaultContext, false)] + $definition->getBindings());
105-
}
106-
}
10760

108-
private function configureSerializer(ContainerBuilder $container, string $id, array $normalizers, array $encoders, string $serializerName): void
109-
{
11061
if ($container->getParameter('kernel.debug') && $container->hasDefinition('serializer.data_collector')) {
11162
foreach ($normalizers as $i => $normalizer) {
11263
$normalizers[$i] = $container->register('.debug.serializer.normalizer.'.$normalizer, TraceableNormalizer::class)
113-
->setArguments([$normalizer, new Reference('serializer.data_collector'), $serializerName]);
64+
->setArguments([$normalizer, new Reference('serializer.data_collector')]);
11465
}
11566

11667
foreach ($encoders as $i => $encoder) {
11768
$encoders[$i] = $container->register('.debug.serializer.encoder.'.$encoder, TraceableEncoder::class)
118-
->setArguments([$encoder, new Reference('serializer.data_collector'), $serializerName]);
69+
->setArguments([$encoder, new Reference('serializer.data_collector')]);
11970
}
12071
}
12172

122-
$serializerDefinition = $container->getDefinition($id);
73+
$serializerDefinition = $container->getDefinition('serializer');
12374
$serializerDefinition->replaceArgument(0, $normalizers);
12475
$serializerDefinition->replaceArgument(1, $encoders);
12576
}
126-
127-
private function configureNamedSerializers(ContainerBuilder $container): void
128-
{
129-
$defaultSerializerNameConverter = $container->hasParameter('.serializer.name_converter')
130-
? $container->getParameter('.serializer.name_converter') : null;
131-
132-
foreach ($container->getParameter('.serializer.named_serializers') as $serializerName => $config) {
133-
$config += ['default_context' => [], 'name_converter' => null];
134-
$serializerId = 'serializer.'.$serializerName;
135-
136-
if (!$normalizers = $this->findAndSortTaggedServices('serializer.normalizer.'.$serializerName, $container)) {
137-
throw new RuntimeException(\sprintf('The named serializer "%1$s" requires at least one registered normalizer. Tag the normalizers as "serializer.normalizer" with the "serializer" attribute set to "%1$s".', $serializerName));
138-
}
139-
140-
if (!$encoders = $this->findAndSortTaggedServices('serializer.encoder.'.$serializerName, $container)) {
141-
throw new RuntimeException(\sprintf('The named serializer "%1$s" requires at least one registered encoder. Tag the encoders as "serializer.encoder" with the "serializer" attribute set to "%1$s".', $serializerName));
142-
}
143-
144-
$config['name_converter'] = $defaultSerializerNameConverter !== $config['name_converter']
145-
? $this->buildChildNameConverterDefinition($container, $config['name_converter'])
146-
: self::NAME_CONVERTER_METADATA_AWARE_ID;
147-
148-
$normalizers = $this->buildChildDefinitions($container, $serializerName, $normalizers, $config);
149-
$encoders = $this->buildChildDefinitions($container, $serializerName, $encoders, $config);
150-
151-
$this->bindDefaultContext($container, array_merge($normalizers, $encoders), $config['default_context']);
152-
153-
$container->registerChild($serializerId, 'serializer');
154-
$container->registerAliasForArgument($serializerId, SerializerInterface::class, $serializerName.'.serializer');
155-
156-
$this->configureSerializer($container, $serializerId, $normalizers, $encoders, $serializerName);
157-
158-
if ($container->getParameter('kernel.debug') && $container->hasDefinition('debug.serializer')) {
159-
$container->registerChild($debugId = 'debug.'.$serializerId, 'debug.serializer')
160-
->setDecoratedService($serializerId)
161-
->replaceArgument(0, new Reference($debugId.'.inner'))
162-
->replaceArgument(2, $serializerName);
163-
}
164-
}
165-
}
166-
167-
private function buildChildNameConverterDefinition(ContainerBuilder $container, ?string $nameConverter): ?string
168-
{
169-
$childId = self::NAME_CONVERTER_METADATA_AWARE_ID.'.'.ContainerBuilder::hash($nameConverter);
170-
171-
if (!$container->hasDefinition($childId)) {
172-
$childDefinition = $container->registerChild($childId, self::NAME_CONVERTER_METADATA_AWARE_ID.'.abstract');
173-
if (null !== $nameConverter) {
174-
$childDefinition->addArgument(new Reference($nameConverter));
175-
}
176-
}
177-
178-
return $childId;
179-
}
180-
181-
private function buildChildDefinitions(ContainerBuilder $container, string $serializerName, array $services, array $config): array
182-
{
183-
foreach ($services as &$id) {
184-
$childId = $id.'.'.$serializerName;
185-
186-
$definition = $container->registerChild($childId, (string) $id);
187-
188-
if (null !== $nameConverterIndex = $this->findNameConverterIndex($container, (string) $id)) {
189-
$definition->replaceArgument($nameConverterIndex, new Reference($config['name_converter']));
190-
}
191-
192-
$id = new Reference($childId);
193-
}
194-
195-
return $services;
196-
}
197-
198-
private function findNameConverterIndex(ContainerBuilder $container, string $id): int|string|null
199-
{
200-
foreach ($container->getDefinition($id)->getArguments() as $index => $argument) {
201-
if ($argument instanceof Reference && self::NAME_CONVERTER_METADATA_AWARE_ID === (string) $argument) {
202-
return $index;
203-
}
204-
}
205-
206-
return null;
207-
}
20877
}

src/Symfony/Component/Serializer/Serializer.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,12 @@ class Serializer implements SerializerInterface, NormalizerInterface, Denormaliz
7575
/**
7676
* @param array<NormalizerInterface|DenormalizerInterface> $normalizers
7777
* @param array<EncoderInterface|DecoderInterface> $encoders
78+
* @param array<string, mixed> $defaultContext
7879
*/
7980
public function __construct(
8081
private array $normalizers = [],
8182
array $encoders = [],
83+
private array $defaultContext = [],
8284
) {
8385
foreach ($normalizers as $normalizer) {
8486
if ($normalizer instanceof SerializerAwareInterface) {
@@ -149,17 +151,16 @@ public function normalize(mixed $data, ?string $format = null, array $context =
149151
if ($normalizer = $this->getNormalizer($data, $format, $context)) {
150152
return $normalizer->normalize($data, $format, $context);
151153
}
152-
153154
if (null === $data || \is_scalar($data)) {
154155
return $data;
155156
}
156157

157-
if (\is_array($data) && !$data && ($context[self::EMPTY_ARRAY_AS_OBJECT] ?? false)) {
158+
if (\is_array($data) && !$data && ($context[self::EMPTY_ARRAY_AS_OBJECT] ?? $this->defaultContext[self::EMPTY_ARRAY_AS_OBJECT] ?? false)) {
158159
return new \ArrayObject();
159160
}
160161

161162
if (is_iterable($data)) {
162-
if ($data instanceof \Countable && ($context[AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS] ?? false) && !\count($data)) {
163+
if ($data instanceof \Countable && ($context[AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS] ?? $this->defaultContext[AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS] ?? false) && !\count($data)) {
163164
return new \ArrayObject();
164165
}
165166

@@ -211,7 +212,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a
211212
throw new NotNormalizableValueException(\sprintf('Could not denormalize object of type "%s", no supporting normalizer found.', $type));
212213
}
213214

214-
if (isset($context[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS])) {
215+
if (isset($context[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS]) || isset($this->defaultContext[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS])) {
215216
unset($context[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS]);
216217
$context['not_normalizable_value_exceptions'] = [];
217218
$errors = &$context['not_normalizable_value_exceptions'];

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,17 +83,20 @@ public function testServicesAreOrderedAccordingToPriority()
8383

8484
public function testBindSerializerDefaultContext()
8585
{
86+
$context = ['enable_max_depth' => true];
87+
8688
$container = new ContainerBuilder();
8789
$container->setParameter('kernel.debug', false);
88-
$container->register('serializer')->setArguments([null, null]);
89-
$container->setParameter('serializer.default_context', ['enable_max_depth' => true]);
90+
$container->register('serializer')->setArguments([null, null, null]);
91+
$container->setParameter('serializer.default_context', $context);
9092
$definition = $container->register('n1')->addTag('serializer.normalizer')->addTag('serializer.encoder');
9193

9294
$serializerPass = new SerializerPass();
9395
$serializerPass->process($container);
9496

9597
$bindings = $definition->getBindings();
96-
$this->assertEquals($bindings['array $defaultContext'], new BoundArgument(['enable_max_depth' => true], false));
98+
$this->assertEquals($bindings['array $defaultContext'], new BoundArgument($context, false));
99+
$this->assertEquals($context, $container->getDefinition('serializer')->getArgument(2));
97100
}
98101

99102
public function testNormalizersAndEncodersAreDecoratedAndOrderedWhenCollectingData()

0 commit comments

Comments
 (0)
0