monkeyscloud/monkeyslegion-pagination

ORM-agnostic offset + cursor pagination — PHP 8.4 property hooks, RFC 8288 Link headers, JSON:API links, configurable envelopes

Maintainers

Package info

github.com/MonkeysCloud/MonkeysLegion-Pagination

pkg:composer/monkeyscloud/monkeyslegion-pagination

Statistics

Installs: 0

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 0

1.0.0 2026-05-27 02:13 UTC

This package is auto-updated.

Last update: 2026-05-27 02:15:24 UTC


README

ORM-agnostic pagination for PHP 8.4 — offset, cursor, and simple paginators with RFC 8288 Link headers, JSON:API links, and property hooks.

PHP 8.4+ License: MIT

Why Another Pagination Package?

Feature Laravel Symfony ML Pagination
Offset pagination LengthAwarePaginator PaginatorInterface OffsetPaginator
Cursor pagination CursorPaginator CursorPaginator
Simple (no count) Paginator SimplePaginator
PHP 8.4 hooks $p->hasMore, $p->lastPage
RFC 8288 Link headers ❌ manual toLinkHeader()
JSON:API links toJsonApiLinks()
ORM-agnostic ❌ Eloquent-bound ❌ Doctrine-bound ✅ any iterable
Zero dependencies ✅ pure PHP 8.4
Configurable envelope ❌ fixed format PaginationResult

Installation

composer require monkeyscloud/monkeyslegion-pagination

Quick Start

Offset Pagination (with total count)

use MonkeysLegion\Pagination\OffsetPaginator;

$paginator = new OffsetPaginator(
    items: $repository->findPage(page: 3, perPage: 25),
    total: $repository->count(),
    page: 3,
    perPage: 25,
);

// PHP 8.4 property hooks
$paginator->lastPage;       // 4
$paginator->hasMorePages;   // true
$paginator->from;           // 51
$paginator->to;             // 75

// Metadata
$paginator->toMeta();       // ['current_page' => 3, 'last_page' => 4, ...]

// RFC 8288 Link header
$paginator->toLinkHeader('/api/users');
// <.../api/users?page=1&per_page=25>; rel="first", <...>; rel="next", ...

// JSON:API links
$paginator->toJsonApiLinks('/api/users');
// ['self' => '...page[number]=3', 'first' => ..., 'prev' => ..., 'next' => ...]

Cursor Pagination (keyset, no total)

use MonkeysLegion\Pagination\CursorPaginator;

$paginator = new CursorPaginator(
    items: $users,
    perPage: 25,
    cursor: $request->getQueryParams()['cursor'] ?? null,
    nextCursor: $users[count($users) - 1]->id ?? null,
    previousCursor: $firstId,
);

$paginator->hasMorePages;  // true
$paginator->isFirstPage;   // false
$paginator->total();       // null (unknown by design)

Simple Pagination (no count query)

use MonkeysLegion\Pagination\SimplePaginator;

// Fetch perPage + 1 items → auto-detects hasMore
$items = $repository->findAll(limit: 26, offset: 50);

$paginator = new SimplePaginator(
    items: $items,    // Pass 26 items → strips to 25, hasMore=true
    page: 3,
    perPage: 25,
);

$paginator->hasMorePages; // true (auto-detected)
$paginator->count();      // 25
$paginator->total();      // null

PaginationResult (envelope + transformer)

use MonkeysLegion\Pagination\PaginationResult;

$result = new PaginationResult($paginator);

// With transformer callback
$json = $result->toJson(fn(User $u) => [
    'id'    => $u->id,
    'email' => $u->email,
]);

// Custom wrapper
$result->withWrap('items')->toArray();
// { "items": [...], "meta": { ... } }

// No wrapper
$result->withWrap(null)->toArray();
// [...]

Response Integration

// In a MonkeysLegion controller
return new JsonResponse([
    ...(new PaginationResult($paginator))->toArray(
        fn(User $u) => UserResource::make($u)->toArray(),
    ),
], headers: [
    'Link' => $paginator->toLinkHeader($request->getUri()->getPath()),
]);

Paginators Comparison

OffsetPaginator CursorPaginator SimplePaginator
Needs total count ✅ Yes ❌ No ❌ No
Needs cursor ❌ No ✅ Yes ❌ No
O(1) seek ❌ (OFFSET) ✅ (WHERE id >) ❌ (OFFSET)
Concurrent-safe ⚠️ Drift ✅ Stable ⚠️ Drift
lastPage
JSON:API links
Best for Admin panels APIs, feeds Quick lists

License

MIT © MonkeysCloud Team