marko/pagination
Offset and cursor pagination with API-ready serialization --- paginate any result set without coupling to your data layer. Pagination provides two strategies: offset-based (traditional page numbers) and cursor-based (for large or real-time datasets). Both produce structured arrays ready for JSON API responses. A config helper enforces per-page limits so clients cannot request unbounded result sets.
Installation
Section titled “Installation”composer require marko/paginationOffset Pagination
Section titled “Offset Pagination”The most common approach --- pass items, total count, per-page, and current page:
use Marko\Pagination\OffsetPaginator;
$paginator = new OffsetPaginator( items: $items, total: 150, perPage: 15, currentPage: 3,);
$paginator->items(); // Items for page 3$paginator->hasMorePages(); // true$paginator->lastPage(); // 10$paginator->nextPage(); // 4$paginator->previousPage(); // 2Cursor Pagination
Section titled “Cursor Pagination”For large datasets or infinite scroll, use cursor-based pagination:
use Marko\Pagination\Cursor;use Marko\Pagination\CursorPaginator;
$cursor = new Cursor(['id' => 42]);
$paginator = new CursorPaginator( items: $items, perPage: 20, cursor: $cursor, nextCursor: new Cursor(['id' => 62]),);
$paginator->hasMorePages(); // true$paginator->nextCursor()->encode(); // Base64-encoded cursor stringDecoding Cursors from Requests
Section titled “Decoding Cursors from Requests”Parse a cursor string from a query parameter:
use Marko\Pagination\Cursor;
$cursor = Cursor::decode($request->getQueryParams()['cursor']);$lastId = $cursor->parameter('id');API Response Serialization
Section titled “API Response Serialization”Both paginators serialize to a structured array:
$response = $paginator->toArray();// ['items' => [...], 'meta' => [...], 'links' => [...]]Offset paginators include total, per_page, current_page, and last_page in meta, with previous and next page numbers in links. Cursor paginators include per_page and has_more in meta, with base64-encoded previous and next cursor strings in links.
Clamping Per-Page Values
Section titled “Clamping Per-Page Values”Use PaginationConfig to enforce server-side limits on client-requested page sizes:
use Marko\Pagination\Config\PaginationConfig;
public function __construct( private PaginationConfig $paginationConfig,) {}
public function list( int $requestedPerPage,): array { $perPage = $this->paginationConfig->clampPerPage($requestedPerPage); // $perPage is between 1 and max_per_page}PaginationConfig reads from the config repository using pagination.per_page and pagination.max_per_page keys.
Customization
Section titled “Customization”Replace OffsetPaginator or CursorPaginator via Preferences to add custom serialization or metadata:
use Marko\Core\Attributes\Preference;use Marko\Pagination\OffsetPaginator;
#[Preference(replaces: OffsetPaginator::class)]class MyPaginator extends OffsetPaginator{ public function toArray(): array { $data = parent::toArray(); $data['meta']['has_previous'] = $this->previousPage() !== null;
return $data; }}API Reference
Section titled “API Reference”PaginatorInterface (Offset)
Section titled “PaginatorInterface (Offset)”public function items(): array;public function total(): int;public function perPage(): int;public function currentPage(): int;public function lastPage(): int;public function hasMorePages(): bool;public function previousPage(): ?int;public function nextPage(): ?int;public function toArray(): array;CursorPaginatorInterface
Section titled “CursorPaginatorInterface”public function items(): array;public function perPage(): int;public function hasMorePages(): bool;public function cursor(): ?CursorInterface;public function nextCursor(): ?CursorInterface;public function previousCursor(): ?CursorInterface;public function toArray(): array;CursorInterface
Section titled “CursorInterface”public function parameters(): array;public function parameter(string $name): mixed;public function encode(): string;public static function decode(string $encoded): static;PaginationConfig
Section titled “PaginationConfig”public function perPage(): int;public function maxPerPage(): int;public function clampPerPage(int $requested): int;PaginationException
Section titled “PaginationException”Thrown when invalid values are provided. Extends MarkoException with context and suggestions.
| Factory Method | Trigger |
|---|---|
invalidPage(int $page) | Page number less than 1 |
invalidPerPage(int $perPage) | Per-page value less than 1 |
invalidCursor(string $encoded) | Cursor string that cannot be base64/JSON decoded |