Skip to content

Commit 62248c9

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

Some content is hidden

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

49 files changed

+637
-119
lines changed

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 <samuel.roze@gmail.com>
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 <dunglas@gmail.com>
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 <dunglas@gmail.com>
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 <dunglas@gmail.com>
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 <bourigaultfabien@gmail.com>
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: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,17 @@
2929
*/
3030
class AnnotationLoader implements LoaderInterface
3131
{
32+
private const KNOWN_ANNOTATIONS = [
33+
DiscriminatorMap::class => true,
34+
Groups::class => true,
35+
Ignore:: class => true,
36+
MaxDepth::class => true,
37+
SerializedName::class => true,
38+
];
39+
3240
private $reader;
3341

34-
public function __construct(Reader $reader)
42+
public function __construct(Reader $reader = null)
3543
{
3644
$this->reader = $reader;
3745
}
@@ -47,7 +55,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)
4755

4856
$attributesMetadata = $classMetadata->getAttributesMetadata();
4957

50-
foreach ($this->reader->getClassAnnotations($reflectionClass) as $annotation) {
58+
foreach ($this->loadAnnotations($reflectionClass) as $annotation) {
5159
if ($annotation instanceof DiscriminatorMap) {
5260
$classMetadata->setClassDiscriminatorMapping(new ClassDiscriminatorMapping(
5361
$annotation->getTypeProperty(),
@@ -63,7 +71,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)
6371
}
6472

6573
if ($property->getDeclaringClass()->name === $className) {
66-
foreach ($this->reader->getPropertyAnnotations($property) as $annotation) {
74+
foreach ($this->loadAnnotations($property) as $annotation) {
6775
if ($annotation instanceof Groups) {
6876
foreach ($annotation->getGroups() as $group) {
6977
$attributesMetadata[$property->name]->addGroup($group);
@@ -98,7 +106,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)
98106
}
99107
}
100108

101-
foreach ($this->reader->getMethodAnnotations($method) as $annotation) {
109+
foreach ($this->loadAnnotations($method) as $annotation) {
102110
if ($annotation instanceof Groups) {
103111
if (!$accessorOrMutator) {
104112
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 +137,32 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)
129137

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

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"=AbstractDummyFirstChild::class,
19+
* "second"=AbstractDummySecondChild::class,
20+
* "third"=AbstractDummyThirdChild::class,
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 <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\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+
}
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 <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\Tests\Fixtures\Annotations;
13+
14+
use Symfony\Component\Serializer\Tests\Fixtures\DummySecondChildQuux;
15+
16+
class AbstractDummySecondChild extends AbstractDummy
17+
{
18+
public $baz;
19+
20+
/** @var DummySecondChildQuux|null */
21+
public $quux;
22+
23+
public function __construct($foo = null, $baz = null)
24+
{
25+
parent::__construct($foo);
26+
27+
$this->baz = $baz;
28+
}
29+
30+
public function getQuux(): ?DummySecondChildQuux
31+
{
32+
return $this->quux;
33+
}
34+
35+
public function setQuux(DummySecondChildQuux $quux): void
36+
{
37+
$this->quux = $quux;
38+
}
39+
}

0 commit comments

Comments
 (0)