ruwork/paginator
A pagination package for Laravel and PHP applications that helps split large data sets into manageable pages. Suitable for list cards, search results, and other paginated views, with a simple API for adding page navigation to your project.
The package is framework-agnostic (no Laravel service provider or facade ships with it), so treat it as a plain Composer dependency rather than a Laravel extension.
composer require ruwork/paginator
Before writing any code, open the repository in this order:
README.md — expect it to be terse; the API surface is small.src/ — read the paginator class and any PageInterface / PaginatorInterface contracts first. These are the entire public API.composer.json — check the PHP constraint (^7.4/^8.x) and whether it pulls in any adapter (Doctrine, Pagerfanta, etc.). If there are no adapters, you are paginating in-memory arrays, not query builders.tests/ — with 0 stars and 0 dependents this is your real documentation. Skim the test cases to see the intended call signatures.Your first use case is almost certainly paginating an already-hydrated collection inside a service:
use Ruwork\Paginator\Paginator;
$paginator = new Paginator($items); // array or Traversable
$paginator->paginate($page = 1, $perPage = 20);
$slice = $paginator->getItems(); // current page slice
$total = $paginator->getTotalCount();
Wire it into a controller, serialize getItems() plus the page metadata, and confirm the slicing behavior against your own fixtures before trusting it in a query path.
Service-layer pagination, not query-layer. Because the package is adapter-light, the clean pattern is to let your repository return a full result set (or a lightweight countable wrapper) and hand it to the paginator. Do not try to push LIMIT/OFFSET through the paginator unless you have verified it exposes an adapter for your ORM—if it does not, you will be loading entire tables to slice them, which is a real production trap for anything beyond small datasets.
Wrap it behind your own contract. Given the project's maturity (zero dependents), isolate the dependency:
interface PageResult
{
public function items(): array;
public function total(): int;
public function perPage(): int;
public function currentPage(): int;
public function lastPage(): int;
}
final class RuworkPageAdapter implements PageResult { /* delegates to Paginator */ }
This costs ~40 lines and means a future swap to LengthAwarePaginator or Pagerfanta is a one-class change.
Normalize to Laravel's shape at the boundary. Your views and JSON clients expect Laravel's paginator envelope. Map the ruwork output into it so front-end code does not learn a second contract:
return [
'data' => $adapter->items(),
'current_page' => $adapter->currentPage(),
'per_page' => $adapter->perPage(),
'total' => $adapter->total(),
'last_page' => $adapter->lastPage(),
];
Clamp inputs at the controller. Page numbers from the request are untrusted. Cast to int, floor at 1, and cap at lastPage() before constructing the paginator—do not rely on the package's own bounds handling, which for a 0-star library is unlikely to be defensive.
Testing pattern. Fixture-drive the paginator with arrays of known length (0, 1, exactly perPage, perPage + 1) and assert the slice boundaries and lastPage() math. That single test table catches essentially every edge case worth worrying about in a paginator.
"ruwork/paginator": "^1.0") and add a composer outdated check to CI so a PHP upgrade that breaks it surfaces early.Traversable, which can consume a generator or issue a second query. Inspect getTotalCount()/count() before pointing it at a Collection backed by a DB cursor. If it iterates, pass an explicit total instead.PaginatorServiceProvider, so app(Paginator::class) will fail. Construct it manually or register your own binding in AppServiceProvider::register() if you want it injected.paginate() returns $this or a value object. This varies between implementations and determines whether you can chain. One quick REPL check (var_dump($p->paginate(1, 20))) settles it.?per_page=1000000 with a whitelist ([10, 25, 50, 100]) in your own code—do not assume the package validates it.PageInterface rather than subclassing the concrete paginator; the interface is the contract the rest of the package is written against and is far more stable than the class internals.page, perPage, and computed offset together in one line. Pagination bugs are almost always an off-by-one in offset ((page - 1) * perPage) or a 0-indexed page leaking in from the request.illuminate/pagination (framework-native, zero install cost) and Pagerfanta (widely used, adapters for Doctrine and arrays) belongs in your ADR. Adopt ruwork/paginator only if you specifically want its narrow, framework-free API—otherwise the maintenance trade-off rarely favors it.How can I help you explore Laravel packages today?