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

Pagination Bundle Laravel Package

tiloweb/pagination-bundle

Tiloweb PaginationBundle is a lightweight Symfony bundle for paginating Doctrine queries and rendering clean pagination controls in Twig. It simplifies paged results, configurable page sizes, and consistent navigation, letting you add reliable pagination to Symfony apps with minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install the bundle

    composer require tiloweb/pagination-bundle
    
  2. Register it (Symfony 4+/5+/6+ uses config/bundles.php):

    return [
        // ...
        Tiloweb\PaginationBundle\TilowebPaginationBundle::class => ['all' => true],
    ];
    

    On legacy Symfony (3.x) register it in AppKernel::registerBundles() instead.

  3. Where to look first: the bundle is small — read in this order:

    • Resources/views/ → the default pagination template (this is the piece you will almost certainly override).
    • The Twig extension class → it exposes the one function you'll actually call in templates.
    • DependencyInjection/ → confirms there is essentially no configuration surface; treat it as a drop-in renderer, not a pagination engine.
  4. First use case — a controller that builds a Doctrine Paginator and a Twig template that renders it:

    use Doctrine\ORM\Tools\Pagination\Paginator;
    
    public function list(Request $request, EntityManagerInterface $em): Response
    {
        $page    = max(1, (int) $request->query->get('page', 1));
        $perPage = 20;
    
        $qb = $em->createQueryBuilder()
            ->select('p')
            ->from(Product::class, 'p')
            ->orderBy('p.id', 'DESC');
    
        $paginator = new Paginator($qb->getQuery(), fetchJoinCollection: true);
        $paginator->getQuery()
            ->setFirstResult(($page - 1) * $perPage)
            ->setMaxResults($perPage);
    
        return $this->render('product/list.html.twig', ['paginator' => $paginator]);
    }
    
    <ul>
      {% for product in paginator %}
        <li>{{ product.name }}</li>
      {% endfor %}
    </ul>
    
    {{ tiloweb_pagination_render(paginator) }}
    

    The {{ tiloweb_pagination_render(paginator) }} call is the entire point of the bundle — it turns a Doctrine Paginator into prev/next page links without you writing a Twig macro.

Implementation Patterns

  • Controller builds, Twig renders. The bundle does not create paginators, count pages, or read the page query param for you. Keep all of that in the controller (or a service) and only hand a fully configured Paginator to Twig. This separation is the cleanest way to keep the bundle replaceable later.

  • Preserve filters through links. The most common real-world need is passing current filters to the rendered links so pagination works with search:

    {{ tiloweb_pagination_render(paginator, {
         route: 'product_list',
         params: { q: app.request.query.get('q'), sort: app.request.query.get('sort') },
         page_query_param: 'page'
    }) }}
    

    Check the extension's signature in your installed version — option names such as params, route, and the page parameter are the usual knobs, but confirm before relying on them.

  • Reusable pagination trait / helper. Since every list action looks the same, extract the "page → firstResult/maxResults" math:

    trait PaginatesRequests
    {
        protected function paginate(Query $query, Request $request, int $perPage = 20): Paginator
        {
            $page = max(1, (int) $request->query->get('page', 1));
            $paginator = new Paginator($query, fetchJoinCollection: true);
            $paginator->getQuery()
                ->setFirstResult(($page - 1) * $perPage)
                ->setMaxResults($perPage);
            return $paginator;
        }
    }
    

    Now the bundle's rendering stays consistent across every listing screen because every screen passes the same shape of Paginator.

  • Customize the markup by overriding the template. Don't fork the bundle. Register a bundle-level template override (config/packages/twig.yaml → twig.paths, or Symfony's standard templates/bundles/TilowebPaginationBundle/... override convention) and point the render call at your template if the extension supports a template option. This is the intended extension point for matching Bootstrap/Tailwind/UX markup.

  • Multiple paginators per page. Because rendering is per-call and stateless, you can render two independent paginators (e.g. "recent" and "popular" tabs) by using distinct query parameters (e.g. page_recent, page_popular) and passing the matching value via the options — do not rely on a single shared page param, or the two will fight.

  • API/JSON integration tip. Use the same controller logic, and instead of tiloweb_pagination_render, serialize count($paginator) plus the current page into your JSON envelope. This keeps the bundle out of your API layer and lets the same paging math serve HTML and JSON.

  • QueryBuilder vs DQL. Paginator accepts a Query or QueryBuilder. If you rely on fetchJoinCollection: true for LEFT JOINed relations, be aware the bundle has no opinion on this — set it correctly in the controller or you'll get duplicate rows across pages.

Gotchas and Tips

  • Set firstResult/maxResults on the inner query, not on the Paginator. $paginator->getQuery()->setMaxResults(...) is the correct call. Setting limits on a QueryBuilder before constructing the Paginator also works, but be consistent. Mixing the two is the #1 source of "everything is on page 1" bugs.

  • The bundle does not read the page query parameter. If the pager renders but always lands on page 1, the bug is in your controller (the $request->query->get('page') line), not in the bundle.

  • Route context matters. tiloweb_pagination_render builds links from a route name and current request params. When you call it from a template rendered by an embedded controller or a subrequest, app.request may not be the URL you expect — pass the route and params explicitly rather than relying on implicit context.

  • Query string preservation is opt-in. Filters, sort orders, and search terms are not carried through automatically unless you pass them in params. Symptom: paging works, but clicking page 2 clears the user's search. Always whitelist the current query string in the render call.

  • Counting cost. count($paginator) issues a second count query. That's inherent to Doctrine's paginator, not this bundle, but it's the performance culprit people usually blame the bundle for. Add proper indexes on the ordered/filtered columns and consider fetchJoinCollection: false unless you actually join to-many relations.

  • Template override path is version-sensitive. Older releases use TilowebPaginationBundle:Default:pagination.html.twig; newer ones use @TilowebPagination/.... Verify against the installed package's Resources/views directory before writing an override — an override put in the wrong directory silently does nothing.

  • Extremely small ecosystem — pin your version. With single-digit stars and no dependents, this is effectively a personal utility bundle. Lock a specific version in composer.json, and keep the pagination render call isolated in one template partial (e.g. _pager.html.twig) so you can swap it for a pagerfanta/knp_paginator macro in an afternoon if maintenance stops.

  • Debugging render issues. If the pager disappears: (1) dump count($paginator) and the current page in the template to confirm the input is non-empty, (2) check that the template override isn't swallowing output, (3) confirm the bundle is actually registered in config/bundles.php — an unregistered bundle produces a confusing Twig "unknown function" error rather than a missing-bundle error.

  • Extension points are limited by design. There is no plugin system, no event dispatching, and negligible configuration. The template override is your only real customization surface; anything else (custom page windows, ellipsis logic, accessibility markup) belongs in your own Twig macro that calls the bundle or replaces it entirely.

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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle