Skip to content

Commit 91ec138

Browse files
crynoboneStyleCIBottaylorotwell
authored
[JSON:API] Allows to configure maximum nested relationships (#58082)
* wip Signed-off-by: Mior Muhammad Zaki <[email protected]> * wip Signed-off-by: Mior Muhammad Zaki <[email protected]> * wip Signed-off-by: Mior Muhammad Zaki <[email protected]> * Apply fixes from StyleCI * wip Signed-off-by: Mior Muhammad Zaki <[email protected]> * wip Signed-off-by: Mior Muhammad Zaki <[email protected]> * wip Signed-off-by: Mior Muhammad Zaki <[email protected]> * wip Signed-off-by: Mior Muhammad Zaki <[email protected]> * formatting --------- Signed-off-by: Mior Muhammad Zaki <[email protected]> Co-authored-by: StyleCI Bot <[email protected]> Co-authored-by: Taylor Otwell <[email protected]>
1 parent a998541 commit 91ec138

File tree

5 files changed

+97
-2
lines changed

5 files changed

+97
-2
lines changed

src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,19 @@ trait ResolvesJsonApiElements
4545
*/
4646
protected array $loadedRelationshipIdentifiers = [];
4747

48+
/**
49+
* The maximum relationship depth.
50+
*/
51+
public static int $maxRelationshipDepth = 5;
52+
53+
/**
54+
* Specify the maximum relationship depth.
55+
*/
56+
public static function maxRelationshipDepth(int $depth): void
57+
{
58+
static::$maxRelationshipDepth = max(0, $depth);
59+
}
60+
4861
/**
4962
* Resolves `data` for the resource.
5063
*/
@@ -197,7 +210,9 @@ protected function compileResourceRelationships(JsonApiRequest $request): void
197210
$relatedResourceClass = $relationResolver->resourceClass();
198211

199212
if (! is_null($relatedModels) && $this->includesPreviouslyLoadedRelationships === false) {
200-
$relatedModels->loadMissing($request->sparseIncluded($relationName));
213+
if (! empty($relations = $request->sparseIncluded($relationName))) {
214+
$relatedModels->loadMissing($relations);
215+
}
201216
}
202217

203218
yield from $this->compileResourceRelationshipUsingResolver(

src/Illuminate/Http/Resources/JsonApi/JsonApiRequest.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Illuminate\Http\Resources\JsonApi;
44

55
use Illuminate\Http\Request;
6+
use Illuminate\Support\Arr;
67
use Illuminate\Support\Collection;
78

89
class JsonApiRequest extends Request
@@ -59,7 +60,12 @@ public function sparseIncluded(?string $key = null): ?array
5960
}
6061

6162
return transform($this->cachedSparseIncluded[$key] ?? null, function ($value) {
62-
return array_filter($value);
63+
return (new Collection(Arr::wrap($value)))
64+
->transform(function ($item) {
65+
$item = implode('.', Arr::take(explode('.', $item), JsonApiResource::$maxRelationshipDepth - 1));
66+
67+
return ! empty($item) ? $item : null;
68+
})->filter()->all();
6369
}) ?? [];
6470
}
6571
}

src/Illuminate/Http/Resources/JsonApi/JsonApiResource.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,5 +250,6 @@ public static function flushState()
250250
parent::flushState();
251251

252252
static::$jsonApiInformation = [];
253+
static::$maxRelationshipDepth = 3;
253254
}
254255
}

tests/Integration/Http/Resources/JsonApi/JsonApiRequestTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Illuminate\Tests\Integration\Http\Resources\JsonApi;
44

55
use Illuminate\Http\Resources\JsonApi\JsonApiRequest;
6+
use Illuminate\Http\Resources\JsonApi\JsonApiResource;
67

78
class JsonApiRequestTest extends TestCase
89
{
@@ -41,6 +42,20 @@ public function testItCanResolveSparseIncluded()
4142
$this->assertSame(['user.profile'], $request->sparseIncluded('profile'));
4243
}
4344

45+
public function testItCanREsolveSparseIncludedWithMaxRelationshipNesting()
46+
{
47+
JsonApiResource::maxRelationshipDepth(2);
48+
49+
$request = JsonApiRequest::create(uri: '/?'.http_build_query([
50+
'include' => 'teams,posts.author,posts.comments,profile.user.profile',
51+
]));
52+
53+
$this->assertSame(['teams', 'posts', 'profile'], $request->sparseIncluded());
54+
$this->assertSame([], $request->sparseIncluded('teams'));
55+
$this->assertSame(['author', 'comments'], $request->sparseIncluded('posts'));
56+
$this->assertSame(['user'], $request->sparseIncluded('profile'));
57+
}
58+
4459
public function testItCanResolveEmptySparseIncluded()
4560
{
4661
$request = JsonApiRequest::create(uri: '/');

tests/Integration/Http/Resources/JsonApi/JsonApiResourceTest.php

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

33
namespace Illuminate\Tests\Integration\Http\Resources\JsonApi;
44

5+
use Illuminate\Http\Resources\JsonApi\JsonApiResource;
56
use Illuminate\Tests\Integration\Http\Resources\JsonApi\Fixtures\Comment;
67
use Illuminate\Tests\Integration\Http\Resources\JsonApi\Fixtures\Post;
78
use Illuminate\Tests\Integration\Http\Resources\JsonApi\Fixtures\Profile;
@@ -465,6 +466,63 @@ public function testItCanResolveRelationshipWithRecursiveNestedRelationship()
465466
->assertJsonMissing(['jsonapi']);
466467
}
467468

469+
public function testItCanResolveRelationshipWithRecursiveNestedRelationshipLimitedToDepthConfiguration()
470+
{
471+
JsonApiResource::maxRelationshipDepth(2);
472+
473+
$now = $this->freezeSecond();
474+
475+
$user = User::factory()->create();
476+
477+
$profile = Profile::factory()->create([
478+
'user_id' => $user->getKey(),
479+
'date_of_birth' => '2011-06-09',
480+
'timezone' => 'America/Chicago',
481+
]);
482+
483+
$this->getJson("/users/{$user->getKey()}?".http_build_query(['include' => 'profile.user.profile']))
484+
->assertHeader('Content-type', 'application/vnd.api+json')
485+
->assertExactJson([
486+
'data' => [
487+
'attributes' => [
488+
'email' => $user->email,
489+
'name' => $user->name,
490+
],
491+
'id' => (string) $user->getKey(),
492+
'type' => 'users',
493+
'relationships' => [
494+
'profile' => [
495+
'data' => ['id' => (string) $profile->getKey(), 'type' => 'profiles'],
496+
],
497+
],
498+
],
499+
'included' => [
500+
[
501+
'attributes' => [
502+
'date_of_birth' => '2011-06-09',
503+
'timezone' => 'America/Chicago',
504+
],
505+
'id' => (string) $profile->getKey(),
506+
'type' => 'profiles',
507+
'relationships' => [
508+
'user' => [
509+
'data' => ['id' => (string) $user->getKey(), 'type' => 'users'],
510+
],
511+
],
512+
],
513+
[
514+
'attributes' => [
515+
'email' => $user->email,
516+
'name' => $user->name,
517+
],
518+
'id' => (string) $user->getKey(),
519+
'type' => 'users',
520+
],
521+
],
522+
])
523+
->assertJsonMissing(['jsonapi']);
524+
}
525+
468526
public function testItCanResolveRelationshipWithoutRedundantIncludedRelationship()
469527
{
470528
$now = $this->freezeSecond();

0 commit comments

Comments
 (0)