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 d1d1ceb

Browse filesBrowse files
fbourigaultdunglas
authored andcommitted
[Serialized] allow configuring the serialized name of properties through metadata
1 parent 5a0cad2 commit d1d1ceb
Copy full SHA for d1d1ceb

22 files changed

+516
-6
lines changed

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@
7474
use Symfony\Component\PropertyAccess\PropertyAccessor;
7575
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
7676
use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface;
77-
use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface;
7877
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
78+
use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface;
7979
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
8080
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
8181
use Symfony\Component\Routing\Loader\AnnotationDirectoryLoader;
@@ -1363,7 +1363,7 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder
13631363
}
13641364

13651365
if (isset($config['name_converter']) && $config['name_converter']) {
1366-
$container->getDefinition('serializer.normalizer.object')->replaceArgument(1, new Reference($config['name_converter']));
1366+
$container->getDefinition('serializer.name_converter.metadata_aware')->setArgument(1, new Reference($config['name_converter']));
13671367
}
13681368

13691369
if (isset($config['circular_reference_handler']) && $config['circular_reference_handler']) {

‎src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml
+5-1Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858

5959
<service id="serializer.normalizer.object" class="Symfony\Component\Serializer\Normalizer\ObjectNormalizer">
6060
<argument type="service" id="serializer.mapping.class_metadata_factory" />
61-
<argument>null</argument> <!-- name converter -->
61+
<argument type="service" id="serializer.name_converter.metadata_aware" />
6262
<argument type="service" id="serializer.property_accessor" />
6363
<argument type="service" id="property_info" on-invalid="ignore" />
6464
<argument type="service" id="serializer.mapping.class_discriminator_resolver" on-invalid="ignore" />
@@ -119,6 +119,10 @@
119119
<!-- Name converter -->
120120
<service id="serializer.name_converter.camel_case_to_snake_case" class="Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter" />
121121

122+
<service id="serializer.name_converter.metadata_aware" class="Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter" >
123+
<argument type="service" id="serializer.mapping.class_metadata_factory"/>
124+
</service>
125+
122126
<!-- PropertyInfo extractor -->
123127
<service id="property_info.serializer_extractor" class="Symfony\Component\PropertyInfo\Extractor\SerializerExtractor">
124128
<argument type="service" id="serializer.mapping.class_metadata_factory" />

‎src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -976,7 +976,7 @@ public function testSerializerEnabled()
976976
$this->assertCount(2, $argument);
977977
$this->assertEquals('Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader', $argument[0]->getClass());
978978
$this->assertNull($container->getDefinition('serializer.mapping.class_metadata_factory')->getArgument(1));
979-
$this->assertEquals(new Reference('serializer.name_converter.camel_case_to_snake_case'), $container->getDefinition('serializer.normalizer.object')->getArgument(1));
979+
$this->assertEquals(new Reference('serializer.name_converter.camel_case_to_snake_case'), $container->getDefinition('serializer.name_converter.metadata_aware')->getArgument(1));
980980
$this->assertEquals(new Reference('property_info', ContainerBuilder::IGNORE_ON_INVALID_REFERENCE), $container->getDefinition('serializer.normalizer.object')->getArgument(3));
981981
$this->assertEquals(array('setCircularReferenceHandler', array(new Reference('my.circular.reference.handler'))), $container->getDefinition('serializer.normalizer.object')->getMethodCalls()[0]);
982982
$this->assertEquals(array('setMaxDepthHandler', array(new Reference('my.max.depth.handler'))), $container->getDefinition('serializer.normalizer.object')->getMethodCalls()[1]);

‎src/Symfony/Bundle/FrameworkBundle/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/composer.json
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
"symfony/process": "~3.4|~4.0",
4646
"symfony/security-core": "~3.4|~4.0",
4747
"symfony/security-csrf": "~3.4|~4.0",
48-
"symfony/serializer": "^4.1",
48+
"symfony/serializer": "^4.2",
4949
"symfony/stopwatch": "~3.4|~4.0",
5050
"symfony/translation": "~4.2",
5151
"symfony/templating": "~3.4|~4.0",
+48Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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+
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
15+
16+
/**
17+
* Annotation class for @SerializedName().
18+
*
19+
* @Annotation
20+
* @Target({"PROPERTY", "METHOD"})
21+
*
22+
* @author Fabien Bourigault <bourigaultfabien@gmail.com>
23+
*/
24+
final class SerializedName
25+
{
26+
/**
27+
* @var string
28+
*/
29+
private $serializedName;
30+
31+
public function __construct(array $data)
32+
{
33+
if (!isset($data['value'])) {
34+
throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" should be set.', \get_class($this)));
35+
}
36+
37+
if (!\is_string($data['value']) || empty($data['value'])) {
38+
throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a non-empty string.', \get_class($this)));
39+
}
40+
41+
$this->serializedName = $data['value'];
42+
}
43+
44+
public function getSerializedName(): string
45+
{
46+
return $this->serializedName;
47+
}
48+
}

‎src/Symfony/Component/Serializer/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ CHANGELOG
2222
either `EncoderInterface` or `DecoderInterface`
2323
* added the optional `$objectClassResolver` argument in `AbstractObjectNormalizer`
2424
and `ObjectNormalizer` constructor
25+
* added `MetadataAwareNameConverter` to configure the serialized name of properties through metadata
2526

2627
4.1.0
2728
-----

‎src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php
+31-1Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ class AttributeMetadata implements AttributeMetadataInterface
4141
*/
4242
public $maxDepth;
4343

44+
/**
45+
* @var string|null
46+
*
47+
* @internal This property is public in order to reduce the size of the
48+
* class' serialized representation. Do not access it. Use
49+
* {@link getSerializedName()} instead.
50+
*/
51+
public $serializedName;
52+
4453
public function __construct(string $name)
4554
{
4655
$this->name = $name;
@@ -88,6 +97,22 @@ public function getMaxDepth()
8897
return $this->maxDepth;
8998
}
9099

100+
/**
101+
* {@inheritdoc}
102+
*/
103+
public function setSerializedName(string $serializedName = null)
104+
{
105+
$this->serializedName = $serializedName;
106+
}
107+
108+
/**
109+
* {@inheritdoc}
110+
*/
111+
public function getSerializedName(): ?string
112+
{
113+
return $this->serializedName;
114+
}
115+
91116
/**
92117
* {@inheritdoc}
93118
*/
@@ -101,6 +126,11 @@ public function merge(AttributeMetadataInterface $attributeMetadata)
101126
if (null === $this->maxDepth) {
102127
$this->maxDepth = $attributeMetadata->getMaxDepth();
103128
}
129+
130+
// Overwrite only if not defined
131+
if (null === $this->serializedName) {
132+
$this->serializedName = $attributeMetadata->getSerializedName();
133+
}
104134
}
105135

106136
/**
@@ -110,6 +140,6 @@ public function merge(AttributeMetadataInterface $attributeMetadata)
110140
*/
111141
public function __sleep()
112142
{
113-
return array('name', 'groups', 'maxDepth');
143+
return array('name', 'groups', 'maxDepth', 'serializedName');
114144
}
115145
}

‎src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php
+10Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,16 @@ public function setMaxDepth($maxDepth);
5757
*/
5858
public function getMaxDepth();
5959

60+
/**
61+
* Sets the serialization name for this attribute.
62+
*/
63+
public function setSerializedName(string $serializedName = null);
64+
65+
/**
66+
* Gets the serialization name for this attribute.
67+
*/
68+
public function getSerializedName(): ?string;
69+
6070
/**
6171
* Merges an {@see AttributeMetadataInterface} with in the current one.
6272
*/

‎src/Symfony/Component/Serializer/Mapping/Loader/AnnotationLoader.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Mapping/Loader/AnnotationLoader.php
+9Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
1616
use Symfony\Component\Serializer\Annotation\Groups;
1717
use Symfony\Component\Serializer\Annotation\MaxDepth;
18+
use Symfony\Component\Serializer\Annotation\SerializedName;
1819
use Symfony\Component\Serializer\Exception\MappingException;
1920
use Symfony\Component\Serializer\Mapping\AttributeMetadata;
2021
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping;
@@ -68,6 +69,8 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)
6869
}
6970
} elseif ($annotation instanceof MaxDepth) {
7071
$attributesMetadata[$property->name]->setMaxDepth($annotation->getMaxDepth());
72+
} elseif ($annotation instanceof SerializedName) {
73+
$attributesMetadata[$property->name]->setSerializedName($annotation->getSerializedName());
7174
}
7275

7376
$loaded = true;
@@ -107,6 +110,12 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)
107110
}
108111

109112
$attributeMetadata->setMaxDepth($annotation->getMaxDepth());
113+
} elseif ($annotation instanceof SerializedName) {
114+
if (!$accessorOrMutator) {
115+
throw new MappingException(sprintf('SerializedName on "%s::%s" cannot be added. SerializedName can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name));
116+
}
117+
118+
$attributeMetadata->setSerializedName($annotation->getSerializedName());
110119
}
111120

112121
$loaded = true;

‎src/Symfony/Component/Serializer/Mapping/Loader/XmlFileLoader.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Mapping/Loader/XmlFileLoader.php
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)
6666
if (isset($attribute['max-depth'])) {
6767
$attributeMetadata->setMaxDepth((int) $attribute['max-depth']);
6868
}
69+
70+
if (isset($attribute['serialized-name'])) {
71+
$attributeMetadata->setSerializedName((string) $attribute['serialized-name']);
72+
}
6973
}
7074

7175
if (isset($xml->{'discriminator-map'})) {

‎src/Symfony/Component/Serializer/Mapping/Loader/YamlFileLoader.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Mapping/Loader/YamlFileLoader.php
+8Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,14 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)
8585

