Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit bd4277c

Browse files
boennernicolas-grekas
authored andcommitted
[Serializer] Flatten nested attributes
1 parent c63bd7e commit bd4277c

25 files changed

+616
-34
lines changed

src/Symfony/Component/Serializer/Annotation/SerializedName.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ final class SerializedName
2727
{
2828
public function __construct(private string $serializedName)
2929
{
30-
if (empty($serializedName)) {
31-
throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a non-empty string.', static::class));
30+
if ('' === $serializedName) {
31+
throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a non-empty string.', self::class));
3232
}
3333
}
3434

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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 @SerializedPath().
18+
*
19+
* @Annotation
20+
*
21+
* @NamedArgumentConstructor
22+
*
23+
* @Target({"PROPERTY", "METHOD"})
24+
*
25+
* @author Tobias Bönner <[email protected]>
26+
*/
27+
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)]
28+
final class SerializedPath
29+
{
30+
public function __construct(private array|string $serializedPath) {
31+
if ([] === $serializedPath || '' === $serializedPath) {
32+
throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must not be empty.', self::class));
33+
}
34+
}
35+
36+
public function getSerializedPath(): array
37+
{
38+
return (array) $this->serializedPath;
39+
}
40+
}

src/Symfony/Component/Serializer/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ CHANGELOG
1111
* Change the signature of `AttributeMetadataInterface::setSerializedName()` to `setSerializedName(?string)`
1212
* Change the signature of `ClassMetadataInterface::setClassDiscriminatorMapping()` to `setClassDiscriminatorMapping(?ClassDiscriminatorMapping)`
1313
* Add option YamlEncoder::YAML_INDENTATION to YamlEncoder constructor options to configure additional indentation for each level of nesting. This allows configuring indentation in the service configuration.
14+
* Add `SerializedPath` annotation to flatten nested attributes
1415

1516
6.1
1617
---

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

+21-9
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ class AttributeMetadata implements AttributeMetadataInterface
4848
*/
4949
public $serializedName;
5050

51+
/**
52+
* @internal This property is public in order to reduce the size of the
53+
* class' serialized representation. Do not access it. Use
54+
* {@link getSerializedPath()} instead.
55+
*/
56+
public ?array $serializedPath = null;
57+
5158
/**
5259
* @var bool
5360
*
@@ -121,6 +128,16 @@ public function getSerializedName(): ?string
121128
return $this->serializedName;
122129
}
123130

131+
public function setSerializedPath(?array $serializedPath): void
132+
{
133+
$this->serializedPath = $serializedPath;
134+
}
135+
136+
public function getSerializedPath(): ?array
137+
{
138+
return $this->serializedPath;
139+
}
140+
124141
public function setIgnore(bool $ignore)
125142
{
126143
$this->ignore = $ignore;
@@ -190,14 +207,9 @@ public function merge(AttributeMetadataInterface $attributeMetadata)
190207
}
191208

192209
// Overwrite only if not defined
193-
if (null === $this->maxDepth) {
194-
$this->maxDepth = $attributeMetadata->getMaxDepth();
195-
}
196-
197-
// Overwrite only if not defined
198-
if (null === $this->serializedName) {
199-
$this->serializedName = $attributeMetadata->getSerializedName();
200-
}
210+
$this->maxDepth ??= $attributeMetadata->getMaxDepth();
211+
$this->serializedName ??= $attributeMetadata->getSerializedName();
212+
$this->serializedPath ??= $attributeMetadata->getSerializedPath();
201213

202214
// Overwrite only if both contexts are empty
203215
if (!$this->normalizationContexts && !$this->denormalizationContexts) {
@@ -217,6 +229,6 @@ public function merge(AttributeMetadataInterface $attributeMetadata)
217229
*/
218230
public function __sleep(): array
219231
{
220-
return ['name', 'groups', 'maxDepth', 'serializedName', 'ignore', 'normalizationContexts', 'denormalizationContexts'];
232+
return ['name', 'groups', 'maxDepth', 'serializedName', 'serializedPath', 'ignore', 'normalizationContexts', 'denormalizationContexts'];
221233
}
222234
}

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

+4
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ public function setSerializedName(?string $serializedName);
5959
*/
6060
public function getSerializedName(): ?string;
6161

62+
public function setSerializedPath(?array $serializedPath): void;
63+
64+
public function getSerializedPath(): ?array;
65+
6266
/**
6367
* Sets if this attribute must be ignored or not.
6468
*/

src/Symfony/Component/Serializer/Mapping/Factory/ClassMetadataFactoryCompiler.php

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ private function generateDeclaredClassMetadata(array $classMetadatas): string
4848
$attributeMetadata->getGroups(),
4949
$attributeMetadata->getMaxDepth(),
5050
$attributeMetadata->getSerializedName(),
51+
$attributeMetadata->getSerializedPath(),
5152
];
5253
}
5354

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

+10
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Symfony\Component\Serializer\Annotation\Ignore;
1919
use Symfony\Component\Serializer\Annotation\MaxDepth;
2020
use Symfony\Component\Serializer\Annotation\SerializedName;
21+
use Symfony\Component\Serializer\Annotation\SerializedPath;
2122
use Symfony\Component\Serializer\Exception\MappingException;
2223
use Symfony\Component\Serializer\Mapping\AttributeMetadata;
2324
use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface;
@@ -38,6 +39,7 @@ class AnnotationLoader implements LoaderInterface
3839
Ignore::class,
3940
MaxDepth::class,
4041
SerializedName::class,
42+
SerializedPath::class,
4143
Context::class,
4244
];
4345

