Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions UPGRADE-7.4.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ Serializer
* Make `AttributeMetadata` and `ClassMetadata` final
* Deprecate class aliases in the `Annotation` namespace, use attributes instead
* Deprecate getters in attribute classes in favor of public properties
* Deprecate `ClassMetadataFactoryCompiler`

String
------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
use Symfony\Component\Serializer\Mapping\Loader\LoaderChain;
use Symfony\Component\Serializer\Mapping\Loader\LoaderInterface;
use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader;
use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader;

/**
* Warms up XML and YAML serializer metadata.
* Warms up serializer metadata.
*
* @author Titouan Galopin <galopintitouan@gmail.com>
*
Expand Down Expand Up @@ -66,14 +67,14 @@ protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter, ?strin
/**
* @param LoaderInterface[] $loaders
*
* @return XmlFileLoader[]|YamlFileLoader[]
* @return list<XmlFileLoader|YamlFileLoader|AttributeLoader>
*/
private function extractSupportedLoaders(array $loaders): array
{
$supportedLoaders = [];

foreach ($loaders as $loader) {
if ($loader instanceof XmlFileLoader || $loader instanceof YamlFileLoader) {
if (method_exists($loader, 'getMappedClasses')) {
$supportedLoaders[] = $loader;
} elseif ($loader instanceof LoaderChain) {
$supportedLoaders = array_merge($supportedLoaders, $this->extractSupportedLoaders($loader->getLoaders()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,10 @@
use Symfony\Component\Semaphore\Semaphore;
use Symfony\Component\Semaphore\SemaphoreFactory;
use Symfony\Component\Semaphore\Store\StoreFactory as SemaphoreStoreFactory;
use Symfony\Component\Serializer\Attribute as SerializerMapping;
use Symfony\Component\Serializer\DependencyInjection\AttributeMetadataPass as SerializerAttributeMetadataPass;
use Symfony\Component\Serializer\Encoder\DecoderInterface;
use Symfony\Component\Serializer\Encoder\EncoderInterface;
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader;
use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader;
use Symfony\Component\Serializer\NameConverter\SnakeCaseToCamelCaseNameConverter;
Expand Down Expand Up @@ -2073,10 +2074,38 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder
}

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

$serializerLoaders[] = $attributeLoader;
// When attributes are disabled, it means from runtime-discovery only; autoconfiguration should still happen.
// And when runtime-discovery of attributes is enabled, we can skip compile-time autoconfiguration in debug mode.
if (class_exists(SerializerAttributeMetadataPass::class) && (!($config['enable_attributes'] ?? false) || !$container->getParameter('kernel.debug'))) {
// The $reflector argument hints at where the attribute could be used
$configurator = function (ChildDefinition $definition, object $attribute, \ReflectionClass|\ReflectionMethod|\ReflectionProperty $reflector) {
$definition->addTag('serializer.attribute_metadata');
};
$container->registerAttributeForAutoconfiguration(SerializerMapping\Context::class, $configurator);
$container->registerAttributeForAutoconfiguration(SerializerMapping\Groups::class, $configurator);

$configurator = function (ChildDefinition $definition, object $attribute, \ReflectionMethod|\ReflectionProperty $reflector) {
$definition->addTag('serializer.attribute_metadata');
};
$container->registerAttributeForAutoconfiguration(SerializerMapping\Ignore::class, $configurator);
$container->registerAttributeForAutoconfiguration(SerializerMapping\MaxDepth::class, $configurator);
$container->registerAttributeForAutoconfiguration(SerializerMapping\SerializedName::class, $configurator);
$container->registerAttributeForAutoconfiguration(SerializerMapping\SerializedPath::class, $configurator);

$container->registerAttributeForAutoconfiguration(SerializerMapping\DiscriminatorMap::class, function (ChildDefinition $definition) {
$definition->addTag('serializer.attribute_metadata');
});
}

if (($config['enable_attributes'] ?? false) || class_exists(SerializerAttributeMetadataPass::class)) {
$serializerLoaders[] = new Reference('serializer.mapping.attribute_loader');

$container->getDefinition('serializer.mapping.attribute_loader')
->replaceArgument(0, $config['enable_attributes'] ?? false);
} else {
// BC with symfony/serializer < 7.4
$container->removeDefinition('serializer.mapping.attribute_services_loader');
}

$fileRecorder = function ($extension, $path) use (&$serializerLoaders) {
Expand Down
2 changes: 2 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
use Symfony\Component\Routing\DependencyInjection\RoutingResolverPass;
use Symfony\Component\Runtime\SymfonyRuntime;
use Symfony\Component\Scheduler\DependencyInjection\AddScheduleMessengerPass;
use Symfony\Component\Serializer\DependencyInjection\AttributeMetadataPass as SerializerAttributeMetadataPass;
use Symfony\Component\Serializer\DependencyInjection\SerializerPass;
use Symfony\Component\Translation\DependencyInjection\DataCollectorTranslatorPass;
use Symfony\Component\Translation\DependencyInjection\LoggingTranslatorPass;
Expand Down Expand Up @@ -170,6 +171,7 @@ public function build(ContainerBuilder $container): void
$this->addCompilerPassIfExists($container, TranslationDumperPass::class);
$container->addCompilerPass(new FragmentRendererPass());
$this->addCompilerPassIfExists($container, SerializerPass::class);
$this->addCompilerPassIfExists($container, SerializerAttributeMetadataPass::class);
$this->addCompilerPassIfExists($container, PropertyInfoPass::class);
$this->addCompilerPassIfExists($container, PropertyInfoConstructorPass::class);
$container->addCompilerPass(new ControllerArgumentValueResolverPass());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
use Symfony\Component\Serializer\Mapping\Loader\LoaderChain;
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter;
Expand Down Expand Up @@ -151,6 +152,9 @@
->set('serializer.mapping.chain_loader', LoaderChain::class)
->args([[]])

->set('serializer.mapping.attribute_loader', AttributeLoader::class)
->args([true, []])

// Class Metadata Factory
->set('serializer.mapping.class_metadata_factory', ClassMetadataFactory::class)
->args([service('serializer.mapping.chain_loader')])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
'handle_all_throwables' => true,
'php_errors' => ['log' => true],
'serializer' => [
'enable_attributes' => false,
'enable_attributes' => true,
'mapping' => [
'paths' => [
'%kernel.project_dir%/Fixtures/TestBundle/Resources/config/serializer_mapping/files',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<framework:config http-method-override="false" handle-all-throwables="true">
<framework:annotations enabled="false" />
<framework:php-errors log="true" />
<framework:serializer enable-attributes="false">
<framework:serializer enable-attributes="true">
<framework:mapping>
<framework:path>%kernel.project_dir%/Fixtures/TestBundle/Resources/config/serializer_mapping/files</framework:path>
<framework:path>%kernel.project_dir%/Fixtures/TestBundle/Resources/config/serializer_mapping/serialization.yml</framework:path>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ framework:
php_errors:
log: true
serializer:
enable_attributes: false
enable_attributes: true
mapping:
paths:
- "%kernel.project_dir%/Fixtures/TestBundle/Resources/config/serializer_mapping/files"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@
use Symfony\Component\PropertyAccess\PropertyAccessor;
use Symfony\Component\Security\Core\AuthenticationEvents;
use Symfony\Component\Serializer\DependencyInjection\SerializerPass;
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader;
use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader;
use Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer;
Expand Down Expand Up @@ -1571,7 +1570,7 @@ public function testSerializerEnabled()
$argument = $container->getDefinition('serializer.mapping.chain_loader')->getArgument(0);

$this->assertCount(2, $argument);
$this->assertEquals(AttributeLoader::class, $argument[0]->getClass());
$this->assertEquals(new Reference('serializer.mapping.attribute_loader'), $argument[0]);
$this->assertEquals(new Reference('serializer.name_converter.camel_case_to_snake_case'), $container->getDefinition('serializer.name_converter.metadata_aware')->getArgument(1));
$this->assertEquals(new Reference('property_info', ContainerBuilder::IGNORE_ON_INVALID_REFERENCE), $container->getDefinition('serializer.normalizer.object')->getArgument(3));
}
Expand Down Expand Up @@ -1761,6 +1760,7 @@ public function testSerializerMapping()
$projectDir = $container->getParameter('kernel.project_dir');
$configDir = __DIR__.'/Fixtures/TestBundle/Resources/config';
$expectedLoaders = [
new Reference('serializer.mapping.attribute_loader'),
new Definition(XmlFileLoader::class, [$configDir.'/serialization.xml']),
new Definition(YamlFileLoader::class, [$configDir.'/serialization.yml']),
new Definition(YamlFileLoader::class, [$projectDir.'/config/serializer/foo.yml']),
Expand All @@ -1770,15 +1770,15 @@ public function testSerializerMapping()
new Definition(YamlFileLoader::class, [$configDir.'/serializer_mapping/serialization.yaml']),
];

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

$loaders = $container->getDefinition('serializer.mapping.chain_loader')->getArgument(0);
foreach ($loaders as $loader) {
if (is_file($arg = $loader->getArgument(0))) {
if ($loader instanceof Definition && is_file($arg = $loader->getArgument(0))) {
$loader->replaceArgument(0, strtr($arg, '/', \DIRECTORY_SEPARATOR));
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/Symfony/Component/Serializer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ CHANGELOG
7.4
---

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

7.3
---
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Serializer\DependencyInjection;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;

/**
* @author Nicolas Grekas <p@tchwork.com>
*/
final class AttributeMetadataPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
if (!$container->hasDefinition('serializer.mapping.attribute_loader')) {
return;
}

$resolve = $container->getParameterBag()->resolveValue(...);
$taggedClasses = [];
foreach ($container->getDefinitions() as $id => $definition) {
if (!$definition->hasTag('serializer.attribute_metadata')) {
continue;
}
if (!$definition->hasTag('container.excluded')) {
throw new InvalidArgumentException(\sprintf('The resource "%s" tagged "serializer.attribute_metadata" is missing the "container.excluded" tag.', $id));
}
$taggedClasses[$resolve($definition->getClass())] = true;
}

ksort($taggedClasses);

if ($taggedClasses) {
$container->getDefinition('serializer.mapping.attribute_loader')
->replaceArgument(1, array_keys($taggedClasses));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@
use Symfony\Component\Serializer\Mapping\ClassMetadataInterface;
use Symfony\Component\VarExporter\VarExporter;

trigger_deprecation('symfony/serializer', '7.4', 'The "%s" class is deprecated.', ClassMetadataFactoryCompiler::class);

/**
* @author Fabien Bourigault <bourigaultfabien@gmail.com>
*
* @deprecated since Symfony 7.4
*/
final class ClassMetadataFactoryCompiler
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,31 @@ class AttributeLoader implements LoaderInterface
Context::class,
];

public function __construct()
/**
* @param bool|null $allowAnyClass Null is allowed for BC with Symfony <= 6
* @param class-string[] $mappedClasses
*/
public function __construct(
private ?bool $allowAnyClass = true,
private array $mappedClasses = [],
) {
$this->allowAnyClass ??= true;
}

/**
* @return class-string[]
*/
public function getMappedClasses(): array
{
return $this->mappedClasses;
}

public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool
{
if (!$this->allowAnyClass && !\in_array($classMetadata->getName(), $this->mappedClasses, true)) {
return false;
}

$reflectionClass = $classMetadata->getReflectionClass();
$className = $reflectionClass->name;
$loaded = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class XmlFileLoader extends FileLoader
/**
* An array of {@class \SimpleXMLElement} instances.
*
* @var \SimpleXMLElement[]|null
* @var array<class-string, \SimpleXMLElement>|null
*/
private ?array $classes = null;

Expand Down Expand Up @@ -121,7 +121,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool
/**
* Return the names of the classes mapped in this file.
*
* @return string[]
* @return class-string[]
*/
public function getMappedClasses(): array
{
Expand All @@ -144,6 +144,9 @@ private function parseFile(string $file): \SimpleXMLElement
return simplexml_import_dom($dom);
}

/**
* @return array<class-string, \SimpleXMLElement>
*/
private function getClassesFromXml(): array
{
$xml = $this->parseFile($this->file);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class YamlFileLoader extends FileLoader
private ?Parser $yamlParser = null;

/**
* An array of YAML class descriptions.
* @var array<class-string, array>
*/
private ?array $classes = null;

Expand Down Expand Up @@ -144,13 +144,16 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool
/**
* Return the names of the classes mapped in this file.
*
* @return string[]
* @return class-string[]
*/
public function getMappedClasses(): array
{
return array_keys($this->classes ??= $this->getClassesFromYaml());
}

/**
* @return array<class-string, array>
*/
private function getClassesFromYaml(): array
{
if (!stream_is_local($this->file)) {
Expand Down
Loading
Loading