diff --git a/README.md b/README.md index ba83c47..56f63aa 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ composer require cerbero/lazy-json-pages * [🏛️ Pagination structure](#%EF%B8%8F-pagination-structure) * [📏 Length-aware paginations](#-length-aware-paginations) * [↪️ Cursor and next-page paginations](#%EF%B8%8F-cursor-and-next-page-paginations) +* [👽 Custom pagination](#-custom-pagination) * [🚀 Requests optimization](#-requests-optimization) * [💢 Errors handling](#-errors-handling) @@ -177,6 +178,40 @@ LazyJsonPages::from($source) > The documentation of this feature is a work in progress. +### 👽 Custom pagination + +Lazy JSON Pages provides several methods to extract items from the most popular pagination mechanisms. However if we need a custom solution, we can implement our own pagination. + +To implement a custom pagination, we need to extend `Pagination` and implement 1 method: + +```php +use Cerbero\LazyJsonPages\Paginations\Pagination; +use Traversable; + +class CustomPagination extends Pagination +{ + public function getIterator(): Traversable + { + // return a Traversable holding the paginated items + } +} +``` + +The parent class `Pagination` gives us access to 2 properties: +- `$source`: the mean pointing to the paginated JSON API (see [sources](#-sources)) +- `$config`: the configuration that we generated by chaining methods like `totalPages()` + +The method `getIterator()` defines the logic to extract paginated items in a memory-efficient way. Please refer to the [already existing paginations](https://github.com/cerbero90/json-parser/tree/master/src/Paginations) to see some implementations. + +Once the custom pagination is implemented, we can instruct Lazy JSON Pages to use it: + +```php +LazyJsonPages::from($source)->pagination(CustomPagination::class); +``` + +If you find yourself implementing the same custom pagination in different projects, feel free to send a PR and we will consider to support your custom pagination by default. Thank you in advance for any contribution! + + ### 🚀 Requests optimization > [!WARNING] diff --git a/src/Dtos/Config.php b/src/Dtos/Config.php index 9aab571..29244b8 100644 --- a/src/Dtos/Config.php +++ b/src/Dtos/Config.php @@ -4,10 +4,19 @@ namespace Cerbero\LazyJsonPages\Dtos; +use Cerbero\LazyJsonPages\Paginations\Pagination; use Closure; +/** + * The configuration + * + * @property-read class-string $pagination + */ final class Config { + /** + * Instantiate the class. + */ public function __construct( public readonly string $pointer, public readonly string $pageName = 'page', @@ -22,6 +31,7 @@ public function __construct( public readonly ?string $nextPageKey = null, public readonly ?int $lastPage = null, public readonly ?string $offsetKey = null, + public readonly ?string $pagination = null, public readonly int $async = 3, public readonly int $attempts = 3, public readonly ?Closure $backoff = null, diff --git a/src/Exceptions/InvalidPaginationException.php b/src/Exceptions/InvalidPaginationException.php new file mode 100644 index 0000000..97f4012 --- /dev/null +++ b/src/Exceptions/InvalidPaginationException.php @@ -0,0 +1,23 @@ +config['pagination'] = $class; + + return $this; + } + /** * Fetch pages synchronously. */ diff --git a/src/Paginations/AnyPagination.php b/src/Paginations/AnyPagination.php index 2c01700..dd56b4e 100644 --- a/src/Paginations/AnyPagination.php +++ b/src/Paginations/AnyPagination.php @@ -19,23 +19,13 @@ class AnyPagination extends Pagination */ protected array $supportedPaginations = [ // CursorPagination::class, - // CustomPagination::class, + CustomPagination::class, // LastPageAwarePagination::class, - // LimitPagination::class, // LinkHeaderPagination::class, - // OffsetPagination::class, TotalItemsAwarePagination::class, TotalPagesAwarePagination::class, ]; - /** - * Determine whether this pagination matches the configuration. - */ - public function matches(): bool - { - return true; - } - /** * Yield the paginated items. * diff --git a/src/Paginations/CustomPagination.php b/src/Paginations/CustomPagination.php new file mode 100644 index 0000000..18339f2 --- /dev/null +++ b/src/Paginations/CustomPagination.php @@ -0,0 +1,36 @@ +config->pagination !== null; + } + + /** + * Yield the paginated items. + * + * @return Traversable + */ + public function getIterator(): Traversable + { + if (!is_subclass_of($this->config->pagination, Pagination::class)) { + throw new InvalidPaginationException($this->config->pagination); + } + + yield from new $this->config->pagination($this->source, $this->config); + } +} diff --git a/src/Paginations/Pagination.php b/src/Paginations/Pagination.php index 9de6856..c9cfd80 100644 --- a/src/Paginations/Pagination.php +++ b/src/Paginations/Pagination.php @@ -32,11 +32,6 @@ abstract class Pagination implements IteratorAggregate */ protected readonly int $itemsPerPage; - /** - * Determine whether the configuration matches this pagination. - */ - abstract public function matches(): bool; - /** * Yield the paginated items. * @@ -44,10 +39,21 @@ abstract public function matches(): bool; */ abstract public function getIterator(): Traversable; + /** + * Instantiate the class. + */ final public function __construct( protected readonly Source $source, protected readonly Config $config, ) { $this->book = new Book(); } + + /** + * Determine whether the configuration matches this pagination. + */ + public function matches(): bool + { + return true; + } } diff --git a/tests/Feature/PaginationTest.php b/tests/Feature/PaginationTest.php index 7013a45..1ee9e58 100644 --- a/tests/Feature/PaginationTest.php +++ b/tests/Feature/PaginationTest.php @@ -1,6 +1,8 @@ 'lengthAware/page3.json', ]); }); + +it('supports custom paginations', function () { + $lazyCollection = LazyJsonPages::from('https://example.com/api/v1/users') + ->pagination(TotalPagesAwarePagination::class) + ->totalPages('meta.total_pages') + ->collect('data.*'); + + expect($lazyCollection)->toLoadItemsViaRequests([ + '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('fails if an invalid custom pagination is provided', function () { + $lazyCollection = LazyJsonPages::from('https://example.com/api/v1/users') + ->pagination('Invalid') + ->collect('data.*'); + + expect($lazyCollection)->toLoadItemsViaRequests([ + 'https://example.com/api/v1/users' => 'lengthAware/page1.json', + ]); +})->throws(InvalidPaginationException::class, 'The class [Invalid] should extend [Cerbero\LazyJsonPages\Paginations\Pagination].');