Skip to content

Commit 5db1522

Browse files
alanpoulaindunglas
authored andcommitted
Fix: max depth not working when fetch eager is disabled (#2215)
* Add failing test * Fix HAL normalizer
1 parent e17a44e commit 5db1522

File tree

6 files changed

+148
-22
lines changed

6 files changed

+148
-22
lines changed

features/bootstrap/FeatureContext.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FooDummy;
3636
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FourthLevel;
3737
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Greeting;
38+
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\MaxDepthDummy;
3839
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Node;
3940
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Person;
4041
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\PersonToPet;
@@ -1024,4 +1025,22 @@ public function thereIsAPersonWithAGreeting(string $name, string $message)
10241025
$this->manager->flush();
10251026
$this->manager->clear();
10261027
}
1028+
1029+
/**
1030+
* @Given there is a max depth dummy with :level level of descendants
1031+
*/
1032+
public function thereIsAMaxDepthDummyWithLevelOfDescendants(int $level)
1033+
{
1034+
$maxDepthDummy = new MaxDepthDummy();
1035+
$maxDepthDummy->name = "level $level";
1036+
$this->manager->persist($maxDepthDummy);
1037+
1038+
for ($i = 1; $i <= $level; ++$i) {
1039+
$maxDepthDummy = $maxDepthDummy->child = new MaxDepthDummy();
1040+
$maxDepthDummy->name = 'level '.($i + 1);
1041+
$this->manager->persist($maxDepthDummy);
1042+
}
1043+
1044+
$this->manager->flush();
1045+
}
10271046
}

features/hal/max_depth.feature

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Feature: Max depth handling
77
Scenario: Create a resource with 1 level of descendants
88
When I add "Accept" header equal to "application/hal+json"
99
And I add "Content-Type" header equal to "application/json"
10-
And I send a "POST" request to "/max_depth_dummies" with body:
10+
And I send a "POST" request to "/max_depth_eager_dummies" with body:
1111
"""
1212
{
1313
"name": "level 1",
@@ -24,17 +24,17 @@ Feature: Max depth handling
2424
{
2525
"_links": {
2626
"self": {
27-
"href": "\/max_depth_dummies\/1"
27+
"href": "\/max_depth_eager_dummies\/1"
2828
},
2929
"child": {
30-
"href": "\/max_depth_dummies\/2"
30+
"href": "\/max_depth_eager_dummies\/2"
3131
}
3232
},
3333
"_embedded": {
3434
"child": {
3535
"_links": {
3636
"self": {
37-
"href": "\/max_depth_dummies\/2"
37+
"href": "\/max_depth_eager_dummies\/2"
3838
}
3939
},
4040
"id": 2,
@@ -46,8 +46,54 @@ Feature: Max depth handling
4646
}
4747
"""
4848

