From cbabe7f057f0f94768f4523ffd36e484a8980f60 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Thu, 11 Dec 2025 13:36:23 +0800 Subject: [PATCH 1/9] wip Signed-off-by: Mior Muhammad Zaki --- .../Concerns/ResolvesJsonApiElements.php | 42 ++++++++++---- .../Resources/JsonApi/JsonApiResource.php | 1 + .../Resources/JsonApi/JsonApiResourceTest.php | 58 +++++++++++++++++++ 3 files changed, 91 insertions(+), 10 deletions(-) diff --git a/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php b/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php index 4209cd96ae3a..b838b5a47a8b 100644 --- a/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php +++ b/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php @@ -33,6 +33,8 @@ trait ResolvesJsonApiElements */ protected bool $includesPreviouslyLoadedRelationships = false; + protected static int $nestedRelationshipsDepth = 3; + /** * Cached loaded relationships map. * @@ -45,6 +47,14 @@ trait ResolvesJsonApiElements */ protected array $loadedRelationshipIdentifiers = []; + /** + * Determine maximum relationship nesting. + */ + public static function maxRelationshipNesting(int $depth): void + { + static::$nestedRelationshipsDepth = max(0, $depth); + } + /** * Resolves `data` for the resource. */ @@ -205,6 +215,7 @@ protected function compileResourceRelationships(JsonApiRequest $request): void $this->resource, $relationResolver, $relatedModels, + depth: 1 ); } }))->all(); @@ -217,7 +228,8 @@ protected function compileResourceRelationshipUsingResolver( JsonApiRequest $request, mixed $resource, RelationResolver $relationResolver, - Collection|Model|null $relatedModels + Collection|Model|null $relatedModels, + int $depth ): Generator { $relationName = $relationResolver->relationName; $resourceClass = $relationResolver->resourceClass(); @@ -236,15 +248,15 @@ protected function compileResourceRelationshipUsingResolver( $isUnique = ! $relationship instanceof BelongsToMany; - yield $relationName => ['data' => $relatedModels->map(function ($relatedModel) use ($request, $resourceClass, $isUnique) { + yield $relationName => ['data' => $relatedModels->map(function ($relatedModel) use ($request, $resourceClass, $isUnique, $depth) { $relatedResource = rescue(fn () => $relatedModel->toResource($resourceClass), new JsonApiResource($relatedModel)); return transform( [$relatedResource->resolveResourceType($request), $relatedResource->resolveResourceIdentifier($request)], - function ($uniqueKey) use ($request, $relatedModel, $relatedResource, $isUnique) { + function ($uniqueKey) use ($request, $relatedModel, $relatedResource, $isUnique, $depth) { $this->loadedRelationshipsMap[] = [$relatedResource, ...$uniqueKey, $isUnique]; - $this->compileIncludedNestedRelationshipsMap($request, $relatedModel, $relatedResource); + $this->compileIncludedNestedRelationshipsMap($request, $relatedModel, $relatedResource, depth: $depth); return [ 'id' => $uniqueKey[1], @@ -275,10 +287,10 @@ function ($uniqueKey) use ($request, $relatedModel, $relatedResource, $isUnique) yield $relationName => ['data' => transform( [$relatedResource->resolveResourceType($request), $relatedResource->resolveResourceIdentifier($request)], - function ($uniqueKey) use ($relatedModel, $relatedResource, $request) { + function ($uniqueKey) use ($relatedModel, $relatedResource, $request, $depth) { $this->loadedRelationshipsMap[] = [$relatedResource, ...$uniqueKey, true]; - $this->compileIncludedNestedRelationshipsMap($request, $relatedModel, $relatedResource); + $this->compileIncludedNestedRelationshipsMap($request, $relatedModel, $relatedResource, depth: $depth); return [ 'id' => $uniqueKey[1], @@ -291,14 +303,24 @@ function ($uniqueKey) use ($relatedModel, $relatedResource, $request) { /** * Compile included relationships map. */ - protected function compileIncludedNestedRelationshipsMap(JsonApiRequest $request, Model $relation, JsonApiResource $resource): void - { + protected function compileIncludedNestedRelationshipsMap( + JsonApiRequest $request, + Model $relation, + JsonApiResource $resource, + int $depth + ): void { + if ($depth > static::$nestedRelationshipsDepth) { + return; + } + + $depth++; + (new Collection($resource->toRelationships($request))) ->transform(fn ($value, $key) => is_int($key) ? new RelationResolver($value) : new RelationResolver($key, $value)) ->mapWithKeys(fn ($relationResolver) => [$relationResolver->relationName => $relationResolver]) ->filter(fn ($value, $key) => in_array($key, array_keys($relation->getRelations()))) - ->each(function ($relationResolver, $key) use ($relation, $request) { - $this->compileResourceRelationshipUsingResolver($request, $relation, $relationResolver, $relation->getRelation($key)); + ->each(function ($relationResolver, $key) use ($relation, $request, $depth) { + $this->compileResourceRelationshipUsingResolver($request, $relation, $relationResolver, $relation->getRelation($key), depth: $depth); }); } diff --git a/src/Illuminate/Http/Resources/JsonApi/JsonApiResource.php b/src/Illuminate/Http/Resources/JsonApi/JsonApiResource.php index fa00c3e93e14..b86bab0ae813 100644 --- a/src/Illuminate/Http/Resources/JsonApi/JsonApiResource.php +++ b/src/Illuminate/Http/Resources/JsonApi/JsonApiResource.php @@ -250,5 +250,6 @@ public static function flushState() parent::flushState(); static::$jsonApiInformation = []; + static::$nestedRelationshipsDepth = 3; } } diff --git a/tests/Integration/Http/Resources/JsonApi/JsonApiResourceTest.php b/tests/Integration/Http/Resources/JsonApi/JsonApiResourceTest.php index fca806565c39..fcb6a281ebb3 100644 --- a/tests/Integration/Http/Resources/JsonApi/JsonApiResourceTest.php +++ b/tests/Integration/Http/Resources/JsonApi/JsonApiResourceTest.php @@ -2,6 +2,7 @@ namespace Illuminate\Tests\Integration\Http\Resources\JsonApi; +use Illuminate\Http\Resources\JsonApi\JsonApiResource; use Illuminate\Tests\Integration\Http\Resources\JsonApi\Fixtures\Comment; use Illuminate\Tests\Integration\Http\Resources\JsonApi\Fixtures\Post; use Illuminate\Tests\Integration\Http\Resources\JsonApi\Fixtures\Profile; @@ -465,6 +466,63 @@ public function testItCanResolveRelationshipWithRecursiveNestedRelationship() ->assertJsonMissing(['jsonapi']); } + public function testItCanResolveRelationshipWithRecursiveNestedRelationshipLimitedToDepthConfiguration() + { + JsonApiResource::maxRelationshipNesting(2); + + $now = $this->freezeSecond(); + + $user = User::factory()->create(); + + $profile = Profile::factory()->create([ + 'user_id' => $user->getKey(), + 'date_of_birth' => '2011-06-09', + 'timezone' => 'America/Chicago', + ]); + + $this->getJson("/users/{$user->getKey()}?".http_build_query(['include' => 'profile.user.profile'])) + ->assertHeader('Content-type', 'application/vnd.api+json') + ->assertExactJson([ + 'data' => [ + 'attributes' => [ + 'email' => $user->email, + 'name' => $user->name, + ], + 'id' => (string) $user->getKey(), + 'type' => 'users', + 'relationships' => [ + 'profile' => [ + 'data' => ['id' => (string) $profile->getKey(), 'type' => 'profiles'], + ], + ], + ], + 'included' => [ + [ + 'attributes' => [ + 'date_of_birth' => '2011-06-09', + 'timezone' => 'America/Chicago', + ], + 'id' => (string) $profile->getKey(), + 'type' => 'profiles', + 'relationships' => [ + 'user' => [ + 'data' => ['id' => (string) $user->getKey(), 'type' => 'users'], + ], + ], + ], + [ + 'attributes' => [ + 'email' => $user->email, + 'name' => $user->name, + ], + 'id' => (string) $user->getKey(), + 'type' => 'users', + ], + ], + ]) + ->assertJsonMissing(['jsonapi']); + } + public function testItCanResolveRelationshipWithoutRedundantIncludedRelationship() { $now = $this->freezeSecond(); From e54d2f3c912fd568da01c38fb52da8cd9504f013 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Thu, 11 Dec 2025 13:38:31 +0800 Subject: [PATCH 2/9] wip Signed-off-by: Mior Muhammad Zaki --- .../Resources/JsonApi/Concerns/ResolvesJsonApiElements.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php b/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php index b838b5a47a8b..3699898d9464 100644 --- a/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php +++ b/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php @@ -215,7 +215,7 @@ protected function compileResourceRelationships(JsonApiRequest $request): void $this->resource, $relationResolver, $relatedModels, - depth: 1 + depth: 0 ); } }))->all(); @@ -309,7 +309,7 @@ protected function compileIncludedNestedRelationshipsMap( JsonApiResource $resource, int $depth ): void { - if ($depth > static::$nestedRelationshipsDepth) { + if ($depth >= static::$nestedRelationshipsDepth) { return; } From a47eaa0181cbf8b593f0f6af499c8e0dde9da222 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Thu, 11 Dec 2025 17:51:04 +0800 Subject: [PATCH 3/9] wip Signed-off-by: Mior Muhammad Zaki --- .../Concerns/ResolvesJsonApiElements.php | 49 ++++++++++--------- .../Http/Resources/JsonApi/JsonApiRequest.php | 8 ++- .../Resources/JsonApi/JsonApiRequestTest.php | 16 ++++++ 3 files changed, 48 insertions(+), 25 deletions(-) diff --git a/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php b/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php index 3699898d9464..aad48e6b79c7 100644 --- a/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php +++ b/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php @@ -33,7 +33,9 @@ trait ResolvesJsonApiElements */ protected bool $includesPreviouslyLoadedRelationships = false; - protected static int $nestedRelationshipsDepth = 3; + public static int $nestedRelationshipsDepth = 3; + + public static int $currentNestedRelationshipDepth = 0; /** * Cached loaded relationships map. @@ -207,15 +209,18 @@ protected function compileResourceRelationships(JsonApiRequest $request): void $relatedResourceClass = $relationResolver->resourceClass(); if (! is_null($relatedModels) && $this->includesPreviouslyLoadedRelationships === false) { - $relatedModels->loadMissing($request->sparseIncluded($relationName)); + $relations = $request->sparseIncluded($relationName); + + if (! empty($relations)) { + $relatedModels->loadMissing($relations); + } } yield from $this->compileResourceRelationshipUsingResolver( $request, $this->resource, $relationResolver, - $relatedModels, - depth: 0 + $relatedModels ); } }))->all(); @@ -228,8 +233,7 @@ protected function compileResourceRelationshipUsingResolver( JsonApiRequest $request, mixed $resource, RelationResolver $relationResolver, - Collection|Model|null $relatedModels, - int $depth + Collection|Model|null $relatedModels ): Generator { $relationName = $relationResolver->relationName; $resourceClass = $relationResolver->resourceClass(); @@ -248,15 +252,15 @@ protected function compileResourceRelationshipUsingResolver( $isUnique = ! $relationship instanceof BelongsToMany; - yield $relationName => ['data' => $relatedModels->map(function ($relatedModel) use ($request, $resourceClass, $isUnique, $depth) { + yield $relationName => ['data' => $relatedModels->map(function ($relatedModel) use ($request, $resourceClass, $isUnique) { $relatedResource = rescue(fn () => $relatedModel->toResource($resourceClass), new JsonApiResource($relatedModel)); return transform( [$relatedResource->resolveResourceType($request), $relatedResource->resolveResourceIdentifier($request)], - function ($uniqueKey) use ($request, $relatedModel, $relatedResource, $isUnique, $depth) { + function ($uniqueKey) use ($request, $relatedModel, $relatedResource, $isUnique) { $this->loadedRelationshipsMap[] = [$relatedResource, ...$uniqueKey, $isUnique]; - $this->compileIncludedNestedRelationshipsMap($request, $relatedModel, $relatedResource, depth: $depth); + $this->compileIncludedNestedRelationshipsMap($request, $relatedModel, $relatedResource); return [ 'id' => $uniqueKey[1], @@ -287,10 +291,10 @@ function ($uniqueKey) use ($request, $relatedModel, $relatedResource, $isUnique, yield $relationName => ['data' => transform( [$relatedResource->resolveResourceType($request), $relatedResource->resolveResourceIdentifier($request)], - function ($uniqueKey) use ($relatedModel, $relatedResource, $request, $depth) { + function ($uniqueKey) use ($relatedModel, $relatedResource, $request) { $this->loadedRelationshipsMap[] = [$relatedResource, ...$uniqueKey, true]; - $this->compileIncludedNestedRelationshipsMap($request, $relatedModel, $relatedResource, depth: $depth); + $this->compileIncludedNestedRelationshipsMap($request, $relatedModel, $relatedResource); return [ 'id' => $uniqueKey[1], @@ -306,22 +310,19 @@ function ($uniqueKey) use ($relatedModel, $relatedResource, $request, $depth) { protected function compileIncludedNestedRelationshipsMap( JsonApiRequest $request, Model $relation, - JsonApiResource $resource, - int $depth + JsonApiResource $resource ): void { - if ($depth >= static::$nestedRelationshipsDepth) { - return; + if (static::$currentNestedRelationshipDepth < static::$nestedRelationshipsDepth) { + (new Collection($resource->toRelationships($request))) + ->transform(fn ($value, $key) => is_int($key) ? new RelationResolver($value) : new RelationResolver($key, $value)) + ->mapWithKeys(fn ($relationResolver) => [$relationResolver->relationName => $relationResolver]) + ->filter(fn ($value, $key) => in_array($key, array_keys($relation->getRelations()))) + ->each(function ($relationResolver, $key) use ($relation, $request) { + $this->compileResourceRelationshipUsingResolver($request, $relation, $relationResolver, $relation->getRelation($key)); + }); } - $depth++; - - (new Collection($resource->toRelationships($request))) - ->transform(fn ($value, $key) => is_int($key) ? new RelationResolver($value) : new RelationResolver($key, $value)) - ->mapWithKeys(fn ($relationResolver) => [$relationResolver->relationName => $relationResolver]) - ->filter(fn ($value, $key) => in_array($key, array_keys($relation->getRelations()))) - ->each(function ($relationResolver, $key) use ($relation, $request, $depth) { - $this->compileResourceRelationshipUsingResolver($request, $relation, $relationResolver, $relation->getRelation($key), depth: $depth); - }); + static::$currentNestedRelationshipDepth++; } /** diff --git a/src/Illuminate/Http/Resources/JsonApi/JsonApiRequest.php b/src/Illuminate/Http/Resources/JsonApi/JsonApiRequest.php index c869f1382578..49c11d741e14 100644 --- a/src/Illuminate/Http/Resources/JsonApi/JsonApiRequest.php +++ b/src/Illuminate/Http/Resources/JsonApi/JsonApiRequest.php @@ -3,6 +3,7 @@ namespace Illuminate\Http\Resources\JsonApi; use Illuminate\Http\Request; +use Illuminate\Support\Arr; use Illuminate\Support\Collection; class JsonApiRequest extends Request @@ -59,7 +60,12 @@ public function sparseIncluded(?string $key = null): ?array } return transform($this->cachedSparseIncluded[$key] ?? null, function ($value) { - return array_filter($value); + return (new Collection(Arr::wrap($value))) + ->transform(function ($item) { + $item = implode('.', Arr::take(explode('.', $item), (JsonApiResource::$nestedRelationshipsDepth - 1))); + + return ! empty($item) ? $item : null; + })->filter()->all(); }) ?? []; } } diff --git a/tests/Integration/Http/Resources/JsonApi/JsonApiRequestTest.php b/tests/Integration/Http/Resources/JsonApi/JsonApiRequestTest.php index 38f02fa07219..d155939e4eff 100644 --- a/tests/Integration/Http/Resources/JsonApi/JsonApiRequestTest.php +++ b/tests/Integration/Http/Resources/JsonApi/JsonApiRequestTest.php @@ -3,6 +3,7 @@ namespace Illuminate\Tests\Integration\Http\Resources\JsonApi; use Illuminate\Http\Resources\JsonApi\JsonApiRequest; +use Illuminate\Http\Resources\JsonApi\JsonApiResource; class JsonApiRequestTest extends TestCase { @@ -41,6 +42,21 @@ public function testItCanResolveSparseIncluded() $this->assertSame(['user.profile'], $request->sparseIncluded('profile')); } + public function testItCanREsolveSparseIncludedWithMaxRelationshipNesting() + { + JsonApiResource::maxRelationshipNesting(2); + + $request = JsonApiRequest::create(uri: '/?'.http_build_query([ + 'include' => 'teams,posts.author,posts.comments,profile.user.profile', + ])); + + $this->assertSame(['teams', 'posts', 'profile'], $request->sparseIncluded()); + $this->assertSame([], $request->sparseIncluded('teams')); + $this->assertSame(['author', 'comments'], $request->sparseIncluded('posts')); + $this->assertSame(['user'], $request->sparseIncluded('profile')); + + } + public function testItCanResolveEmptySparseIncluded() { $request = JsonApiRequest::create(uri: '/'); From 7e97c3abf76404be7f0438b0a87d5b4b584a3b6e Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Thu, 11 Dec 2025 13:42:50 +0000 Subject: [PATCH 4/9] Apply fixes from StyleCI --- src/Illuminate/Http/Resources/JsonApi/JsonApiRequest.php | 2 +- tests/Integration/Http/Resources/JsonApi/JsonApiRequestTest.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Illuminate/Http/Resources/JsonApi/JsonApiRequest.php b/src/Illuminate/Http/Resources/JsonApi/JsonApiRequest.php index 49c11d741e14..dc63353e2caa 100644 --- a/src/Illuminate/Http/Resources/JsonApi/JsonApiRequest.php +++ b/src/Illuminate/Http/Resources/JsonApi/JsonApiRequest.php @@ -62,7 +62,7 @@ public function sparseIncluded(?string $key = null): ?array return transform($this->cachedSparseIncluded[$key] ?? null, function ($value) { return (new Collection(Arr::wrap($value))) ->transform(function ($item) { - $item = implode('.', Arr::take(explode('.', $item), (JsonApiResource::$nestedRelationshipsDepth - 1))); + $item = implode('.', Arr::take(explode('.', $item), JsonApiResource::$nestedRelationshipsDepth - 1)); return ! empty($item) ? $item : null; })->filter()->all(); diff --git a/tests/Integration/Http/Resources/JsonApi/JsonApiRequestTest.php b/tests/Integration/Http/Resources/JsonApi/JsonApiRequestTest.php index d155939e4eff..ccecb82495f6 100644 --- a/tests/Integration/Http/Resources/JsonApi/JsonApiRequestTest.php +++ b/tests/Integration/Http/Resources/JsonApi/JsonApiRequestTest.php @@ -54,7 +54,6 @@ public function testItCanREsolveSparseIncludedWithMaxRelationshipNesting() $this->assertSame([], $request->sparseIncluded('teams')); $this->assertSame(['author', 'comments'], $request->sparseIncluded('posts')); $this->assertSame(['user'], $request->sparseIncluded('profile')); - } public function testItCanResolveEmptySparseIncluded() From e3325433591c78e00b1e249bd4d7f02e363438a1 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Thu, 11 Dec 2025 21:44:24 +0800 Subject: [PATCH 5/9] wip Signed-off-by: Mior Muhammad Zaki --- .../Concerns/ResolvesJsonApiElements.php | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php b/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php index aad48e6b79c7..bc554785aac7 100644 --- a/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php +++ b/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php @@ -35,8 +35,6 @@ trait ResolvesJsonApiElements public static int $nestedRelationshipsDepth = 3; - public static int $currentNestedRelationshipDepth = 0; - /** * Cached loaded relationships map. * @@ -307,20 +305,15 @@ function ($uniqueKey) use ($relatedModel, $relatedResource, $request) { /** * Compile included relationships map. */ - protected function compileIncludedNestedRelationshipsMap( - JsonApiRequest $request, - Model $relation, - JsonApiResource $resource - ): void { - if (static::$currentNestedRelationshipDepth < static::$nestedRelationshipsDepth) { - (new Collection($resource->toRelationships($request))) - ->transform(fn ($value, $key) => is_int($key) ? new RelationResolver($value) : new RelationResolver($key, $value)) - ->mapWithKeys(fn ($relationResolver) => [$relationResolver->relationName => $relationResolver]) - ->filter(fn ($value, $key) => in_array($key, array_keys($relation->getRelations()))) - ->each(function ($relationResolver, $key) use ($relation, $request) { - $this->compileResourceRelationshipUsingResolver($request, $relation, $relationResolver, $relation->getRelation($key)); - }); - } + protected function compileIncludedNestedRelationshipsMap(JsonApiRequest $request, Model $relation, JsonApiResource $resource): void + { + (new Collection($resource->toRelationships($request))) + ->transform(fn ($value, $key) => is_int($key) ? new RelationResolver($value) : new RelationResolver($key, $value)) + ->mapWithKeys(fn ($relationResolver) => [$relationResolver->relationName => $relationResolver]) + ->filter(fn ($value, $key) => in_array($key, array_keys($relation->getRelations()))) + ->each(function ($relationResolver, $key) use ($relation, $request) { + $this->compileResourceRelationshipUsingResolver($request, $relation, $relationResolver, $relation->getRelation($key)); + }); static::$currentNestedRelationshipDepth++; } From b5a6562d46133d0dfbb8e606939fb524030a27d9 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Thu, 11 Dec 2025 21:45:02 +0800 Subject: [PATCH 6/9] wip Signed-off-by: Mior Muhammad Zaki --- .../Resources/JsonApi/Concerns/ResolvesJsonApiElements.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php b/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php index bc554785aac7..5971b002e357 100644 --- a/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php +++ b/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php @@ -207,9 +207,7 @@ protected function compileResourceRelationships(JsonApiRequest $request): void $relatedResourceClass = $relationResolver->resourceClass(); if (! is_null($relatedModels) && $this->includesPreviouslyLoadedRelationships === false) { - $relations = $request->sparseIncluded($relationName); - - if (! empty($relations)) { + if (! empty($relations = $request->sparseIncluded($relationName))) { $relatedModels->loadMissing($relations); } } From 943838efbeca3800c27acba5e922197ee7d60d36 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Thu, 11 Dec 2025 21:45:27 +0800 Subject: [PATCH 7/9] wip Signed-off-by: Mior Muhammad Zaki --- .../Resources/JsonApi/Concerns/ResolvesJsonApiElements.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php b/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php index 5971b002e357..d7356283938f 100644 --- a/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php +++ b/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php @@ -216,7 +216,7 @@ protected function compileResourceRelationships(JsonApiRequest $request): void $request, $this->resource, $relationResolver, - $relatedModels + $relatedModels, ); } }))->all(); @@ -312,8 +312,6 @@ protected function compileIncludedNestedRelationshipsMap(JsonApiRequest $request ->each(function ($relationResolver, $key) use ($relation, $request) { $this->compileResourceRelationshipUsingResolver($request, $relation, $relationResolver, $relation->getRelation($key)); }); - - static::$currentNestedRelationshipDepth++; } /** From e8499acfdb9629bae1c43e51cc423d97e1f1231e Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Thu, 11 Dec 2025 21:46:47 +0800 Subject: [PATCH 8/9] wip Signed-off-by: Mior Muhammad Zaki --- .../Resources/JsonApi/Concerns/ResolvesJsonApiElements.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php b/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php index d7356283938f..c06efd92beb6 100644 --- a/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php +++ b/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php @@ -33,6 +33,9 @@ trait ResolvesJsonApiElements */ protected bool $includesPreviouslyLoadedRelationships = false; + /** + * Determine nested relationships depth allowed for each resources. + */ public static int $nestedRelationshipsDepth = 3; /** From cb031fa58c201a0b1df1e6339618084afd22e845 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Thu, 11 Dec 2025 16:07:44 -0600 Subject: [PATCH 9/9] formatting --- .../JsonApi/Concerns/ResolvesJsonApiElements.php | 16 ++++++++-------- .../Http/Resources/JsonApi/JsonApiRequest.php | 2 +- .../Http/Resources/JsonApi/JsonApiResource.php | 2 +- .../Resources/JsonApi/JsonApiRequestTest.php | 2 +- .../Resources/JsonApi/JsonApiResourceTest.php | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php b/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php index c06efd92beb6..5cb26268313f 100644 --- a/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php +++ b/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiElements.php @@ -33,11 +33,6 @@ trait ResolvesJsonApiElements */ protected bool $includesPreviouslyLoadedRelationships = false; - /** - * Determine nested relationships depth allowed for each resources. - */ - public static int $nestedRelationshipsDepth = 3; - /** * Cached loaded relationships map. * @@ -51,11 +46,16 @@ trait ResolvesJsonApiElements protected array $loadedRelationshipIdentifiers = []; /** - * Determine maximum relationship nesting. + * The maximum relationship depth. + */ + public static int $maxRelationshipDepth = 5; + + /** + * Specify the maximum relationship depth. */ - public static function maxRelationshipNesting(int $depth): void + public static function maxRelationshipDepth(int $depth): void { - static::$nestedRelationshipsDepth = max(0, $depth); + static::$maxRelationshipDepth = max(0, $depth); } /** diff --git a/src/Illuminate/Http/Resources/JsonApi/JsonApiRequest.php b/src/Illuminate/Http/Resources/JsonApi/JsonApiRequest.php index dc63353e2caa..075f52ce312b 100644 --- a/src/Illuminate/Http/Resources/JsonApi/JsonApiRequest.php +++ b/src/Illuminate/Http/Resources/JsonApi/JsonApiRequest.php @@ -62,7 +62,7 @@ public function sparseIncluded(?string $key = null): ?array return transform($this->cachedSparseIncluded[$key] ?? null, function ($value) { return (new Collection(Arr::wrap($value))) ->transform(function ($item) { - $item = implode('.', Arr::take(explode('.', $item), JsonApiResource::$nestedRelationshipsDepth - 1)); + $item = implode('.', Arr::take(explode('.', $item), JsonApiResource::$maxRelationshipDepth - 1)); return ! empty($item) ? $item : null; })->filter()->all(); diff --git a/src/Illuminate/Http/Resources/JsonApi/JsonApiResource.php b/src/Illuminate/Http/Resources/JsonApi/JsonApiResource.php index b86bab0ae813..06100158d7f1 100644 --- a/src/Illuminate/Http/Resources/JsonApi/JsonApiResource.php +++ b/src/Illuminate/Http/Resources/JsonApi/JsonApiResource.php @@ -250,6 +250,6 @@ public static function flushState() parent::flushState(); static::$jsonApiInformation = []; - static::$nestedRelationshipsDepth = 3; + static::$maxRelationshipDepth = 3; } } diff --git a/tests/Integration/Http/Resources/JsonApi/JsonApiRequestTest.php b/tests/Integration/Http/Resources/JsonApi/JsonApiRequestTest.php index ccecb82495f6..ff96ec1f7879 100644 --- a/tests/Integration/Http/Resources/JsonApi/JsonApiRequestTest.php +++ b/tests/Integration/Http/Resources/JsonApi/JsonApiRequestTest.php @@ -44,7 +44,7 @@ public function testItCanResolveSparseIncluded() public function testItCanREsolveSparseIncludedWithMaxRelationshipNesting() { - JsonApiResource::maxRelationshipNesting(2); + JsonApiResource::maxRelationshipDepth(2); $request = JsonApiRequest::create(uri: '/?'.http_build_query([ 'include' => 'teams,posts.author,posts.comments,profile.user.profile', diff --git a/tests/Integration/Http/Resources/JsonApi/JsonApiResourceTest.php b/tests/Integration/Http/Resources/JsonApi/JsonApiResourceTest.php index fcb6a281ebb3..1d73038fcbfd 100644 --- a/tests/Integration/Http/Resources/JsonApi/JsonApiResourceTest.php +++ b/tests/Integration/Http/Resources/JsonApi/JsonApiResourceTest.php @@ -468,7 +468,7 @@ public function testItCanResolveRelationshipWithRecursiveNestedRelationship() public function testItCanResolveRelationshipWithRecursiveNestedRelationshipLimitedToDepthConfiguration() { - JsonApiResource::maxRelationshipNesting(2); + JsonApiResource::maxRelationshipDepth(2); $now = $this->freezeSecond();