8686
$attributeMetadata->setMaxDepth($data['max_depth']);
8787
}
88+
89+
if (isset($data['serialized_name'])) {
90+
if (!\is_string($data['serialized_name']) || empty($data['serialized_name'])) {
91+
throw new MappingException(sprintf('The "serialized_name" value must be a non-empty string in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName()));
92+
}
93+
94+
$attributeMetadata->setSerializedName($data['serialized_name']);
95+
}
8896
}
8997
}
9098

‎src/Symfony/Component/Serializer/Mapping/Loader/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Mapping/Loader/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd
+7Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@
7171
</xsd:restriction>
7272
</xsd:simpleType>
7373
</xsd:attribute>
74+
<xsd:attribute name="serialized-name">
75+
<xsd:simpleType>
76+
<xsd:restriction base="xsd:string">
77+
<xsd:minLength value="1" />
78+
</xsd:restriction>
79+
</xsd:simpleType>
80+
</xsd:attribute>
7481
</xsd:complexType>
7582

7683
</xsd:schema>
+119Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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\NameConverter;
13+
14+
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
15+
16+
/**
17+
* @author Fabien Bourigault <bourigaultfabien@gmail.com>
18+
*/
19+
final class MetadataAwareNameConverter implements AdvancedNameConverterInterface
20+
{
21+
private $metadataFactory;
22+
23+
/**
24+
* @var NameConverterInterface|AdvancedNameConverterInterface|null
25+
*/
26+
private $fallbackNameConverter;
27+
28+
private static $normalizeCache = array();
29+
30+
private static $denormalizeCache = array();
31+
32+
private static $attributesMetadataCache = array();
33+
34+
public function __construct(ClassMetadataFactoryInterface $metadataFactory, NameConverterInterface $fallbackNameConverter = null)
35+
{
36+
$this->metadataFactory = $metadataFactory;
37+
$this->fallbackNameConverter = $fallbackNameConverter;
38+
}
39+
40+
/**
41+
* {@inheritdoc}
42+
*/
43+
public function normalize($propertyName, string $class = null, string $format = null, array $context = array())
44+
{
45+
if (null === $class) {
46+
return $this->normalizeFallback($propertyName, $class, $format, $context);
47+
}
48+
49+
if (!isset(self::$normalizeCache[$class][$propertyName])) {
50+
self::$normalizeCache[$class][$propertyName] = $this->getCacheValueForNormalization($propertyName, $class);
51+
}
52+
53+
return self::$normalizeCache[$class][$propertyName] ?? $this->normalizeFallback($propertyName, $class, $format, $context);
54+
}
55+
56+
/**
57+
* {@inheritdoc}
58+
*/
59+
public function denormalize($propertyName, string $class = null, string $format = null, array $context = array())
60+
{
61+
if (null === $class) {
62+
return $this->denormalizeFallback($propertyName, $class, $format, $context);
63+
}
64+
65+
if (!isset(self::$denormalizeCache[$class][$propertyName])) {
66+
self::$denormalizeCache[$class][$propertyName] = $this->getCacheValueForDenormalization($propertyName, $class);
67+
}
68+
69+
return self::$denormalizeCache[$class][$propertyName] ?? $this->denormalizeFallback($propertyName, $class, $format, $context);
70+
}
71+
72+
private function getCacheValueForNormalization(string $propertyName, string $class): ?string
73+
{
74+
if (!$this->metadataFactory->hasMetadataFor($class)) {
75+
return null;
76+
}
77+
78+
return $this->metadataFactory->getMetadataFor($class)->getAttributesMetadata()[$propertyName]->getSerializedName() ?? null;
79+
}
80+
81+
private function normalizeFallback(string $propertyName, string $class = null, string $format = null, array $context = array()): string
82+
{
83+
return $this->fallbackNameConverter ? $this->fallbackNameConverter->normalize($propertyName, $class, $format, $context) : $propertyName;
84+
}
85+
86+
private function getCacheValueForDenormalization(string $propertyName, string $class): ?string
87+
{
88+
if (!isset(self::$attributesMetadataCache[$class])) {
89+
self::$attributesMetadataCache[$class] = $this->getCacheValueForAttributesMetadata($class);
90+
}
91+
92+
return self::$attributesMetadataCache[$class][$propertyName] ?? null;
93+
}
94+
95+
private function denormalizeFallback(string $propertyName, string $class = null, string $format = null, array $context = array()): string
96+
{
97+
return $this->fallbackNameConverter ? $this->fallbackNameConverter->denormalize($propertyName, $class, $format, $context) : $propertyName;
98+
}
99+
100+
private function getCacheValueForAttributesMetadata(string $class): array
101+
{
102+
if (!$this->metadataFactory->hasMetadataFor($class)) {
103+
return array();
104+
}
105+
106+
$classMetadata = $this->metadataFactory->getMetadataFor($class);
107+
108+
$cache = array();
109+
foreach ($classMetadata->getAttributesMetadata() as $name => $metadata) {
110+
if (null === $metadata->getSerializedName()) {
111+
continue;
112+
}
113+
114+
$cache[$metadata->getSerializedName()] = $name;
115+
}
116+
117+
return $cache;
118+
}
119+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.