Skip to content

Commit cfb9986

Browse files
committed
[Serializer] Enabled mapping configuration via attributes.
1 parent 177cd1a commit cfb9986

Some content is hidden

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

50 files changed

+638
-119
lines changed

.github/patch-types.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
case false !== strpos($file, '/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Php80Dummy.php'):
3636
case false !== strpos($file, '/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures'):
3737
case false !== strpos($file, '/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ObjectOuter.php'):
38+
case false !== strpos($file, '/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/'):
3839
case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/LotsOfAttributes.php'):
3940
case false !== strpos($file, '/src/Symfony/Component/Validator/Tests/Fixtures/Attribute/'):
4041
case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/MyAttribute.php'):

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

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
*
2222
* @author Samuel Roze <[email protected]>
2323
*/
24+
#[\Attribute(\Attribute::TARGET_CLASS)]
2425
class DiscriminatorMap
2526
{
2627
/**
@@ -34,20 +35,29 @@ class DiscriminatorMap
3435
private $mapping;
3536

3637
/**
38+
* @param string|array $typeProperty
39+
*
3740
* @throws InvalidArgumentException
3841
*/
39-
public function __construct(array $data)
42+
public function __construct($typeProperty, array $mapping = null)
4043
{
41-
if (empty($data['typeProperty'])) {
44+
if (\is_array($typeProperty)) {
45+
$mapping = $typeProperty['mapping'] ?? null;
46+
$typeProperty = $typeProperty['typeProperty'] ?? null;
47+
} elseif (!\is_string($typeProperty)) {
48+
throw new \TypeError(sprintf('"%s": Argument $typeProperty was expected to be a string or array, got "%s".', __METHOD__, get_debug_type($typeProperty)));
49+
}
50+
51+
if (empty($typeProperty)) {
4252
throw new InvalidArgumentException(sprintf('Parameter "typeProperty" of annotation "%s" cannot be empty.', static::class));
4353
}
4454

45-
if (empty($data['mapping'])) {
55+
if (empty($mapping)) {
4656
throw new InvalidArgumentException(sprintf('Parameter "mapping" of annotation "%s" cannot be empty.', static::class));
4757
}
4858

49-
$this->typeProperty = $data['typeProperty'];
50-
$this->mapping = $data['mapping'];
59+
$this->typeProperty = $typeProperty;
60+
$this->mapping = $mapping;
5161
}
5262

5363
public function getTypeProperty(): string

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

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
*
2222
* @author Kévin Dunglas <[email protected]>
2323
*/
24+
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)]
2425
class Groups
2526
{
2627
/**
@@ -31,20 +32,22 @@ class Groups
3132
/**
3233
* @throws InvalidArgumentException
3334
*/
34-
public function __construct(array $data)
35+
public function __construct(array $groups)
3536
{
36-
if (!isset($data['value']) || !$data['value']) {
37+
if (isset($groups['value'])) {
38+
$groups = (array) $groups['value'];
39+
}
40+
if (empty($groups)) {
3741
throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" cannot be empty.', static::class));
3842
}
3943

40-
$value = (array) $data['value'];
41-
foreach ($value as $group) {
44+
foreach ($groups as $group) {
4245
if (!\is_string($group)) {
4346
throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a string or an array of strings.', static::class));
4447
}
4548
}
4649

47-
$this->groups = $value;
50+
$this->groups = $groups;
4851
}
4952

5053
/**

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
*
2020
* @author Kévin Dunglas <[email protected]>
2121
*/
22+
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)]
2223
final class Ignore
2324
{
2425
}

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,24 +21,31 @@
2121
*
2222
* @author Kévin Dunglas <[email protected]>
2323
*/
24+
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)]
2425
class MaxDepth
2526
{
2627
/**
2728
* @var int
2829
*/
2930
private $maxDepth;
3031

31-
public function __construct(array $data)
32+
/**
33+
* @param int|array $maxDepth
34+
*/
35+
public function __construct($maxDepth)
3236
{
33-
if (!isset($data['value'])) {
34-
throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" should be set.', static::class));
37+
if (\is_array($maxDepth)) {
38+
if (!isset($maxDepth['value'])) {
39+
throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" should be set.', static::class));
40+
}
41+
$maxDepth = $maxDepth['value'];
3542
}
3643

37-
if (!\is_int($data['value']) || $data['value'] <= 0) {
44+
if (!\is_int($maxDepth) || $maxDepth <= 0) {
3845
throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a positive integer.', static::class));
3946
}
4047

41-
$this->maxDepth = $data['value'];
48+
$this->maxDepth = $maxDepth;
4249
}
4350

4451
public function getMaxDepth()

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,24 +21,31 @@
2121
*
2222
* @author Fabien Bourigault <[email protected]>
2323
*/
24+
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)]
2425
final class SerializedName
2526
{
2627
/**
2728
* @var string
2829
*/
2930
private $serializedName;
3031

31-
public function __construct(array $data)
32+
/**
33+
* @param string|array $serializedName
34+
*/
35+
public function __construct($serializedName)
3236
{
33-
if (!isset($data['value'])) {
34-
throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" should be set.', static::class));
37+
if (\is_array($serializedName)) {
38+
if (!isset($serializedName['value'])) {
39+
throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" should be set.', static::class));
40+
}
41+
$serializedName = $serializedName['value'];
3542
}
3643

37-
if (!\is_string($data['value']) || empty($data['value'])) {
44+
if (!\is_string($serializedName) || empty($serializedName)) {
3845
throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a non-empty string.', static::class));
3946
}
4047

41-
$this->serializedName = $data['value'];
48+
$this->serializedName = $serializedName;
4249
}
4350

4451
public function getSerializedName(): string

src/Symfony/Component/Serializer/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ CHANGELOG
88
* added `UidNormalizer`
99
* added `FormErrorNormalizer`
1010
* added `MimeMessageNormalizer`
11+
* serializer mapping can be configured using php attributes
1112

1213
5.1.0
1314
-----

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

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,21 @@
2626
* Annotation loader.
2727
*
2828
* @author Kévin Dunglas <[email protected]>
29+
* @author Alexander M. Turek <[email protected]>
2930
*/
3031
class AnnotationLoader implements LoaderInterface
3132
{
33+
private const KNOWN_ANNOTATIONS = [
34+
DiscriminatorMap::class => true,
35+
Groups::class => true,
36+
Ignore:: class => true,
37+
MaxDepth::class => true,
38+
SerializedName::class => true,
39+
];
40+
3241
private $reader;
3342

34-
public function __construct(Reader $reader)
43+
public function __construct(Reader $reader = null)
3544
{
3645
$this->reader = $reader;
3746
}
@@ -47,7 +56,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)
4756

4857
$attributesMetadata = $classMetadata->getAttributesMetadata();
4958

50-
foreach ($this->reader->getClassAnnotations($reflectionClass) as $annotation) {
59+
foreach ($this->loadAnnotations($reflectionClass) as $annotation) {
5160
if ($annotation instanceof DiscriminatorMap) {
5261
$classMetadata->setClassDiscriminatorMapping(new ClassDiscriminatorMapping(
5362
$annotation->getTypeProperty(),
@@ -63,7 +72,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)
6372
}
6473

6574
if ($property->getDeclaringClass()->name === $className) {
66-
foreach ($this->reader->getPropertyAnnotations($property) as $annotation) {
75+
foreach ($this->loadAnnotations($property) as $annotation) {
6776
if ($annotation instanceof Groups) {
6877
foreach ($annotation->getGroups() as $group) {
6978
$attributesMetadata[$property->name]->addGroup($group);
@@ -98,7 +107,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)
98107
}
99108
}
100109

101-
foreach ($this->reader->getMethodAnnotations($method) as $annotation) {
110+
foreach ($this->loadAnnotations($method) as $annotation) {
102111
if ($annotation instanceof Groups) {
103112
if (!$accessorOrMutator) {
104113
throw new MappingException(sprintf('Groups on "%s::%s" cannot be added. Groups can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name));
@@ -129,4 +138,32 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)
129138

130139
return $loaded;
131140
}
141+
142+
/**
143+
* @param \ReflectionClass|\ReflectionMethod|\ReflectionProperty $reflector
144+
*/
145+
public function loadAnnotations(object $reflector): iterable
146+
{
147+
if (\PHP_VERSION_ID >= 80000) {
148+
foreach ($reflector->getAttributes() as $attribute) {
149+
if (self::KNOWN_ANNOTATIONS[$attribute->getName()] ?? false) {
150+
yield $attribute->newInstance();
151+
}
152+
}
153+
}
154+
155+
if (null === $this->reader) {
156+
return;
157+
}
158+
159+
if ($reflector instanceof \ReflectionClass) {
160+
yield from $this->reader->getClassAnnotations($reflector);
161+
}
162+
if ($reflector instanceof \ReflectionMethod) {
163+
yield from $this->reader->getMethodAnnotations($reflector);
164+
}
165+
if ($reflector instanceof \ReflectionProperty) {
166+
yield from $this->reader->getPropertyAnnotations($reflector);
167+
}
168+
}
132169
}

src/Symfony/Component/Serializer/Tests/Fixtures/AbstractDummy.php renamed to src/Symfony/Component/Serializer/Tests/Fixtures/Annotations/AbstractDummy.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@
99
* file that was distributed with this source code.
1010
*/
1111

12-
namespace Symfony\Component\Serializer\Tests\Fixtures;
12+
namespace Symfony\Component\Serializer\Tests\Fixtures\Annotations;
1313

1414
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
1515

1616
/**
1717
* @DiscriminatorMap(typeProperty="type", mapping={
18-
* "first"="Symfony\Component\Serializer\Tests\Fixtures\AbstractDummyFirstChild",
19-
* "second"="Symfony\Component\Serializer\Tests\Fixtures\AbstractDummySecondChild",
20-
* "third"="Symfony\Component\Serializer\Tests\Fixtures\AbstractDummyThirdChild",
18+
* "first"="Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummyFirstChild",
19+
* "second"="Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummySecondChild",
20+
* "third"="Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummyThirdChild",
2121
* })
2222
*/
2323
abstract class AbstractDummy
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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\Tests\Fixtures\Annotations;
13+
14+
use Symfony\Component\Serializer\Tests\Fixtures\DummyFirstChildQuux;
15+
16+
class AbstractDummyFirstChild extends AbstractDummy
17+
{
18+
public $bar;
19+
20+
/** @var DummyFirstChildQuux|null */
21+
public $quux;
22+
23+
public function __construct($foo = null, $bar = null)
24+
{
25+
parent::__construct($foo);
26+
27+
$this->bar = $bar;
28+
}
29+
30+
public function getQuux(): ?DummyFirstChildQuux
31+
{
32+
return $this->quux;
33+
}
34+
35+
public function setQuux(DummyFirstChildQuux $quux): void
36+
{
37+
$this->quux = $quux;
38+
}
39+
}

0 commit comments

Comments
 (0)