From 327eb7e19a1e7a05d7feacc8f0fd6f97d54b72c1 Mon Sep 17 00:00:00 2001 From: Sam Snelling Date: Thu, 26 May 2022 11:53:49 -0500 Subject: [PATCH 1/5] Fix keys in included --- src/JsonApiResourceCollection.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/JsonApiResourceCollection.php b/src/JsonApiResourceCollection.php index dd8687e..7f1539c 100644 --- a/src/JsonApiResourceCollection.php +++ b/src/JsonApiResourceCollection.php @@ -60,7 +60,8 @@ public function with($request) 'included' => $this->collection ->map(fn (JsonApiResource $resource): Collection => $resource->included($request)) ->flatten() - ->uniqueStrict(fn (JsonApiResource $resource): string => $resource->toUniqueResourceIdentifier($request)), + ->uniqueStrict(fn (JsonApiResource $resource): string => $resource->toUniqueResourceIdentifier($request)) + ->values(), 'jsonapi' => JsonApiResource::serverImplementationResolver()($request), ]; } From 8fa7721f34b0bfdda95927c7993b9052960e4d61 Mon Sep 17 00:00:00 2001 From: Sam Snelling Date: Thu, 26 May 2022 13:12:24 -0500 Subject: [PATCH 2/5] Test --- tests/Feature/RelationshipsTest.php | 142 ++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/tests/Feature/RelationshipsTest.php b/tests/Feature/RelationshipsTest.php index 56758cb..5e04413 100644 --- a/tests/Feature/RelationshipsTest.php +++ b/tests/Feature/RelationshipsTest.php @@ -1087,6 +1087,148 @@ public function testItFiltersOutDuplicateIncludesForACollectionOfResources(): vo $this->assertValidJsonApi($response); } + public function testItFiltersOutDuplicateIncludesForACollectionOfResourcesWhenHasNumericKeys(): void + { + $users = [ + (new BasicModel( + [ + 'id' => 'user-id-1', + 'name' => 'user-name-1', + ] + ))->setRelation('avatar', + (new BasicModel( + [ + 'id' => 1, + 'url' => 'https://example.com/avatar1.png', + ] + )) + ), + (new BasicModel( + [ + 'id' => 'user-id-2', + 'name' => 'user-name-2', + ] + ))->setRelation('avatar', + (new BasicModel( + [ + 'id' => 1, + 'url' => 'https://example.com/avatar1.png', + ] + )) + ), + (new BasicModel( + [ + 'id' => 'user-id-3', + 'name' => 'user-name-3', + ] + ))->setRelation('avatar', + (new BasicModel( + [ + 'id' => 2, + 'url' => 'https://example.com/avatar2.png', + ] + )) + ), + ]; + Route::get('test-route', fn () => UserResource::collection($users)); + + $response = $this->getJson('test-route?include=avatar'); + + $response->assertOk(); + $response->assertExactJson( + [ + 'data' => [ + [ + 'id' => 'user-id-1', + 'type' => 'basicModels', + 'attributes' => [ + 'name' => 'user-name-1', + ], + 'relationships' => [ + 'avatar' => [ + 'data' => [ + 'id' => '1', + 'type' => 'basicModels', + 'meta' => [], + ], + 'links' => [], + 'meta' => [], + ], + ], + 'meta' => [], + 'links' => [], + ], + [ + 'id' => 'user-id-2', + 'type' => 'basicModels', + 'attributes' => [ + 'name' => 'user-name-2', + ], + 'relationships' => [ + 'avatar' => [ + 'data' => [ + 'id' => '1', + 'type' => 'basicModels', + 'meta' => [], + ], + 'links' => [], + 'meta' => [], + ], + ], + 'meta' => [], + 'links' => [], + ], + [ + 'id' => 'user-id-3', + 'type' => 'basicModels', + 'attributes' => [ + 'name' => 'user-name-3', + ], + 'relationships' => [ + 'avatar' => [ + 'data' => [ + 'id' => '2', + 'type' => 'basicModels', + 'meta' => [], + ], + 'links' => [], + 'meta' => [], + ], + ], + 'meta' => [], + 'links' => [], + ], + ], + 'jsonapi' => [ + 'version' => '1.0', + 'meta' => [], + ], + 'included' => [ + [ + 'id' => '1', + 'type' => 'basicModels', + 'attributes' => [ + 'url' => 'https://example.com/avatar1.png', + ], + 'relationships' => [], + 'links' => [], + 'meta' => [], + ], + [ + 'id' => '2', + 'type' => 'basicModels', + 'attributes' => [ + 'url' => 'https://example.com/avatar2.png', + ], + 'relationships' => [], + 'links' => [], + 'meta' => [], + ], + ], + ] + ); + } + public function testItFiltersOutDuplicateResourceObjectsIncludesForASingleResource(): void { $user = (new BasicModel([ From 995d768f3551b4531a1ce49ced451f4bf0bd51c7 Mon Sep 17 00:00:00 2001 From: Sam Snelling Date: Thu, 26 May 2022 19:35:33 -0500 Subject: [PATCH 3/5] rename test --- tests/Feature/RelationshipsTest.php | 142 ---------------------------- 1 file changed, 142 deletions(-) diff --git a/tests/Feature/RelationshipsTest.php b/tests/Feature/RelationshipsTest.php index 5e04413..56758cb 100644 --- a/tests/Feature/RelationshipsTest.php +++ b/tests/Feature/RelationshipsTest.php @@ -1087,148 +1087,6 @@ public function testItFiltersOutDuplicateIncludesForACollectionOfResources(): vo $this->assertValidJsonApi($response); } - public function testItFiltersOutDuplicateIncludesForACollectionOfResourcesWhenHasNumericKeys(): void - { - $users = [ - (new BasicModel( - [ - 'id' => 'user-id-1', - 'name' => 'user-name-1', - ] - ))->setRelation('avatar', - (new BasicModel( - [ - 'id' => 1, - 'url' => 'https://example.com/avatar1.png', - ] - )) - ), - (new BasicModel( - [ - 'id' => 'user-id-2', - 'name' => 'user-name-2', - ] - ))->setRelation('avatar', - (new BasicModel( - [ - 'id' => 1, - 'url' => 'https://example.com/avatar1.png', - ] - )) - ), - (new BasicModel( - [ - 'id' => 'user-id-3', - 'name' => 'user-name-3', - ] - ))->setRelation('avatar', - (new BasicModel( - [ - 'id' => 2, - 'url' => 'https://example.com/avatar2.png', - ] - )) - ), - ]; - Route::get('test-route', fn () => UserResource::collection($users)); - - $response = $this->getJson('test-route?include=avatar'); - - $response->assertOk(); - $response->assertExactJson( - [ - 'data' => [ - [ - 'id' => 'user-id-1', - 'type' => 'basicModels', - 'attributes' => [ - 'name' => 'user-name-1', - ], - 'relationships' => [ - 'avatar' => [ - 'data' => [ - 'id' => '1', - 'type' => 'basicModels', - 'meta' => [], - ], - 'links' => [], - 'meta' => [], - ], - ], - 'meta' => [], - 'links' => [], - ], - [ - 'id' => 'user-id-2', - 'type' => 'basicModels', - 'attributes' => [ - 'name' => 'user-name-2', - ], - 'relationships' => [ - 'avatar' => [ - 'data' => [ - 'id' => '1', - 'type' => 'basicModels', - 'meta' => [], - ], - 'links' => [], - 'meta' => [], - ], - ], - 'meta' => [], - 'links' => [], - ], - [ - 'id' => 'user-id-3', - 'type' => 'basicModels', - 'attributes' => [ - 'name' => 'user-name-3', - ], - 'relationships' => [ - 'avatar' => [ - 'data' => [ - 'id' => '2', - 'type' => 'basicModels', - 'meta' => [], - ], - 'links' => [], - 'meta' => [], - ], - ], - 'meta' => [], - 'links' => [], - ], - ], - 'jsonapi' => [ - 'version' => '1.0', - 'meta' => [], - ], - 'included' => [ - [ - 'id' => '1', - 'type' => 'basicModels', - 'attributes' => [ - 'url' => 'https://example.com/avatar1.png', - ], - 'relationships' => [], - 'links' => [], - 'meta' => [], - ], - [ - 'id' => '2', - 'type' => 'basicModels', - 'attributes' => [ - 'url' => 'https://example.com/avatar2.png', - ], - 'relationships' => [], - 'links' => [], - 'meta' => [], - ], - ], - ] - ); - } - public function testItFiltersOutDuplicateResourceObjectsIncludesForASingleResource(): void { $user = (new BasicModel([ From 55a2ce9615caf510d2dce4f6e1239e59ec53d3bd Mon Sep 17 00:00:00 2001 From: Sam Snelling Date: Thu, 26 May 2022 19:35:50 -0500 Subject: [PATCH 4/5] Fix circular reference with numeric keys for single item --- src/JsonApiResource.php | 3 +- tests/Feature/RelationshipsTest.php | 260 ++++++++++++++++++++++++++++ 2 files changed, 262 insertions(+), 1 deletion(-) diff --git a/src/JsonApiResource.php b/src/JsonApiResource.php index 3bd762b..c1f4699 100644 --- a/src/JsonApiResource.php +++ b/src/JsonApiResource.php @@ -143,7 +143,8 @@ public function with($request) { return [ 'included' => $this->included($request) - ->uniqueStrict(fn (JsonApiResource $resource): string => $resource->toUniqueResourceIdentifier($request)), + ->uniqueStrict(fn (JsonApiResource $resource): string => $resource->toUniqueResourceIdentifier($request)) + ->values(), 'jsonapi' => self::serverImplementationResolver()($request), ]; } diff --git a/tests/Feature/RelationshipsTest.php b/tests/Feature/RelationshipsTest.php index 56758cb..df91e37 100644 --- a/tests/Feature/RelationshipsTest.php +++ b/tests/Feature/RelationshipsTest.php @@ -1306,6 +1306,266 @@ public function testItFlushesTheRelationshipCache(): void $this->assertNull($resource->requestedRelationshipsCache()); } + public function testItHasArrayOfIncludesForACollectionOfResourcesWhenHasNumericKeys(): void + { + $users = [ + (new BasicModel( + [ + 'id' => 'user-id-1', + 'name' => 'user-name-1', + ] + ))->setRelation('avatar', + (new BasicModel( + [ + 'id' => 1, + 'url' => 'https://example.com/avatar1.png', + ] + )) + ), + (new BasicModel( + [ + 'id' => 'user-id-2', + 'name' => 'user-name-2', + ] + ))->setRelation('avatar', + (new BasicModel( + [ + 'id' => 1, + 'url' => 'https://example.com/avatar1.png', + ] + )) + ), + (new BasicModel( + [ + 'id' => 'user-id-3', + 'name' => 'user-name-3', + ] + ))->setRelation('avatar', + (new BasicModel( + [ + 'id' => 2, + 'url' => 'https://example.com/avatar2.png', + ] + )) + ), + ]; + Route::get('test-route', fn () => UserResource::collection($users)); + + $response = $this->getJson('test-route?include=avatar'); + + $response->assertOk(); + $response->assertExactJson( + [ + 'data' => [ + [ + 'id' => 'user-id-1', + 'type' => 'basicModels', + 'attributes' => [ + 'name' => 'user-name-1', + ], + 'relationships' => [ + 'avatar' => [ + 'data' => [ + 'id' => '1', + 'type' => 'basicModels', + 'meta' => [], + ], + 'links' => [], + 'meta' => [], + ], + ], + 'meta' => [], + 'links' => [], + ], + [ + 'id' => 'user-id-2', + 'type' => 'basicModels', + 'attributes' => [ + 'name' => 'user-name-2', + ], + 'relationships' => [ + 'avatar' => [ + 'data' => [ + 'id' => '1', + 'type' => 'basicModels', + 'meta' => [], + ], + 'links' => [], + 'meta' => [], + ], + ], + 'meta' => [], + 'links' => [], + ], + [ + 'id' => 'user-id-3', + 'type' => 'basicModels', + 'attributes' => [ + 'name' => 'user-name-3', + ], + 'relationships' => [ + 'avatar' => [ + 'data' => [ + 'id' => '2', + 'type' => 'basicModels', + 'meta' => [], + ], + 'links' => [], + 'meta' => [], + ], + ], + 'meta' => [], + 'links' => [], + ], + ], + 'jsonapi' => [ + 'version' => '1.0', + 'meta' => [], + ], + 'included' => [ + [ + 'id' => '1', + 'type' => 'basicModels', + 'attributes' => [ + 'url' => 'https://example.com/avatar1.png', + ], + 'relationships' => [], + 'links' => [], + 'meta' => [], + ], + [ + 'id' => '2', + 'type' => 'basicModels', + 'attributes' => [ + 'url' => 'https://example.com/avatar2.png', + ], + 'relationships' => [], + 'links' => [], + 'meta' => [], + ], + ], + ] + ); + } + + public function testItHasArrayOfIncludesForASingleResourceWhenHasNumericKeys(): void + { + $user = (new BasicModel( + [ + 'id' => 1, + 'name' => 'user-name-1', + ] + )); + $user->posts = [ + (new BasicModel( + [ + 'id' => 2, + 'title' => 'Title 1', + 'content' => 'Content 1', + ] + ))->setRelation('comments', [ + (new BasicModel( + [ + 'id' => 3, + 'content' => 'Comment 1', + ] + ))->setRelation('author', (new BasicModel( + [ + 'id' => 1, + 'name' => 'user-name-1', + ] + ))), + ]), + (new BasicModel( + [ + 'id' => 2, + 'title' => 'Title 2', + 'content' => 'Content 2', + ] + ))->setRelation('comments', new Collection()), + ]; + + Route::get('test-route', fn () => UserResource::make($user)); + + $response = $this->getJson('test-route?include=posts,posts.comments,posts.comments.author'); + + $response->assertOk(); + $response->assertExactJson( + [ + 'data' => [ + 'id' => '1', + 'type' => 'basicModels', + 'attributes' => [ + 'name' => 'user-name-1', + ], + 'relationships' => [ + 'posts' => [ + [ + 'data' => [ + 'id' => '2', + 'type' => 'basicModels', + 'meta' => [], + ], + 'meta' => [], + 'links' => [], + ], + [ + 'data' => [ + 'id' => '2', + 'type' => 'basicModels', + 'meta' => [], + ], + 'meta' => [], + 'links' => [], + ], + ], + ], + 'meta' => [], + 'links' => [], + ], + 'included' => [ + [ + 'id' => '2', + 'type' => 'basicModels', + 'attributes' => [ + 'title' => 'Title 1', + 'content' => 'Content 1', + ], + 'relationships' => [ + 'comments' => [ + [ + 'data' => [ + 'id' => '3', + 'type' => 'basicModels', + 'meta' => [], + ], + 'meta' => [], + 'links' => [], + ], + ], + ], + 'meta' => [], + 'links' => [], + ], + [ + 'id' => '3', + 'type' => 'basicModels', + 'attributes' => [ + 'content' => 'Comment 1', + ], + 'relationships' => [], + 'meta' => [], + 'links' => [], + ], + ], + 'jsonapi' => [ + 'version' => '1.0', + 'meta' => [], + ], + ] + ); + } + public function testItRemovesPotentiallyMissingRelationships(): void { $user = new BasicModel([ From e9c6f6846c8e248661c5bece86f3eafe7ba383e7 Mon Sep 17 00:00:00 2001 From: Tim MacDonald Date: Fri, 27 Jan 2023 09:34:05 +1100 Subject: [PATCH 5/5] Formatting --- tests/Feature/RelationshipsTest.php | 292 ++++++++++++---------------- 1 file changed, 126 insertions(+), 166 deletions(-) diff --git a/tests/Feature/RelationshipsTest.php b/tests/Feature/RelationshipsTest.php index df91e37..d59b089 100644 --- a/tests/Feature/RelationshipsTest.php +++ b/tests/Feature/RelationshipsTest.php @@ -1306,184 +1306,153 @@ public function testItFlushesTheRelationshipCache(): void $this->assertNull($resource->requestedRelationshipsCache()); } - public function testItHasArrayOfIncludesForACollectionOfResourcesWhenHasNumericKeys(): void + public function testCollectionIncludesDoesntBecomeNumericKeyedObjectAfterFilteringDuplicateRecords(): void { $users = [ - (new BasicModel( - [ - 'id' => 'user-id-1', - 'name' => 'user-name-1', - ] - ))->setRelation('avatar', - (new BasicModel( - [ - 'id' => 1, - 'url' => 'https://example.com/avatar1.png', - ] - )) - ), - (new BasicModel( - [ - 'id' => 'user-id-2', - 'name' => 'user-name-2', - ] - ))->setRelation('avatar', - (new BasicModel( - [ - 'id' => 1, - 'url' => 'https://example.com/avatar1.png', - ] - )) - ), - (new BasicModel( - [ - 'id' => 'user-id-3', - 'name' => 'user-name-3', - ] - ))->setRelation('avatar', - (new BasicModel( - [ - 'id' => 2, - 'url' => 'https://example.com/avatar2.png', - ] - )) - ), + BasicModel::make([ + 'id' => 'user-id-1', + 'name' => 'user-name-1', + ])->setRelation('avatar', BasicModel::make([ + 'id' => 1, + 'url' => 'https://example.com/avatar1.png', + ])), + BasicModel::make([ + 'id' => 'user-id-2', + 'name' => 'user-name-2', + ])->setRelation('avatar', BasicModel::make([ + 'id' => 1, + 'url' => 'https://example.com/avatar1.png', + ])), + BasicModel::make([ + 'id' => 'user-id-3', + 'name' => 'user-name-3', + ])->setRelation('avatar', BasicModel::make([ + 'id' => 2, + 'url' => 'https://example.com/avatar2.png', + ])), ]; Route::get('test-route', fn () => UserResource::collection($users)); $response = $this->getJson('test-route?include=avatar'); $response->assertOk(); - $response->assertExactJson( - [ - 'data' => [ - [ - 'id' => 'user-id-1', - 'type' => 'basicModels', - 'attributes' => [ - 'name' => 'user-name-1', - ], - 'relationships' => [ - 'avatar' => [ - 'data' => [ - 'id' => '1', - 'type' => 'basicModels', - 'meta' => [], - ], - 'links' => [], + $response->assertExactJson([ + 'data' => [ + [ + 'id' => 'user-id-1', + 'type' => 'basicModels', + 'attributes' => [ + 'name' => 'user-name-1', + ], + 'relationships' => [ + 'avatar' => [ + 'data' => [ + 'id' => '1', + 'type' => 'basicModels', 'meta' => [], ], + 'links' => [], + 'meta' => [], ], - 'meta' => [], - 'links' => [], ], - [ - 'id' => 'user-id-2', - 'type' => 'basicModels', - 'attributes' => [ - 'name' => 'user-name-2', - ], - 'relationships' => [ - 'avatar' => [ - 'data' => [ - 'id' => '1', - 'type' => 'basicModels', - 'meta' => [], - ], - 'links' => [], + 'meta' => [], + 'links' => [], + ], + [ + 'id' => 'user-id-2', + 'type' => 'basicModels', + 'attributes' => [ + 'name' => 'user-name-2', + ], + 'relationships' => [ + 'avatar' => [ + 'data' => [ + 'id' => '1', + 'type' => 'basicModels', 'meta' => [], ], + 'links' => [], + 'meta' => [], ], - 'meta' => [], - 'links' => [], ], - [ - 'id' => 'user-id-3', - 'type' => 'basicModels', - 'attributes' => [ - 'name' => 'user-name-3', - ], - 'relationships' => [ - 'avatar' => [ - 'data' => [ - 'id' => '2', - 'type' => 'basicModels', - 'meta' => [], - ], - 'links' => [], + 'meta' => [], + 'links' => [], + ], + [ + 'id' => 'user-id-3', + 'type' => 'basicModels', + 'attributes' => [ + 'name' => 'user-name-3', + ], + 'relationships' => [ + 'avatar' => [ + 'data' => [ + 'id' => '2', + 'type' => 'basicModels', 'meta' => [], ], + 'links' => [], + 'meta' => [], ], - 'meta' => [], - 'links' => [], ], - ], - 'jsonapi' => [ - 'version' => '1.0', 'meta' => [], + 'links' => [], ], - 'included' => [ - [ - 'id' => '1', - 'type' => 'basicModels', - 'attributes' => [ - 'url' => 'https://example.com/avatar1.png', - ], - 'relationships' => [], - 'links' => [], - 'meta' => [], + ], + 'jsonapi' => [ + 'version' => '1.0', + 'meta' => [], + ], + 'included' => [ + [ + 'id' => '1', + 'type' => 'basicModels', + 'attributes' => [ + 'url' => 'https://example.com/avatar1.png', ], - [ - 'id' => '2', - 'type' => 'basicModels', - 'attributes' => [ - 'url' => 'https://example.com/avatar2.png', - ], - 'relationships' => [], - 'links' => [], - 'meta' => [], + 'relationships' => [], + 'links' => [], + 'meta' => [], + ], + [ + 'id' => '2', + 'type' => 'basicModels', + 'attributes' => [ + 'url' => 'https://example.com/avatar2.png', ], + 'relationships' => [], + 'links' => [], + 'meta' => [], ], - ] - ); + ], + ]); } - public function testItHasArrayOfIncludesForASingleResourceWhenHasNumericKeys(): void + public function testSingleResourceIncludesDoesntBecomeNumericKeyedObjectAfterFilteringDuplicateRecords(): void { - $user = (new BasicModel( - [ - 'id' => 1, - 'name' => 'user-name-1', - ] - )); - $user->posts = [ - (new BasicModel( - [ - 'id' => 2, - 'title' => 'Title 1', - 'content' => 'Content 1', - ] - ))->setRelation('comments', [ - (new BasicModel( - [ - 'id' => 3, - 'content' => 'Comment 1', - ] - ))->setRelation('author', (new BasicModel( - [ - 'id' => 1, - 'name' => 'user-name-1', - ] - ))), + $user = BasicModel::make([ + 'id' => 1, + 'name' => 'user-name-1', + ])->setRelation('posts', [ + BasicModel::make([ + 'id' => 2, + 'title' => 'Title 1', + 'content' => 'Content 1', + ])->setRelation('comments', [ + BasicModel::make([ + 'id' => 3, + 'content' => 'Comment 1', + ])->setRelation('author', BasicModel::make([ + 'id' => 1, + 'name' => 'user-name-1', + ])), ]), - (new BasicModel( - [ - 'id' => 2, - 'title' => 'Title 2', - 'content' => 'Content 2', - ] - ))->setRelation('comments', new Collection()), - ]; + BasicModel::make([ + 'id' => 2, + 'title' => 'Title 2', + 'content' => 'Content 2', + ])->setRelation('comments', new Collection()), + ]); Route::get('test-route', fn () => UserResource::make($user)); @@ -1500,24 +1469,15 @@ public function testItHasArrayOfIncludesForASingleResourceWhenHasNumericKeys(): ], 'relationships' => [ 'posts' => [ - [ - 'data' => [ - 'id' => '2', - 'type' => 'basicModels', - 'meta' => [], - ], - 'meta' => [], - 'links' => [], - ], - [ - 'data' => [ + 'data' => [ + [ 'id' => '2', 'type' => 'basicModels', 'meta' => [], ], - 'meta' => [], - 'links' => [], ], + 'meta' => [], + 'links' => [], ], ], 'meta' => [], @@ -1533,15 +1493,15 @@ public function testItHasArrayOfIncludesForASingleResourceWhenHasNumericKeys(): ], 'relationships' => [ 'comments' => [ - [ - 'data' => [ + 'data' => [ + [ 'id' => '3', 'type' => 'basicModels', 'meta' => [], ], - 'meta' => [], - 'links' => [], ], + 'meta' => [], + 'links' => [], ], ], 'meta' => [],