10000 [Serializer] Add support for auto generated custom normalizers · symfony/symfony@b91c59b · GitHub
[go: up one dir, main page]

Skip to content

Commit b91c59b

Browse files
committed
[Serializer] Add support for auto generated custom normalizers
1 parent dc330b0 commit b91c59b

File tree

71 files changed

+4908
-4
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+4908
-4
lines changed

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1115,6 +1115,40 @@ private function addSerializerSection(ArrayNodeDefinition $rootNode, callable $e
11151115
->defaultValue([])
11161116
->prototype('variable')->end()
11171117
->end()
1118+
->arrayNode('auto_normalizer')
1119+
->addDefaultsIfNotSet()
1120+
->fixXmlConfig('path')
1121+
->children()
1122+
->arrayNode('paths')
1123+
->validate()
1124+
->ifTrue(function ($data): bool {
1125+
foreach ($data as $key => $value) {
1126+
if (!\is_string($key)) {
1127+
return true;
1128+
}
1129+
if (!\is_string($value)) {
1130+
return true;
1131+
}
1132+
}
1133+
1134+
return false;
1135+
})
1136+
->thenInvalid('The value must be an array with keys and values. Keys should be the start of a namespace and the values should be a file path.')
1137+
->end()
1138+
->info('Paths where we store classes we want to automatically create normalizers for.')
1139+
E377 ->normalizeKeys(false)
1140+
->defaultValue([])
1141+
->example(['App\\Model' => 'src/Model', 'App\\Entity' => 'src/Entity'])
1142+
->useAttributeAsKey('name')
1143+
->variablePrototype()
1144+
->validate()
1145+
->ifTrue(fn ($value): bool => !\is_string($value))
1146+
->thenInvalid('The value must be a string representing a path relative to the project root.')
1147+
->end()
1148+
->end()
1149+
->end()
1150+
->end()
1151+
->end()
11181152
->end()
11191153
->end()
11201154
->end()

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1861,6 +1861,8 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder
18611861
$container->removeDefinition('serializer.normalizer.translatable');
18621862
}
18631863

1864+
$container->getDefinition('serializer.custom_normalizer_helper')->replaceArgument(2, $config['auto_normalizer']['paths']);
1865+
18641866
$serializerLoaders = [];
18651867
if (isset($config['enable_attributes']) && $config['enable_attributes']) {
18661868
$attributeLoader = new Definition(AttributeLoader::class);
@@ -1938,12 +1940,14 @@ private function registerPropertyInfoConfiguration(ContainerBuilder $container,
19381940
) {
19391941
$definition = $container->register('property_info.phpstan_extractor', PhpStanExtractor::class);
19401942
$definition->addTag('property_info.type_extractor', ['priority' => -1000]);
1943+
$definition->addTag('property_info.property_info.constructor_argument_type_extractor');
19411944
}
19421945

19431946
if (ContainerBuilder::willBeAvailable('phpdocumentor/reflection-docblock', DocBlockFactoryInterface::class, ['symfony/framework-bundle', 'symfony/property-info'], true)) {
19441947
$definition = $container->register('property_info.php_doc_extractor', PhpDocExtractor::class);
19451948
$definition->addTag('property_info.description_extractor', ['priority' => -1000]);
19461949
$definition->addTag('property_info.type_extractor', ['priority' => -1001]);
1950+
$definition->addTag('property_info.property_info.constructor_argument_type_extractor');
19471951
}
19481952

19491953
if ($container->getParameter('kernel.debug')) {

src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,8 @@ public function build(ContainerBuilder $container): void
150150
$this->addCompilerPassIfExists($container, TranslationExtractorPass::class);
151151
$this->addCompilerPassIfExists($container, TranslationDumperPass::class);
152152
$container->addCompilerPass(new FragmentRendererPass());
153-
$this->addCompilerPassIfExists($container, SerializerPass::class);
154153
$this->addCompilerPassIfExists($container, PropertyInfoPass::class);
154+
$this->addCompilerPassIfExists($container, SerializerPass::class);
155155
$container->addCompilerPass(new ControllerArgumentValueResolverPass());
156156
$container->addCompilerPass(new CachePoolPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 32);
157157
$container->addCompilerPass(new CachePoolClearerPass(), PassConfig::TYPE_AFTER_REMOVING);

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
1313

14+
use Symfony\Component\PropertyInfo\Extractor\ConstructorArgumentTypeExtractorAggregate;
15+
use Symfony\Component\PropertyInfo\Extractor\ConstructorArgumentTypeExtractorInterface;
1416
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
1517
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
1618
use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface;
@@ -45,8 +47,14 @@
4547
->tag('property_info.type_extractor', ['priority' => -1002])
4648
->tag('property_info.access_extractor', ['priority' => -1000])
4749
->tag('property_info.initializable_extractor', ['priority' => -1000])
50+
->tag('property_info.property_info.constructor_argument_type_extractor')
4851

4952
->alias(PropertyReadInfoExtractorInterface::class, 'property_info.reflection_extractor')
5053
->alias(PropertyWriteInfoExtractorInterface::class, 'property_info.reflection_extractor')
54+
55+
->set('property_info.constructor_argument_type_extractor_aggregate', ConstructorArgumentTypeExtractorAggregate::class)
56+
->args([tagged_iterator('property_info.constructor_argument_type_extractor')])
57+
->alias(ConstructorArgumentTypeExtractorInterface::class, 'property_info.constructor_argument_type_extractor_aggregate')
58+
5159
;
5260
};

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,14 @@
1616
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
1717
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
1818
use Symfony\Component\ErrorHandler\ErrorRenderer\SerializerErrorRenderer;
19+
use Symfony\Component\PropertyInfo\Extractor\ConstructorArgumentTypeExtractorInterface;
1920
use Symfony\Component\PropertyInfo\Extractor\SerializerExtractor;
21+
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
22+
use Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface;
23+
use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface;
24+
use Symfony\Component\Serializer\Builder\DefinitionExtractor;
25+
use Symfony\Component\Serializer\Builder\NormalizerBuilder;
26+
use Symfony\Component\Serializer\DependencyInjection\CustomNormalizerHelper;
2027
use Symfony\Component\Serializer\Encoder\CsvEncoder;
2128
use Symfony\Component\Serializer\Encoder\DecoderInterface;
2229
use Symfony\Component\Serializer\Encoder\EncoderInterface;
@@ -169,6 +176,25 @@
169176
service('serializer.mapping.cache.symfony'),
170177
])
171178

