Pagination sur Symfony2 avec Pangerfanta et Doctrine2

Lorsque le résultat d’une requête doctrine vous retourne trop d’objets, le plus souvent vous souhaiterez étaler ce contenu sur plusieurs pages. Voici ici une des façon de procéder.

Installation

Avec composer, l’installation est très simple, tapez :

composer require "white-october/pagerfanta-bundle": "dev-master"

Si vous utilisez le fichier deps (sf 2.0.x), c’est un chouilla plus long

[Pagerfanta]
    git=https://github.com/whiteoctober/Pagerfanta.git
    target=/pagerfanta

[WhiteOctoberPagerFanta]
    git=https://github.com/whiteoctober/WhiteOctoberPagerfantaBundle.git
    target=/bundles/WhiteOctober/PagerfantaBundle
    version=origin/symfony2.0

Puis un petit coup de php bin/vendor install.
Ensuite, ajoutez les nouveaux namespaces à l’autoloader du framework  :

// app/autoload.php

$loader->registerNamespaces(array(
    // ...
    'WhiteOctober'     => __DIR__.'/../vendor/bundles',
    'Pagerfanta'       => __DIR__.'/../vendor/pagerfanta/src',
));

Puis activez le bundle WhiteOctoberPagerfantaBundle dans le kernel :

// app/AppKernel.php

public function registerBundles()
{
    $bundles = array(
        // ...
        new WhiteOctober\PagerfantaBundle\WhiteOctoberPagerfantaBundle(),
    );
}

Utilisation

Voici un exemple d’action de controller utilisant la pagination (basé initialement sur une génération automatique de crud).

// src/Acme/HelloBundle/Controller/CarController.php
/**
* Lists all Cars entities.
*
* @Route("/cars", defaults={"page" = 1}, name="cars")
* @Route("/cars/page{page}", name="cars_paginated")
* @Template()
*/
public function indexAction($page)
{
    $qb = $this->getDoctrine()->getEntityManager()->createQueryBuilder()
        ->select('c')
        ->from('Me\CarsBundle\Entity\Car', 'c')
    ;

    // Classe spécifique à Doctrine, il existe un équivalent pour Propel.
    // Prend le QueryBuilder de notre requête en paramètre de son constructeur.
    // Vous pouriez aussi utiliser un DoctrineCollectionAdapter pour paginer une collection déjà récupérée.
    $adapter = new DoctrineORMAdapter($qb);
    $pagerfanta = new Pagerfanta($adapter);

    try {
        $entities = $pagerfanta
            // Le nombre maximum d'éléments par page
            ->setMaxPerPage(10)
            // Notre position actuelle (numéro de page)
            ->setCurrentPage($page)
            // On récupère nos entités via Pagerfanta,
            // celui-ci s'occupe de limiter la requête en fonction de nos réglages.
            ->getCurrentPageResults()
        ;
    } catch (\Pagerfanta\Exception\NotValidCurrentPageException $e) {
        throw $this->createNotFoundException("Cette page n'existe pas.");
    }

    return array(
        'entities' => $entities,
        'pager' => $pagerfanta,
    );
}

Et le code twig qui va avec :

<h1>Liste des voitures</h1>

{% if entities is not empty %}
    <ul>
        {% for entity in entities %}</ul>
            <li>{{ entity.name }}</li>
        {% endfor %}
    </ul>

    {% if pager.haveToPaginate %}
        {{ pagerfanta(pager, 'default_translated', {'routeName': 'cars_paginated'}) }}
    {% endif %}
{% else %}
    <p>Aucune voiture trouvée.</p>
{% endif %}

La petite particularité de cet exemple, est que nous utilisons deux routes pour notre action d’index. Ceci n’est pas obligatoire et vous pourriez aussi n’en n’utiliser qu’une. L’exemple à l’avantage de permettre d’accéder à la liste des voitures via l’url « /cars » qui sera identique à « /cars/page1″, tout en évitant d’avoir à préciser une page à chaque fois que l’on souhaite faire un lien vers la liste des voitures. De plus, cela évite de se retrouver avec une url du type « /cars/pag » lorsque l’on est sur la page1.

Info : Il est préférable d’ajouter également une redirection http de /cars/page1 vers /cars, afin d’éviter le « duplicate content » et donc améliorer votre SEO.

Astuce : si vous utilisez le Twitter Boostrap, vous pouvez remplacer le template « default_translated » par « twitter_bootstrap_translated ».

Versions utilisées : 2.0, 2.1, 2.2, 2.3