diff --git a/src/Concerns/Relationships.php b/src/Concerns/Relationships.php index 8d6bf7d..4d5a9af 100644 --- a/src/Concerns/Relationships.php +++ b/src/Concerns/Relationships.php @@ -53,11 +53,12 @@ public static function guessRelationshipResourceUsing(callable|null $callback) */ public function withIncludePrefix(string $prefix) { - $this->includePrefix = "{$this->includePrefix}{$prefix}."; + $this->includePrefix = self::joinIncludes($this->includePrefix, $prefix); return $this; } + /** * @internal * @@ -117,7 +118,9 @@ private function resolveInclude(mixed $resource, string $prefix) { return match (true) { $resource instanceof PotentiallyMissing && $resource->isMissing() => null, - $resource instanceof JsonApiResource || $resource instanceof JsonApiResourceCollection => $resource->withIncludePrefix($prefix), + $resource instanceof JsonApiResource || $resource instanceof JsonApiResourceCollection => $resource->withIncludePrefix( + self::joinIncludes($this->includePrefix, $prefix) + ), default => throw UnknownRelationshipException::from($resource), }; } @@ -198,4 +201,20 @@ private static function guessRelationshipResource(string $relationship, JsonApiR throw new RuntimeException('Unable to guess the resource class for relationship ['.$value.'] for ['.$resource::class.'].'); })($relationship, $resource); } + + /** + * @internal + */ + private static function joinIncludes(string $start, string $finish): string + { + $prefix = ''; + + if ($start !== '') { + $prefix = Str::finish($start, '.'); + } + + $prefix .= Str::finish($finish, '.'); + + return $prefix; + } } diff --git a/tests/Feature/RelationshipsTest.php b/tests/Feature/RelationshipsTest.php index 1317407..8712f9f 100644 --- a/tests/Feature/RelationshipsTest.php +++ b/tests/Feature/RelationshipsTest.php @@ -1401,4 +1401,155 @@ public function toRelationships($request): array ]); $this->assertValidJsonApi($response); } + + public function testItCanIncludeDeepNestedResourcesForASingleResource(): void + { + $post = new BasicModel([ + 'id' => 'post-id', + 'title' => 'post-title', + 'content' => 'post-content', + ]); + $post->author = new BasicModel([ + 'id' => 'author-id', + 'name' => 'author-name', + ]); + $post->author->license = new BasicModel([ + 'id' => 'license-id', + 'key' => 'license-key', + ]); + $post->author->license->user = new BasicModel([ + 'id' => 'user-id', + 'name' => 'Average Joe', + ]); + $post->author->license->user->posts = Collection::make([ + new BasicModel([ + 'id' => 'nested-post-id', + 'title' => 'Hello world!', + ]), + ]); + $post->author->license->user->posts[0]->author = new BasicModel([ + 'id' => 'nested-post-author-id', + 'name' => 'Tim Mac', + ]); + $post->author->license->user->posts[0]->comments = Collection::make([ + new BasicModel([ + 'id' => 'nested-post-comment-id', + 'content' => 'Oh hey there!', + ]), + ]); + Route::get('test-route', fn () => PostResource::make($post)); + + $response = $this->getJson('test-route?include=author.license.user.posts.comments,author.license.user.posts.author'); + + $response->assertOk(); + $response->assertExactJson([ + 'data' => [ + 'id' => 'post-id', + 'type' => 'basicModels', + 'attributes' => [ + 'title' => 'post-title', + 'content' => 'post-content', + ], + 'relationships' => [ + 'author' => [ + 'data' => [ + 'id' => 'author-id', + 'type' => 'basicModels', + ], + ], + ], + ], + 'jsonapi' => [ + 'version' => '1.0', + ], + 'included' => [ + [ + 'id' => 'author-id', + 'type' => 'basicModels', + 'attributes' => [ + 'name' => 'author-name', + ], + 'relationships' => [ + 'license' => [ + 'data' => [ + 'id' => 'license-id', + 'type' => 'basicModels', + ], + ], + ], + ], + [ + 'id' => 'license-id', + 'type' => 'basicModels', + 'attributes' => [ + 'key' => 'license-key', + ], + 'relationships' => [ + 'user' => [ + 'data' => [ + 'id' => 'user-id', + 'type' => 'basicModels', + ], + ], + ], + ], + [ + 'id' => 'user-id', + 'type' => 'basicModels', + 'attributes' => [ + 'name' => 'Average Joe', + ], + 'relationships' => [ + 'posts' => [ + 'data' => [ + [ + 'id' => 'nested-post-id', + 'type' => 'basicModels', + ] + ] + ] + ] + ], + [ + 'id' => 'nested-post-id', + 'type' => 'basicModels', + 'attributes' => [ + 'title' => 'Hello world!', + 'content' => null, + ], + 'relationships' => [ + 'author' => [ + 'data' => [ + 'id' => 'nested-post-author-id', + 'type' => 'basicModels', + ] + ], + 'comments' => [ + 'data' => [ + [ + 'id' => 'nested-post-comment-id', + 'type' => 'basicModels', + ] + ] + ] + ] + ], + [ + 'id' => 'nested-post-author-id', + 'type' => 'basicModels', + 'attributes' => [ + 'name' => 'Tim Mac', + ], + ], + [ + 'id' => 'nested-post-comment-id', + 'type' => 'basicModels', + 'attributes' => [ + 'content' => 'Oh hey there!', + ], + ] + ], + ]); + $this->assertValidJsonApi($response); + } }