49-
@dropSchema
5049
Scenario: Add a 2nd level of descendants
50+
When I add "Accept" header equal to "application/hal+json"
51+
And I add "Content-Type" header equal to "application/json"
52+
And I send a "PUT" request to "max_depth_eager_dummies/1" with body:
53+
"""
54+
{
55+
"id": "/max_depth_eager_dummies/1",
56+
"child": {
57+
"id": "/max_depth_eager_dummies/2",
58+
"child": {
59+
"name": "level 3"
60+
}
61+
}
62+
}
63+
"""
64+
And the response status code should be 200
65+
And the response should be in JSON
66+
And the header "Content-Type" should be equal to "application/hal+json; charset=utf-8"
67+
And the JSON should be equal to:
68+
"""
69+
{
70+
"_links": {
71+
"self": {
72+
"href": "\/max_depth_eager_dummies\/1"
73+
},
74+
"child": {
75+
"href": "\/max_depth_eager_dummies\/2"
76+
}
77+
},
78+
"_embedded": {
79+
"child": {
80+
"_links": {
81+
"self": {
82+
"href": "\/max_depth_eager_dummies\/2"
83+
}
84+
},
85+
"id": 2,
86+
"name": "level 2"
87+
}
88+
},
89+
"id": 1,
90+
"name": "level 1"
91+
}
92+
"""
93+
94+
@dropSchema
95+
Scenario: Add a 2nd level of descendants when eager fetching is disabled
96+
Given there is a max depth dummy with 1 level of descendants
5197
When I add "Accept" header equal to "application/hal+json"
5298
And I add "Content-Type" header equal to "application/json"
5399
And I send a "PUT" request to "max_depth_dummies/1" with body:
@@ -62,7 +108,7 @@ Feature: Max depth handling
62108
}
63109
}
64110
"""
65-
And the response status code should be 200
111+
Then the response status code should be 200
66112
And the response should be in JSON
67113
And the header "Content-Type" should be equal to "application/hal+json; charset=utf-8"
68114
And the JSON should be equal to:

features/jsonld/max_depth.feature

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Feature: Max depth handling
66
@createSchema
77
Scenario: Create a resource with 1 level of descendants
88
When I add "Content-Type" header equal to "application/ld+json"
9-
And I send a "POST" request to "/max_depth_dummies" with body:
9+
And I send a "POST" request to "/max_depth_eager_dummies" with body:
1010
"""
1111
{
1212
"name": "level 1",
@@ -21,14 +21,14 @@ Feature: Max depth handling
2121
And the JSON should be equal to:
2222
"""
2323
{
24-
"@context": "/contexts/MaxDepthDummy",
25-
"@id": "/max_depth_dummies/1",
26-
"@type": "MaxDepthDummy",
24+
"@context": "/contexts/MaxDepthEagerDummy",
25+
"@id": "/max_depth_eager_dummies/1",
26+
"@type": "MaxDepthEagerDummy",
2727
"id": 1,
2828
"name": "level 1",
2929
"child": {
30-
"@id": "/max_depth_dummies/2",
31-
"@type": "MaxDepthDummy",
30+
"@id": "/max_depth_eager_dummies/2",
31+
"@type": "MaxDepthEagerDummy",
3232
"id": 2,
3333
"name": "level 2"
3434
}
@@ -38,12 +38,12 @@ Feature: Max depth handling
3838
@dropSchema
3939
Scenario: Add a 2nd level of descendants
4040
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:
41+
And I send a "PUT" request to "max_depth_eager_dummies/1" with body:
4242
"""
4343
{
44-
"@id": "/max_depth_dummies/1",
44+
"@id": "/max_depth_eager_dummies/1",
4545
"child": {
46-
"@id": "/max_depth_dummies/2",
46+
"@id": "/max_depth_eager_dummies/2",
4747
"child": {
4848
"name": "level 3"
4949
}
@@ -56,14 +56,14 @@ Feature: Max depth handling
5656
And the JSON should be equal to:
5757
"""
5858
{
59-
"@context": "/contexts/MaxDepthDummy",
60-
"@id": "/max_depth_dummies/1",
61-
"@type": "MaxDepthDummy",
59+
"@context": "/contexts/MaxDepthEagerDummy",
60+
"@id": "/max_depth_eager_dummies/1",
61+
"@type": "MaxDepthEagerDummy",
6262
"id": 1,
6363
"name": "level 1",
6464
"child": {
65-
"@id": "/max_depth_dummies/2",
66-
"@type": "MaxDepthDummy",
65+
"@id": "/max_depth_eager_dummies/2",
66+
"@type": "MaxDepthEagerDummy",
6767
"id": 2,
6868
"name": "level 2"
6969
}

src/Hal/Serializer/ItemNormalizer.php

Lines changed: 4 additions & 2 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 ApiPlatform\Core\Util\ClassInfoTrait;
1920
use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface;
2021

2122
/**
@@ -26,6 +27,7 @@
2627
final class ItemNormalizer extends AbstractItemNormalizer
2728
{
2829
use ContextTrait;
30+
use ClassInfoTrait;
2931

3032
const FORMAT = 'jsonhal';
3133

@@ -102,7 +104,7 @@ protected function getAttributes($object, $format = null, array $context)
102104
*/
103105
private function getComponents($object, string $format = null, array $context)
104106
{
105-
$cacheKey = \get_class($object).'-'.$context['cache_key'];
107+
$cacheKey = $this->getObjectClass($object).'-'.$context['cache_key'];
106108

107109
if (isset($this->componentsCache[$cacheKey])) {
108110
return $this->componentsCache[$cacheKey];
@@ -160,7 +162,7 @@ private function getComponents($object, string $format = null, array $context)
160162
*/
161163
private function populateRelation(array $data, $object, string $format = null, array $context, array $components, string $type): array
162164
{
163-
$class = \get_class($object);
165+
$class = $this->getObjectClass($object);
164166

165167
$attributesMetadata = \array_key_exists($class, $this->attributesMetadataCache) ?
166168
$this->attributesMetadataCache[$class] :

tests/Fixtures/TestBundle/Entity/MaxDepthDummy.php

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

1414
namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity;
1515

16+
use ApiPlatform\Core\Annotation\ApiProperty;
1617
use ApiPlatform\Core\Annotation\ApiResource;
1718
use Doctrine\ORM\Mapping as ORM;
1819
use Symfony\Component\Serializer\Annotation\Groups;
@@ -45,6 +46,7 @@ class MaxDepthDummy
4546

4647
/**
4748
* @ORM\ManyToOne(targetEntity="MaxDepthDummy", cascade={"persist"})
49+
* @ApiProperty(attributes={"fetch_eager"=false})
4850
* @Groups({"default"})
4951
* @MaxDepth(1)
5052
*/
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[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+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity;
15+
16+
use ApiPlatform\Core\Annotation\ApiResource;
17+
use Doctrine\ORM\Mapping as ORM;
18+
use Symfony\Component\Serializer\Annotation\Groups;
19+
use Symfony\Component\Serializer\Annotation\MaxDepth;
20+
21+
/** *
22+
* @ApiResource(attributes={
23+
* "normalization_context"={"groups"={"default"}, "enable_max_depth"=true},
24+
* "denormalization_context"={"groups"={"default"}, "enable_max_depth"=true}
25+
* })
26+
* @ORM\Entity
27+
*
28+
* @author Brian Fox <[email protected]>
29+
*/
30+
class MaxDepthEagerDummy
31+
{
32+
/**
33+
* @ORM\Column(type="integer")
34+
* @ORM\Id
35+
* @ORM\GeneratedValue(strategy="AUTO")
36+
* @Groups({"default"})
37+
*/
38+
private $id;
39+
40+
/**
41+
* @ORM\Column(name="name", type="string", length=30)
42+
* @Groups({"default"})
43+
*/
44+
public $name;
45+
46+
/**
47+
* @ORM\ManyToOne(targetEntity="MaxDepthEagerDummy", cascade={"persist"})
48+
* @Groups({"default"})
49+
* @MaxDepth(1)
50+
*/
51+
public $child;
52+
53+
public function getId()
54+
{
55+
return $this->id;
56+
}
57+
}

0 commit comments

Comments
 (0)