Skip to content

Commit 2a87450

Browse files
authored
Merge pull request #32 from rebuy-de/support-max-depth
Add support for max-depth
2 parents 2fd9e21 + 8c6bc19 commit 2a87450

File tree

9 files changed

+104
-8
lines changed

9 files changed

+104
-8
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* When running with PHP 8, process attributes in addition to the phpdoc annotations.
66
* Support doctrine collections
77
* Support `identical` property naming strategy
8+
* Add support for the `MaxDepth` annotation from JMS
89

910
# 0.5.0
1011

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,29 @@ class Product
130130

131131
### Expected Recursion: Working with Flawed Models
132132

133+
#### JMS annotation
134+
135+
If you are using JMS annotations, you can add the `MaxDepth` annotation to
136+
properties that might be recursive.
137+
138+
The following example will tell the metadata parser that the recursion is
139+
expected up to a maximum depth of `3`.
140+
141+
```php
142+
use JMS\Serializer\Annotation as JMS;
143+
144+
class RecursionModel
145+
{
146+
/**
147+
* @JMS\MaxDepth(3)
148+
* @JMS\Type("RecursionModel")
149+
*/
150+
public $recursion;
151+
}
152+
```
153+
154+
#### Pure PHP
155+
133156
The RecursionChecker accepts a second parameter to specify places where to
134157
break recursion. This is useful if your model tree looks like it has recursions
135158
but actually does not have them. JMSSerializer always acts on the actual data

src/Metadata/AbstractPropertyMetadata.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ abstract class AbstractPropertyMetadata implements \JsonSerializable
3939
*/
4040
private $versionRange;
4141

