Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit b91c59b

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

File tree

71 files changed

+4908
-4
lines changed
Filter options

Some content is hidden

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

71 files changed

+4908
-4
lines changed

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
+34Lines 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+
->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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+4Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
+1-1Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/property_info.php
+8Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php
+26Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/PropertyInfo/CHANGELOG.md
+2Lines 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
---
+43Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/PropertyInfo/Extractor/ConstructorArgumentTypeExtractorInterface.php
-2Lines 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
{
+9Lines 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
+1Lines 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
+21Lines 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+
}
+27Lines 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+
}
+35Lines 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)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.