Skip to content

Commit e21e3a8

Browse files
[Serializer] Allow using attributes to declare compile-time serialization metadata
1 parent c539c77 commit e21e3a8

File tree

19 files changed

+239
-21
lines changed

19 files changed

+239
-21
lines changed

UPGRADE-7.4.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ Serializer
9595
* Make `AttributeMetadata` and `ClassMetadata` final
9696
* Deprecate class aliases in the `Annotation` namespace, use attributes instead
9797
* Deprecate getters in attribute classes in favor of public properties
98+
* Deprecate `ClassMetadataFactoryCompiler`
9899

99100
String
100101
------

src/Symfony/Bundle/FrameworkBundle/CacheWarmer/SerializerCacheWarmer.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@
1414
use Symfony\Component\Cache\Adapter\ArrayAdapter;
1515
use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory;
1616
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
17+
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
1718
use Symfony\Component\Serializer\Mapping\Loader\LoaderChain;
1819
use Symfony\Component\Serializer\Mapping\Loader\LoaderInterface;
1920
use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader;
2021
use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader;
2122

2223
/**
23-
* Warms up XML and YAML serializer metadata.
24+
* Warms up serializer metadata.
2425
*
2526
* @author Titouan Galopin <galopintitouan@gmail.com>
2627
*
@@ -66,14 +67,14 @@ protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter, ?strin
6667
/**
6768
* @param LoaderInterface[] $loaders
6869
*
69-
* @return XmlFileLoader[]|YamlFileLoader[]
70+
* @return list<XmlFileLoader|YamlFileLoader|AttributeLoader>
7071
*/
7172
private function extractSupportedLoaders(array $loaders): array
7273
{
7374
$supportedLoaders = [];
7475

7576
foreach ($loaders as $loader) {
76-
if ($loader instanceof XmlFileLoader || $loader instanceof YamlFileLoader) {
77+
if (method_exists($loader, 'getMappedClasses')) {
7778
$supportedLoaders[] = $loader;
7879
} elseif ($loader instanceof LoaderChain) {
7980
$supportedLoaders = array_merge($supportedLoaders, $this->extractSupportedLoaders($loader->getLoaders()));

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

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -187,9 +187,10 @@
187187
use Symfony\Component\Semaphore\Semaphore;
188188
use Symfony\Component\Semaphore\SemaphoreFactory;
189189
use Symfony\Component\Semaphore\Store\StoreFactory as SemaphoreStoreFactory;
190+
use Symfony\Component\Serializer\Attribute as SerializerMapping;
191+
use Symfony\Component\Serializer\DependencyInjection\AttributeMetadataPass as SerializerAttributeMetadataPass;
190192
use Symfony\Component\Serializer\Encoder\DecoderInterface;
191193
use Symfony\Component\Serializer\Encoder\EncoderInterface;
192-
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
193194
use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader;
194195
use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader;
195196
use Symfony\Component\Serializer\NameConverter\SnakeCaseToCamelCaseNameConverter;
@@ -2073,10 +2074,38 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder
20732074
}
20742075

20752076
$serializerLoaders = [];
2076-
if ($config['enable_attributes'] ?? false) {
2077-
$attributeLoader = new Definition(AttributeLoader::class);
20782077

2079-
$serializerLoaders[] = $attributeLoader;
2078+
// When attributes are disabled, it means from runtime-discovery only; autoconfiguration should still happen.
2079+
// And when runtime-discovery of attributes is enabled, we can skip compile-time autoconfiguration in debug mode.
2080+
if (class_exists(SerializerAttributeMetadataPass::class) && (!($config['enable_attributes'] ?? false) || !$container->getParameter('kernel.debug'))) {
2081+
// The $reflector argument hints at where the attribute could be used
2082+
$configurator = function (ChildDefinition $definition, object $attribute, \ReflectionClass|\ReflectionMethod|\ReflectionProperty $reflector) {
2083+
$definition->addTag('serializer.attribute_metadata');
2084+
};
2085+
$container->registerAttributeForAutoconfiguration(SerializerMapping\Context::class, $configurator);
2086+
$container->registerAttributeForAutoconfiguration(SerializerMapping\Groups::class, $configurator);
2087+
2088+
$configurator = function (ChildDefinition $definition, object $attribute, \ReflectionMethod|\ReflectionProperty $reflector) {
2089+
$definition->addTag('serializer.attribute_metadata');
2090+
};
2091+
$container->registerAttributeForAutoconfiguration(SerializerMapping\Ignore::class, $configurator);
2092+
$container->registerAttributeForAutoconfiguration(SerializerMapping\MaxDepth::class, $configurator);
2093+
$container->registerAttributeForAutoconfiguration(SerializerMapping\SerializedName::class, $configurator);
2094+
$container->registerAttributeForAutoconfiguration(SerializerMapping\SerializedPath::class, $configurator);
2095+
2096+
$container->registerAttributeForAutoconfiguration(SerializerMapping\DiscriminatorMap::class, function (ChildDefinition $definition) {
2097+
$definition->addTag('serializer.attribute_metadata');
2098+
});
2099+
}
2100+
2101+
if (($config['enable_attributes'] ?? false) || class_exists(SerializerAttributeMetadataPass::class)) {
2102+
$serializerLoaders[] = new Reference('serializer.mapping.attribute_loader');
2103+
2104+
$container->getDefinition('serializer.mapping.attribute_loader')
2105+
->replaceArgument(0, $config['enable_attributes'] ?? false);
2106+
} else {
2107+
// BC with symfony/serializer < 7.4
2108+
$container->removeDefinition('serializer.mapping.attribute_services_loader');
20802109
}
20812110

20822111
$fileRecorder = function ($extension, $path) use (&$serializerLoaders) {

src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
use Symfony\Component\Routing\DependencyInjection\RoutingResolverPass;
6666
use Symfony\Component\Runtime\SymfonyRuntime;
6767
use Symfony\Component\Scheduler\DependencyInjection\AddScheduleMessengerPass;
68+
use Symfony\Component\Serializer\DependencyInjection\AttributeMetadataPass as SerializerAttributeMetadataPass;
6869
use Symfony\Component\Serializer\DependencyInjection\SerializerPass;
6970
use Symfony\Component\Translation\DependencyInjection\DataCollectorTranslatorPass;
7071
use Symfony\Component\Translation\DependencyInjection\LoggingTranslatorPass;
@@ -170,6 +171,7 @@ public function build(ContainerBuilder $container): void
170171
$this->addCompilerPassIfExists($container, TranslationDumperPass::class);
171172
$container->addCompilerPass(new FragmentRendererPass());
172173
$this->addCompilerPassIfExists($container, SerializerPass::class);
174+
$this->addCompilerPassIfExists($container, SerializerAttributeMetadataPass::class);
173175
$this->addCompilerPassIfExists($container, PropertyInfoPass::class);
174176
$this->addCompilerPassIfExists($container, PropertyInfoConstructorPass::class);
175177
$container->addCompilerPass(new ControllerArgumentValueResolverPass());

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory;
2929
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
3030
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
31+
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
3132
use Symfony\Component\Serializer\Mapping\Loader\LoaderChain;
3233
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
3334
use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter;
@@ -151,6 +152,9 @@
151152
->set('serializer.mapping.chain_loader', LoaderChain::class)
152153
->args([[]])
153154

155+
->set('serializer.mapping.attribute_loader', AttributeLoader::class)
156+
->args([true, []])
157+
154158
// Class Metadata Factory
155159
->set('serializer.mapping.class_metadata_factory', ClassMetadataFactory::class)
156160
->args([service('serializer.mapping.chain_loader')])

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/serializer_mapping_without_annotations.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
'handle_all_throwables' => true,
77
'php_errors' => ['log' => true],
88
'serializer' => [
9-
'enable_attributes' => false,
9+
'enable_attributes' => true,
1010
'mapping' => [
1111
'paths' => [
1212
'%kernel.project_dir%/Fixtures/TestBundle/Resources/config/serializer_mapping/files',

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/serializer_mapping_without_annotations.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<framework:config http-method-override="false" handle-all-throwables="true">
88
<framework:annotations enabled="false" />
99
<framework:php-errors log="true" />
10-
<framework:serializer enable-attributes="false">
10+
<framework:serializer enable-attributes="true">
1111
<framework:mapping>
1212
<framework:path>%kernel.project_dir%/Fixtures/TestBundle/Resources/config/serializer_mapping/files</framework:path>
1313
<framework:path>%kernel.project_dir%/Fixtures/TestBundle/Resources/config/serializer_mapping/serialization.yml</framework:path>

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/serializer_mapping_without_annotations.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ framework:
55
php_errors:
66
log: true
77
serializer:
8-
enable_attributes: false
8+
enable_attributes: true
99
mapping:
1010
paths:
1111
- "%kernel.project_dir%/Fixtures/TestBundle/Resources/config/serializer_mapping/files"

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@
7777
use Symfony\Component\PropertyAccess\PropertyAccessor;
7878
use Symfony\Component\Security\Core\AuthenticationEvents;
7979
use Symfony\Component\Serializer\DependencyInjection\SerializerPass;
80-
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
8180
use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader;
8281
use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader;
8382
use Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer;
@@ -1571,7 +1570,7 @@ public function testSerializerEnabled()
15711570
$argument = $container->getDefinition('serializer.mapping.chain_loader')->getArgument(0);
15721571

15731572
$this->assertCount(2, $argument);
1574-
$this->assertEquals(AttributeLoader::class, $argument[0]->getClass());
1573+
$this->assertEquals(new Reference('serializer.mapping.attribute_loader'), $argument[0]);
15751574
$this->assertEquals(new Reference('serializer.name_converter.camel_case_to_snake_case'), $container->getDefinition('serializer.name_converter.metadata_aware')->getArgument(1));
15761575
$this->assertEquals(new Reference('property_info', ContainerBuilder::IGNORE_ON_INVALID_REFERENCE), $container->getDefinition('serializer.normalizer.object')->getArgument(3));
15771576
}
@@ -1761,6 +1760,7 @@ public function testSerializerMapping()
17611760
$projectDir = $container->getParameter('kernel.project_dir');
17621761
$configDir = __DIR__.'/Fixtures/TestBundle/Resources/config';
17631762
$expectedLoaders = [
1763+
new Reference('serializer.mapping.attribute_loader'),
17641764
new Definition(XmlFileLoader::class, [$configDir.'/serialization.xml']),
17651765
new Definition(YamlFileLoader::class, [$configDir.'/serialization.yml']),
17661766
new Definition(YamlFileLoader::class, [$projectDir.'/config/serializer/foo.yml']),
@@ -1770,15 +1770,15 @@ public function testSerializerMapping()
17701770
new Definition(YamlFileLoader::class, [$configDir.'/serializer_mapping/serialization.yaml']),
17711771
];
17721772

1773-
foreach ($expectedLoaders as $definition) {
1774-
if (is_file($arg = $definition->getArgument(0))) {
1775-
$definition->replaceArgument(0, strtr($arg, '/', \DIRECTORY_SEPARATOR));
1773+
foreach ($expectedLoaders as $loader) {
1774+
if ($loader instanceof Definition && is_file($arg = $loader->getArgument(0))) {
1775+
$loader->replaceArgument(0, strtr($arg, '/', \DIRECTORY_SEPARATOR));
17761776
}
17771777
}
17781778

17791779
$loaders = $container->getDefinition('serializer.mapping.chain_loader')->getArgument(0);
17801780
foreach ($loaders as $loader) {
1781-
if (is_file($arg = $loader->getArgument(0))) {
1781+
if ($loader instanceof Definition && is_file($arg = $loader->getArgument(0))) {
17821782
$loader->replaceArgument(0, strtr($arg, '/', \DIRECTORY_SEPARATOR));
17831783
}
17841784
}

src/Symfony/Component/Serializer/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ CHANGELOG
44
7.4
55
---
66

7+
* Add `AttributeMetadataPass` to declare compile-time constraint metadata using attributes
78
* Add `CDATA_WRAPPING_NAME_PATTERN` support to `XmlEncoder`
89
* Add support for `can*()` methods to `AttributeLoader`
910
* Make `AttributeMetadata` and `ClassMetadata` final
1011
* Deprecate class aliases in the `Annotation` namespace, use attributes instead
1112
* Deprecate getters in attribute classes in favor of public properties
13+
* Deprecate `ClassMetadataFactoryCompiler`
1214

1315
7.3
1416
---

0 commit comments

Comments
 (0)