Skip to content

Commit

Permalink
Support custom pagination
Browse files Browse the repository at this point in the history
  • Loading branch information
cerbero90 committed Jan 30, 2024
1 parent 51b81c0 commit 209c060
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 16 deletions.
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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]
Expand Down
10 changes: 10 additions & 0 deletions src/Dtos/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,19 @@

namespace Cerbero\LazyJsonPages\Dtos;

use Cerbero\LazyJsonPages\Paginations\Pagination;
use Closure;

/**
* The configuration
*
* @property-read class-string<Pagination> $pagination
*/
final class Config
{
/**
* Instantiate the class.
*/
public function __construct(
public readonly string $pointer,
public readonly string $pageName = 'page',
Expand All @@ -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,
Expand Down
23 changes: 23 additions & 0 deletions src/Exceptions/InvalidPaginationException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Cerbero\LazyJsonPages\Exceptions;

use Cerbero\LazyJsonPages\Paginations\Pagination;

/**
* The exception to throw when the given pagination is invalid.
*/
class InvalidPaginationException extends LazyJsonPagesException
{
/**
* Instantiate the class.
*/
public function __construct(public readonly string $class)
{
$pagination = Pagination::class;

parent::__construct("The class [{$class}] should extend [{$pagination}].");
}
}
10 changes: 10 additions & 0 deletions src/LazyJsonPages.php
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,16 @@ public function offset(string $key = 'offset'): self
return $this;
}

/**
* Set the custom pagination.
*/
public function pagination(string $class): self
{
$this->config['pagination'] = $class;

return $this;
}

/**
* Fetch pages synchronously.
*/
Expand Down
12 changes: 1 addition & 11 deletions src/Paginations/AnyPagination.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
36 changes: 36 additions & 0 deletions src/Paginations/CustomPagination.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Cerbero\LazyJsonPages\Paginations;

use Cerbero\LazyJsonPages\Exceptions\InvalidPaginationException;
use Traversable;

/**
* The user-defined pagination.
*/
class CustomPagination extends LengthAwarePagination
{
/**
* Determine whether the configuration matches this pagination.
*/
public function matches(): bool
{
return $this->config->pagination !== null;
}

/**
* Yield the paginated items.
*
* @return Traversable<int, mixed>
*/
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);
}
}
16 changes: 11 additions & 5 deletions src/Paginations/Pagination.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,28 @@ 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.
*
* @return Traversable<int, mixed>
*/
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;
}
}
25 changes: 25 additions & 0 deletions tests/Feature/PaginationTest.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<?php

use Cerbero\LazyJsonPages\Exceptions\InvalidPaginationException;
use Cerbero\LazyJsonPages\LazyJsonPages;
use Cerbero\LazyJsonPages\Paginations\TotalPagesAwarePagination;

it('supports paginations aware of their total pages', function () {
$lazyCollection = LazyJsonPages::from('https://example.com/api/v1/users')
Expand All @@ -25,3 +27,26 @@
'https://example.com/api/v1/users?page=3' => '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].');

0 comments on commit 209c060

Please sign in to comment.