From 29bd8ce6c2a87e0be80f948aec04eb514060a50e Mon Sep 17 00:00:00 2001 From: HypeMC Date: Mon, 20 May 2024 06:59:56 +0200 Subject: [PATCH] [Serializer] Introduce named serializers --- .../Bundle/FrameworkBundle/CHANGELOG.md | 1 + .../DependencyInjection/Configuration.php | 43 +- .../FrameworkExtension.php | 3 + .../Resources/config/messenger.php | 2 +- .../Resources/config/schema/symfony-1.0.xsd | 11 + .../Resources/config/serializer.php | 44 +- .../Resources/config/serializer_debug.php | 1 + .../DependencyInjection/ConfigurationTest.php | 18 + .../DependencyInjection/Fixtures/php/full.php | 7 + .../DependencyInjection/Fixtures/xml/full.xml | 5 + .../DependencyInjection/Fixtures/yml/full.yml | 6 + .../FrameworkExtensionTestCase.php | 20 + .../Bundle/WebProfilerBundle/CHANGELOG.md | 5 + .../views/Collector/serializer.html.twig | 85 +-- .../Bundle/WebProfilerBundle/composer.json | 3 +- src/Symfony/Component/Serializer/CHANGELOG.md | 3 + .../DataCollector/SerializerDataCollector.php | 90 +-- .../Serializer/Debug/TraceableEncoder.php | 5 +- .../Serializer/Debug/TraceableNormalizer.php | 5 +- .../Serializer/Debug/TraceableSerializer.php | 13 +- .../DependencyInjection/SerializerPass.php | 152 ++++- .../SerializerDataCollectorTest.php | 174 ++++-- .../Tests/Debug/TraceableEncoderTest.php | 26 +- .../Tests/Debug/TraceableNormalizerTest.php | 26 +- .../Tests/Debug/TraceableSerializerTest.php | 20 +- .../SerializerPassTest.php | 520 +++++++++++++++++- .../Component/Serializer/composer.json | 2 +- 27 files changed, 1095 insertions(+), 195 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 7b5bbaeda8e29..f6fb34737cd56 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -15,6 +15,7 @@ CHANGELOG * Register `Symfony\Component\Serializer\NameConverter\SnakeCaseToCamelCaseNameConverter` as a service named `serializer.name_converter.snake_case_to_camel_case` if available * Deprecate `session.sid_length` and `session.sid_bits_per_character` config options * Add the ability to use an existing service as a lock/semaphore resource + * Add support for configuring multiple serializer instances via the configuration 7.1 --- diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 02872f1b805bd..0914a0d22789b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -1099,10 +1099,22 @@ private function addAnnotationsSection(ArrayNodeDefinition $rootNode): void private function addSerializerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { + $defaultContextNode = fn () => (new NodeBuilder()) + ->arrayNode('default_context') + ->normalizeKeys(false) + ->validate() + ->ifTrue(fn () => $this->debug && class_exists(JsonParser::class)) + ->then(fn (array $v) => $v + [JsonDecode::DETAILED_ERROR_MESSAGES => true]) + ->end() + ->defaultValue([]) + ->prototype('variable')->end() + ; + $rootNode ->children() ->arrayNode('serializer') ->info('Serializer configuration') + ->fixXmlConfig('named_serializer', 'named_serializers') ->{$enableIfStandalone('symfony/serializer', Serializer::class)}() ->children() ->booleanNode('enable_attributes')->{class_exists(FullStack::class) ? 'defaultFalse' : 'defaultTrue'}()->end() @@ -1118,19 +1130,36 @@ private function addSerializerSection(ArrayNodeDefinition $rootNode, callable $e ->end() ->end() ->end() - ->arrayNode('default_context') - ->normalizeKeys(false) + ->append($defaultContextNode()) + ->arrayNode('named_serializers') + ->useAttributeAsKey('name') + ->arrayPrototype() + ->children() + ->scalarNode('name_converter')->end() + ->append($defaultContextNode()) + ->booleanNode('include_built_in_normalizers') + ->info('Whether to include the built-in normalizers') + ->defaultTrue() + ->end() + ->booleanNode('include_built_in_encoders') + ->info('Whether to include the built-in encoders') + ->defaultTrue() + ->end() + ->end() + ->end() ->validate() - ->ifTrue(fn () => $this->debug && class_exists(JsonParser::class)) - ->then(fn (array $v) => $v + [JsonDecode::DETAILED_ERROR_MESSAGES => true]) + ->ifTrue(fn ($v) => isset($v['default'])) + ->thenInvalid('"default" is a reserved name.') ->end() - ->defaultValue([]) - ->prototype('variable')->end() ->end() ->end() ->validate() ->ifTrue(fn ($v) => $this->debug && class_exists(JsonParser::class) && !isset($v['default_context'][JsonDecode::DETAILED_ERROR_MESSAGES])) - ->then(function ($v) { $v['default_context'][JsonDecode::DETAILED_ERROR_MESSAGES] = true; return $v; }) + ->then(function ($v) { + $v['default_context'][JsonDecode::DETAILED_ERROR_MESSAGES] = true; + + return $v; + }) ->end() ->end() ->end() diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 44a27f9035278..17c779276c379 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -1906,6 +1906,7 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder $container->getDefinition('serializer.mapping.cache_warmer')->replaceArgument(0, $serializerLoaders); if (isset($config['name_converter']) && $config['name_converter']) { + $container->setParameter('.serializer.name_converter', $config['name_converter']); $container->getDefinition('serializer.name_converter.metadata_aware')->setArgument(1, new Reference($config['name_converter'])); } @@ -1934,6 +1935,8 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder $container->getDefinition('serializer.normalizer.object')->setArgument(6, $context); $container->getDefinition('serializer.normalizer.property')->setArgument(5, $defaultContext); + + $container->setParameter('.serializer.named_serializers', $config['named_serializers'] ?? []); } private function registerPropertyInfoConfiguration(ContainerBuilder $container, PhpFileLoader $loader): void diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php index df247609653f3..40f5b84caa2e0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php @@ -72,7 +72,7 @@ ]) ->set('serializer.normalizer.flatten_exception', FlattenExceptionNormalizer::class) - ->tag('serializer.normalizer', ['priority' => -880]) + ->tag('serializer.normalizer', ['built_in' => true, 'priority' => -880]) ->set('messenger.transport.native_php_serializer', PhpSerializer::class) ->alias('messenger.default_serializer', 'messenger.transport.native_php_serializer') diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index c6816fbd089db..64e9c76cbd765 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -320,6 +320,7 @@ + @@ -332,6 +333,16 @@ + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php index 36c8b89b5a9ea..d689d42995ef9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php @@ -80,46 +80,46 @@ ->set('serializer.normalizer.constraint_violation_list', ConstraintViolationListNormalizer::class) ->args([1 => service('serializer.name_converter.metadata_aware')]) ->autowire(true) - ->tag('serializer.normalizer', ['priority' => -915]) + ->tag('serializer.normalizer', ['built_in' => true, 'priority' => -915]) ->set('serializer.normalizer.mime_message', MimeMessageNormalizer::class) ->args([service('serializer.normalizer.property')]) - ->tag('serializer.normalizer', ['priority' => -915]) + ->tag('serializer.normalizer', ['built_in' => true, 'priority' => -915]) ->set('serializer.normalizer.datetimezone', DateTimeZoneNormalizer::class) - ->tag('serializer.normalizer', ['priority' => -915]) + ->tag('serializer.normalizer', ['built_in' => true, 'priority' => -915]) ->set('serializer.normalizer.dateinterval', DateIntervalNormalizer::class) - ->tag('serializer.normalizer', ['priority' => -915]) + ->tag('serializer.normalizer', ['built_in' => true, 'priority' => -915]) ->set('serializer.normalizer.data_uri', DataUriNormalizer::class) ->args([service('mime_types')->nullOnInvalid()]) - ->tag('serializer.normalizer', ['priority' => -920]) + ->tag('serializer.normalizer', ['built_in' => true, 'priority' => -920]) ->set('serializer.normalizer.datetime', DateTimeNormalizer::class) - ->tag('serializer.normalizer', ['priority' => -910]) + ->tag('serializer.normalizer', ['built_in' => true, 'priority' => -910]) ->set('serializer.normalizer.json_serializable', JsonSerializableNormalizer::class) ->args([null, null]) - ->tag('serializer.normalizer', ['priority' => -950]) + ->tag('serializer.normalizer', ['built_in' => true, 'priority' => -950]) ->set('serializer.normalizer.problem', ProblemNormalizer::class) ->args([param('kernel.debug'), '$translator' => service('translator')->nullOnInvalid()]) - ->tag('serializer.normalizer', ['priority' => -890]) + ->tag('serializer.normalizer', ['built_in' => true, 'priority' => -890]) ->set('serializer.denormalizer.unwrapping', UnwrappingDenormalizer::class) ->args([service('serializer.property_accessor')]) - ->tag('serializer.normalizer', ['priority' => 1000]) + ->tag('serializer.normalizer', ['built_in' => true, 'priority' => 1000]) ->set('serializer.normalizer.uid', UidNormalizer::class) - ->tag('serializer.normalizer', ['priority' => -890]) + ->tag('serializer.normalizer', ['built_in' => true, 'priority' => -890]) ->set('serializer.normalizer.translatable', TranslatableNormalizer::class) ->args(['$translator' => service('translator')]) - ->tag('serializer.normalizer', ['priority' => -920]) + ->tag('serializer.normalizer', ['built_in' => true, 'priority' => -920]) ->set('serializer.normalizer.form_error', FormErrorNormalizer::class) - ->tag('serializer.normalizer', ['priority' => -915]) + ->tag('serializer.normalizer', ['built_in' => true, 'priority' => -915]) ->set('serializer.normalizer.object', ObjectNormalizer::class) ->args([ @@ -132,7 +132,7 @@ null, service('property_info')->ignoreOnInvalid(), ]) - ->tag('serializer.normalizer', ['priority' => -1000]) + ->tag('serializer.normalizer', ['built_in' => true, 'priority' => -1000]) ->set('serializer.normalizer.property', PropertyNormalizer::class) ->args([ @@ -144,7 +144,7 @@ ]) ->set('serializer.denormalizer.array', ArrayDenormalizer::class) - ->tag('serializer.normalizer', ['priority' => -990]) + ->tag('serializer.normalizer', ['built_in' => true, 'priority' => -990]) // Loader ->set('serializer.mapping.chain_loader', LoaderChain::class) @@ -174,26 +174,30 @@ // Encoders ->set('serializer.encoder.xml', XmlEncoder::class) - ->tag('serializer.encoder') + ->tag('serializer.encoder', ['built_in' => true]) ->set('serializer.encoder.json', JsonEncoder::class) ->args([null, null]) - ->tag('serializer.encoder') + ->tag('serializer.encoder', ['built_in' => true]) ->set('serializer.encoder.yaml', YamlEncoder::class) ->args([null, null]) - ->tag('serializer.encoder') + ->tag('serializer.encoder', ['built_in' => true]) ->set('serializer.encoder.csv', CsvEncoder::class) - ->tag('serializer.encoder') + ->tag('serializer.encoder', ['built_in' => true]) // Name converters ->set('serializer.name_converter.camel_case_to_snake_case', CamelCaseToSnakeCaseNameConverter::class) ->set('serializer.name_converter.snake_case_to_camel_case', SnakeCaseToCamelCaseNameConverter::class) - ->set('serializer.name_converter.metadata_aware', MetadataAwareNameConverter::class) + ->set('serializer.name_converter.metadata_aware.abstract', MetadataAwareNameConverter::class) + ->abstract() ->args([service('serializer.mapping.class_metadata_factory')]) + ->set('serializer.name_converter.metadata_aware') + ->parent('serializer.name_converter.metadata_aware.abstract') + // PropertyInfo extractor ->set('property_info.serializer_extractor', SerializerExtractor::class) ->args([service('serializer.mapping.class_metadata_factory')]) @@ -216,6 +220,6 @@ ]) ->set('serializer.normalizer.backed_enum', BackedEnumNormalizer::class) - ->tag('serializer.normalizer', ['priority' => -915]) + ->tag('serializer.normalizer', ['built_in' => true, 'priority' => -915]) ; }; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer_debug.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer_debug.php index 45b764fdd6b7d..520d145cbcf56 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer_debug.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer_debug.php @@ -21,6 +21,7 @@ ->args([ service('debug.serializer.inner'), service('serializer.data_collector'), + 'default', ]) ->set('serializer.data_collector', SerializerDataCollector::class) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index c4c81538c61ad..73e6c20256fdf 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -784,6 +784,7 @@ protected static function getBundleDefaultConfig() 'enabled' => true, 'enable_attributes' => !class_exists(FullStack::class), 'mapping' => ['paths' => []], + 'named_serializers' => [], ], 'property_access' => [ 'enabled' => true, @@ -958,4 +959,21 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor ], ]; } + + public function testNamedSerializersReservedName() + { + $processor = new Processor(); + $configuration = new Configuration(true); + + $this->expectException(InvalidConfigurationException::class); + $this->expectExceptionMessage('Invalid configuration for path "framework.serializer.named_serializers": "default" is a reserved name.'); + + $processor->processConfiguration($configuration, [[ + 'serializer' => [ + 'named_serializers' => [ + 'default' => ['include_built_in_normalizers' => false], + ], + ], + ]]); + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php index 8c97c84a5d8b7..0a32ce8b36434 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php @@ -66,6 +66,13 @@ 'circular_reference_handler' => 'my.circular.reference.handler', 'max_depth_handler' => 'my.max.depth.handler', 'default_context' => ['enable_max_depth' => true], + 'named_serializers' => [ + 'api' => [ + 'include_built_in_normalizers' => true, + 'include_built_in_encoders' => true, + 'default_context' => ['enable_max_depth' => false], + ], + ], ], 'property_info' => true, 'type_info' => true, diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml index 48c5e6f6d44d1..c01e857838bc3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml @@ -38,6 +38,11 @@ true + + + false + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml index 69a19baa4b73b..7550749eb1a1e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml @@ -57,6 +57,12 @@ framework: max_depth_handler: my.max.depth.handler default_context: enable_max_depth: true + named_serializers: + api: + include_built_in_normalizers: true + include_built_in_encoders: true + default_context: + enable_max_depth: false type_info: ~ property_info: ~ ide: file%%link%%format diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php index b4d7af1042c64..8d902bd47a54d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php @@ -1463,6 +1463,26 @@ public function testSerializerWithoutTranslator() $this->assertFalse($container->hasDefinition('serializer.normalizer.translatable')); } + public function testSerializerDefaultParameters() + { + $container = $this->createContainerFromFile('serializer_enabled'); + $this->assertFalse($container->hasParameter('.serializer.name_converter')); + $this->assertFalse($container->hasParameter('serializer.default_context')); + $this->assertTrue($container->hasParameter('.serializer.named_serializers')); + $this->assertSame([], $container->getParameter('.serializer.named_serializers')); + } + + public function testSerializerParametersAreSet() + { + $container = $this->createContainerFromFile('full'); + $this->assertTrue($container->hasParameter('.serializer.name_converter')); + $this->assertSame('serializer.name_converter.camel_case_to_snake_case', $container->getParameter('.serializer.name_converter')); + $this->assertTrue($container->hasParameter('serializer.default_context')); + $this->assertSame(['enable_max_depth' => true], $container->getParameter('serializer.default_context')); + $this->assertTrue($container->hasParameter('.serializer.named_serializers')); + $this->assertSame(['api' => ['include_built_in_normalizers' => true, 'include_built_in_encoders' => true, 'default_context' => ['enable_max_depth' => false]]], $container->getParameter('.serializer.named_serializers')); + } + public function testRegisterSerializerExtractor() { $container = $this->createContainerFromFile('full'); diff --git a/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md b/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md index f1cb83280b9d8..6d2f8eb554644 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.2 +--- + + * Add support for displaying profiles of multiple serializer instances + 7.1 --- diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/serializer.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/serializer.html.twig index b297ebffb729a..8276385c2257f 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/serializer.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/serializer.html.twig @@ -115,20 +115,33 @@
- {{ _self.render_serialize_tab(collector.data, true) }} - {{ _self.render_serialize_tab(collector.data, false) }} - - {{ _self.render_normalize_tab(collector.data, true) }} - {{ _self.render_normalize_tab(collector.data, false) }} - - {{ _self.render_encode_tab(collector.data, true) }} - {{ _self.render_encode_tab(collector.data, false) }} + {% for serializer in collector.serializerNames %} + {{ _self.render_serializer_tab(collector, serializer) }} + {% endfor %}
{% endif %} {% endblock %} -{% macro render_serialize_tab(collectorData, serialize) %} +{% macro render_serializer_tab(collector, serializer) %} +
+

