Skip to content

Commit 23a8dff

Browse files
authored
Merge pull request #1992 from meyerbaptiste/merge_2.2
Merge 2.2
2 parents 0d31d5c + 03f801d commit 23a8dff

File tree

12 files changed

+356
-142
lines changed

12 files changed

+356
-142
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
"symfony/validator": "^3.3 || ^4.0",
6868
"symfony/web-profiler-bundle": "^3.3 || ^4.0",
6969
"symfony/yaml": "^3.3 || ^4.0",
70-
"webonyx/graphql-php": "^0.11.5"
70+
"webonyx/graphql-php": ">=0.12 <1.0"
7171
},
7272
"conflict": {
7373
"symfony/dependency-injection": "<3.4"

features/hal/max_depth.feature

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
Feature: Max depth handling
2+
In order to handle MaxChildDepth resources
3+
As a developer
4+
I need to be able to limit their depth with @maxDepth
5+
6+
@createSchema
7+
Scenario: Create a resource with 1 level of descendants
8+
When I add "Accept" header equal to "application/hal+json"
9+
And I add "Content-Type" header equal to "application/json"
10+
And I send a "POST" request to "/max_depth_dummies" with body:
11+
"""
12+
{
13+
"name": "level 1",
14+
"child": {
15+
"name": "level 2"
16+
}
17+
}
18+
"""
19+
Then the response status code should be 201
20+
And the response should be in JSON
21+
And the header "Content-Type" should be equal to "application/hal+json; charset=utf-8"
22+
And the JSON should be equal to:
23+
"""
24+
{
25+
"_links": {
26+
"self": {
27+
"href": "\/max_depth_dummies\/1"
28+
},
29+
"child": {
30+
"href": "\/max_depth_dummies\/2"
31+
}
32+
},
33+
"_embedded": {
34+
"child": {
35+
"_links": {
36+
"self": {
37+
"href": "\/max_depth_dummies\/2"
38+
}
39+
},
40+
"id": 2,
41+
"name": "level 2"
42+
}
43+
},
44+
"id": 1,
45+
"name": "level 1"
46+
}
47+
"""
48+
49+
@dropSchema
50+
Scenario: Add a 2nd level of descendants
51+
When I add "Accept" header equal to "application/hal+json"
52+
And I add "Content-Type" header equal to "application/json"
53+
And I send a "PUT" request to "max_depth_dummies/1" with body:
54+
"""
55+
{
56+
"id": "/max_depth_dummies/1",
57+
"child": {
58+
"id": "/max_depth_dummies/2",
59+
"child": {
60+
"name": "level 3"
61+
}
62+
}
63+
}
64+
"""
65+
And the response status code should be 200
66+
And the response should be in JSON
67+
And the header "Content-Type" should be equal to "application/hal+json; charset=utf-8"
68+
And the JSON should be equal to:
69+
"""
70+
{
71+
"_links": {
72+
"self": {
73+
"href": "\/max_depth_dummies\/1"
74+
},
75+
"child": {
76+
"href": "\/max_depth_dummies\/2"
77+
}
78+
},
79+
"_embedded": {
80+
"child": {
81+
"_links": {
82+
"self": {
83+
"href": "\/max_depth_dummies\/2"
84+
}
85+
},
86+
"id": 2,
87+
"name": "level 2"
88+
}
89+
},
90+
"id": 1,
91+
"name": "level 1"
92+
}
93+
"""

features/jsonld/max_depth.feature

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
Feature: Max depth handling
2+
In order to handle MaxDepthDummy resources
3+
As a developer
4+
I need to be able to limit their depth with @maxDepth
5+
6+
@createSchema
7+
Scenario: Create a resource with 1 level of descendants
8+
When I add "Content-Type" header equal to "application/ld+json"
9+
And I send a "POST" request to "/max_depth_dummies" with body:
10+
"""
11+
{
12+
"name": "level 1",
13+
"child": {
14+
"name": "level 2"
15+
}
16+
}
17+
"""
18+
Then the response status code should be 201
19+
And the response should be in JSON
20+
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
21+
And the JSON should be equal to:
22+
"""
23+
{
24+
"@context": "/contexts/MaxDepthDummy",
25+
"@id": "/max_depth_dummies/1",
26+
"@type": "MaxDepthDummy",
27+
"id": 1,
28+
"name": "level 1",
29+
"child": {
30+
"@id": "/max_depth_dummies/2",
31+
"@type": "MaxDepthDummy",
32+
"id": 2,
33+
"name": "level 2"
34+
}
35+
}
36+
"""
37+
38+
@dropSchema
39+
Scenario: Add a 2nd level of descendants
40+
When I add "Content-Type" header equal to "application/ld+json"
41+
And I send a "PUT" request to "max_depth_dummies/1" with body:
42+
"""
43+
{
44+
"@id": "/max_depth_dummies/1",
45+
"child": {
46+
"@id": "/max_depth_dummies/2",
47+
"child": {
48+
"name": "level 3"
49+
}
50+
}
51+
}
52+
"""
53+
And the response status code should be 200
54+
And the response should be in JSON
55+
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
56+
And the JSON should be equal to:
57+
"""
58+
{
59+
"@context": "/contexts/MaxDepthDummy",
60+
"@id": "/max_depth_dummies/1",
61+
"@type": "MaxDepthDummy",
62+
"id": 1,
63+
"name": "level 1",
64+
"child": {
65+
"@id": "/max_depth_dummies/2",
66+
"@type": "MaxDepthDummy",
67+
"id": 2,
68+
"name": "level 2"
69+
}
70+
}
71+
"""
72+

features/main/recursive.feature

Lines changed: 0 additions & 70 deletions
This file was deleted.

src/EventListener/EventPriorities.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ final class EventPriorities
3030
const POST_VALIDATE = 63;
3131
const PRE_WRITE = 33;
3232
const POST_WRITE = 31;
33+
const PRE_SERIALIZE = 17;
34+
const POST_SERIALIZE = 15;
3335
const PRE_RESPOND = 9;
3436
// kernel.response
3537
const POST_RESPOND = 0;

src/GraphQl/Type/Definition/IterableType.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
namespace ApiPlatform\Core\GraphQl\Type\Definition;
1515

1616
use GraphQL\Error\Error;
17-
use GraphQL\Error\InvariantViolation;
1817
use GraphQL\Language\AST\BooleanValueNode;
1918
use GraphQL\Language\AST\FloatValueNode;
2019
use GraphQL\Language\AST\IntValueNode;
@@ -48,7 +47,7 @@ public function serialize($value)
4847
{
4948
// is_iterable
5049
if (!(\is_array($value) || $value instanceof \Traversable)) {
51-
throw new InvariantViolation(sprintf('Iterable cannot represent non iterable value: %s', Utils::printSafe($value)));
50+
throw new Error(sprintf('Iterable cannot represent non iterable value: %s', Utils::printSafe($value)));
5251
}
5352

5453
return $value;
@@ -70,13 +69,14 @@ public function parseValue($value)
7069
/**
7170
* {@inheritdoc}
7271
*/
73-
public function parseLiteral($valueNode)
72+
public function parseLiteral($valueNode, array $variables = null)
7473
{
7574
if ($valueNode instanceof ObjectValueNode || $valueNode instanceof ListValueNode) {
7675
return $this->parseIterableLiteral($valueNode);
7776
}
7877

79-
return null;
78+
// Intentionally without message, as all information already in wrapped Exception
79+
throw new \Exception();
8080
}
8181

8282
/**

src/Hal/Serializer/ItemNormalizer.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use ApiPlatform\Core\Exception\RuntimeException;
1717
use ApiPlatform\Core\Serializer\AbstractItemNormalizer;
1818
use ApiPlatform\Core\Serializer\ContextTrait;
19+
use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface;
1920

2021
/**
2122
* Converts between objects and array including HAL metadata.
@@ -167,8 +168,15 @@ private function getComponents($object, string $format = null, array $context)
167168
*/
168169
private function populateRelation(array $data, $object, string $format = null, array $context, array $components, string $type): array
169170
{
171+
$class = \get_class($object);
172+
$attributesMetadata = $this->classMetadataFactory ? $this->classMetadataFactory->getMetadataFor($object)->getAttributesMetadata() : null;
173+
170174
$key = '_'.$type;
171175
foreach ($components[$type] as $relation) {
176+
if (null !== $attributesMetadata && $this->isMaxDepthReached($attributesMetadata, $class, $relation['name'], $context)) {
177+
continue;
178+
}
179+
172180
$attributeValue = $this->getAttributeValue($object, $relation['name'], $format, $context);
173181
if (empty($attributeValue)) {
174182
continue;
@@ -232,4 +240,35 @@ private function getHalCacheKey(string $format = null, array $context)
232240
return false;
233241
}
234242
}
243+
244+
/**
245+
* Is the max depth reached for the given attribute?
246+
*
247+
* @param AttributeMetadataInterface[] $attributesMetadata
248+
*/
249+
private function isMaxDepthReached(array $attributesMetadata, string $class, string $attribute, array &$context): bool
250+
{
251+
if (
252+
!($context[static::ENABLE_MAX_DEPTH] ?? false) ||
253+
!isset($attributesMetadata[$attribute]) ||
254+
null === $maxDepth = $attributesMetadata[$attribute]->getMaxDepth()
255+
) {
256+
return false;
257+
}
258+
259+
$key = sprintf(static::DEPTH_KEY_PATTERN, $class, $attribute);
260+
if (!isset($context[$key])) {
261+
$context[$key] = 1;
262+
263+
return false;
264+
}
265+
266+
if ($context[$key] === $maxDepth) {
267+
return true;
268+
}
269+
270+
++$context[$key];
271+
272+
return false;
273+
}
235274
}

tests/EventListener/EventPrioritiesTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ public function testConstants()
3131
$this->assertEquals(63, EventPriorities::POST_VALIDATE);
3232
$this->assertEquals(33, EventPriorities::PRE_WRITE);
3333
$this->assertEquals(31, EventPriorities::POST_WRITE);
34+
$this->assertEquals(17, EventPriorities::PRE_SERIALIZE);
35+
$this->assertEquals(15, EventPriorities::POST_SERIALIZE);
3436
$this->assertEquals(9, EventPriorities::PRE_RESPOND);
3537
$this->assertEquals(0, EventPriorities::POST_RESPOND);
3638
}

tests/Fixtures/TestBundle/Entity/Recursive.php renamed to tests/Fixtures/TestBundle/Entity/MaxDepthDummy.php

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@
1818
use Symfony\Component\Serializer\Annotation\Groups;
1919
use Symfony\Component\Serializer\Annotation\MaxDepth;
2020

21-
/**
22-
* Recursive.
23-
*
21+
/** *
2422
* @ApiResource(attributes={
2523
* "normalization_context"={"groups"={"default"}, "enable_max_depth"=true},
2624
* "denormalization_context"={"groups"={"default"}, "enable_max_depth"=true}
@@ -29,7 +27,7 @@
2927
*
3028
* @author Brian Fox <brian@brianfox.fr>
3129
*/
32-
class Recursive
30+
class MaxDepthDummy
3331
{
3432
/**
3533
* @ORM\Column(type="integer")
@@ -46,9 +44,9 @@ class Recursive
4644
public $name;
4745

4846
/**
49-
* @ORM\ManyToOne(targetEntity="RecursiveChild", inversedBy="parent", cascade={"persist"})
47+
* @ORM\ManyToOne(targetEntity="MaxDepthDummy", cascade={"persist"})
5048
* @Groups({"default"})
51-
* @MaxDepth(2)
49+
* @MaxDepth(1)
5250
*/
5351
public $child;
5452

0 commit comments

Comments
 (0)