Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Pager Bundle Laravel Package

symfony-twig/pager-bundle

symfony-twig/pager-bundle is a Symfony bundle for adding Twig-powered pagination to your projects. It provides reusable pager rendering and page navigation controls, helping you display paginated lists with configurable ranges and simple template integration.

View on GitHub
Deep Wiki
Context7

Getting Started

First, resolve the framework boundary — everything else depends on it. symfony-twig/pager-bundle ships a Bundle class, a service definition, and Twig templates. It boots through Symfony's kernel and renders through Twig. In a Laravel app the PHP classes will autoload, but no service provider registers the pager, no Twig environment exists to call the helper, and the bundle's Resources/ templates never resolve. There is no php artisan vendor:publish path and no config file to fall back on. If the target codebase is Laravel, the day-to-day answer is: use LengthAwarePaginator / paginate() and skip this package entirely.

If (and only if) you maintain a Symfony ≥3 app, the first twenty minutes look like this:

  1. Read composer.json before installing. Check type (symfony-bundle), the symfony/* constraint range, and the twig/twig range. A package pinned to Symfony 3 will drag in EOL constraints and trigger Composer security-advisory warnings on install — know that before you open the PR.
  2. Register the bundle. Symfony 4+: add the bundle class to config/bundles.php. Symfony 3: AppKernel::registerBundles().
  3. Find the two things that matter in the source. There is no real documentation beyond a description file, so open the code and locate (a) the pager service class — its constructor args (page size, visible range) and its public mutators; and (b) the Twig extension — the function or filter name it registers and the template path it renders. Everything you need is in those two files.
  4. Wire one use case end-to-end before committing further. The canonical first case is a repository-backed list view: take a ?page= query param, feed a count + a sliced result set into the pager, render the controls in a partial.

A minimal Symfony first pass:

// Controller
$page = max(1, (int) $request->query->get('page', 1));
$perPage = 20;

$total = $repo->countAll($filters);
$items = $repo->findPage($filters, $offset = ($page - 1) * $perPage, $perPage);

$pager = $this->get('pager')          // whatever the service id is in services.xml
    ->setTotal($total)
    ->setPage($page)
    ->setPerPage($perPage);

return $this->render('list.html.twig', ['items' => $items, 'pager' => $pager]);

Then in Twig, call the registered helper (name it from the extension class) and pass the incoming query string so filters and sort survive page navigation. Validate that query-string preservation works before you build anything else on top — it's the single most common reason a paginator gets ripped out mid-sprint.

If you're on Laravel and landed here anyway, the equivalent first pass is Model::query()->filter($filters)->paginate($perPage)->withQueryString() in the controller, {{ $items->links() }} in Blade, and a published pagination::tailwind (or custom) view if the design system needs different markup. You get all of this in the box.

Implementation Patterns

Wrap it behind your own interface — the single most important pattern here. Given zero stars, zero dependents, and a single maintainer, treat the vendor call site as something you will replace. Define an internal contract and implement it with this bundle:

interface PaginatorInterface {
    public function paginate(object $query, int $perPage, int $page): PageResult;
}

PageResult is a neutral DTO — items, total, perPage, currentPage, lastPage. Controllers and templates depend on PageResult, never on the bundle's pager class. When you migrate to Pagerfanta (or off Symfony entirely), one adapter file changes. Without this, the vendor type leaks into every controller and Twig template and the swap becomes a multi-day refactor.

Config-driven page size, with a hard ceiling. Don't scatter 20 and 50 through controllers. Put a default in parameters/services.yaml, inject it into the pager, and clamp anything user-supplied:

$perPage = min((int) $request->query->get('per_page', $default), $maxPageSize);

This is the difference between a paginator and a denial-of-service vector. If the bundle doesn't clamp for you, clamp in your adapter.

One shared Twig partial for all list views. Even if the bundle's helper is adequate, wrap the helper output in your own _pager.html.twig that includes the current filters, the empty state, and your design-system classes. Every list screen (include '_pager.html.twig' with {pager: pager}) then inherits a consistent contract — page-size selector, "showing X–Y of Z", ellipsis rules. This is the actual value of a shared pagination component; it is also something you can build in a morning, which is the point.

Preserve query state on every link. Merging the current request query (minus page) into generated URLs is non-negotiable for filterable/searchable lists. If the bundle's helper doesn't do it, pass app.request.query.all into the template and merge. Test with at least two active filters plus a sort.

Handle out-of-range pages deliberately. Decide once: clamp to lastPage, or 404/redirect. Deep-linking to ?page=9999 after a filter shrinks the result set is common; unpaginated behaviour (empty table with valid controls) confuses users and generates tickets.

Laravel equivalent, for parity of thinking. The native paginator already implements all of the above — paginate() reads ?page=, withQueryString() preserves filters, links() renders controls, onEachSide() tunes the window. The pattern to carry over is containment and a shared Blade view, not the bundle's API.

Gotchas and Tips

  • Type mismatch is the headline gotcha. A Symfony bundle is not a Laravel package. composer require will succeed; nothing will work. If a ticket or catalogue entry sent you here from a Laravel stack, close it and use the native paginator.
  • EOL constraints will bite at upgrade time. Symfony 3 has been EOL since late 2021. Expect Composer advisories on install, PHP 8.1+ deprecations in the source (implicit nullable parameters, dynamic properties), and Twig 3 changes (spaceless removal, escaping defaults) that can break rendering with no test suite to catch it. Budget for a fork within twelve months of adoption.
  • COUNT(*) is where mis-pagination hides. With GROUP BY, HAVING, DISTINCT, or to-many joins, a naive count returns the wrong total and you get duplicate or skipped rows across page boundaries. Count DISTINCT on the primary key, or count a subquery. Users notice this before your monitoring does.
  • Query-string links are a reflected-XSS surface. Page/filter parameters round-tripped into href attributes must be escaped consistently. An unaudited, template-driven paginator is exactly where ?q="><script> shows up. Verify escaping output manually on the first screen you build.
  • The page parameter is user input. Cast to int, clamp to >= 1, clamp perPage to a maximum, and never interpolate it into SQL. Offset arithmetic is ($page - 1) * $perPage — an unbounded perPage turns one request into a table scan.
  • Deep OFFSET doesn't scale, and this package doesn't address it. For large tables, keyset (cursor) pagination is the correct answer; keep offset pagination for admin screens and low-cardinality lists. Document the threshold where teams should switch.
  • Zero dependents means you are the QA. No issue tracker history, no field reports, no CI badge. Write a contract test against your PaginatorInterface (page 0, page past end, empty set, exact multiple of page size) and run it on every framework upgrade. That test is your only regression net.
  • Extension points are limited to templates and DI. If the bundle exposes its Twig template path, override via Symfony bundle inheritance rather than editing vendor/. For anything the pager service doesn't support (custom ellipsis logic, AJAX links), fork into your own namespace under MIT — and assign an owner before you merge it.
  • Set an adoption threshold and move on. This evaluation is the argument for a documented rule (e.g. minimum stars, active commits, at least one dependent) so that commodity-UI micro-packages don't consume review cycles. The deliverable here is a written pagination standard — default page size, max page size, query-string contract, cursor for high-volume — not a dependency.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect