Skip to content

Commit

Permalink
Implement cursor pagination
Browse files Browse the repository at this point in the history
  • Loading branch information
cerbero90 committed Feb 7, 2024
1 parent 77b43d3 commit 0766e3c
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 42 deletions.
51 changes: 10 additions & 41 deletions src/LazyJsonPages.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,22 +95,6 @@ public function totalPages(string $key): self
return $this;
}

/**
* Retrieve an integer from the response.
*/
private function integerFromResponse(Closure|string $key, int $minimum = 0): int
{
return (int) max($minimum, $this->valueFromResponse($key));
}

/**
* Retrieve a value from the response.
*/
private function valueFromResponse(Closure|string $key): mixed
{
return $key instanceof Closure ? $key($this->source->response()) : $this->source->response($key);
}

/**
* Set the total number of items.
*/
Expand All @@ -122,23 +106,11 @@ public function totalItems(string $key): self
}

/**
* Set the number of items per page and optionally override it.
* Set the cursor or next page.
*/
public function perPage(int $items, ?string $key = null, int $firstPageItems = 1): self
public function cursor(string $key): self
{
$this->config['perPage'] = max(1, $key ? $firstPageItems : $items);
$this->config['perPageKey'] = $key;
$this->config['perPageOverride'] = $key ? max(1, $items) : null;

return $this;
}

/**
* Set the next page.
*/
public function nextPage(Closure|string $key): self
{
$this->config['nextPage'] = $this->valueFromResponse($key);
$this->config['cursorKey'] = $key;

return $this;
}
Expand Down Expand Up @@ -236,23 +208,20 @@ public function backoff(Closure $callback): self
* Retrieve a lazy collection yielding the paginated items.
*
* @return LazyCollection<int, mixed>
* @throws UnsupportedPaginationException
*/
public function collect(string $dot = '*'): LazyCollection
{
$this->config['pointer'] = DotsConverter::toPointer($dot);

Client::configure($this->requestOptions);

return new LazyCollection(function () {
$items = new AnyPagination($this->source, new Config(...$this->config));
$config = new Config(...$this->config, itemsPointer: DotsConverter::toPointer($dot));

// yield each item within a loop - instead of using `yield from` - to ignore the actual item index
// and ensure indexes continuity, otherwise the index of items always starts from 0 on every page.
foreach ($items as $item) {
yield $item;
return new LazyCollection(function () use ($config) {
try {
yield from new AnyPagination($this->source, $config);
} finally {
Client::reset();
}

Client::reset();
});
}
}
15 changes: 14 additions & 1 deletion src/Paginations/AnyPagination.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class AnyPagination extends Pagination
* @var class-string<Pagination>[]
*/
protected array $supportedPaginations = [
// CursorPagination::class,
CursorPagination::class,
CustomPagination::class,
LastPageAwarePagination::class,
// LinkHeaderPagination::class,
Expand All @@ -30,8 +30,21 @@ class AnyPagination extends Pagination
* Yield the paginated items.
*
* @return Traversable<int, mixed>
* @throws UnsupportedPaginationException
*/
public function getIterator(): Traversable
{
// yield only items and not their related index to ensure indexes continuity
// otherwise the actual indexes always start from 0 on every page.
foreach ($this->matchingPagination() as $item) {
yield $item;
}
}

/**
* Retrieve the pagination matching with the configuration.
*/
protected function matchingPagination(): Pagination
{
foreach ($this->supportedPaginations as $class) {
$pagination = new $class($this->source, $this->config);
Expand Down
44 changes: 44 additions & 0 deletions src/Paginations/CursorPagination.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace Cerbero\LazyJsonPages\Paginations;

use Cerbero\LazyJsonPages\Services\Client;
use Traversable;

/**
* The pagination aware of the cursor of the next page.
*/
class CursorPagination extends Pagination
{
/**
* Determine whether the configuration matches this pagination.
*/
public function matches(): bool
{
return $this->config->cursorKey !== null
&& $this->config->totalItemsKey === null
&& $this->config->totalPagesKey === null
&& $this->config->lastPageKey === null;
}

/**
* Yield the paginated items.
*
* @return Traversable<int, mixed>
*/
public function getIterator(): Traversable
{
yield from $generator = $this->yieldItemsAndReturnKey($this->source->response(), $this->config->cursorKey);

$request = clone $this->source->request();

while ($cursor = $this->toPage($generator->getReturn(), onlyNumerics: false)) {
$uri = $this->uriForPage($request->getUri(), (string) $cursor);
$response = Client::instance()->send($request->withUri($uri));

yield from $generator = $this->yieldItemsAndReturnKey($response, $this->config->cursorKey);
}
}
}

0 comments on commit 0766e3c

Please sign in to comment.