eonx-com/easy-pagination
Easy Pagination is a lightweight Laravel/PHP package that simplifies paginating query results and collections. It offers a clean, consistent API for building paginators, controlling page sizes, and integrating with Eloquent and the query builder, making reliable pagination easy to add to APIs and vi...
This package is a pagination input library, not a paginator. It parses what a client asked for (page, per_page, sorting) into a transport-agnostic value object you hand to your own query layer. Laravel's paginate() fuses request-reading, query execution, and response shaping into one call; this package solves only the first third, but solves it consistently across HTTP, CLI, and queue contexts.
Before you write production code, confirm the actual API surface. With 2 stars, 0 dependents, and docs-only maturity, the README is a starting point, not a spec. Read the source and the tests.
composer require eonx-com/easy-pagination
Then, in order:
README.md — intended usage and any constructor/factory signatures.src/ — find the concrete pagination class(es) and any interfaces. Interfaces signal the intended extension points.tests/ — the most reliable documentation of edge-case behaviour (zero page? negative per-page? missing params? string vs int?).composer.json — check the PHP constraint and whether it pulls in illuminate/*. A genuinely generic package should have no Laravel dependency, which is what makes it reusable outside HTTP requests.First use case — parse the request into a bounded object, then feed your query builder:
use Eonx\EasyPagination\Pagination; // confirm the exact FQCN in src/
public function index(Request $request)
{
$pagination = Pagination::fromArray($request->query()); // verify factory name
return UserResource::collection(
User::query()
->orderBy($pagination->getSort() ?? 'id')
->forPage($pagination->getPage(), $pagination->getPerPage())
->get()
);
}
If the API turns out to be one or two value objects (likely, given the name), budget an hour to read the source and decide whether to adopt or copy. At this maturity, a 40-line DTO of your own is a legitimate competitor — decide with eyes open.
Controller → service → repository. Keep parsing at the edge and pass the value object inward. It becomes the seam that lets you test pagination logic without an HTTP request:
final class ListOrders
{
public function __construct(private OrderRepository $orders) {}
public function __invoke(PaginationInterface $pagination): Collection
{
return $this->orders->paginate($pagination);
}
}
Normalise once, at construction. Don't scatter ?? 1 and max(1, min(100, ...)) across controllers. Build the object with defaults and clamping in one place — a base controller method, a form request, or a small factory:
protected function paginationFrom(Request $request): Pagination
{
$pagination = Pagination::fromArray($request->query());
// Defensive clamp regardless of what the package does with per_page
if ($pagination->getPerPage() > 100) {
$pagination = $pagination->withPerPage(100); // verify a wither exists
}
return $pagination;
}
Bridge to Laravel's output layer. You still want LengthAwarePaginator for a consistent JSON envelope (data, meta, links). Build it manually from the value object so you own the query and the shape:
$paginator = new LengthAwarePaginator(
$items,
$total,
$pagination->getPerPage(),
$pagination->getPage(),
['path' => $request->url(), 'query' => $request->query()]
);
return OrderResource::collection($paginator);
Wrap it behind an internal interface. This is the load-bearing pattern for a low-adoption package. Define PaginationParamsFactory (or similar) in your app, have one small class delegate to the vendor, and let controllers/services depend on your interface:
interface PaginationParamsFactory
{
public function fromRequest(Request $request): PaginationParams;
}
Call sites never see Eonx\EasyPagination\*. If the package is orphaned — or you simply outgrow it — you replace one class instead of grepping the codebase.
Reuse across contexts. Because the object is transport-agnostic, a console command or queued job can construct it from an array (['page' => 2, 'per_page' => 50]) instead of an HTTP request, and share the same query path. This is the clearest ergonomic win over raw $request->get('page').
Pilot one endpoint. Migrate a single low-traffic list endpoint first. If it works without friction, follow it endpoint-by-endpoint as you touch them — do not big-bang rewrite working pagination.
fromArray, fromRequest?), withers (withPerPage, withPage?), and sort handling are all guesses until you read src/. Cheap to check, expensive to assume.per_page, a caller can request 100,000 rows and your database takes the hit. Enforce the cap in your adapter regardless.page=0, page=-1, and per_page=abc produce in tests/. If they pass through unchanged, forPage(-1, ...) will misbehave silently.composer.json for framework coupling. If it depends on illuminate/http, this is a Laravel-only utility and you should not pretend otherwise. If it's PSR-only, you get the CLI/queue reuse promised above. Either is fine — just know which you bought."1.2.3" not "^1.2"), commit the lockfile, and record the commit hash. It makes the eventual audit or replacement trivial.easy-* creep. One install can drag in sibling packages, multiplying the unmaintained surface. Run composer why after install and confirm the dependency graph is what you expect.Eonx\EasyPagination\Pagination directly in controllers and repositories. That works today and is a one-line change tomorrow — every time. The internal interface costs ~15 lines once.PaginationInterface with getPage()/getPerPage()/getSort(), your repository can accept that interface and stay decoupled from the concrete class. If there is no interface, define your own and adapt — same wrapper, slightly more code.['page' => ..., 'per_page' => ..., 'sort' => ...]). Nine times out of ten it's a query-string name mismatch (per_page vs perPage vs limit) or a framework middleware stripping query params — not a package bug.How can I help you explore Laravel packages today?