From 2e2c181739dad2653a81783387c024c3d03323b9 Mon Sep 17 00:00:00 2001 From: mpyw Date: Sun, 3 Nov 2019 16:08:19 +0900 Subject: [PATCH 1/7] Implement collection logic --- .../Resources/CollectsPaginationResult.php | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/Http/Resources/CollectsPaginationResult.php diff --git a/src/Http/Resources/CollectsPaginationResult.php b/src/Http/Resources/CollectsPaginationResult.php new file mode 100644 index 0000000..e07046c --- /dev/null +++ b/src/Http/Resources/CollectsPaginationResult.php @@ -0,0 +1,48 @@ +collects(); + + $this->collection = $collects && !$resource->first() instanceof $collects + ? $resource->mapInto($collects) + : $resource->toBase(); + + if ($resource instanceof AbstractPaginator) { + $resource->setCollection($this->collection); + return $resource; + } + if ($resource instanceof PaginationResult) { + $resource->records = $this->collection; + return $resource; + } + + return $this->collection; + } +} From 9e8ec5bff823e84a281115c77096ffceda80b1cf Mon Sep 17 00:00:00 2001 From: mpyw Date: Sun, 3 Nov 2019 16:09:01 +0900 Subject: [PATCH 2/7] Implement response logic --- .../Json/PaginationResultResourceResponse.php | 23 +++++++++++++ .../Json/RespondsWithPaginationResult.php | 33 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 src/Http/Resources/Json/PaginationResultResourceResponse.php create mode 100644 src/Http/Resources/Json/RespondsWithPaginationResult.php diff --git a/src/Http/Resources/Json/PaginationResultResourceResponse.php b/src/Http/Resources/Json/PaginationResultResourceResponse.php new file mode 100644 index 0000000..cccee51 --- /dev/null +++ b/src/Http/Resources/Json/PaginationResultResourceResponse.php @@ -0,0 +1,23 @@ +resource->resource->toArray(), 'records'); + } +} diff --git a/src/Http/Resources/Json/RespondsWithPaginationResult.php b/src/Http/Resources/Json/RespondsWithPaginationResult.php new file mode 100644 index 0000000..faee413 --- /dev/null +++ b/src/Http/Resources/Json/RespondsWithPaginationResult.php @@ -0,0 +1,33 @@ +resource instanceof AbstractPaginator) { + return (new PaginatedResourceResponse($this))->toResponse($request); + } + if ($this->resource instanceof PaginationResult) { + return (new PaginationResultResourceResponse($this))->toResponse($request); + } + + return parent::toResponse($request); + } +} From 7a71b1f5d75f62f52ceae77583022d3674cd02d2 Mon Sep 17 00:00:00 2001 From: mpyw Date: Sun, 3 Nov 2019 16:09:59 +0900 Subject: [PATCH 3/7] Implement support for AnonymousResourceCollection --- ...aginationResultAwareResourceCollection.php | 18 +++++++++++++ ...aginationResultAwareResourceCollection.php | 26 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 src/Http/Resources/Json/AnonymousPaginationResultAwareResourceCollection.php create mode 100644 src/Http/Resources/Json/MakesAnonymousPaginationResultAwareResourceCollection.php diff --git a/src/Http/Resources/Json/AnonymousPaginationResultAwareResourceCollection.php b/src/Http/Resources/Json/AnonymousPaginationResultAwareResourceCollection.php new file mode 100644 index 0000000..2981061 --- /dev/null +++ b/src/Http/Resources/Json/AnonymousPaginationResultAwareResourceCollection.php @@ -0,0 +1,18 @@ +preserveKeys = (new static([]))->preserveKeys === true; + } + }); + } +} From 7bab7991bc457068a34defdca2cbd23120f54c80 Mon Sep 17 00:00:00 2001 From: mpyw Date: Sun, 3 Nov 2019 16:10:18 +0900 Subject: [PATCH 4/7] Add helper traits --- src/LampagerResourceCollectionTrait.php | 17 +++++++++++++++++ src/LampagerResourceTrait.php | 13 +++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 src/LampagerResourceCollectionTrait.php create mode 100644 src/LampagerResourceTrait.php diff --git a/src/LampagerResourceCollectionTrait.php b/src/LampagerResourceCollectionTrait.php new file mode 100644 index 0000000..1e7da3f --- /dev/null +++ b/src/LampagerResourceCollectionTrait.php @@ -0,0 +1,17 @@ + Date: Sun, 3 Nov 2019 16:10:31 +0900 Subject: [PATCH 5/7] Add tests --- tests/PostResource.php | 27 +++ tests/PostResourceCollection.php | 14 ++ tests/ResourceTest.php | 261 +++++++++++++++++++++ tests/StructuredPostResourceCollection.php | 28 +++ tests/TagResource.php | 25 ++ 5 files changed, 355 insertions(+) create mode 100644 tests/PostResource.php create mode 100644 tests/PostResourceCollection.php create mode 100644 tests/ResourceTest.php create mode 100644 tests/StructuredPostResourceCollection.php create mode 100644 tests/TagResource.php diff --git a/tests/PostResource.php b/tests/PostResource.php new file mode 100644 index 0000000..d54d881 --- /dev/null +++ b/tests/PostResource.php @@ -0,0 +1,27 @@ + true, + ]; + } +} diff --git a/tests/PostResourceCollection.php b/tests/PostResourceCollection.php new file mode 100644 index 0000000..dc49ff1 --- /dev/null +++ b/tests/PostResourceCollection.php @@ -0,0 +1,14 @@ +assertSame( + json_decode(json_encode($expected), true), + json_decode(json_encode($actual), true) + ); + } + + /** + * @return \Lampager\Laravel\PaginationResult + */ + protected function getLampagerPagination() + { + return Post::lampager() + ->forward()->limit(3) + ->orderBy('updated_at') + ->orderBy('id') + ->seekable() + ->paginate(['id' => 3, 'updated_at' => '2017-01-01 10:00:00']); + } + + /** + * @return \Illuminate\Contracts\Pagination\Paginator + */ + protected function getStandardPagination() + { + return Post::query() + ->where('id', '>', 1) + ->orderBy('updated_at') + ->orderBy('id') + ->simplePaginate(3); + } + + /** + * @test + */ + public function testRawArrayOutput() + { + $expected = [ + [ + 'id' => 3, + 'updated_at' => '2017-01-01 10:00:00', + 'post_resource' => true, + ], + [ + 'id' => 5, + 'updated_at' => '2017-01-01 10:00:00', + 'post_resource' => true, + ], + [ + 'id' => 2, + 'updated_at' => '2017-01-01 11:00:00', + 'post_resource' => true, + ], + ]; + + $pagination = $this->getLampagerPagination(); + $records = $pagination->records; + $standardPagination = $this->getStandardPagination(); + + $this->assertResultSame($expected, (new PostResourceCollection($pagination))->resolve()); + $this->assertResultSame($expected, (new PostResourceCollection($records))->resolve()); + $this->assertResultSame($expected, (new PostResourceCollection($standardPagination))->resolve()); + } + + /** + * @test + */ + public function testStructuredArrayOutput() + { + $expected = [ + 'data' => [ + [ + 'id' => 3, + 'updated_at' => '2017-01-01 10:00:00', + 'post_resource' => true, + ], + [ + 'id' => 5, + 'updated_at' => '2017-01-01 10:00:00', + 'post_resource' => true, + ], + [ + 'id' => 2, + 'updated_at' => '2017-01-01 11:00:00', + 'post_resource' => true, + ], + ], + 'post_resource_collection' => true, + ]; + + $pagination = $this->getLampagerPagination(); + $records = $pagination->records; + $standardPagination = $this->getStandardPagination(); + + $this->assertResultSame($expected, (new StructuredPostResourceCollection($pagination))->resolve()); + $this->assertResultSame($expected, (new StructuredPostResourceCollection($records))->resolve()); + $this->assertResultSame($expected, (new StructuredPostResourceCollection($standardPagination))->resolve()); + + $this->assertResultSame($expected, (new StructuredPostResourceCollection($records)) + ->toResponse(null)->getData() + ); + $this->assertResultSame($expected, (new PostResourceCollection($records)) + ->additional(['post_resource_collection' => true]) + ->toResponse(null)->getData() + ); + } + + /** + * @test + */ + public function testLampagerPaginationOutput() + { + $expected1 = [ + 'data' => [ + [ + 'id' => 3, + 'updated_at' => '2017-01-01 10:00:00', + 'post_resource' => true, + ], + [ + 'id' => 5, + 'updated_at' => '2017-01-01 10:00:00', + 'post_resource' => true, + ], + [ + 'id' => 2, + 'updated_at' => '2017-01-01 11:00:00', + 'post_resource' => true, + ], + ], + 'post_resource_collection' => true, + 'has_previous' => true, + 'previous_cursor' => ['updated_at' => '2017-01-01 10:00:00', 'id' => 1], + 'has_next' => true, + 'next_cursor' => ['updated_at' => '2017-01-01 11:00:00', 'id' => 4], + ]; + // different order + $expected2 = Arr::except($expected1, 'post_resource_collection') + ['post_resource_collection' => true]; + + $pagination = $this->getLampagerPagination(); + + $this->assertResultSame($expected1, (new StructuredPostResourceCollection($pagination)) + ->toResponse(null)->getData() + ); + $this->assertResultSame($expected2, (new PostResourceCollection($pagination)) + ->additional(['post_resource_collection' => true]) + ->toResponse(null)->getData() + ); + } + + /** + * @test + */ + public function testStandardPaginationOutput() + { + $expected1 = [ + 'data' => [ + [ + 'id' => 3, + 'updated_at' => '2017-01-01 10:00:00', + 'post_resource' => true, + ], + [ + 'id' => 5, + 'updated_at' => '2017-01-01 10:00:00', + 'post_resource' => true, + ], + [ + 'id' => 2, + 'updated_at' => '2017-01-01 11:00:00', + 'post_resource' => true, + ], + ], + 'post_resource_collection' => true, + 'links' => [ + 'first' => 'http://localhost?page=1', + 'last' => null, + 'prev' => null, + 'next' => 'http://localhost?page=2', + ], + 'meta' => [ + 'current_page' => 1, + 'from' => 1, + 'path' => 'http://localhost', + 'per_page' => 3, + 'to' => 3, + ], + ]; + // different order + $expected2 = Arr::except($expected1, 'post_resource_collection') + ['post_resource_collection' => true]; + + $pagination = $this->getStandardPagination(); + + $this->assertResultSame($expected1, (new StructuredPostResourceCollection($pagination)) + ->toResponse(null)->getData() + ); + $this->assertResultSame($expected2, (new PostResourceCollection($pagination)) + ->additional(['post_resource_collection' => true]) + ->toResponse(null)->getData() + ); + } + + /** + * @test + */ + public function testMissingValue() + { + $expected = ['id' => 1]; + $actual = (new TagResource(Tag::find(1)))->resolve(); + + $this->assertResultSame($expected, $actual); + } + + /** + * @test + */ + public function testAnonymousResourceCollection() + { + $collection = PostResource::collection($this->getLampagerPagination()); + $this->assertInstanceOf(AnonymousResourceCollection::class, $collection); + + $expected = [ + 'data' => [ + [ + 'id' => 3, + 'updated_at' => '2017-01-01 10:00:00', + 'post_resource' => true, + ], + [ + 'id' => 5, + 'updated_at' => '2017-01-01 10:00:00', + 'post_resource' => true, + ], + [ + 'id' => 2, + 'updated_at' => '2017-01-01 11:00:00', + 'post_resource' => true, + ], + ], + 'has_previous' => true, + 'previous_cursor' => ['updated_at' => '2017-01-01 10:00:00', 'id' => 1], + 'has_next' => true, + 'next_cursor' => ['updated_at' => '2017-01-01 11:00:00', 'id' => 4], + ]; + $this->assertResultSame($expected, $collection->toResponse(null)->getData()); + } +} diff --git a/tests/StructuredPostResourceCollection.php b/tests/StructuredPostResourceCollection.php new file mode 100644 index 0000000..4e9079d --- /dev/null +++ b/tests/StructuredPostResourceCollection.php @@ -0,0 +1,28 @@ + parent::toArray($request), + 'post_resource_collection' => true, + ]; + } +} diff --git a/tests/TagResource.php b/tests/TagResource.php new file mode 100644 index 0000000..e1fdf2a --- /dev/null +++ b/tests/TagResource.php @@ -0,0 +1,25 @@ + new PostResourceCollection($this->whenLoaded('posts')), + ]); + } +} From 449a9acb35179e530cd4a6f88519ff93c382825b Mon Sep 17 00:00:00 2001 From: mpyw Date: Sun, 3 Nov 2019 16:12:27 +0900 Subject: [PATCH 6/7] Update README --- README.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/README.md b/README.md index 355b899..a3df90f 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,52 @@ And you'll get } ``` +## Resource Collection + +Lampager supports Laravel's API Resources. + +- [Eloquent: API Resources - Laravel - The PHP Framework For Web Artisans](https://laravel.com/docs/6.x/eloquent-resources) + +Use helper traits on Resource and ResourceCollection. + +```php +use Illuminate\Http\Resources\Json\JsonResource; +use Lampager\Laravel\LampagerResourceTrait; + +class PostResource extends JsonResource +{ + use LampagerResourceTrait; +} +``` + +```php +use Illuminate\Http\Resources\Json\ResourceCollection; +use Lampager\Laravel\LampagerResourceCollectionTrait; + +class PostResourceCollection extends ResourceCollection +{ + use LampagerResourceCollectionTrait; +} +``` + +```php +$posts = App\Post::lampager() + ->orderByDesc('id') + ->paginate(); + +return new PostResourceCollection($posts); +``` + +```json5 +{ + "data": [/* ... */], + "has_previous": false, + "previous_cursor": null, + "has_next": true, + "next_cursor": {/* ... */} +} +``` + ## Classes Note: See also [lampager/lampager](https://github.com/lampager/lampager). @@ -155,6 +201,8 @@ Note: See also [lampager/lampager](https://github.com/lampager/lampager). | Lampager\\Laravel\\`Processor` | Class | Lampager\\`AbstractProcessor` | Processor implementation for Laravel | | Lampager\\Laravel\\`PaginationResult` | Class | Lampager\\`PaginationResult` | PaginationResult implementation for Laravel | | Lampager\\Laravel\\`MacroServiceProvider` | Class | Illuminate\\Support\\`ServiceProvider` | Enable macros chainable from QueryBuilder, ElqouentBuilder and Relation | +| Lampager\\Laravel\\`LampagerResourceTrait` | Trait | | Support for Laravel JsonResource | +| Lampager\\Laravel\\`LampagerResourceCollectionTrait` | Trait | | Support for Laravel ResourceCollection | `Paginator`, `Processor` and `PaginationResult` are macroable. From 9484a4d0a4bbdcf0d44289ac24d183bf3d0dbbc5 Mon Sep 17 00:00:00 2001 From: mpyw Date: Sun, 3 Nov 2019 17:18:04 +0900 Subject: [PATCH 7/7] Correct English --- .../MakesAnonymousPaginationResultAwareResourceCollection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Resources/Json/MakesAnonymousPaginationResultAwareResourceCollection.php b/src/Http/Resources/Json/MakesAnonymousPaginationResultAwareResourceCollection.php index 28dea31..508b5da 100644 --- a/src/Http/Resources/Json/MakesAnonymousPaginationResultAwareResourceCollection.php +++ b/src/Http/Resources/Json/MakesAnonymousPaginationResultAwareResourceCollection.php @@ -10,7 +10,7 @@ trait MakesAnonymousPaginationResultAwareResourceCollection { /** - * Create new anonymous resource collection. + * Create a new anonymous resource collection. * * @param mixed $resource * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection