Skip to content

Commit 420989f

Browse files
committed
feature #17113 [Serializer] Add a MaxDepth option (dunglas)
This PR was squashed before being merged into the 3.1-dev branch (closes #17113). Discussion ---------- [Serializer] Add a MaxDepth option | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #14924 (comment), api-platform/core#104 (comment) | License | MIT | Doc PR | todo Add a max depth option during the normalization process. Especially useful when normalizing trees. Usage: ```php use Symfony\Component\Serializer\Annotation\MaxDepth; class MyObj { /** * @MaxDepth(2) */ public $foo; /** * @var self */ public $child; } use Doctrine\Common\Annotations\AnnotationReader; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); $normalizer = new ObjectNormalizer($classMetadataFactory); $serializer = new Serializer(array($this->normalizer)); $level1 = new MyObj(); $level1->foo = 'level1'; $level2 = new MyObj(); $level2->foo = 'level2'; $level1->child = $level2; $level3 = new MyObj(); $level3->foo = 'level3'; $level2->child = $level3; $result = $serializer->normalize($level1, null, array(ObjectNormalizer::ENABLE_MAX_DEPTH => true)); /* $result = array( 'foo' => 'level1', 'child' => array( 'foo' => 'level2', 'child' => array( 'child' => null, ), ), ); */ ``` * [x] Metadata support * [x] `@MaxDepth(2)` annotation and loader * [x] XML loader * [x] YAML loader * [x] Delegate recursive normalization at the end of the process * [x] Use constants with Late Static Binding in the abstract class instead of raw strings * [x] Move common logic to the abstract class /cc @mRoca @csarrazi Commits ------- a44bead [Serializer] Add a MaxDepth option
2 parents c5a7886 + a44bead commit 420989f

19 files changed

+505
-37
lines changed
Lines 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 @MaxDepth().
18+
*
19+
* @Annotation
20+
* @Target({"PROPERTY", "METHOD"})
21+
*
22+
* @author Kévin Dunglas <dunglas@gmail.com>
23+
*/
24+
class MaxDepth
25+
{
26+
/**
27+
* @var int
28+
*/
29+
private $maxDepth;
30+
31+
public function __construct(array $data)
32+
{
33+
if (!isset($data['value']) || !$data['value']) {
34+
throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" cannot be empty.', get_class($this)));
35+
}
36+
37+
if (!is_int($data['value']) || $data['value'] <= 0) {
38+
throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a positive integer.', get_class($this)));
39+
}
40+
41+
$this->maxDepth = $data['value'];
42+
}
43+
44+
public function getMaxDepth()
45+
{
46+
return $this->maxDepth;
47+
}
48+
}

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

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,15 @@ class AttributeMetadata implements AttributeMetadataInterface
3636
*/
3737
public $groups = array();
3838

39+
/**
40+
* @var int|null
41+
*
42+
* @internal This property is public in order to reduce the size of the
43+
* class' serialized representation. Do not access it. Use
44+
* {@link getMaxDepth()} instead.
45+
*/
46+
public $maxDepth;
47+
3948
/**
4049
* Constructs a metadata for the given attribute.
4150
*
@@ -72,6 +81,22 @@ public function getGroups()
7281
return $this->groups;
7382
}
7483

84+
/**
85+
* {@inheritdoc}
86+
*/
87+
public function setMaxDepth($maxDepth)
88+
{
89+
$this->maxDepth = $maxDepth;
90+
}
91+
92+
/**
93+
* {@inheritdoc}
94+
*/
95+
public function getMaxDepth()
96+
{
97+
return $this->maxDepth;
98+
}
99+
75100
/**
76101
* {@inheritdoc}
77102
*/
@@ -80,6 +105,11 @@ public function merge(AttributeMetadataInterface $attributeMetadata)
80105
foreach ($attributeMetadata->getGroups() as $group) {
81106
$this->addGroup($group);
82107
}
108+
109+
// Overwrite only if not defined
110+
if (null === $this->maxDepth) {
111+
$this->maxDepth = $attributeMetadata->getMaxDepth();
112+
}
83113
}
84114

85115
/**
@@ -89,6 +119,6 @@ public function merge(AttributeMetadataInterface $attributeMetadata)
89119
*/
90120
public function __sleep()
91121
{
92-
return array('name', 'groups');
122+
return array('name', 'groups', 'maxDepth');
93123
}
94124
}

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,20 @@ public function addGroup($group);
4343
*/
4444
public function getGroups();
4545

