From e762f75e69266acf09cd84064d81687bac14fc48 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Tue, 23 Jan 2024 23:55:25 +1000 Subject: [PATCH] Support paginations aware of their total items --- README.md | 8 ---- src/Paginations/AnyPagination.php | 2 +- src/Paginations/TotalItemsAwarePagination.php | 48 +++++++++++++++++++ tests/Feature/PaginationTest.php | 29 +++++++++++ 4 files changed, 78 insertions(+), 9 deletions(-) create mode 100644 src/Paginations/TotalItemsAwarePagination.php create mode 100644 tests/Feature/PaginationTest.php diff --git a/README.md b/README.md index 86a0c76..c6d4176 100644 --- a/README.md +++ b/README.md @@ -151,14 +151,6 @@ LazyJsonPages::from($source)->lastPage('X-Last-Page'); APIs can expose their length information in the form of numbers (`total_pages: 10`) or URIs (`last_page: "https://example.com?page=10"`), Lazy JSON Pages supports both. -To save more memory when setting the total number of items, we can also define the number of items shown in each page: - -```php -LazyJsonPages::from($source) - ->totalItems('pagination.total_items') - ->perPage(20); -``` - When dealing with a lot of data, it may be a good idea to fetch only 1 item on the first page and leverage the length information on that page to calculate the total number of pages/items without having to load all the other items of that page. We can do that by calling `perPage()` with: diff --git a/src/Paginations/AnyPagination.php b/src/Paginations/AnyPagination.php index 13d92f3..2c01700 100644 --- a/src/Paginations/AnyPagination.php +++ b/src/Paginations/AnyPagination.php @@ -24,7 +24,7 @@ class AnyPagination extends Pagination // LimitPagination::class, // LinkHeaderPagination::class, // OffsetPagination::class, - // TotalItemsAwarePagination::class, + TotalItemsAwarePagination::class, TotalPagesAwarePagination::class, ]; diff --git a/src/Paginations/TotalItemsAwarePagination.php b/src/Paginations/TotalItemsAwarePagination.php new file mode 100644 index 0000000..025e7fd --- /dev/null +++ b/src/Paginations/TotalItemsAwarePagination.php @@ -0,0 +1,48 @@ +config->totalItemsKey !== null + && $this->config->totalPagesKey === null + && $this->config->perPage === null; + } + + /** + * Yield the paginated items. + * + * @return Traversable + */ + public function getIterator(): Traversable + { + $perPage = 0; + $generator = $this->yieldItemsAndReturnKey($this->source->response(), $this->config->totalItemsKey); + + foreach ($generator as $item) { + yield $item; + ++$perPage; + } + + if (!is_numeric($totalItems = $generator->getReturn())) { + throw new InvalidKeyException($this->config->totalItemsKey); + } + + $totalPages = $perPage > 0 ? (int) ceil(intval($totalItems) / $perPage) : 0; + + yield from $this->yieldItemsUntilPage($totalPages); + } +} diff --git a/tests/Feature/PaginationTest.php b/tests/Feature/PaginationTest.php new file mode 100644 index 0000000..db8ddc7 --- /dev/null +++ b/tests/Feature/PaginationTest.php @@ -0,0 +1,29 @@ +totalPages('meta.total_pages') + ->collect('data.*'); + + expect($lazyCollection)->toLoadItemsViaRequests($expectedItems, [ + 'https://example.com/api/v1/users' => 'lengthAware/page1.json', + 'https://example.com/api/v1/users?page=2' => 'lengthAware/page2.json', + 'https://example.com/api/v1/users?page=3' => 'lengthAware/page3.json', + ]); +}); + +it('supports paginations aware of their total items', function () { + $expectedItems = require fixture('items.php'); + $lazyCollection = LazyJsonPages::from('https://example.com/api/v1/users') + ->totalItems('meta.total_items') + ->collect('data.*'); + + expect($lazyCollection)->toLoadItemsViaRequests($expectedItems, [ + 'https://example.com/api/v1/users' => 'lengthAware/page1.json', + 'https://example.com/api/v1/users?page=2' => 'lengthAware/page2.json', + 'https://example.com/api/v1/users?page=3' => 'lengthAware/page3.json', + ]); +});