pagination in your Symfony projects with the KnpPaginatorBundle

To list a large result set on your page can end up with long loading times and/or a confusing look. This is where pagination comes into play, it's a widely used concept to divide your content into clean blocks of data.

The KnpPaginatorBundle is a great bundle for pagination inside Symfony. The bundle requires Symfony >=3.4 and Twig >=2.0 if you use the twig templating engine in your project.

install KnpPaginatorBundle

The bundle can be installed using composer with the following command:

composer require knplabs/knp-paginator-bundle

If you don't use flex you have to register the bundle before you can use it.

use the pager service inside a controller

We will rewrite following controller method and view template to use the pager service provided by the KnpPaginatorBundle:

## /src/Controller/ArticleController.php

namespace App\Controller;

use App\Repository\ArticleRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;

class ArticleController extends AbstractController
{
    /**
     * @Route("/", name="article_list")
     */
    public function list(ArticleRepository $repository)
    {
        $articles = $repository->findAll();
        return $this->render('article/list.html.twig', [
            'articles' => $articles
        ]);
    }
{# /templates/article/list.html.twig #}

{% for article in articles %}
    <h1>{{ article.title }}</h1>
    <p>{{ article.description }}</p>
    <hr />
{% endfor %}

We use DI to get access to the pager service inside the controller and pass its result instead of the articles to the template:

## /src/Controller/ArticleController.php

namespace App\Controller;

use App\Repository\ArticleRepository;
use Knp\Component\Pager\PaginatorInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;

class ArticleController extends AbstractController
{
    /**
     * @Route("/", name="article_list")
     */
    public function list(ArticleRepository $repository, PaginatorInterface $paginator, Request $request)
    {
        $pagination = $paginator->paginate(
            $repository->findAll(),
            $request->query->getInt('page', 1),
            5
        );

        return $this->render('article/list.html.twig', [
            'pagination' => $pagination
        ]);
    }

Render the articles and the pagination:

{# /templates/article/list.html.twig #}

{% for article in pagination %}
    <h1>{{ article.title }}</h1>
    <p>{{ article.description }}</p>
    <hr />
{% endfor %}

<div>
    {{ knp_pagination_render(pagination) }}
</div>
<div>
    articles counted: {{ pagination.getTotalItemCount }}
</div>

We added a use statement for the PaginatorInterface at line 6 and injected the Interface into the list method as second argument. In addition to these changes we added a use statement for the Request object at line 8, which we injected as third argument to the method.

Inside the list method of the controller we utilize the paginate method of the pager service. The first argument contains the data we want to paginate. You can find a list of the objects that can be paginated in the documentation.

$request->query->getInt('page', 1)

This line is used as second argument and tries to retrieve the query string parameter page, if no query string is given it will return the Integer 1.

The last argument contains the amount of items that should be displayed per page. The pager service adds an OFFSET and a LIMIT to the query depending on those arguments.

use friendly URLs

In the example above we implemented the pager service as described in the documentation. This comes with the downside of "ugly" URLs in form of www.domain.tld/?page=2. To change the URLs into domain.tld/2 we just have to tweak the list method and its annotation a little bit:

## /src/Controller/ArticleController.php

namespace App\Controller;

use App\Repository\ArticleRepository;
use Knp\Component\Pager\PaginatorInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;

class ArticleController extends AbstractController
{
    /**
    * @Route("/{page}", name="article_list", defaults={"page"=1})
    */
    public function list(ArticleRepository $repository, PaginatorInterface $paginator, $page)
    {
        $pagination = $paginator->paginate(
            $repository->findAll(),
            $page,
            5
        );

        return $this->render('article/list.html.twig', [
            'pagination' => $pagination
        ]);
    }

In this example we added an optional page parameter, with the default value 1, in the route annotation. The use statement for the Request object got removed and instead of the Request object we pass the page parameter as third argument. The second argument of the paginate method is using the page parameter from the annotation instead of trying to retrieve the query parameter from the URL. The rendered paginator inside the template will use friendly URLs now.

configure the bundle

The bundle ships with no configuration file, however there are some default values we can use to customize the rendered paginator.

The bundle provides additional pagination templates. We are going to create a configuration file inside the /config/packages folder to use the bootstrap4 pagination template:

## /config/packages/knp_paginator.yaml

knp_paginator:
    template:
        pagination: '@KnpPaginator/Pagination/twitter_bootstrap_v4_pagination.html.twig'

Voila, we have a clean pagination for our data with almost zero effort. The KnpPaginatorBundle certainly is one of those bundles you are going to use in a lot of your projects. As always, if you have any questions related to the KnpPaginatorBundle - use the comment section and I will respond asap.

With best regards,
Sam

comment successfully committed
Hafez 09.11.2020

Precise and decent thanks

Amit 04.08.2019

Thanks, Very good article it help me very much... :)

leave a comment:

please enter your first name above
please enter a valid email above
please enter your message before you send the form

connect

please enter your name
please enter a valid email above
please enter the subject for your request
please enter your message before you send the form
mail successfully committed