@@ -81,6 +83,8 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool
8183
$attributesMetadata[$property->name]->setMaxDepth($annotation->getMaxDepth());
8284
} elseif ($annotation instanceof SerializedName) {
8385
$attributesMetadata[$property->name]->setSerializedName($annotation->getSerializedName());
86+
} elseif ($annotation instanceof SerializedPath) {
87+
$attributesMetadata[$property->name]->setSerializedPath($annotation->getSerializedPath());
8488
} elseif ($annotation instanceof Ignore) {
8589
$attributesMetadata[$property->name]->setIgnore(true);
8690
} elseif ($annotation instanceof Context) {
@@ -134,6 +138,12 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool
134138
}
135139

136140
$attributeMetadata->setSerializedName($annotation->getSerializedName());
141+
} elseif ($annotation instanceof SerializedPath) {
142+
if (!$accessorOrMutator) {
143+
throw new MappingException(sprintf('SerializedPath on "%s::%s()" cannot be added. SerializedPath can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name));
144+
}
145+
146+
$attributeMetadata->setSerializedPath($annotation->getSerializedPath());
137147
} elseif ($annotation instanceof Ignore) {
138148
if (!$accessorOrMutator) {
139149
throw new MappingException(sprintf('Ignore on "%s::%s()" cannot be added. Ignore can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name));

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

+4
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool
6868
$attributeMetadata->setSerializedName((string) $attribute['serialized-name']);
6969
}
7070

71+
if (isset($attribute->serialized_path)) {
72+
$attributeMetadata->setSerializedPath((array) $attribute->serialized_path->path);
73+
}
74+
7175
if (isset($attribute['ignore'])) {
7276
$attributeMetadata->setIgnore(XmlUtils::phpize($attribute['ignore']));
7377
}

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

+9-1
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,21 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool
8484
}
8585

8686
if (isset($data['serialized_name'])) {
87-
if (!\is_string($data['serialized_name']) || empty($data['serialized_name'])) {
87+
if (!\is_string($data['serialized_name']) || '' === $data['serialized_name']) {
8888
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()));
8989
}
9090

9191
$attributeMetadata->setSerializedName($data['serialized_name']);
9292
}
9393

94+
if (isset($data['serialized_path'])) {
95+
if ('' === $data['serialized_path'] || [] === $data['serialized_path']) {
96+
throw new MappingException(sprintf('The "serialized_path" value must not be empty in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName()));
97+
}
98+
99+
$attributeMetadata->setSerializedPath($data['serialized_path']);
100+
}
101+
94102
if (isset($data['ignore'])) {
95103
if (!\is_bool($data['ignore'])) {
96104
throw new MappingException(sprintf('The "ignore" value must be a boolean in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName()));

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

+7
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
<xsd:element name="context" type="context" maxOccurs="unbounded" />
6666
<xsd:element name="normalization_context" type="context" maxOccurs="unbounded" />
6767
<xsd:element name="denormalization_context" type="context" maxOccurs="unbounded" />
68+
<xsd:element name="serialized_path" type="path" maxOccurs="1" />
6869
</xsd:choice>
6970
<xsd:attribute name="name" type="xsd:string" use="required" />
7071
<xsd:attribute name="max-depth">
@@ -84,6 +85,12 @@
8485
<xsd:attribute name="ignore" type="xsd:boolean" />
8586
</xsd:complexType>
8687

88+
<xsd:complexType name="path">
89+
<xsd:sequence minOccurs="1">
90+
<xsd:element name="path" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />
91+
</xsd:sequence>
92+
</xsd:complexType>
93+
8794
<xsd:complexType name="context">
8895
<xsd:choice maxOccurs="unbounded">
8996
<xsd:element name="group" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />

src/Symfony/Component/Serializer/NameConverter/MetadataAwareNameConverter.php

+9
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\Serializer\NameConverter;
1313

14+
use Symfony\Component\Serializer\Exception\LogicException;
1415
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
1516
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
1617

@@ -76,6 +77,10 @@ private function getCacheValueForNormalization(string $propertyName, string $cla
7677
return null;
7778
}
7879

80+
if (null !== $attributesMetadata[$propertyName]->getSerializedName() && null !== $attributesMetadata[$propertyName]->getSerializedPath()) {
81+
throw new LogicException(sprintf('Found SerializedName and SerializedPath annotations on property "%s" of class "%s".', $propertyName, $class));
82+
}
83+
7984
return $attributesMetadata[$propertyName]->getSerializedName() ?? null;
8085
}
8186

@@ -113,6 +118,10 @@ private function getCacheValueForAttributesMetadata(string $class, array $contex
113118
continue;
114119
}
115120

121+
if (null !== $metadata->getSerializedName() && null !== $metadata->getSerializedPath()) {
122+
throw new LogicException(sprintf('Found SerializedName and SerializedPath annotations on property "%s" of class "%s".', $name, $class));
123+
}
124+
116125
$groups = $metadata->getGroups();
117126
if (!$groups && ($context[AbstractNormalizer::GROUPS] ?? [])) {
118127
continue;

0 commit comments

Comments
 (0)