nadialabs/paginator-bundle
NadiaLabs PaginatorBundle provides pagination support for PHP applications, helping split large result sets into manageable pages. It offers configurable page navigation and template-friendly controls, making long lists easier to browse and navigate in web interfaces.
First: confirm what you're actually holding. Despite the "paginator" name, nadialabs/paginator-bundle is a Symfony bundle, not a Laravel package. There is no service provider, no config publish, no Blade views, and no LengthAwarePaginator compatibility. A Laravel developer's day-to-day use of this package is therefore almost always "read it as a reference, don't composer require it". If you try to install it into a Laravel app you will either get a Composer constraint conflict on symfony/* or a bundle class that Laravel's container never boots.
Verify before you spend time. Open composer.json and check the require block. You are looking for symfony/framework-bundle, symfony/dependency-injection, and a type: "symfony-bundle" entry. If those are present, you are done evaluating for Laravel — the integration cost (bridging Symfony's kernel/config tree into a Laravel service provider) is disproportionate to a concern Laravel already solves.
If you are in a Symfony service (or evaluating to port the logic), the setup is the standard bundle dance:
composer require nadialabs/paginator-bundle
// config/bundles.php
return [
// ...
NadiaLabs\PaginatorBundle\NadiaLabsPaginatorBundle::class => ['all' => true],
];
Confirm the real class name from autoload.psr-4 — with 2 stars, expect the namespace guess to be wrong.
Where to look first, in this order:
README.md — usually the only usage example that exists.src/DependencyInjection/ and src/Resources/config/services.* — gives you the service ID, constructor arguments, and configurable defaults (page size, page query-parameter name).src/ for the paginator class and any Twig/ extension — this is the actual public API surface.composer.json require — PHP and symfony/* constraints. A 2-star bundle is unlikely to track current Symfony LTS.First use case (Symfony): paginate a Doctrine QueryBuilder in a controller and hand the result to Twig.
#[Route('/articles', name: 'article_index')]
public function index(Request $request, PaginatorInterface $paginator): Response
{
$qb = $this->getDoctrine()
->getRepository(Article::class)
->createQueryBuilder('a')
->orderBy('a.createdAt', 'DESC');
$pager = $paginator->paginate($qb, $request->query->getInt('page', 1));
return $this->render('article/index.html.twig', ['pager' => $pager]);
}
Equivalent day-to-day Laravel use case — this is the code you would actually write instead:
$articles = Article::query()
->orderByDesc('created_at')
->paginate(perPage: 20)
->withQueryString();
return view('article.index', compact('articles'));
// or: return ArticleResource::collection($articles);
The rest of this assessment is written for a Laravel developer who has to evaluate or translate this bundle, because that is the realistic daily interaction.
Pattern 1 — Treat it as a reference implementation, not a dependency. Extract ideas, reimplement behind Laravel-native contracts. If you like the bundle's paginate($qb, $page) call shape, wrap Laravel's paginator in a thin service so controllers read the same way:
namespace App\Pagination;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
class Paginator
{
public function paginate(Builder $query, int $page = 1, int $perPage = 20): LengthAwarePaginator
{
return $query->paginate(perPage: $perPage, page: $page);
}
}
This gives you the "single call, read items/count/metadata" ergonomics the bundle advertises, without the Symfony baggage.
Pattern 2 — Keep the pagination metadata envelope as your own contract. The most valuable thing the bundle demonstrates is that pagination metadata should be a first-class, reusable output. Do this with API Resources:
class ArticleCollection extends ResourceCollection
{
public function toArray($request): array
{
return [
'data' => ArticleResource::collection($this->collection),
'meta' => [
'current_page' => $this->currentPage(),
'per_page' => $this->perPage(),
'total' => $this->total(),
'last_page' => $this->lastPage(),
],
'links' => [
'next' => $this->nextPageUrl(),
'prev' => $this->previousPageUrl(),
],
];
}
}
You own this shape; you do not let a third-party bundle impose it.
Pattern 3 — Swap in cursor pagination where offset fails. The bundle is offset-based (page number + page size). For feeds, activity streams, or fast-changing tables, use Laravel's cursor paginator — the bundle has no equivalent:
$articles = Article::orderByDesc('id')->cursorPaginate(20);
Pattern 4 — If you must bridge across to a Symfony service, do not share the bundle. Expose paginated data over an API and let the Laravel side consume it. Sharing a Symfony bundle into a Laravel app is the anti-pattern the TPM assessment calls out; the anti-corruption layer costs more than reimplementation.
Workflow integration tips:
page query parameter; both this bundle and Laravel's native paginator will happily accept a nonsense page number and either 404 or silently return an empty set. Decide your behavior explicitly.ORDER BY including a unique tiebreaker (ORDER BY created_at DESC, id DESC). Without it, offset pagination produces duplicated/skipped rows under concurrent writes — a bug class the bundle does not solve for you.composer require this into Laravel. The -Bundle suffix, Symfony DI assumptions, and Twig coupling mean any "integration" is a fork. Confirm via composer.json before investing further.CHANGELOG, add tests, and assign an owner — otherwise the fork rots silently.OFFSET values and COUNT(*) over big tables are slow regardless of which paginator wraps them. The bundle does not document its count strategy — assume one count query per page. If this is your bottleneck, the fix is cursor pagination and indexes, not a library swap.rel="next"/"prev"), empty states, "load more" vs numbered, or accessibility. Those are your product decisions either way — do not expect the bundle to save you that work.config/pagination.php and a form request, not by mirroring the bundle's mechanism.last_page, and pages with filters applied. Both this bundle and Laravel's need a deliberate decision (clamp vs. 404 vs. empty result) — pick one and cover it with a test.Bottom line for a Laravel developer: do not add this as a dependency. Read its README and service definitions for the pagination pattern (single-call paginate, config-driven page size, metadata-first output), then implement that pattern with Laravel's native LengthAwarePaginator / CursorPaginator and your own API Resource envelope. The one-hour investment in reading it is the entire leverage you get.
How can I help you explore Laravel packages today?