46+
/**
47+
* Sets the serialization max depth for this attribute.
48+
*
49+
* @param int|null $maxDepth
50+
*/
51+
public function setMaxDepth($maxDepth);
52+
53+
/**
54+
* Gets the serialization max depth for this attribute.
55+
*
56+
* @return int|null
57+
*/
58+
public function getMaxDepth();
59+
4660
/**
4761
* Merges an {@see AttributeMetadataInterface} with in the current one.
4862
*

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

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Doctrine\Common\Annotations\Reader;
1515
use Symfony\Component\Serializer\Annotation\Groups;
16+
use Symfony\Component\Serializer\Annotation\MaxDepth;
1617
use Symfony\Component\Serializer\Exception\MappingException;
1718
use Symfony\Component\Serializer\Mapping\AttributeMetadata;
1819
use Symfony\Component\Serializer\Mapping\ClassMetadataInterface;
@@ -55,11 +56,13 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)
5556
}
5657

5758
if ($property->getDeclaringClass()->name === $className) {
58-
foreach ($this->reader->getPropertyAnnotations($property) as $groups) {
59-
if ($groups instanceof Groups) {
60-
foreach ($groups->getGroups() as $group) {
59+
foreach ($this->reader->getPropertyAnnotations($property) as $annotation) {
60+
if ($annotation instanceof Groups) {
61+
foreach ($annotation->getGroups() as $group) {
6162
$attributesMetadata[$property->name]->addGroup($group);
6263
}
64+
} elseif ($annotation instanceof MaxDepth) {
65+
$attributesMetadata[$property->name]->setMaxDepth($annotation->getMaxDepth());
6366
}
6467

6568
$loaded = true;
@@ -68,29 +71,40 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)
6871
}
6972

7073
foreach ($reflectionClass->getMethods() as $method) {
71-
if ($method->getDeclaringClass()->name === $className) {
72-
foreach ($this->reader->getMethodAnnotations($method) as $groups) {
73-
if ($groups instanceof Groups) {
74-
if (preg_match('/^(get|is|has|set)(.+)$/i', $method->name, $matches)) {
75-
$attributeName = lcfirst($matches[2]);
76-
77-
if (isset($attributesMetadata[$attributeName])) {
78-
$attributeMetadata = $attributesMetadata[$attributeName];
79-
} else {
80-
$attributesMetadata[$attributeName] = $attributeMetadata = new AttributeMetadata($attributeName);
81-
$classMetadata->addAttributeMetadata($attributeMetadata);
82-
}
83-
84-
foreach ($groups->getGroups() as $group) {
85-
$attributeMetadata->addGroup($group);
86-
}
87-
} else {
88-
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));
89-
}
74+
if ($method->getDeclaringClass()->name !== $className) {
75+
continue;
76+
}
77+
78+
$accessorOrMutator = preg_match('/^(get|is|has|set)(.+)$/i', $method->name, $matches);
79+
if ($accessorOrMutator) {
80+
$attributeName = lcfirst($matches[2]);
81+
82+
if (isset($attributesMetadata[$attributeName])) {
83+
$attributeMetadata = $attributesMetadata[$attributeName];
84+
} else {
85+
$attributesMetadata[$attributeName] = $attributeMetadata = new AttributeMetadata($attributeName);
86+
$classMetadata->addAttributeMetadata($attributeMetadata);
87+
}
88+
}
89+
90+
foreach ($this->reader->getMethodAnnotations($method) as $annotation) {
91+
if ($annotation instanceof Groups) {
92+
if (!$accessorOrMutator) {
93+
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));
9094
}
9195

92-
$loaded = true;
96+
foreach ($annotation->getGroups() as $group) {
97+
$attributeMetadata->addGroup($group);
98+
}
99+
} elseif ($annotation instanceof MaxDepth) {
100+
if (!$accessorOrMutator) {
101+
throw new MappingException(sprintf('MaxDepth on "%s::%s" cannot be added. MaxDepth can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name));
102+
}
103+
104+
$attributeMetadata->setMaxDepth($annotation->getMaxDepth());
93105
}
106+
107+
$loaded = true;
94108
}
95109
}
96110

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)
6262
foreach ($attribute->group as $group) {
6363
$attributeMetadata->addGroup((string) $group);
6464
}
65+
66+
if (isset($attribute['max-depth'])) {
67+
$attributeMetadata->setMaxDepth((int) $attribute['max-depth']);
68+
}
6569
}
6670

6771
return true;

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,14 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)
7878
$attributeMetadata->addGroup($group);
7979
}
8080
}
81+
82+
if (isset($data['max_depth'])) {
83+
if (!is_int($data['max_depth'])) {
84+
throw new MappingException('The "max_depth" value must an integer in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName());
85+
}
86+
87+
$attributeMetadata->setMaxDepth($data['max_depth']);
88+
}
8189
}
8290
}
8391

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,20 @@
4444
<xsd:complexType name="attribute">
4545
<xsd:annotation>
4646
<xsd:documentation><![CDATA[
47-
Contains serialization groups for a attributes. The name of the attribute should be given in the "name" option.
47+
Contains serialization groups and max depth for attributes. The name of the attribute should be given in the "name" option.
4848
]]></xsd:documentation>
4949
</xsd:annotation>
50-
<xsd:sequence>
50+
<xsd:sequence minOccurs="0">
5151
<xsd:element name="group" type="xsd:string" maxOccurs="unbounded" />
5252
</xsd:sequence>
5353
<xsd:attribute name="name" type="xsd:string" use="required" />
54+
<xsd:attribute name="max-depth">
55+
<xsd:simpleType>
56+
<xsd:restriction base="xsd:integer">
57+
<xsd:minInclusive value="0" />
58+
</xsd:restriction>
59+
</xsd:simpleType>
60+
</xsd:attribute>
5461
</xsd:complexType>
5562

5663
</xsd:schema>

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy