mouf/utils.common.paginable-interface
A tiny PHP interface from Mouf's common utilities for pagination. PaginableInterface lets classes expose paging info—such as offset and limit—so grids, lists, and query builders can handle page navigation uniformly. Lightweight, framework-agnostic, and easy to drop into existing PHP projects.
Install and inspect. There is nothing to configure — this is a contract-only package:
composer require mouf/utils.common.paginable-interface
Open vendor/mouf/utils.common.paginable-interface/src/ (or the corresponding Mouf\Utils\Common namespace folder) immediately. The whole package is one interface with a single paginate($limit, $offset) method (confirm the exact namespace and parameter order from the source before you implement it — see Gotchas). There is no service provider, no config file, no runtime code.
Where to look first. The interface definition itself, and nothing else. Once you've read the method signature, you've read the package.
First use case — a repository that pages its results. Implement the interface on a class that already owns a query:
use Mouf\Utils\Common\PaginableInterface;
class UserRepository implements PaginableInterface
{
public function paginate($limit, $offset): array
{
return $this->db->query(
'SELECT * FROM users ORDER BY id LIMIT ? OFFSET ?',
[(int) $limit, (int) $offset]
)->fetchAll();
}
}
Then type-hint against the interface wherever you consume it, so callers never care whether the data comes from MySQL, an API, or an array. That's the entire value proposition: a shared, framework-agnostic name for "something that can hand me a page."
Repository/DAO implementation (the primary pattern). Implement on whatever class already encapsulates a data source. Let the underlying driver do the LIMIT/OFFSET — never fetch everything and slice in PHP unless the source genuinely has no paging ability.
Adapter pattern for third-party sources. When you can't modify a class (Eloquent builder, Doctrine Query, a vendor SDK), write a thin adapter that implements the interface and delegates:
class EloquentPaginableAdapter implements PaginableInterface
{
public function __construct(private $query) {}
public function paginate($limit, $offset): array
{
return $this->query->offset($offset)->limit($limit)->get()->all();
}
}
This is how you make heterogeneous sources (SQL, Redis, REST) look uniform to a view layer.
In-memory implementation for arrays. For small, already-loaded collections:
public function paginate($limit, $offset): array
{
return array_slice($this->items, (int) $offset, (int) $limit);
}
Controller/action convention. Standardise the request → offset math in one place and keep it out of your paginable classes:
$page = max(1, (int) $request->get('page', 1));
$perPage = min(100, max(1, (int) $request->get('per_page', 20)));
$offset = ($page - 1) * $perPage;
$rows = $userRepository->paginate($perPage, $offset);
Because the interface has no page concept, this conversion belongs in your controller or a small pagination helper you own.
Decorator pattern for cross-cutting concerns. Caching, logging, or access filtering compose cleanly around a PaginableInterface:
class CachedPaginable implements PaginableInterface
{
public function __construct(private PaginableInterface $inner, private Cache $cache) {}
public function paginate($limit, $offset): array
{
$key = sprintf('page:%d:%d', $limit, $offset);
return $this->cache->remember($key, fn () => $this->inner->paginate($limit, $offset));
}
}
Composite for merged sources. Concatenate pages from two paginables, then array_slice the merged result. Fine for small fan-out, wrong for large datasets.
Container wiring. In a Mouf DI container (or any PSR-11 container), bind the interface to your concrete implementation once and type-hint the interface everywhere. This is where the package earns its keep: swapping implementations becomes a one-line config change.
Testing. Write a FakePaginable that returns a fixed array per (limit, offset) pair. Then test controllers and views without touching a database.
No count, no total, no metadata. The interface returns data only — there is no total(), no last-page number, no next/prev flags. Your pagination UI needs a total; expose a separate count() method on your concrete class (or define a CountablePaginableInterface of your own) and type-hint that where you render page links. Don't try to infer the total from a short page — it breaks when the last page happens to be exactly full.
No page parameter. Offset is raw and, by convention, zero-based. All page math (($page - 1) * $perPage), bounds clamping, and validation is your responsibility. Guard against negative offsets and zero/negative limits at the boundary.
Verify the exact signature. Community code around this era is inconsistent about ordering; make sure the interface really is paginate($limit, $offset) and not ($offset, $limit) before you implement it, and mirror the parameter names in your implementation for readability. Also confirm the namespace (Mouf\Utils\Common\...) from the installed source rather than guessing.
It's interface-only. No traits, no abstract base class, no helper functions, no built-in paginator object. If you want ArrayPaginable, CallbackPaginable, or a result-value object, you build them. Plan for a tiny internal utility package of your own.
Return type discipline. Always return an array (or, if you deviate, a documented iterable). Never return null for an empty page — return []. Callers will loop and count() it.
Abandonware risk. Last release was May 2017. It's a one-method interface, so the surface area is stable, but there's no maintenance and no PHP 8-specific tooling. If you hit friction, vendoring the single interface file into your own codebase is a legitimate escape hatch — the "dependency" is about ten lines.
Debugging tips. Log the computed (limit, offset) at the repository boundary; most pagination bugs are off-by-one offset math, not SQL. Test boundaries explicitly: offset 0, offset equal to the dataset size, offset beyond it, and limit larger than the dataset. Assert the returned count is min(limit, remaining).
Keep implementations stateless. paginate() should not mutate internal paging state — same inputs, same page. Any cursor or statefulness belongs in a concrete class outside the interface contract.
Extension point. If you need richer returns (rows + total + cursor), define your own PaginatedResultInterface in your application and let your classes implement both this interface and yours. That keeps interoperability with any third-party code expecting the original contract.
How can I help you explore Laravel packages today?