{{ serializer }} {{ collector.handledCount(serializer) }}

+
+
+ {{ _self.render_serialize_tab(collector.data(serializer), true, serializer) }} + {{ _self.render_serialize_tab(collector.data(serializer), false, serializer) }} + + {{ _self.render_normalize_tab(collector.data(serializer), true, serializer) }} + {{ _self.render_normalize_tab(collector.data(serializer), false, serializer) }} + + {{ _self.render_encode_tab(collector.data(serializer), true, serializer) }} + {{ _self.render_encode_tab(collector.data(serializer), false, serializer) }} +
+
+
+{% endmacro %} + +{% macro render_serialize_tab(collectorData, serialize, serializer) %} {% set data = serialize ? collectorData.serialize : collectorData.deserialize %} {% set cellPrefix = serialize ? 'serialize' : 'deserialize' %} @@ -154,12 +167,12 @@ {% for item in data %} - {{ _self.render_data_cell(item, loop.index, cellPrefix) }} - {{ _self.render_context_cell(item, loop.index, cellPrefix) }} - {{ _self.render_normalizer_cell(item, loop.index, cellPrefix) }} - {{ _self.render_encoder_cell(item, loop.index, cellPrefix) }} + {{ _self.render_data_cell(item, loop.index, cellPrefix, serializer) }} + {{ _self.render_context_cell(item, loop.index, cellPrefix, serializer) }} + {{ _self.render_normalizer_cell(item, loop.index, cellPrefix, serializer) }} + {{ _self.render_encoder_cell(item, loop.index, cellPrefix, serializer) }} {{ _self.render_time_cell(item) }} - {{ _self.render_caller_cell(item, loop.index, cellPrefix) }} + {{ _self.render_caller_cell(item, loop.index, cellPrefix, serializer) }} {% endfor %} @@ -169,8 +182,10 @@ {% endmacro %} -{% macro render_caller_cell(item, index, method) %} +{% macro render_caller_cell(item, index, method, serializer) %} {% if item.caller is defined %} + {% set trace_id = 'sf-trace-' ~ serializer ~ '-' ~ method ~ '-' ~ index %} + - {% endmacro %} -{% macro render_context_cell(item, index, method) %} - {% set context_id = 'context-' ~ method ~ '-' ~ index %} +{% macro render_context_cell(item, index, method, serializer) %} + {% set context_id = 'context-' ~ serializer ~ '-' ~ method ~ '-' ~ index %} {% if item.type %} Type: {{ item.type }} @@ -308,8 +323,8 @@ {% endmacro %} -{% macro render_normalizer_cell(item, index, method) %} - {% set nested_normalizers_id = 'nested-normalizers-' ~ method ~ '-' ~ index %} +{% macro render_normalizer_cell(item, index, method, serializer) %} + {% set nested_normalizers_id = 'nested-normalizers-' ~ serializer ~ '-' ~ method ~ '-' ~ index %} {% if item.normalizer is defined %} {{ item.normalizer.class }} ({{ '%.2f'|format(item.normalizer.time * 1000) }} ms) @@ -329,8 +344,8 @@ {% endif %} {% endmacro %} -{% macro render_encoder_cell(item, index, method) %} - {% set nested_encoders_id = 'nested-encoders-' ~ method ~ '-' ~ index %} +{% macro render_encoder_cell(item, index, method, serializer) %} + {% set nested_encoders_id = 'nested-encoders-' ~ serializer ~ '-' ~ method ~ '-' ~ index %} {% if item.encoder is defined %} {{ item.encoder.class }} ({{ '%.2f'|format(item.encoder.time * 1000) }} ms) diff --git a/src/Symfony/Bundle/WebProfilerBundle/composer.json b/src/Symfony/Bundle/WebProfilerBundle/composer.json index a6efec177dbaa..22752414ce6dc 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/composer.json +++ b/src/Symfony/Bundle/WebProfilerBundle/composer.json @@ -33,7 +33,8 @@ "conflict": { "symfony/form": "<6.4", "symfony/mailer": "<6.4", - "symfony/messenger": "<6.4" + "symfony/messenger": "<6.4", + "symfony/serializer": "<7.2" }, "autoload": { "psr-4": { "Symfony\\Bundle\\WebProfilerBundle\\": "" }, diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md index b62f84ba21451..79a14b50fccfd 100644 --- a/src/Symfony/Component/Serializer/CHANGELOG.md +++ b/src/Symfony/Component/Serializer/CHANGELOG.md @@ -9,6 +9,9 @@ CHANGELOG * Add `SnakeCaseToCamelCaseNameConverter` * Support subclasses of `\DateTime` and `\DateTimeImmutable` for denormalization * Add the `UidNormalizer::NORMALIZATION_FORMAT_RFC9562` constant + * Add support for configuring multiple serializer instances with different + default contexts, name converters, sets of normalizers and encoders + * Add support for collection profiles of multiple serializer instances 7.1 --- diff --git a/src/Symfony/Component/Serializer/DataCollector/SerializerDataCollector.php b/src/Symfony/Component/Serializer/DataCollector/SerializerDataCollector.php index 2880dea37d8f2..e87c51ca1c35a 100644 --- a/src/Symfony/Component/Serializer/DataCollector/SerializerDataCollector.php +++ b/src/Symfony/Component/Serializer/DataCollector/SerializerDataCollector.php @@ -25,11 +25,22 @@ */ class SerializerDataCollector extends DataCollector implements LateDataCollectorInterface { + private const DATA_TEMPLATE = [ + 'serialize' => [], + 'deserialize' => [], + 'normalize' => [], + 'denormalize' => [], + 'encode' => [], + 'decode' => [], + ]; + + private array $dataGroupedByName; private array $collected = []; public function reset(): void { $this->data = []; + unset($this->dataGroupedByName); $this->collected = []; } @@ -43,14 +54,14 @@ public function getName(): string return 'serializer'; } - public function getData(): Data|array + public function getData(?string $name = null): Data|array { - return $this->data; + return null === $name ? $this->data : $this->getDataGroupedByName()[$name]; } - public function getHandledCount(): int + public function getHandledCount(?string $name = null): int { - return array_sum(array_map('count', $this->data)); + return array_sum(array_map('count', $this->getData($name))); } public function getTotalTime(): float @@ -64,110 +75,108 @@ public function getTotalTime(): float return $totalTime; } - public function collectSerialize(string $traceId, mixed $data, string $format, array $context, float $time, array $caller): void + public function getSerializerNames(): array + { + return array_keys($this->getDataGroupedByName()); + } + + public function collectSerialize(string $traceId, mixed $data, string $format, array $context, float $time, array $caller, string $name): void { unset($context[TraceableSerializer::DEBUG_TRACE_ID]); $this->collected[$traceId] = array_merge( $this->collected[$traceId] ?? [], - compact('data', 'format', 'context', 'time', 'caller'), + compact('data', 'format', 'context', 'time', 'caller', 'name'), ['method' => 'serialize'], ); } - public function collectDeserialize(string $traceId, mixed $data, string $type, string $format, array $context, float $time, array $caller): void + public function collectDeserialize(string $traceId, mixed $data, string $type, string $format, array $context, float $time, array $caller, string $name): void { unset($context[TraceableSerializer::DEBUG_TRACE_ID]); $this->collected[$traceId] = array_merge( $this->collected[$traceId] ?? [], - compact('data', 'format', 'type', 'context', 'time', 'caller'), + compact('data', 'format', 'type', 'context', 'time', 'caller', 'name'), ['method' => 'deserialize'], ); } - public function collectNormalize(string $traceId, mixed $data, ?string $format, array $context, float $time, array $caller): void + public function collectNormalize(string $traceId, mixed $data, ?string $format, array $context, float $time, array $caller, string $name): void { unset($context[TraceableSerializer::DEBUG_TRACE_ID]); $this->collected[$traceId] = array_merge( $this->collected[$traceId] ?? [], - compact('data', 'format', 'context', 'time', 'caller'), + compact('data', 'format', 'context', 'time', 'caller', 'name'), ['method' => 'normalize'], ); } - public function collectDenormalize(string $traceId, mixed $data, string $type, ?string $format, array $context, float $time, array $caller): void + public function collectDenormalize(string $traceId, mixed $data, string $type, ?string $format, array $context, float $time, array $caller, string $name): void { unset($context[TraceableSerializer::DEBUG_TRACE_ID]); $this->collected[$traceId] = array_merge( $this->collected[$traceId] ?? [], - compact('data', 'format', 'type', 'context', 'time', 'caller'), + compact('data', 'format', 'type', 'context', 'time', 'caller', 'name'), ['method' => 'denormalize'], ); } - public function collectEncode(string $traceId, mixed $data, ?string $format, array $context, float $time, array $caller): void + public function collectEncode(string $traceId, mixed $data, ?string $format, array $context, float $time, array $caller, string $name): void { unset($context[TraceableSerializer::DEBUG_TRACE_ID]); $this->collected[$traceId] = array_merge( $this->collected[$traceId] ?? [], - compact('data', 'format', 'context', 'time', 'caller'), + compact('data', 'format', 'context', 'time', 'caller', 'name'), ['method' => 'encode'], ); } - public function collectDecode(string $traceId, mixed $data, ?string $format, array $context, float $time, array $caller): void + public function collectDecode(string $traceId, mixed $data, ?string $format, array $context, float $time, array $caller, string $name): void { unset($context[TraceableSerializer::DEBUG_TRACE_ID]); $this->collected[$traceId] = array_merge( $this->collected[$traceId] ?? [], - compact('data', 'format', 'context', 'time', 'caller'), + compact('data', 'format', 'context', 'time', 'caller', 'name'), ['method' => 'decode'], ); } - public function collectNormalization(string $traceId, string $normalizer, float $time): void + public function collectNormalization(string $traceId, string $normalizer, float $time, string $name): void { $method = 'normalize'; - $this->collected[$traceId]['normalization'][] = compact('normalizer', 'method', 'time'); + $this->collected[$traceId]['normalization'][] = compact('normalizer', 'method', 'time', 'name'); } - public function collectDenormalization(string $traceId, string $normalizer, float $time): void + public function collectDenormalization(string $traceId, string $normalizer, float $time, string $name): void { $method = 'denormalize'; - $this->collected[$traceId]['normalization'][] = compact('normalizer', 'method', 'time'); + $this->collected[$traceId]['normalization'][] = compact('normalizer', 'method', 'time', 'name'); } - public function collectEncoding(string $traceId, string $encoder, float $time): void + public function collectEncoding(string $traceId, string $encoder, float $time, string $name): void { $method = 'encode'; - $this->collected[$traceId]['encoding'][] = compact('encoder', 'method', 'time'); + $this->collected[$traceId]['encoding'][] = compact('encoder', 'method', 'time', 'name'); } - public function collectDecoding(string $traceId, string $encoder, float $time): void + public function collectDecoding(string $traceId, string $encoder, float $time, string $name): void { $method = 'decode'; - $this->collected[$traceId]['encoding'][] = compact('encoder', 'method', 'time'); + $this->collected[$traceId]['encoding'][] = compact('encoder', 'method', 'time', 'name'); } public function lateCollect(): void { - $this->data = [ - 'serialize' => [], - 'deserialize' => [], - 'normalize' => [], - 'denormalize' => [], - 'encode' => [], - 'decode' => [], - ]; + $this->data = self::DATA_TEMPLATE; foreach ($this->collected as $collected) { if (!isset($collected['data'])) { @@ -184,6 +193,7 @@ public function lateCollect(): void 'normalization' => [], 'encoding' => [], 'caller' => $collected['caller'] ?? null, + 'name' => $collected['name'], ]; if (isset($collected['normalization'])) { @@ -220,6 +230,22 @@ public function lateCollect(): void } } + private function getDataGroupedByName(): array + { + if (!isset($this->dataGroupedByName)) { + $this->dataGroupedByName = []; + + foreach ($this->data as $method => $items) { + foreach ($items as $item) { + $this->dataGroupedByName[$item['name']] ??= self::DATA_TEMPLATE; + $this->dataGroupedByName[$item['name']][$method][] = $item; + } + } + } + + return $this->dataGroupedByName; + } + private function getMethodLocation(string $class, string $method): array { $reflection = new \ReflectionClass($class); diff --git a/src/Symfony/Component/Serializer/Debug/TraceableEncoder.php b/src/Symfony/Component/Serializer/Debug/TraceableEncoder.php index 42bf4868a7eb2..39e75e34f04cf 100644 --- a/src/Symfony/Component/Serializer/Debug/TraceableEncoder.php +++ b/src/Symfony/Component/Serializer/Debug/TraceableEncoder.php @@ -30,6 +30,7 @@ class TraceableEncoder implements EncoderInterface, DecoderInterface, Serializer public function __construct( private EncoderInterface|DecoderInterface $encoder, private SerializerDataCollector $dataCollector, + private readonly string $serializerName = 'default', ) { } @@ -44,7 +45,7 @@ public function encode(mixed $data, string $format, array $context = []): string $time = microtime(true) - $startTime; if ($traceId = ($context[TraceableSerializer::DEBUG_TRACE_ID] ?? null)) { - $this->dataCollector->collectEncoding($traceId, $this->encoder::class, $time); + $this->dataCollector->collectEncoding($traceId, $this->encoder::class, $time, $this->serializerName); } return $encoded; @@ -70,7 +71,7 @@ public function decode(string $data, string $format, array $context = []): mixed $time = microtime(true) - $startTime; if ($traceId = ($context[TraceableSerializer::DEBUG_TRACE_ID] ?? null)) { - $this->dataCollector->collectDecoding($traceId, $this->encoder::class, $time); + $this->dataCollector->collectDecoding($traceId, $this->encoder::class, $time, $this->serializerName); } return $encoded; diff --git a/src/Symfony/Component/Serializer/Debug/TraceableNormalizer.php b/src/Symfony/Component/Serializer/Debug/TraceableNormalizer.php index c8063675232cd..1b143e295f5a7 100644 --- a/src/Symfony/Component/Serializer/Debug/TraceableNormalizer.php +++ b/src/Symfony/Component/Serializer/Debug/TraceableNormalizer.php @@ -31,6 +31,7 @@ class TraceableNormalizer implements NormalizerInterface, DenormalizerInterface, public function __construct( private NormalizerInterface|DenormalizerInterface $normalizer, private SerializerDataCollector $dataCollector, + private readonly string $serializerName = 'default', ) { } @@ -50,7 +51,7 @@ public function normalize(mixed $object, ?string $format = null, array $context $time = microtime(true) - $startTime; if ($traceId = ($context[TraceableSerializer::DEBUG_TRACE_ID] ?? null)) { - $this->dataCollector->collectNormalization($traceId, $this->normalizer::class, $time); + $this->dataCollector->collectNormalization($traceId, $this->normalizer::class, $time, $this->serializerName); } return $normalized; @@ -76,7 +77,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a $time = microtime(true) - $startTime; if ($traceId = ($context[TraceableSerializer::DEBUG_TRACE_ID] ?? null)) { - $this->dataCollector->collectDenormalization($traceId, $this->normalizer::class, $time); + $this->dataCollector->collectDenormalization($traceId, $this->normalizer::class, $time, $this->serializerName); } return $denormalized; diff --git a/src/Symfony/Component/Serializer/Debug/TraceableSerializer.php b/src/Symfony/Component/Serializer/Debug/TraceableSerializer.php index ab766bf43ce83..a05bf4bf8655c 100644 --- a/src/Symfony/Component/Serializer/Debug/TraceableSerializer.php +++ b/src/Symfony/Component/Serializer/Debug/TraceableSerializer.php @@ -32,6 +32,7 @@ class TraceableSerializer implements SerializerInterface, NormalizerInterface, D public function __construct( private SerializerInterface&NormalizerInterface&DenormalizerInterface&EncoderInterface&DecoderInterface $serializer, private SerializerDataCollector $dataCollector, + private readonly string $serializerName = 'default', ) { } @@ -45,7 +46,7 @@ public function serialize(mixed $data, string $format, array $context = []): str $caller = $this->getCaller(__FUNCTION__, SerializerInterface::class); - $this->dataCollector->collectSerialize($traceId, $data, $format, $context, $time, $caller); + $this->dataCollector->collectSerialize($traceId, $data, $format, $context, $time, $caller, $this->serializerName); return $result; } @@ -60,7 +61,7 @@ public function deserialize(mixed $data, string $type, string $format, array $co $caller = $this->getCaller(__FUNCTION__, SerializerInterface::class); - $this->dataCollector->collectDeserialize($traceId, $data, $type, $format, $context, $time, $caller); + $this->dataCollector->collectDeserialize($traceId, $data, $type, $format, $context, $time, $caller, $this->serializerName); return $result; } @@ -75,7 +76,7 @@ public function normalize(mixed $object, ?string $format = null, array $context $caller = $this->getCaller(__FUNCTION__, NormalizerInterface::class); - $this->dataCollector->collectNormalize($traceId, $object, $format, $context, $time, $caller); + $this->dataCollector->collectNormalize($traceId, $object, $format, $context, $time, $caller, $this->serializerName); return $result; } @@ -90,7 +91,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a $caller = $this->getCaller(__FUNCTION__, DenormalizerInterface::class); - $this->dataCollector->collectDenormalize($traceId, $data, $type, $format, $context, $time, $caller); + $this->dataCollector->collectDenormalize($traceId, $data, $type, $format, $context, $time, $caller, $this->serializerName); return $result; } @@ -105,7 +106,7 @@ public function encode(mixed $data, string $format, array $context = []): string $caller = $this->getCaller(__FUNCTION__, EncoderInterface::class); - $this->dataCollector->collectEncode($traceId, $data, $format, $context, $time, $caller); + $this->dataCollector->collectEncode($traceId, $data, $format, $context, $time, $caller, $this->serializerName); return $result; } @@ -120,7 +121,7 @@ public function decode(string $data, string $format, array $context = []): mixed $caller = $this->getCaller(__FUNCTION__, DecoderInterface::class); - $this->dataCollector->collectDecode($traceId, $data, $format, $context, $time, $caller); + $this->dataCollector->collectDecode($traceId, $data, $format, $context, $time, $caller, $this->serializerName); return $result; } diff --git a/src/Symfony/Component/Serializer/DependencyInjection/SerializerPass.php b/src/Symfony/Component/Serializer/DependencyInjection/SerializerPass.php index 2a429054b0c7b..bc1c6e10e3727 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\SerializerInterface; /** * Adds all services with the tags "serializer.encoder" and "serializer.normalizer" as @@ -31,44 +32,177 @@ class SerializerPass implements CompilerPassInterface { use PriorityTaggedServiceTrait; + private const NAME_CONVERTER_METADATA_AWARE_ID = 'serializer.name_converter.metadata_aware'; + public function process(ContainerBuilder $container): void { if (!$container->hasDefinition('serializer')) { return; } - if (!$normalizers = $this->findAndSortTaggedServices('serializer.normalizer', $container)) { + $namedSerializers = $container->hasParameter('.serializer.named_serializers') + ? $container->getParameter('.serializer.named_serializers') : []; + + $this->createNamedSerializerTags($container, 'serializer.normalizer', 'include_built_in_normalizers', $namedSerializers); + $this->createNamedSerializerTags($container, 'serializer.encoder', 'include_built_in_encoders', $namedSerializers); + + if (!$normalizers = $this->findAndSortTaggedServices('serializer.normalizer.default', $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)) { + if (!$encoders = $this->findAndSortTaggedServices('serializer.encoder.default', $container)) { throw new RuntimeException('You must tag at least one service as "serializer.encoder" to use the "serializer" service.'); } if ($container->hasParameter('serializer.default_context')) { $defaultContext = $container->getParameter('serializer.default_context'); - foreach (array_merge($normalizers, $encoders) as $service) { - $definition = $container->getDefinition($service); - $definition->setBindings(['array $defaultContext' => new BoundArgument($defaultContext, false)] + $definition->getBindings()); + $this->bindDefaultContext($container, array_merge($normalizers, $encoders), $defaultContext); + $container->getParameterBag()->remove('serializer.default_context'); + } + + $this->configureSerializer($container, 'serializer', $normalizers, $encoders, 'default'); + + if ($namedSerializers) { + $this->configureNamedSerializers($container); + } + } + + private function createNamedSerializerTags(ContainerBuilder $container, string $tagName, string $configName, array $namedSerializers): void + { + $serializerNames = array_keys($namedSerializers); + $withBuiltIn = array_filter($serializerNames, fn (string $name) => $namedSerializers[$name][$configName] ?? false); + + foreach ($container->findTaggedServiceIds($tagName) as $serviceId => $tags) { + $definition = $container->getDefinition($serviceId); + + foreach ($tags as $tag) { + $names = (array) ($tag['serializer'] ?? []); + + if (!$names) { + $names = ['default']; + } elseif (\in_array('*', $names, true)) { + $names = array_unique(['default', ...$serializerNames]); + } + + if ($tag['built_in'] ?? false) { + $names = array_unique(['default', ...$names, ...$withBuiltIn]); + } + + unset($tag['serializer'], $tag['built_in']); + + foreach ($names as $name) { + $definition->addTag($tagName.'.'.$name, $tag); + } } + } + } - $container->getParameterBag()->remove('serializer.default_context'); + private function bindDefaultContext(ContainerBuilder $container, array $services, array $defaultContext): void + { + foreach ($services as $id) { + $definition = $container->getDefinition((string) $id); + $definition->setBindings(['array $defaultContext' => new BoundArgument($defaultContext, false)] + $definition->getBindings()); } + } + private function configureSerializer(ContainerBuilder $container, string $id, array $normalizers, array $encoders, string $serializerName): void + { if ($container->getParameter('kernel.debug') && $container->hasDefinition('serializer.data_collector')) { foreach ($normalizers as $i => $normalizer) { $normalizers[$i] = $container->register('.debug.serializer.normalizer.'.$normalizer, TraceableNormalizer::class) - ->setArguments([$normalizer, new Reference('serializer.data_collector')]); + ->setArguments([$normalizer, new Reference('serializer.data_collector'), $serializerName]); } foreach ($encoders as $i => $encoder) { $encoders[$i] = $container->register('.debug.serializer.encoder.'.$encoder, TraceableEncoder::class) - ->setArguments([$encoder, new Reference('serializer.data_collector')]); + ->setArguments([$encoder, new Reference('serializer.data_collector'), $serializerName]); } } - $serializerDefinition = $container->getDefinition('serializer'); + $serializerDefinition = $container->getDefinition($id); $serializerDefinition->replaceArgument(0, $normalizers); $serializerDefinition->replaceArgument(1, $encoders); } + + private function configureNamedSerializers(ContainerBuilder $container): void + { + $defaultSerializerNameConverter = $container->hasParameter('.serializer.name_converter') + ? $container->getParameter('.serializer.name_converter') : null; + + foreach ($container->getParameter('.serializer.named_serializers') as $serializerName => $config) { + $config += ['default_context' => [], 'name_converter' => null]; + $serializerId = 'serializer.'.$serializerName; + + if (!$normalizers = $this->findAndSortTaggedServices('serializer.normalizer.'.$serializerName, $container)) { + 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)); + } + + if (!$encoders = $this->findAndSortTaggedServices('serializer.encoder.'.$serializerName, $container)) { + 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)); + } + + $config['name_converter'] = $defaultSerializerNameConverter !== $config['name_converter'] + ? $this->buildChildNameConverterDefinition($container, $config['name_converter']) + : self::NAME_CONVERTER_METADATA_AWARE_ID; + + $normalizers = $this->buildChildDefinitions($container, $serializerName, $normalizers, $config); + $encoders = $this->buildChildDefinitions($container, $serializerName, $encoders, $config); + + $this->bindDefaultContext($container, array_merge($normalizers, $encoders), $config['default_context']); + + $container->registerChild($serializerId, 'serializer'); + $container->registerAliasForArgument($serializerId, SerializerInterface::class, $serializerName.'.serializer'); + + $this->configureSerializer($container, $serializerId, $normalizers, $encoders, $serializerName); + + if ($container->getParameter('kernel.debug') && $container->hasDefinition('debug.serializer')) { + $container->registerChild($debugId = 'debug.'.$serializerId, 'debug.serializer') + ->setDecoratedService($serializerId) + ->replaceArgument(0, new Reference($debugId.'.inner')) + ->replaceArgument(2, $serializerName); + } + } + } + + private function buildChildNameConverterDefinition(ContainerBuilder $container, ?string $nameConverter): ?string + { + $childId = self::NAME_CONVERTER_METADATA_AWARE_ID.'.'.ContainerBuilder::hash($nameConverter); + + if (!$container->hasDefinition($childId)) { + $childDefinition = $container->registerChild($childId, self::NAME_CONVERTER_METADATA_AWARE_ID.'.abstract'); + if (null !== $nameConverter) { + $childDefinition->addArgument(new Reference($nameConverter)); + } + } + + return $childId; + } + + private function buildChildDefinitions(ContainerBuilder $container, string $serializerName, array $services, array $config): array + { + foreach ($services as &$id) { + $childId = $id.'.'.$serializerName; + + $definition = $container->registerChild($childId, (string) $id); + + if (null !== $nameConverterIndex = $this->findNameConverterIndex($container, (string) $id)) { + $definition->replaceArgument($nameConverterIndex, new Reference($config['name_converter'])); + } + + $id = new Reference($childId); + } + + return $services; + } + + private function findNameConverterIndex(ContainerBuilder $container, string $id): int|string|null + { + foreach ($container->getDefinition($id)->getArguments() as $index => $argument) { + if ($argument instanceof Reference && self::NAME_CONVERTER_METADATA_AWARE_ID === (string) $argument) { + return $index; + } + } + + return null; + } } diff --git a/src/Symfony/Component/Serializer/Tests/DataCollector/SerializerDataCollectorTest.php b/src/Symfony/Component/Serializer/Tests/DataCollector/SerializerDataCollectorTest.php index aebde8efa1d82..6a26565a85c0c 100644 --- a/src/Symfony/Component/Serializer/Tests/DataCollector/SerializerDataCollectorTest.php +++ b/src/Symfony/Component/Serializer/Tests/DataCollector/SerializerDataCollectorTest.php @@ -25,8 +25,8 @@ public function testCollectSerialize() $dataCollector = new SerializerDataCollector(); $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; - $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); - $dataCollector->collectDeserialize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 1.0, $caller); + $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); + $dataCollector->collectDeserialize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); $dataCollector->lateCollect(); $collectedData = $this->castCollectedData($dataCollector->getData()); @@ -41,6 +41,7 @@ public function testCollectSerialize() 'normalization' => [], 'encoding' => [], 'caller' => $caller, + 'name' => 'default', ]], $collectedData['serialize']); $this->assertSame([[ @@ -53,6 +54,7 @@ public function testCollectSerialize() 'normalization' => [], 'encoding' => [], 'caller' => $caller, + 'name' => 'default', ]], $collectedData['deserialize']); } @@ -61,8 +63,8 @@ public function testCollectNormalize() $dataCollector = new SerializerDataCollector(); $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; - $dataCollector->collectNormalize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); - $dataCollector->collectDenormalize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 1.0, $caller); + $dataCollector->collectNormalize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); + $dataCollector->collectDenormalize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); $dataCollector->lateCollect(); $collectedData = $this->castCollectedData($dataCollector->getData()); @@ -77,6 +79,7 @@ public function testCollectNormalize() 'normalization' => [], 'encoding' => [], 'caller' => $caller, + 'name' => 'default', ]], $collectedData['normalize']); $this->assertSame([[ @@ -89,6 +92,7 @@ public function testCollectNormalize() 'normalization' => [], 'encoding' => [], 'caller' => $caller, + 'name' => 'default', ]], $collectedData['denormalize']); } @@ -97,8 +101,8 @@ public function testCollectEncode() $dataCollector = new SerializerDataCollector(); $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; - $dataCollector->collectEncode('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); - $dataCollector->collectDecode('traceIdTwo', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); + $dataCollector->collectEncode('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); + $dataCollector->collectDecode('traceIdTwo', 'data', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); $dataCollector->lateCollect(); $collectedData = $this->castCollectedData($dataCollector->getData()); @@ -113,6 +117,7 @@ public function testCollectEncode() 'normalization' => [], 'encoding' => [], 'caller' => $caller, + 'name' => 'default', ]], $collectedData['encode']); $this->assertSame([[ @@ -125,6 +130,7 @@ public function testCollectEncode() 'normalization' => [], 'encoding' => [], 'caller' => $caller, + 'name' => 'default', ]], $collectedData['decode']); } @@ -133,18 +139,18 @@ public function testCollectNormalization() $dataCollector = new SerializerDataCollector(); $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; - $dataCollector->collectNormalize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 20.0, $caller); - $dataCollector->collectDenormalize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 20.0, $caller); + $dataCollector->collectNormalize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 20.0, $caller, 'default'); + $dataCollector->collectDenormalize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 20.0, $caller, 'default'); - $dataCollector->collectNormalization('traceIdOne', DateTimeNormalizer::class, 1.0); - $dataCollector->collectNormalization('traceIdOne', DateTimeNormalizer::class, 2.0); - $dataCollector->collectNormalization('traceIdOne', ObjectNormalizer::class, 5.0); - $dataCollector->collectNormalization('traceIdOne', ObjectNormalizer::class, 10.0); + $dataCollector->collectNormalization('traceIdOne', DateTimeNormalizer::class, 1.0, 'default'); + $dataCollector->collectNormalization('traceIdOne', DateTimeNormalizer::class, 2.0, 'default'); + $dataCollector->collectNormalization('traceIdOne', ObjectNormalizer::class, 5.0, 'default'); + $dataCollector->collectNormalization('traceIdOne', ObjectNormalizer::class, 10.0, 'default'); - $dataCollector->collectNormalization('traceIdTwo', DateTimeNormalizer::class, 1.0); - $dataCollector->collectNormalization('traceIdTwo', DateTimeNormalizer::class, 2.0); - $dataCollector->collectNormalization('traceIdTwo', ObjectNormalizer::class, 5.0); - $dataCollector->collectNormalization('traceIdTwo', ObjectNormalizer::class, 10.0); + $dataCollector->collectNormalization('traceIdTwo', DateTimeNormalizer::class, 1.0, 'default'); + $dataCollector->collectNormalization('traceIdTwo', DateTimeNormalizer::class, 2.0, 'default'); + $dataCollector->collectNormalization('traceIdTwo', ObjectNormalizer::class, 5.0, 'default'); + $dataCollector->collectNormalization('traceIdTwo', ObjectNormalizer::class, 10.0, 'default'); $dataCollector->lateCollect(); $collectedData = $dataCollector->getData(); @@ -189,18 +195,18 @@ public function testCollectEncoding() $dataCollector = new SerializerDataCollector(); $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; - $dataCollector->collectEncode('traceIdOne', 'data', 'format', ['foo' => 'bar'], 20.0, $caller); - $dataCollector->collectDecode('traceIdTwo', 'data', 'format', ['foo' => 'bar'], 20.0, $caller); + $dataCollector->collectEncode('traceIdOne', 'data', 'format', ['foo' => 'bar'], 20.0, $caller, 'default'); + $dataCollector->collectDecode('traceIdTwo', 'data', 'format', ['foo' => 'bar'], 20.0, $caller, 'default'); - $dataCollector->collectEncoding('traceIdOne', JsonEncoder::class, 1.0); - $dataCollector->collectEncoding('traceIdOne', JsonEncoder::class, 2.0); - $dataCollector->collectEncoding('traceIdOne', CsvEncoder::class, 5.0); - $dataCollector->collectEncoding('traceIdOne', CsvEncoder::class, 10.0); + $dataCollector->collectEncoding('traceIdOne', JsonEncoder::class, 1.0, 'default'); + $dataCollector->collectEncoding('traceIdOne', JsonEncoder::class, 2.0, 'default'); + $dataCollector->collectEncoding('traceIdOne', CsvEncoder::class, 5.0, 'default'); + $dataCollector->collectEncoding('traceIdOne', CsvEncoder::class, 10.0, 'default'); - $dataCollector->collectDecoding('traceIdTwo', JsonEncoder::class, 1.0); - $dataCollector->collectDecoding('traceIdTwo', JsonEncoder::class, 2.0); - $dataCollector->collectDecoding('traceIdTwo', CsvEncoder::class, 5.0); - $dataCollector->collectDecoding('traceIdTwo', CsvEncoder::class, 10.0); + $dataCollector->collectDecoding('traceIdTwo', JsonEncoder::class, 1.0, 'default'); + $dataCollector->collectDecoding('traceIdTwo', JsonEncoder::class, 2.0, 'default'); + $dataCollector->collectDecoding('traceIdTwo', CsvEncoder::class, 5.0, 'default'); + $dataCollector->collectDecoding('traceIdTwo', CsvEncoder::class, 10.0, 'default'); $dataCollector->lateCollect(); $collectedData = $dataCollector->getData(); @@ -245,13 +251,13 @@ public function testCountHandled() $dataCollector = new SerializerDataCollector(); $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; - $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); - $dataCollector->collectDeserialize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 1.0, $caller); - $dataCollector->collectNormalize('traceIdThree', 'data', 'format', ['foo' => 'bar'], 20.0, $caller); - $dataCollector->collectDenormalize('traceIdFour', 'data', 'type', 'format', ['foo' => 'bar'], 20.0, $caller); - $dataCollector->collectEncode('traceIdFive', 'data', 'format', ['foo' => 'bar'], 20.0, $caller); - $dataCollector->collectDecode('traceIdSix', 'data', 'format', ['foo' => 'bar'], 20.0, $caller); - $dataCollector->collectSerialize('traceIdSeven', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); + $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); + $dataCollector->collectDeserialize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); + $dataCollector->collectNormalize('traceIdThree', 'data', 'format', ['foo' => 'bar'], 20.0, $caller, 'default'); + $dataCollector->collectDenormalize('traceIdFour', 'data', 'type', 'format', ['foo' => 'bar'], 20.0, $caller, 'default'); + $dataCollector->collectEncode('traceIdFive', 'data', 'format', ['foo' => 'bar'], 20.0, $caller, 'default'); + $dataCollector->collectDecode('traceIdSix', 'data', 'format', ['foo' => 'bar'], 20.0, $caller, 'default'); + $dataCollector->collectSerialize('traceIdSeven', 'data', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); $dataCollector->lateCollect(); @@ -264,13 +270,13 @@ public function testGetTotalTime() $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; - $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); - $dataCollector->collectDeserialize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 2.0, $caller); - $dataCollector->collectNormalize('traceIdThree', 'data', 'format', ['foo' => 'bar'], 3.0, $caller); - $dataCollector->collectDenormalize('traceIdFour', 'data', 'type', 'format', ['foo' => 'bar'], 4.0, $caller); - $dataCollector->collectEncode('traceIdFive', 'data', 'format', ['foo' => 'bar'], 5.0, $caller); - $dataCollector->collectDecode('traceIdSix', 'data', 'format', ['foo' => 'bar'], 6.0, $caller); - $dataCollector->collectSerialize('traceIdSeven', 'data', 'format', ['foo' => 'bar'], 7.0, $caller); + $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); + $dataCollector->collectDeserialize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 2.0, $caller, 'default'); + $dataCollector->collectNormalize('traceIdThree', 'data', 'format', ['foo' => 'bar'], 3.0, $caller, 'default'); + $dataCollector->collectDenormalize('traceIdFour', 'data', 'type', 'format', ['foo' => 'bar'], 4.0, $caller, 'default'); + $dataCollector->collectEncode('traceIdFive', 'data', 'format', ['foo' => 'bar'], 5.0, $caller, 'default'); + $dataCollector->collectDecode('traceIdSix', 'data', 'format', ['foo' => 'bar'], 6.0, $caller, 'default'); + $dataCollector->collectSerialize('traceIdSeven', 'data', 'format', ['foo' => 'bar'], 7.0, $caller, 'default'); $dataCollector->lateCollect(); @@ -282,7 +288,7 @@ public function testReset() $dataCollector = new SerializerDataCollector(); $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; - $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); + $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); $dataCollector->lateCollect(); $this->assertNotSame([], $dataCollector->getData()); @@ -295,10 +301,10 @@ public function testDoNotCollectPartialTraces() { $dataCollector = new SerializerDataCollector(); - $dataCollector->collectNormalization('traceIdOne', DateTimeNormalizer::class, 1.0); - $dataCollector->collectDenormalization('traceIdTwo', DateTimeNormalizer::class, 1.0); - $dataCollector->collectEncoding('traceIdThree', CsvEncoder::class, 10.0); - $dataCollector->collectDecoding('traceIdFour', JsonEncoder::class, 1.0); + $dataCollector->collectNormalization('traceIdOne', DateTimeNormalizer::class, 1.0, 'default'); + $dataCollector->collectDenormalization('traceIdTwo', DateTimeNormalizer::class, 1.0, 'default'); + $dataCollector->collectEncoding('traceIdThree', CsvEncoder::class, 10.0, 'default'); + $dataCollector->collectDecoding('traceIdFour', JsonEncoder::class, 1.0, 'default'); $dataCollector->lateCollect(); @@ -312,6 +318,84 @@ public function testDoNotCollectPartialTraces() $this->assertSame([], $data['decode']); } + public function testNamedSerializers() + { + $dataCollector = new SerializerDataCollector(); + + $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; + $dataCollector->collectNormalization('traceIdOne', DateTimeNormalizer::class, 3.0, 'default'); + $dataCollector->collectEncoding('traceIdOne', CsvEncoder::class, 4.0, 'default'); + $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 7.0, $caller, 'default'); + $dataCollector->collectNormalization('traceIdTwo', ObjectNormalizer::class, 3.0, 'default'); + $dataCollector->collectNormalize('traceIdTwo', 'data', 'format', ['foo' => 'bar'], 5.0, $caller, 'default'); + + $dataCollector->collectEncoding('traceIdThree', JsonEncoder::class, 4.0, 'api'); + $dataCollector->collectEncode('traceIdThree', 'data', 'format', ['foo' => 'bar'], 5.0, $caller, 'api'); + $dataCollector->collectDenormalization('traceIdFour', DateTimeNormalizer::class, 3.0, 'api'); + $dataCollector->collectDecoding('traceIdFour', CsvEncoder::class, 4.0, 'api'); + $dataCollector->collectDeserialize('traceIdFour', 'data', 'type', 'format', ['foo' => 'bar'], 7.0, $caller, 'api'); + $dataCollector->collectDenormalization('traceIdFive', ObjectNormalizer::class, 3.0, 'api'); + $dataCollector->collectDenormalize('traceIdFive', 'data', 'type', 'format', ['foo' => 'bar'], 5.0, $caller, 'api'); + $dataCollector->collectDecoding('traceIdSix', JsonEncoder::class, 4.0, 'api'); + $dataCollector->collectDecode('traceIdSix', 'data', 'format', ['foo' => 'bar'], 5.0, $caller, 'api'); + + $dataCollector->lateCollect(); + + $this->assertSame(6, $dataCollector->getHandledCount()); + + $collectedData = $dataCollector->getData(); + + $this->assertSame('default', $collectedData['serialize'][0]['name']); + $this->assertSame('DateTimeNormalizer', $collectedData['serialize'][0]['normalizer']['class']); + $this->assertSame('CsvEncoder', $collectedData['serialize'][0]['encoder']['class']); + $this->assertSame('default', $collectedData['normalize'][0]['name']); + $this->assertSame('ObjectNormalizer', $collectedData['normalize'][0]['normalizer']['class']); + + $this->assertSame('api', $collectedData['encode'][0]['name']); + $this->assertSame('JsonEncoder', $collectedData['encode'][0]['encoder']['class']); + $this->assertSame('api', $collectedData['deserialize'][0]['name']); + $this->assertSame('DateTimeNormalizer', $collectedData['deserialize'][0]['normalizer']['class']); + $this->assertSame('CsvEncoder', $collectedData['deserialize'][0]['encoder']['class']); + $this->assertSame('api', $collectedData['denormalize'][0]['name']); + $this->assertSame('ObjectNormalizer', $collectedData['denormalize'][0]['normalizer']['class']); + $this->assertSame('api', $collectedData['decode'][0]['name']); + $this->assertSame('JsonEncoder', $collectedData['decode'][0]['encoder']['class']); + + $this->assertSame(['default', 'api'], $dataCollector->getSerializerNames()); + + $this->assertSame(2, $dataCollector->getHandledCount('default')); + + $collectedData = $dataCollector->getData('default'); + + $this->assertSame('default', $collectedData['serialize'][0]['name']); + $this->assertSame('DateTimeNormalizer', $collectedData['serialize'][0]['normalizer']['class']); + $this->assertSame('CsvEncoder', $collectedData['serialize'][0]['encoder']['class']); + $this->assertSame('default', $collectedData['normalize'][0]['name']); + $this->assertSame('ObjectNormalizer', $collectedData['normalize'][0]['normalizer']['class']); + + $this->assertEmpty($collectedData['encode']); + $this->assertEmpty($collectedData['deserialize']); + $this->assertEmpty($collectedData['denormalize']); + $this->assertEmpty($collectedData['decode']); + + $this->assertSame(4, $dataCollector->getHandledCount('api')); + + $collectedData = $dataCollector->getData('api'); + + $this->assertEmpty($collectedData['serialize']); + $this->assertEmpty($collectedData['normalize']); + + $this->assertSame('api', $collectedData['encode'][0]['name']); + $this->assertSame('JsonEncoder', $collectedData['encode'][0]['encoder']['class']); + $this->assertSame('api', $collectedData['deserialize'][0]['name']); + $this->assertSame('DateTimeNormalizer', $collectedData['deserialize'][0]['normalizer']['class']); + $this->assertSame('CsvEncoder', $collectedData['deserialize'][0]['encoder']['class']); + $this->assertSame('api', $collectedData['denormalize'][0]['name']); + $this->assertSame('ObjectNormalizer', $collectedData['denormalize'][0]['normalizer']['class']); + $this->assertSame('api', $collectedData['decode'][0]['name']); + $this->assertSame('JsonEncoder', $collectedData['decode'][0]['encoder']['class']); + } + /** * Cast cloned vars to be able to test nested values. */ diff --git a/src/Symfony/Component/Serializer/Tests/Debug/TraceableEncoderTest.php b/src/Symfony/Component/Serializer/Tests/Debug/TraceableEncoderTest.php index ec38c0ef529d4..2ac0b8f1f0869 100644 --- a/src/Symfony/Component/Serializer/Tests/Debug/TraceableEncoderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Debug/TraceableEncoderTest.php @@ -36,12 +36,14 @@ public function testForwardsToEncoder() ->with('data', 'format', $this->isType('array')) ->willReturn('decoded'); - $this->assertSame('encoded', (new TraceableEncoder($encoder, new SerializerDataCollector()))->encode('data', 'format')); - $this->assertSame('decoded', (new TraceableEncoder($decoder, new SerializerDataCollector()))->decode('data', 'format')); + $this->assertSame('encoded', (new TraceableEncoder($encoder, new SerializerDataCollector(), 'default'))->encode('data', 'format')); + $this->assertSame('decoded', (new TraceableEncoder($decoder, new SerializerDataCollector(), 'default'))->decode('data', 'format')); } public function testCollectEncodingData() { + $serializerName = uniqid('name', true); + $encoder = $this->createMock(EncoderInterface::class); $decoder = $this->createMock(DecoderInterface::class); @@ -49,14 +51,14 @@ public function testCollectEncodingData() $dataCollector ->expects($this->once()) ->method('collectEncoding') - ->with($this->isType('string'), $encoder::class, $this->isType('float')); + ->with($this->isType('string'), $encoder::class, $this->isType('float'), $serializerName); $dataCollector ->expects($this->once()) ->method('collectDecoding') - ->with($this->isType('string'), $decoder::class, $this->isType('float')); + ->with($this->isType('string'), $decoder::class, $this->isType('float'), $serializerName); - (new TraceableEncoder($encoder, $dataCollector))->encode('data', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); - (new TraceableEncoder($decoder, $dataCollector))->decode('data', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); + (new TraceableEncoder($encoder, $dataCollector, $serializerName))->encode('data', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); + (new TraceableEncoder($decoder, $dataCollector, $serializerName))->decode('data', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); } public function testNotCollectEncodingDataIfNoDebugTraceId() @@ -68,22 +70,22 @@ public function testNotCollectEncodingDataIfNoDebugTraceId() $dataCollector->expects($this->never())->method('collectEncoding'); $dataCollector->expects($this->never())->method('collectDecoding'); - (new TraceableEncoder($encoder, $dataCollector))->encode('data', 'format'); - (new TraceableEncoder($decoder, $dataCollector))->decode('data', 'format'); + (new TraceableEncoder($encoder, $dataCollector, 'default'))->encode('data', 'format'); + (new TraceableEncoder($decoder, $dataCollector, 'default'))->decode('data', 'format'); } public function testCannotEncodeIfNotEncoder() { $this->expectException(\BadMethodCallException::class); - (new TraceableEncoder($this->createMock(DecoderInterface::class), new SerializerDataCollector()))->encode('data', 'format'); + (new TraceableEncoder($this->createMock(DecoderInterface::class), new SerializerDataCollector(), 'default'))->encode('data', 'format'); } public function testCannotDecodeIfNotDecoder() { $this->expectException(\BadMethodCallException::class); - (new TraceableEncoder($this->createMock(EncoderInterface::class), new SerializerDataCollector()))->decode('data', 'format'); + (new TraceableEncoder($this->createMock(EncoderInterface::class), new SerializerDataCollector(), 'default'))->decode('data', 'format'); } public function testSupports() @@ -94,8 +96,8 @@ public function testSupports() $decoder = $this->createMock(DecoderInterface::class); $decoder->method('supportsDecoding')->willReturn(true); - $traceableEncoder = new TraceableEncoder($encoder, new SerializerDataCollector()); - $traceableDecoder = new TraceableEncoder($decoder, new SerializerDataCollector()); + $traceableEncoder = new TraceableEncoder($encoder, new SerializerDataCollector(), 'default'); + $traceableDecoder = new TraceableEncoder($decoder, new SerializerDataCollector(), 'default'); $this->assertTrue($traceableEncoder->supportsEncoding('data')); $this->assertTrue($traceableDecoder->supportsDecoding('data')); diff --git a/src/Symfony/Component/Serializer/Tests/Debug/TraceableNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Debug/TraceableNormalizerTest.php index 307bc7b6fc2b5..56c16139262b0 100644 --- a/src/Symfony/Component/Serializer/Tests/Debug/TraceableNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Debug/TraceableNormalizerTest.php @@ -38,12 +38,14 @@ public function testForwardsToNormalizer() ->with('data', 'type', 'format', $this->isType('array')) ->willReturn('denormalized'); - $this->assertSame('normalized', (new TraceableNormalizer($normalizer, new SerializerDataCollector()))->normalize('data', 'format')); - $this->assertSame('denormalized', (new TraceableNormalizer($denormalizer, new SerializerDataCollector()))->denormalize('data', 'type', 'format')); + $this->assertSame('normalized', (new TraceableNormalizer($normalizer, new SerializerDataCollector(), 'default'))->normalize('data', 'format')); + $this->assertSame('denormalized', (new TraceableNormalizer($denormalizer, new SerializerDataCollector(), 'default'))->denormalize('data', 'type', 'format')); } public function testCollectNormalizationData() { + $serializerName = uniqid('name', true); + $normalizer = $this->createMock(NormalizerInterface::class); $normalizer->method('getSupportedTypes')->willReturn(['*' => false]); $denormalizer = $this->createMock(DenormalizerInterface::class); @@ -53,14 +55,14 @@ public function testCollectNormalizationData() $dataCollector ->expects($this->once()) ->method('collectNormalization') - ->with($this->isType('string'), $normalizer::class, $this->isType('float')); + ->with($this->isType('string'), $normalizer::class, $this->isType('float'), $serializerName); $dataCollector ->expects($this->once()) ->method('collectDenormalization') - ->with($this->isType('string'), $denormalizer::class, $this->isType('float')); + ->with($this->isType('string'), $denormalizer::class, $this->isType('float'), $serializerName); - (new TraceableNormalizer($normalizer, $dataCollector))->normalize('data', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); - (new TraceableNormalizer($denormalizer, $dataCollector))->denormalize('data', 'type', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); + (new TraceableNormalizer($normalizer, $dataCollector, $serializerName))->normalize('data', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); + (new TraceableNormalizer($denormalizer, $dataCollector, $serializerName))->denormalize('data', 'type', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); } public function testNotCollectNormalizationDataIfNoDebugTraceId() @@ -74,22 +76,22 @@ public function testNotCollectNormalizationDataIfNoDebugTraceId() $dataCollector->expects($this->never())->method('collectNormalization'); $dataCollector->expects($this->never())->method('collectDenormalization'); - (new TraceableNormalizer($normalizer, $dataCollector))->normalize('data', 'format'); - (new TraceableNormalizer($denormalizer, $dataCollector))->denormalize('data', 'type', 'format'); + (new TraceableNormalizer($normalizer, $dataCollector, 'default'))->normalize('data', 'format'); + (new TraceableNormalizer($denormalizer, $dataCollector, 'default'))->denormalize('data', 'type', 'format'); } public function testCannotNormalizeIfNotNormalizer() { $this->expectException(\BadMethodCallException::class); - (new TraceableNormalizer($this->createMock(DenormalizerInterface::class), new SerializerDataCollector()))->normalize('data'); + (new TraceableNormalizer($this->createMock(DenormalizerInterface::class), new SerializerDataCollector(), 'default'))->normalize('data'); } public function testCannotDenormalizeIfNotDenormalizer() { $this->expectException(\BadMethodCallException::class); - (new TraceableNormalizer($this->createMock(NormalizerInterface::class), new SerializerDataCollector()))->denormalize('data', 'type'); + (new TraceableNormalizer($this->createMock(NormalizerInterface::class), new SerializerDataCollector(), 'default'))->denormalize('data', 'type'); } public function testSupports() @@ -102,8 +104,8 @@ public function testSupports() $denormalizer->method('getSupportedTypes')->willReturn(['*' => false]); $denormalizer->method('supportsDenormalization')->willReturn(true); - $traceableNormalizer = new TraceableNormalizer($normalizer, new SerializerDataCollector()); - $traceableDenormalizer = new TraceableNormalizer($denormalizer, new SerializerDataCollector()); + $traceableNormalizer = new TraceableNormalizer($normalizer, new SerializerDataCollector(), 'default'); + $traceableDenormalizer = new TraceableNormalizer($denormalizer, new SerializerDataCollector(), 'default'); $this->assertTrue($traceableNormalizer->supportsNormalization('data')); $this->assertTrue($traceableDenormalizer->supportsDenormalization('data', 'type')); diff --git a/src/Symfony/Component/Serializer/Tests/Debug/TraceableSerializerTest.php b/src/Symfony/Component/Serializer/Tests/Debug/TraceableSerializerTest.php index ea3c851c6040b..d697b270ff958 100644 --- a/src/Symfony/Component/Serializer/Tests/Debug/TraceableSerializerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Debug/TraceableSerializerTest.php @@ -56,7 +56,7 @@ public function testForwardsToSerializer() ->with('data', 'format', $this->isType('array')) ->willReturn('decoded'); - $traceableSerializer = new TraceableSerializer($serializer, new SerializerDataCollector()); + $traceableSerializer = new TraceableSerializer($serializer, new SerializerDataCollector(), 'default'); $this->assertSame('serialized', $traceableSerializer->serialize('data', 'format')); $this->assertSame('deserialized', $traceableSerializer->deserialize('data', 'type', 'format')); @@ -68,33 +68,35 @@ public function testForwardsToSerializer() public function testCollectData() { + $serializerName = uniqid('name', true); + $dataCollector = $this->createMock(SerializerDataCollector::class); $dataCollector ->expects($this->once()) ->method('collectSerialize') - ->with($this->isType('string'), 'data', 'format', $this->isType('array'), $this->isType('float')); + ->with($this->isType('string'), 'data', 'format', $this->isType('array'), $this->isType('float'), $this->isType('array'), $serializerName); $dataCollector ->expects($this->once()) ->method('collectDeserialize') - ->with($this->isType('string'), 'data', 'type', 'format', $this->isType('array'), $this->isType('float')); + ->with($this->isType('string'), 'data', 'type', 'format', $this->isType('array'), $this->isType('float'), $this->isType('array'), $serializerName); $dataCollector ->expects($this->once()) ->method('collectNormalize') - ->with($this->isType('string'), 'data', 'format', $this->isType('array'), $this->isType('float')); + ->with($this->isType('string'), 'data', 'format', $this->isType('array'), $this->isType('float'), $this->isType('array'), $serializerName); $dataCollector ->expects($this->once()) ->method('collectDenormalize') - ->with($this->isType('string'), 'data', 'type', 'format', $this->isType('array'), $this->isType('float')); + ->with($this->isType('string'), 'data', 'type', 'format', $this->isType('array'), $this->isType('float'), $this->isType('array'), $serializerName); $dataCollector ->expects($this->once()) ->method('collectEncode') - ->with($this->isType('string'), 'data', 'format', $this->isType('array'), $this->isType('float')); + ->with($this->isType('string'), 'data', 'format', $this->isType('array'), $this->isType('float'), $this->isType('array'), $serializerName); $dataCollector ->expects($this->once()) ->method('collectDecode') - ->with($this->isType('string'), 'data', 'format', $this->isType('array'), $this->isType('float')); + ->with($this->isType('string'), 'data', 'format', $this->isType('array'), $this->isType('float'), $this->isType('array'), $serializerName); - $traceableSerializer = new TraceableSerializer(new Serializer(), $dataCollector); + $traceableSerializer = new TraceableSerializer(new Serializer(), $dataCollector, $serializerName); $traceableSerializer->serialize('data', 'format'); $traceableSerializer->deserialize('data', 'type', 'format'); @@ -117,7 +119,7 @@ public function testAddDebugTraceIdInContext() }); } - $traceableSerializer = new TraceableSerializer($serializer, new SerializerDataCollector()); + $traceableSerializer = new TraceableSerializer($serializer, new SerializerDataCollector(), 'default'); $traceableSerializer->serialize('data', 'format'); $traceableSerializer->deserialize('data', 'format', 'type'); diff --git a/src/Symfony/Component/Serializer/Tests/DependencyInjection/SerializerPassTest.php b/src/Symfony/Component/Serializer/Tests/DependencyInjection/SerializerPassTest.php index 037eafdb66665..b721b1ba48298 100644 --- a/src/Symfony/Component/Serializer/Tests/DependencyInjection/SerializerPassTest.php +++ b/src/Symfony/Component/Serializer/Tests/DependencyInjection/SerializerPassTest.php @@ -17,7 +17,9 @@ use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\Serializer\Debug\TraceableEncoder; use Symfony\Component\Serializer\Debug\TraceableNormalizer; +use Symfony\Component\Serializer\Debug\TraceableSerializer; use Symfony\Component\Serializer\DependencyInjection\SerializerPass; +use Symfony\Component\Serializer\SerializerInterface; /** * Tests for the SerializerPass class. @@ -94,7 +96,7 @@ public function testBindSerializerDefaultContext() $this->assertEquals($bindings['array $defaultContext'], new BoundArgument(['enable_max_depth' => true], false)); } - public function testNormalizersAndEncodersAreDecoredAndOrderedWhenCollectingData() + public function testNormalizersAndEncodersAreDecoratedAndOrderedWhenCollectingData() { $container = new ContainerBuilder(); @@ -114,9 +116,525 @@ public function testNormalizersAndEncodersAreDecoredAndOrderedWhenCollectingData $this->assertEquals(TraceableNormalizer::class, $traceableNormalizerDefinition->getClass()); $this->assertEquals(new Reference('n'), $traceableNormalizerDefinition->getArgument(0)); $this->assertEquals(new Reference('serializer.data_collector'), $traceableNormalizerDefinition->getArgument(1)); + $this->assertSame('default', $traceableNormalizerDefinition->getArgument(2)); $this->assertEquals(TraceableEncoder::class, $traceableEncoderDefinition->getClass()); $this->assertEquals(new Reference('e'), $traceableEncoderDefinition->getArgument(0)); $this->assertEquals(new Reference('serializer.data_collector'), $traceableEncoderDefinition->getArgument(1)); + $this->assertSame('default', $traceableEncoderDefinition->getArgument(2)); + } + + /** + * @dataProvider provideDefaultSerializerTagsData + */ + public function testDefaultSerializerTagsAreResolvedCorrectly( + array $normalizerTagAttributes, + array $encoderTagAttributes, + array $expectedNormalizerTags, + array $expectedEncoderTags, + ) { + $container = new ContainerBuilder(); + + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.named_serializers', []); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n0')->addTag('serializer.normalizer', ['serializer' => 'default']); + $container->register('e0')->addTag('serializer.encoder', ['serializer' => 'default']); + + $normalizerDefinition = $container->register('n1')->addTag('serializer.normalizer', $normalizerTagAttributes); + $encoderDefinition = $container->register('e1')->addTag('serializer.encoder', $encoderTagAttributes); + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $this->assertSame($expectedNormalizerTags, $normalizerDefinition->getTag('serializer.normalizer.default')); + $this->assertSame($expectedEncoderTags, $encoderDefinition->getTag('serializer.encoder.default')); + } + + public static function provideDefaultSerializerTagsData(): iterable + { + yield 'include no name' => [ + [], + [], + [[]], + [[]], + ]; + + yield 'include name' => [ + ['serializer' => 'default'], + ['serializer' => 'default'], + [[]], + [[]], + ]; + + yield 'include built-in with different name' => [ + ['built_in' => true, 'serializer' => 'api'], + ['built_in' => true, 'serializer' => 'api'], + [[]], + [[]], + ]; + + yield 'include no name with priority' => [ + ['priority' => 200], + ['priority' => 100], + [['priority' => 200]], + [['priority' => 100]], + ]; + + yield 'include name with priority' => [ + ['serializer' => 'default', 'priority' => 200], + ['serializer' => 'default', 'priority' => 100], + [['priority' => 200]], + [['priority' => 100]], + ]; + + yield 'include wildcard' => [ + ['serializer' => '*'], + ['serializer' => '*'], + [[]], + [[]], + ]; + + yield 'is unique when built-in with name' => [ + ['built_in' => true, 'serializer' => 'default'], + ['built_in' => true, 'serializer' => 'default'], + [[]], + [[]], + ]; + + yield 'do not include different name' => [ + ['serializer' => 'api'], + ['serializer' => 'api'], + [], + [], + ]; + } + + /** + * @dataProvider provideNamedSerializerTagsData + */ + public function testNamedSerializerTagsAreResolvedCorrectly( + array $config, + array $normalizerTagAttributes, + array $encoderTagAttributes, + array $expectedNormalizerTags, + array $expectedEncoderTags, + ) { + $container = new ContainerBuilder(); + + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.named_serializers', ['api' => $config]); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n0')->addTag('serializer.normalizer', ['serializer' => ['default', 'api']]); + $container->register('e0')->addTag('serializer.encoder', ['serializer' => ['default', 'api']]); + + $normalizerDefinition = $container->register('n1')->addTag('serializer.normalizer', $normalizerTagAttributes); + $encoderDefinition = $container->register('e1')->addTag('serializer.encoder', $encoderTagAttributes); + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $this->assertSame($expectedNormalizerTags, $normalizerDefinition->getTag('serializer.normalizer.api')); + $this->assertSame($expectedEncoderTags, $encoderDefinition->getTag('serializer.encoder.api')); + } + + public static function provideNamedSerializerTagsData(): iterable + { + yield 'include built-in' => [ + ['include_built_in_normalizers' => true, 'include_built_in_encoders' => true], + ['built_in' => true], + ['built_in' => true], + [[]], + [[]], + ]; + + yield 'include built-in normalizers only' => [ + ['include_built_in_normalizers' => true, 'include_built_in_encoders' => false], + ['built_in' => true], + ['built_in' => true], + [[]], + [], + ]; + + yield 'include built-in encoders only' => [ + ['include_built_in_normalizers' => false, 'include_built_in_encoders' => true], + ['built_in' => true], + ['built_in' => true], + [], + [[]], + ]; + + yield 'include name' => [ + ['include_built_in_normalizers' => false, 'include_built_in_encoders' => false], + ['serializer' => 'api'], + ['serializer' => 'api'], + [[]], + [[]], + ]; + + yield 'include name with priority' => [ + ['include_built_in_normalizers' => false, 'include_built_in_encoders' => false], + ['serializer' => 'api', 'priority' => 200], + ['serializer' => 'api', 'priority' => 100], + [['priority' => 200]], + [['priority' => 100]], + ]; + + yield 'include wildcard' => [ + ['include_built_in_normalizers' => false, 'include_built_in_encoders' => false], + ['serializer' => '*'], + ['serializer' => '*'], + [[]], + [[]], + ]; + + yield 'do not include when include built-in not set' => [ + [], + ['built_in' => true], + ['built_in' => true], + [], + [], + ]; + + yield 'do not include not built-in and no name' => [ + ['include_built_in_normalizers' => false, 'include_built_in_encoders' => false], + [], + [], + [], + [], + ]; + + yield 'do not include different name' => [ + ['include_built_in_normalizers' => false, 'include_built_in_encoders' => false], + ['serializer' => 'api2'], + ['serializer' => 'api2'], + [], + [], + ]; + } + + public function testMultipleNamedSerializerTagsAreResolvedCorrectly() + { + $container = new ContainerBuilder(); + + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.named_serializers', [ + 'api' => [], + 'api2' => [], + ]); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n0')->addTag('serializer.normalizer', ['serializer' => 'default']); + $container->register('e0')->addTag('serializer.encoder', ['serializer' => 'default']); + + $normalizerDefinition = $container->register('n1')->addTag('serializer.normalizer', ['serializer' => ['api', 'api2']]); + $encoderDefinition = $container->register('e1') + ->addTag('serializer.encoder', ['serializer' => ['api', 'api2']]) + ->addTag('serializer.encoder', ['serializer' => ['api', 'api2'], 'priority' => 100]) + ; + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $this->assertTrue($normalizerDefinition->hasTag('serializer.normalizer.api')); + $this->assertCount(1, $normalizerDefinition->getTag('serializer.normalizer.api')); + $this->assertTrue($normalizerDefinition->hasTag('serializer.normalizer.api2')); + $this->assertCount(1, $normalizerDefinition->getTag('serializer.normalizer.api2')); + + $this->assertTrue($encoderDefinition->hasTag('serializer.encoder.api')); + $this->assertCount(2, $encoderDefinition->getTag('serializer.encoder.api')); + $this->assertTrue($encoderDefinition->hasTag('serializer.encoder.api2')); + $this->assertCount(2, $encoderDefinition->getTag('serializer.encoder.api2')); + } + + public function testThrowExceptionWhenNoNormalizersForNamedSerializers() + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.named_serializers', [ + 'api' => [], + ]); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n0')->addTag('serializer.normalizer'); + $container->register('e0')->addTag('serializer.encoder', ['serializer' => '*']); + + $serializerPass = new SerializerPass(); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('The named serializer "api" requires at least one registered normalizer. Tag the normalizers as "serializer.normalizer" with the "serializer" attribute set to "api".'); + + $serializerPass->process($container); + } + + public function testThrowExceptionWhenNoEncodersForNamedSerializers() + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.named_serializers', [ + 'api' => [], + ]); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n0')->addTag('serializer.normalizer', ['serializer' => '*']); + $container->register('e0')->addTag('serializer.encoder'); + + $serializerPass = new SerializerPass(); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('The named serializer "api" requires at least one registered encoder. Tag the encoders as "serializer.encoder" with the "serializer" attribute set to "api".'); + + $serializerPass->process($container); + } + + /** + * @testWith [null] + * ["some.converter"] + */ + public function testChildNameConverterIsNotBuiltWhenExpected(?string $nameConverter) + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.name_converter', $nameConverter); + $container->setParameter('.serializer.named_serializers', [ + 'api' => ['name_converter' => $nameConverter], + ]); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n')->addTag('serializer.normalizer', ['serializer' => '*']); + $container->register('e')->addTag('serializer.encoder', ['serializer' => '*']); + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $this->assertFalse($container->hasDefinition('serializer.name_converter.metadata_aware.'.ContainerBuilder::hash($nameConverter))); + } + + /** + * @dataProvider provideChildNameConverterCases + */ + public function testChildNameConverterIsBuiltWhenExpected( + ?string $defaultSerializerNameConverter, + ?string $namedSerializerNameConverter, + string $nameConverterIdExists, + string $nameConverterIdDoesNotExist, + array $nameConverterArguments, + ) { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.name_converter', $defaultSerializerNameConverter); + $container->setParameter('.serializer.named_serializers', [ + 'api' => ['name_converter' => $namedSerializerNameConverter], + ]); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n')->addTag('serializer.normalizer', ['serializer' => '*']); + $container->register('e')->addTag('serializer.encoder', ['serializer' => '*']); + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $this->assertFalse($container->hasDefinition($nameConverterIdExists)); + $this->assertTrue($container->hasDefinition($nameConverterIdDoesNotExist)); + $this->assertEquals($nameConverterArguments, $container->getDefinition($nameConverterIdDoesNotExist)->getArguments()); + } + + public static function provideChildNameConverterCases(): iterable + { + $withNull = 'serializer.name_converter.metadata_aware.'.ContainerBuilder::hash(null); + $withConverter = 'serializer.name_converter.metadata_aware.'.ContainerBuilder::hash('some.converter'); + + yield [null, 'some.converter', $withNull, $withConverter, [new Reference('some.converter')]]; + yield ['some.converter', null, $withConverter, $withNull, []]; + } + + /** + * @dataProvider provideDifferentNamedSerializerConfigsCases + */ + public function testNamedSerializersCreateNewServices( + array $defaultSerializerDefaultContext, + ?string $defaultSerializerNameConverter, + array $namedSerializerConfig, + string $nameConverterId, + ) { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->setParameter('serializer.default_context', $defaultSerializerDefaultContext); + $container->setParameter('.serializer.name_converter', $defaultSerializerNameConverter); + $container->setParameter('.serializer.named_serializers', [ + 'api' => $namedSerializerConfig, + ]); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n') + ->addArgument(new Reference('serializer.name_converter.metadata_aware')) + ->addTag('serializer.normalizer', ['serializer' => '*']) + ; + $container->register('e') + ->addArgument(new Reference('serializer.name_converter.metadata_aware')) + ->addTag('serializer.encoder', ['serializer' => '*']) + ; + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $this->assertEquals([new Reference('n.api')], $container->getDefinition('serializer.api')->getArgument(0)); + $this->assertEquals(new Reference($nameConverterId), $container->getDefinition('n.api')->getArgument(0)); + $this->assertEquals([new Reference('e.api')], $container->getDefinition('serializer.api')->getArgument(1)); + $this->assertEquals(new Reference($nameConverterId), $container->getDefinition('e.api')->getArgument(0)); + } + + public static function provideDifferentNamedSerializerConfigsCases(): iterable + { + yield [ + ['a' => true, 'b' => 3], + null, + ['default_context' => ['c' => 3, 'a' => true]], + 'serializer.name_converter.metadata_aware', + ]; + yield [ + [], + 'some.converter', + ['name_converter' => null], + 'serializer.name_converter.metadata_aware.'.ContainerBuilder::hash(null), + ]; + yield [ + ['a' => true, 'b' => 3], + null, + ['default_context' => ['c' => 3, 'a' => true], 'name_converter' => 'some.converter'], + 'serializer.name_converter.metadata_aware.'.ContainerBuilder::hash('some.converter'), + ]; + } + + public function testServicesAreOrderedAccordingToPriorityForNamedSerializers() + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.named_serializers', [ + 'api' => [], + ]); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n2') + ->addTag('serializer.normalizer', ['serializer' => '*', 'priority' => 100]) + ->addTag('serializer.encoder', ['serializer' => '*', 'priority' => 100]) + ; + $container->register('n1') + ->addTag('serializer.normalizer', ['serializer' => 'api', 'priority' => 200]) + ->addTag('serializer.encoder', ['serializer' => 'api', 'priority' => 200]) + ; + $container->register('n3') + ->addTag('serializer.normalizer', ['serializer' => 'api']) + ->addTag('serializer.encoder', ['serializer' => 'api']) + ; + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $this->assertTrue($container->hasDefinition('serializer.api')); + $definition = $container->getDefinition('serializer.api'); + + $expected = [ + new Reference('n1.api'), + new Reference('n2.api'), + new Reference('n3.api'), + ]; + $this->assertEquals($expected, $definition->getArgument(0)); + $this->assertEquals($expected, $definition->getArgument(1)); + } + + public function testBindSerializerDefaultContextToNamedSerializers() + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.named_serializers', [ + 'api' => ['default_context' => $defaultContext = ['enable_max_depth' => true]], + ]); + + $container->register('serializer')->setArguments([null, null]); + $definition = $container->register('n1') + ->addTag('serializer.normalizer', ['serializer' => '*']) + ->addTag('serializer.encoder', ['serializer' => '*']) + ; + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $this->assertEmpty($definition->getBindings()); + + $bindings = $container->getDefinition('n1.api')->getBindings(); + $this->assertArrayHasKey('array $defaultContext', $bindings); + $this->assertEquals($bindings['array $defaultContext'], new BoundArgument($defaultContext, false)); + } + + public function testNamedSerializersAreRegistered() + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.named_serializers', [ + 'api' => [], + 'api2' => [], + ]); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n')->addTag('serializer.normalizer', ['serializer' => '*']); + $container->register('e')->addTag('serializer.encoder', ['serializer' => '*']); + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $this->assertFalse($container->hasAlias(\sprintf('%s $defaultSerializer', SerializerInterface::class))); + + $this->assertTrue($container->hasDefinition('serializer.api')); + $this->assertTrue($container->hasAlias(\sprintf('%s $apiSerializer', SerializerInterface::class))); + $this->assertTrue($container->hasDefinition('serializer.api2')); + $this->assertTrue($container->hasAlias(\sprintf('%s $api2Serializer', SerializerInterface::class))); + } + + public function testNormalizersAndEncodersAreDecoratedAndOrderedWhenCollectingDataForNamedSerializers() + { + $container = new ContainerBuilder(); + + $container->setParameter('kernel.debug', true); + $container->setParameter('.serializer.named_serializers', [ + 'api' => ['default_context' => ['enable_max_depth' => true]], + ]); + $container->register('serializer.data_collector'); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n')->addTag('serializer.normalizer', ['serializer' => '*']); + $container->register('e')->addTag('serializer.encoder', ['serializer' => '*']); + + $container->register('debug.serializer', TraceableSerializer::class) + ->setDecoratedService('serializer') + ->setArguments([ + new Reference('debug.serializer.inner'), + new Reference('serializer.data_collector'), + 'default', + ]) + ; + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $traceableNormalizerDefinition = $container->getDefinition('.debug.serializer.normalizer.n.api'); + $traceableEncoderDefinition = $container->getDefinition('.debug.serializer.encoder.e.api'); + + $traceableSerializerDefinition = $container->getDefinition('debug.serializer.api'); + $this->assertSame('serializer.api', $traceableSerializerDefinition->getDecoratedService()[0]); + $this->assertEquals(new Reference('debug.serializer.api.inner'), $traceableSerializerDefinition->getArgument(0)); + $this->assertSame('api', $traceableSerializerDefinition->getArgument(2)); + + $this->assertEquals(TraceableNormalizer::class, $traceableNormalizerDefinition->getClass()); + $this->assertEquals(new Reference('n.api'), $traceableNormalizerDefinition->getArgument(0)); + $this->assertEquals(new Reference('serializer.data_collector'), $traceableNormalizerDefinition->getArgument(1)); + $this->assertSame('api', $traceableNormalizerDefinition->getArgument(2)); + + $this->assertEquals(TraceableEncoder::class, $traceableEncoderDefinition->getClass()); + $this->assertEquals(new Reference('e.api'), $traceableEncoderDefinition->getArgument(0)); + $this->assertEquals(new Reference('serializer.data_collector'), $traceableEncoderDefinition->getArgument(1)); + $this->assertSame('api', $traceableEncoderDefinition->getArgument(2)); } } diff --git a/src/Symfony/Component/Serializer/composer.json b/src/Symfony/Component/Serializer/composer.json index 0092a9643af2e..4d3af93582201 100644 --- a/src/Symfony/Component/Serializer/composer.json +++ b/src/Symfony/Component/Serializer/composer.json @@ -26,7 +26,7 @@ "symfony/cache": "^6.4|^7.0", "symfony/config": "^6.4|^7.0", "symfony/console": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", + "symfony/dependency-injection": "^7.2", "symfony/error-handler": "^6.4|^7.0", "symfony/filesystem": "^6.4|^7.0", "symfony/form": "^6.4|^7.0",