179+
// Auto Normalizer Builder
180+
->set('serializer.auto_normalizer.builder', NormalizerBuilder::class)
181+
->set('serializer.auto_normalizer.definition_extractor', DefinitionExtractor::class)
182+
->args([
183+
service(PropertyInfoExtractorInterface::class),
184+
service(PropertyReadInfoExtractorInterface::class),
185+
service(PropertyWriteInfoExtractorInterface::class),
186+
service(ConstructorArgumentTypeExtractorInterface::class),
187+
])
188+
189+
->set('serializer.custom_normalizer_helper', CustomNormalizerHelper::class)
190+
->args([
191+
service('serializer.auto_normalizer.builder'),
192+
service('serializer.auto_normalizer.definition_extractor'),
193+
[],
194+
param('kernel.project_dir'),
195+
service('logger')->nullOnInvalid(),
196+
])
197+
172198
// Encoders
173199
->set('serializer.encoder.xml', XmlEncoder::class)
174200
->tag('serializer.encoder')

src/Symfony/Component/PropertyInfo/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ CHANGELOG
55
---
66

77
* Introduce `PropertyDocBlockExtractorInterface` to extract a property's doc block
8+
* Make ConstructorArgumentTypeExtractorInterface non-internal
9+
* Add `ConstructorArgumentTypeExtractorAggregate` to aggregate multiple `ConstructorArgumentTypeExtractorInterface` implementations
810

911
6.4
1012
---
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\PropertyInfo\Extractor;
13+
14+
/**
15+
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
16+
*/
17+
class ConstructorArgumentTypeExtractorAggregate implements ConstructorArgumentTypeExtractorInterface
18+
{
19+
/**
20+
* @param iterable<int, ConstructorArgumentTypeExtractorInterface> $extractors
21+
*/
22+
public function __construct(
23+
private readonly iterable $extractors = [],
24+
) {
25+
}
26+
27+
public function getTypesFromConstructor(string $class, string $property): ?array
28+
{
29+
$output = [];
30+
foreach ($this->extractors as $extractor) {
31+
$value = $extractor->getTypesFromConstructor($class, $property);
32+
if (null !== $value) {
33+
$output[] = $value;
34+
}
35+
}
36+
37+
if ([] === $output) {
38+
return null;
39+
}
40+
41+
return array_merge([], ...$output);
42+
}
43+
}

src/Symfony/Component/PropertyInfo/Extractor/ConstructorArgumentTypeExtractorInterface.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
* Infers the constructor argument type.
1818
*
1919
* @author Dmitrii Poddubnyi <dpoddubny@gmail.com>
20-
*
21-
* @internal
2220
*/
2321
interface ConstructorArgumentTypeExtractorInterface
2422
{
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Ignore paths
2+
[Tests/CodeGenerator/Fixtures/**]
3+
trim_trailing_whitespace = false
4+
5+
[Tests/Fixtures/CustomNormalizer/**/ExpectedNormalizer/**]
6+
trim_trailing_whitespace = false
7+
insert_final_newline = false
8+
indent_size = unset
9+
indent_style = unset
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
vendor/
22
composer.lock
33
phpunit.xml
4+
Tests/_output
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Serializer\Annotation;
13+
14+
class_exists(\Symfony\Component\Serializer\Attribute\Serializable::class);
15+
16+
if (false) {
17+
#[\Attribute(\Attribute::TARGET_CLASS)]
18+
class Serializable extends \Symfony\Component\Serializer\Attribute\Serializable
19+
{
20+
}
21+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Serializer\Attribute;
13+
14+
/**
15+
* Classes with this attribute will get a custom normalizer to improve speed when
16+
* serializing/deserializing.
17+
*
18+
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
19+
*/
20+
#[\Attribute(\Attribute::TARGET_CLASS)]
21+
class Serializable
22+
{
23+
}
24+
25+
if (!class_exists(\Symfony\Component\Serializer\Annotation\Serializable::class, false)) {
26+
class_alias(Serializable::class, \Symfony\Component\Serializer\Annotation\Serializable::class);
27+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Serializer\Builder;
13+
14+
/**
15+
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
16+
*
17+
* @experimental in 7.1
18+
*/
19+
class BuildResult
20+
{
21+
public function __construct(
22+
// The full file location where the class is stored
23+
public readonly string $filePath,
24+
// Just the class name
25+
public readonly string $className,
26+
// Class name with namespace
27+
public readonly string $classNs,
28+
) {
29+
}
30+
31+
public function loadClass()
32+
{
33+
require_once $this->filePath;
34+
}
35+
}

0 commit comments

Comments
 (0)
0