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

Skip to content

Commit 734a006

Browse files
committed
feature #28744 [Serializer] Add an @ignore annotation (dunglas)
This PR was squashed before being merged into the 5.1-dev branch. Discussion ---------- [Serializer] Add an @ignore annotation | Q | A | ------------- | --- | Branch? | master Bug fix? | no | New feature? | yes <!-- don't forget to update src/**/CHANGELOG.md files --> | BC breaks? | no <!-- see https://symfony.com/bc --> | Deprecations? | no <!-- don't forget to update UPGRADE-*.md and src/**/CHANGELOG.md files --> | Tests pass? | yes <!-- please add some, will be required by reviewers --> | Fixed tickets | #24071 | License | MIT | Doc PR | n/a Add an `@Ignore` annotation to configure [ignored attributes](https://symfony.com/doc/current/components/serializer.html#ignoring-attributes) in a convenient way, as well as the related XML and YAML loaders. TODO: * [x] Add tests Commits ------- 8526d7c [Serializer] Add an @ignore annotation
2 parents 260dea0 + 8526d7c commit 734a006

19 files changed

+218
-8
lines changed

src/Symfony/Component/PropertyInfo/Extractor/SerializerExtractor.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ public function getProperties(string $class, array $context = []): ?array
4747
$serializerClassMetadata = $this->classMetadataFactory->getMetadataFor($class);
4848

4949
foreach ($serializerClassMetadata->getAttributesMetadata() as $serializerAttributeMetadata) {
50-
if (array_intersect($context['serializer_groups'], $serializerAttributeMetadata->getGroups())) {
50+
$ignored = method_exists($serializerClassMetadata, 'isIgnored') && $serializerAttributeMetadata->isIgnored();
51+
if (!$ignored && array_intersect($context['serializer_groups'], $serializerAttributeMetadata->getGroups())) {
5152
$properties[] = $serializerAttributeMetadata->getName();
5253
}
5354
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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+
/**
15+
* Annotation class for @Ignore().
16+
*
17+
* @Annotation
18+
* @Target({"PROPERTY", "METHOD"})
19+
*
20+
* @author Kévin Dunglas <[email protected]>
21+
*/
22+
final class Ignore
23+
{
24+
}

src/Symfony/Component/Serializer/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ CHANGELOG
66

77
* added support for scalar values denormalization
88
* added support for `\stdClass` to `ObjectNormalizer`
9+
* added the ability to ignore properties using metadata (e.g. `@Symfony\Component\Serializer\Annotation\Ignore`)
910

1011
5.0.0
1112
-----

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

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@ class AttributeMetadata implements AttributeMetadataInterface
5050
*/
5151
public $serializedName;
5252

53+
/**
54+
* @var bool
55+
*
56+
* @internal This property is public in order to reduce the size of the
57+
* class' serialized representation. Do not access it. Use
58+
* {@link isIgnored()} instead.
59+
*/
60+
public $ignore = false;
61+
5362
public function __construct(string $name)
5463
{
5564
$this->name = $name;
@@ -113,6 +122,22 @@ public function getSerializedName(): ?string
113122
return $this->serializedName;
114123
}
115124

125+
/**
126+
* {@inheritdoc}
127+
*/
128+
public function setIgnore(bool $ignore)
129+
{
130+
$this->ignore = $ignore;
131+
}
132+
133+
/**
134+
* {@inheritdoc}
135+
*/
136+
public function isIgnored(): bool
137+
{
138+
return $this->ignore;
139+
}
140+
116141
/**
117142
* {@inheritdoc}
118143
*/
@@ -131,6 +156,10 @@ public function merge(AttributeMetadataInterface $attributeMetadata)
131156
if (null === $this->serializedName) {
132157
$this->serializedName = $attributeMetadata->getSerializedName();
133158
}
159+
160+
if ($ignore = $attributeMetadata->isIgnored()) {
161+
$this->ignore = $ignore;
162+
}
134163
}
135164

136165
/**
@@ -140,6 +169,6 @@ public function merge(AttributeMetadataInterface $attributeMetadata)
140169
*/
141170
public function __sleep()
142171
{
143-
return ['name', 'groups', 'maxDepth', 'serializedName'];
172+
return ['name', 'groups', 'maxDepth', 'serializedName', 'ignore'];
144173
}
145174
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ public function setSerializedName(string $serializedName = null);
6161
*/
6262
public function getSerializedName(): ?string;
6363

64+
/**
65+
* Sets if this attribute must be ignored or not.
66+
*/
67+
public function setIgnore(bool $ignore);
68+
69+
/**
70+
* Gets if this attribute is ignored or not.
71+
*/
72+
public function isIgnored(): bool;
73+
6474
/**
6575
* Merges an {@see AttributeMetadataInterface} with in the current one.
6676
*/

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Doctrine\Common\Annotations\Reader;
1515
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
1616
use Symfony\Component\Serializer\Annotation\Groups;
17+
use Symfony\Component\Serializer\Annotation\Ignore;
1718
use Symfony\Component\Serializer\Annotation\MaxDepth;
1819
use Symfony\Component\Serializer\Annotation\SerializedName;
1920
use Symfony\Component\Serializer\Exception\MappingException;
@@ -71,6 +72,8 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)
7172
$attributesMetadata[$property->name]->setMaxDepth($annotation->getMaxDepth());
7273
} elseif ($annotation instanceof SerializedName) {
7374
$attributesMetadata[$property->name]->setSerializedName($annotation->getSerializedName());
75+
} elseif ($annotation instanceof Ignore) {
76+
$attributesMetadata[$property->name]->setIgnore(true);
7477
}
7578

7679
$loaded = true;
@@ -116,6 +119,8 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)
116119
}
117120

118121
$attributeMetadata->setSerializedName($annotation->getSerializedName());
122+
} elseif ($annotation instanceof Ignore) {
123+
$attributeMetadata->setIgnore(true);
119124
}
120125

121126
$loaded = true;

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)
7070
if (isset($attribute['serialized-name'])) {
7171
$attributeMetadata->setSerializedName((string) $attribute['serialized-name']);
7272
}
73+
74+
if (isset($attribute['ignore'])) {
75+
$attributeMetadata->setIgnore((bool) $attribute['ignore']);
76+
}
7377
}
7478

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

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,14 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)
9393

9494
$attributeMetadata->setSerializedName($data['serialized_name']);
9595
}
96+
97+
if (isset($data['ignore'])) {
98+
if (!\is_bool($data['ignore'])) {
99+
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()));
100+
}
101+
102+
$attributeMetadata->setIgnore($data['ignore']);
103+
}
96104
}
97105
}
98106

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
</xsd:choice>
4949
<xsd:attribute name="type-property" type="xsd:string" use="required" />
5050
</xsd:complexType>
51-
51+
5252
<xsd:complexType name="discriminator-map-mapping">
5353
<xsd:attribute name="type" type="xsd:string" use="required" />
5454
<xsd:attribute name="class" type="xsd:string" use="required" />
@@ -78,6 +78,7 @@
7878
</xsd:restriction>
7979
</xsd:simpleType>
8080
</xsd:attribute>
81+
<xsd:attribute name="ignore" type="xsd:boolean" />
8182
</xsd:complexType>
8283

8384
</xsd:schema>

src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -239,22 +239,29 @@ protected function getAllowedAttributes($classOrObject, array $context, bool $at
239239

240240
$tmpGroups = $context[self::GROUPS] ?? $this->defaultContext[self::GROUPS] ?? null;
241241
$groups = (\is_array($tmpGroups) || is_scalar($tmpGroups)) ? (array) $tmpGroups : false;
242-
if (false === $groups && $allowExtraAttributes) {
243-
return false;
244-
}
245242

246243
$allowedAttributes = [];
244+
$ignoreUsed = false;
247245
foreach ($this->classMetadataFactory->getMetadataFor($classOrObject)->getAttributesMetadata() as $attributeMetadata) {
248-
$name = $attributeMetadata->getName();
246+
if ($ignore = $attributeMetadata->isIgnored()) {
247+
$ignoreUsed = true;
248+
}
249249

250+
// If you update this check, update accordingly the one in Symfony\Component\PropertyInfo\Extractor\SerializerExtractor::getProperties()
250251
if (
252+
!$ignore &&
251253
(false === $groups || array_intersect($attributeMetadata->getGroups(), $groups)) &&
252-
$this->isAllowedAttribute($classOrObject, $name, null, $context)
254+
$this->isAllowedAttribute($classOrObject, $name = $attributeMetadata->getName(), null, $context)
253255
) {
254256
$allowedAttributes[] = $attributesAsString ? $name : $attributeMetadata;
255257
}
256258
}
257259

260+
if (!$ignoreUsed && false === $groups && $allowExtraAttributes) {
261+
// Backward Compatibility with the code using this method written before the introduction of @Ignore
262+
return false;
263+
}
264+
258265
return $allowedAttributes;
259266
}
260267

Lines 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 <[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;
13+
14+
use Symfony\Component\Serializer\Annotation\Ignore;
15+
16+
/**
17+
* @author Kévin Dunglas <[email protected]>
18+
*/
19+
class IgnoreDummy
20+
{
21+
public $notIgnored;
22+
/**
23+
* @Ignore()
24+
*/
25+
public $ignored1;
26+
private $ignored2;
27+
28+
/**
29+
* @Ignore()
30+
*/
31+
public function getIgnored2()
32+
{
33+
return $this->ignored2;
34+
}
35+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
'Symfony\Component\Serializer\Tests\Fixtures\IgnoreDummy':
2+
attributes:
3+
ignored1:
4+
ignore: foo

src/Symfony/Component/Serializer/Tests/Fixtures/serialization.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,9 @@
3434
<attribute name="foo" />
3535
</class>
3636

37+
<class name="Symfony\Component\Serializer\Tests\Fixtures\IgnoreDummy">
38+
<attribute name="ignored1" ignore="true" />
39+
<attribute name="ignored2" ignore="true" />
40+
</class>
41+
3742
</serializer>

src/Symfony/Component/Serializer/Tests/Fixtures/serialization.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,9 @@
2424
second: 'Symfony\Component\Serializer\Tests\Fixtures\AbstractDummySecondChild'
2525
attributes:
2626
foo: ~
27+
'Symfony\Component\Serializer\Tests\Fixtures\IgnoreDummy':
28+
attributes:
29+
ignored1:
30+
ignore: true
31+
ignored2:
32+
ignore: true

src/Symfony/Component/Serializer/Tests/Mapping/AttributeMetadataTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@ public function testSerializedName()
5757
$this->assertEquals('serialized_name', $attributeMetadata->getSerializedName());
5858
}
5959

60+
public function testIgnore()
61+
{
62+
$attributeMetadata = new AttributeMetadata('ignored');
63+
$this->assertFalse($attributeMetadata->isIgnored());
64+
$attributeMetadata->setIgnore(true);
65+
$this->assertTrue($attributeMetadata->isIgnored());
66+
}
67+
6068
public function testMerge()
6169
{
6270
$attributeMetadata1 = new AttributeMetadata('a1');
@@ -69,11 +77,14 @@ public function testMerge()
6977
$attributeMetadata2->setMaxDepth(2);
7078
$attributeMetadata2->setSerializedName('a3');
7179

80+
$attributeMetadata2->setIgnore(true);
81+
7282
$attributeMetadata1->merge($attributeMetadata2);
7383

7484
$this->assertEquals(['a', 'b', 'c'], $attributeMetadata1->getGroups());
7585
$this->assertEquals(2, $attributeMetadata1->getMaxDepth());
7686
$this->assertEquals('a3', $attributeMetadata1->getSerializedName());
87+
$this->assertTrue($attributeMetadata1->isIgnored());
7788
}
7889

7990
public function testSerialize()

src/Symfony/Component/Serializer/Tests/Mapping/Loader/AnnotationLoaderTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use Symfony\Component\Serializer\Tests\Fixtures\AbstractDummy;
2121
use Symfony\Component\Serializer\Tests\Fixtures\AbstractDummyFirstChild;
2222
use Symfony\Component\Serializer\Tests\Fixtures\AbstractDummySecondChild;
23+
use Symfony\Component\Serializer\Tests\Fixtures\IgnoreDummy;
2324
use Symfony\Component\Serializer\Tests\Mapping\TestClassMetadataFactory;
2425

2526
/**
@@ -105,4 +106,14 @@ public function testLoadClassMetadataAndMerge()
105106

106107
$this->assertEquals(TestClassMetadataFactory::createClassMetadata(true), $classMetadata);
107108
}
109+
110+
public function testLoadIgnore()
111+
{
112+
$classMetadata = new ClassMetadata(IgnoreDummy::class);
113+
$this->loader->loadClassMetadata($classMetadata);
114+
115+
$attributesMetadata = $classMetadata->getAttributesMetadata();
116+
$this->assertTrue($attributesMetadata['ignored1']->isIgnored());
117+
$this->assertTrue($attributesMetadata['ignored2']->isIgnored());
118+
}
108119
}

src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Symfony\Component\Serializer\Tests\Fixtures\AbstractDummy;
2020
use Symfony\Component\Serializer\Tests\Fixtures\AbstractDummyFirstChild;
2121
use Symfony\Component\Serializer\Tests\Fixtures\AbstractDummySecondChild;
22+
use Symfony\Component\Serializer\Tests\Fixtures\IgnoreDummy;
2223
use Symfony\Component\Serializer\Tests\Mapping\TestClassMetadataFactory;
2324

2425
/**
@@ -92,4 +93,14 @@ public function testLoadDiscriminatorMap()
9293

9394
$this->assertEquals($expected, $classMetadata);
9495
}
96+
97+
public function testLoadIgnore()
98+
{
99+
$classMetadata = new ClassMetadata(IgnoreDummy::class);
100+
$this->loader->loadClassMetadata($classMetadata);
101+
102+
$attributesMetadata = $classMetadata->getAttributesMetadata();
103+
$this->assertTrue($attributesMetadata['ignored1']->isIgnored());
104+
$this->assertTrue($attributesMetadata['ignored2']->isIgnored());
105+
}
95106
}

src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@
1212
namespace Symfony\Component\Serializer\Tests\Mapping\Loader;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Serializer\Exception\MappingException;
1516
use Symfony\Component\Serializer\Mapping\AttributeMetadata;
1617
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping;
1718
use Symfony\Component\Serializer\Mapping\ClassMetadata;
1819
use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader;
1920
use Symfony\Component\Serializer\Tests\Fixtures\AbstractDummy;
2021
use Symfony\Component\Serializer\Tests\Fixtures\AbstractDummyFirstChild;
2122
use Symfony\Component\Serializer\Tests\Fixtures\AbstractDummySecondChild;
23+
use Symfony\Component\Serializer\Tests\Fixtures\IgnoreDummy;
2224
use Symfony\Component\Serializer\Tests\Mapping\TestClassMetadataFactory;
2325

2426
/**
@@ -105,4 +107,22 @@ public function testLoadDiscriminatorMap()
105107

106108
$this->assertEquals($expected, $classMetadata);
107109
}
110+
111+
public function testLoadIgnore()
112+
{
113+
$classMetadata = new ClassMetadata(IgnoreDummy::class);
114+
$this->loader->loadClassMetadata($classMetadata);
115+
116+
$attributesMetadata = $classMetadata->getAttributesMetadata();
117+
$this->assertTrue($attributesMetadata['ignored1']->isIgnored());
118+
$this->assertTrue($attributesMetadata['ignored2']->isIgnored());
119+
}
120+
121+
public function testLoadInvalidIgnore()
122+
{
123+
$this->expectException(MappingException::class);
124+
$this->expectExceptionMessage('The "ignore" value must be a boolean');
125+
126+
(new YamlFileLoader(__DIR__.'/../../Fixtures/invalid-ignore.yml'))->loadClassMetadata(new ClassMetadata(IgnoreDummy::class));
127+
}
108128
}

0 commit comments

Comments
 (0)