42+
/**
43+
* @var int|null
44+
*/
45+
private $maxDepth = null;
46+
4247
/**
4348
* Hashmap of custom information about this property.
4449
*
@@ -133,6 +138,7 @@ public function jsonSerialize(): array
133138
'name' => $this->name,
134139
'is_public' => $this->public,
135140
'is_read_only' => $this->readOnly,
141+
'max_depth' => $this->maxDepth,
136142
];
137143

138144
if (\count($this->groups) > 0) {
@@ -194,4 +200,14 @@ protected function setCustomInformation(string $key, $value): void
194200
{
195201
$this->customInformation[$key] = $value;
196202
}
203+
204+
protected function getMaxDepth(): ?int
205+
{
206+
return $this->maxDepth;
207+
}
208+
209+
protected function setMaxDepth(?int $maxDepth)
210+
{
211+
$this->maxDepth = $maxDepth;
212+
}
197213
}

src/Metadata/PropertyMetadata.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ public function __construct(
3131
VersionRange $versionRange = null,
3232
array $groups = [],
3333
PropertyAccessor $accessor = null,
34-
array $customInformation = []
34+
array $customInformation = [],
35+
?int $maxDepth = null
3536
) {
3637
parent::__construct($name, $readOnly, $public);
3738
$this->serializedName = $serializedName;
@@ -44,6 +45,8 @@ public function __construct(
4445
foreach ($customInformation as $key => $value) {
4546
$this->setCustomInformation((string) $key, $value);
4647
}
48+
49+
$this->setMaxDepth($maxDepth);
4750
}
4851

4952
public function __toString(): string
@@ -62,7 +65,8 @@ public static function fromRawProperty(string $serializedName, PropertyVariation
6265
$property->getVersionRange(),
6366
$property->getGroups(),
6467
$property->getAccessor(),
65-
$property->getAllCustomInformation()
68+
$property->getAllCustomInformation(),
69+
$property->getMaxDepth()
6670
);
6771
}
6872

@@ -76,6 +80,11 @@ public function getSerializedName(): string
7680
return $this->serializedName;
7781
}
7882

83+
public function getMaxDepth(): ?int
84+
{
85+
return parent::getMaxDepth();
86+
}
87+
7988
public function jsonSerialize(): array
8089
{
8190
return array_merge(

src/ModelParser/JMSParser.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use JMS\Serializer\Annotation\Exclude;
1919
use JMS\Serializer\Annotation\ExclusionPolicy;
2020
use JMS\Serializer\Annotation\Groups;
21+
use JMS\Serializer\Annotation\MaxDepth;
2122
use JMS\Serializer\Annotation\PostDeserialize;
2223
use JMS\Serializer\Annotation\SerializedName;
2324
use JMS\Serializer\Annotation\Since;
@@ -291,6 +292,10 @@ private function parsePropertyAnnotations(RawClassMetadata $classMetadata, Prope
291292
$property->setVersionRange($property->getVersionRange()->withUntil($annotation->version));
292293
break;
293294

295+
case $annotation instanceof MaxDepth:
296+
$property->setMaxDepth($annotation->depth);
297+
break;
298+
294299
case $annotation instanceof VirtualProperty:
295300
// we handle this separately
296301
case $annotation instanceof SerializedName:

src/ModelParser/RawMetadata/PropertyVariationMetadata.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,16 @@ public function setVersionRange(VersionRange $version): void
8787
parent::setVersionRange($version);
8888
}
8989

90+
public function getMaxDepth(): ?int
91+
{
92+
return parent::getMaxDepth();
93+
}
94+
95+
public function setMaxDepth(?int $maxDepth)
96+
{
97+
parent::setMaxDepth($maxDepth);
98+
}
99+
90100
/**
91101
* The value can be anything that the consumer understands.
92102
*

src/RecursionChecker.php

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,16 @@ private function checkClassMetadata(ClassMetadata $classMetadata, RecursionConte
9696
}
9797
}
9898

99-
/*
100-
* Future feature idea: Instead of 2, we could use a configurable maximum recursion depth.
101-
* One could then allow to unroll a recursion up to a certain number and stop it after that.
102-
*/
103-
if ($propertyContext->countClassNames($propertyClassMetadata->getClassName()) > 2) {
99+
$maxDepth = $property->getMaxDepth();
100+
$stackCount = $propertyContext->countClassNames($classMetadata->getClassName());
101+
if (null === $maxDepth && $stackCount > 2) {
104102
throw RecursionException::forClass($propertyClassMetadata->getClassName(), $propertyContext);
105103
}
106104

105+
if (null !== $maxDepth && $stackCount > $maxDepth) {
106+
return $classMetadata;
107+
}
108+
107109
$type->setClassMetadata($this->checkClassMetadata($propertyClassMetadata, $propertyContext));
108110
}
109111
}

tests/ModelParser/JMSParserTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -677,7 +677,7 @@ public function testUnsupportedPropertyAnnotations(): void
677677
$c = new class() {
678678
/**
679679
* @JMS\Type("string")
680-
* @JMS\MaxDepth(1)
680+
* @JMS\Inline()
681681
*/
682682
private $property;
683683
};

tests/RecursionCheckerTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,36 @@ public function testRecursion(): void
5959
$this->createChecker()->check($classMetadata);
6060
}
6161

62+
public function testRecursionWithMaxDepth(): void
63+
{
64+
$customClassType = new PropertyTypeClass(Recursion::class, true);
65+
$classMetadata = new ClassMetadata(
66+
Recursion::class,
67+
[
68+
new PropertyMetadata(
69+
'property',
70+
'property',
71+
$customClassType,
72+
true,
73+
false,
74+
null,
75+
[],
76+
null,
77+
[],
78+
2
79+
),
80+
]
81+
);
82+
$customClassType->setClassMetadata($classMetadata);
83+
84+
$metadata = $this->createChecker()->check($classMetadata);
85+
86+
/** @var PropertyTypeClass $type */
87+
$type = $metadata->getProperties()[0]->getType();
88+
$this->assertInstanceOf(PropertyTypeClass::class, $type);
89+
$this->assertCount(1, $type->getClassMetadata()->getProperties());
90+
}
91+
6292
public function testRecursionOnArray(): void
6393
{
6494
$customClassType = new PropertyTypeClass(Recursion::class, true);

0 commit comments

Comments
 (0)