Tutoriel : La pagination avec Symfony2
Elliott Chiaradia |01-12-2015
La pagination est une étape quasi obligée pour tous sites ayant un minimum d’articles, posts ou autres joyeuseries. C’est pourquoi le bundle KnpPaginatorBundle est très souvent utilisé par les amateurs de Symfony2. Car celui-ci est simple et efficace. Malheureusement, il charge la totalité des articles avant de les afficher. Cela a pour conséquence de ralentir le site, parfois de façon considérable s’il en contient un grand nombre.
Dans ce tutoriel, nous allons apprendre à créer un système de pagination simple et efficace sans utiliser de bundle, à la main, à la dure ! Mais ne vous en faites pas ! Vous verrez, ça ne sera pas si compliqué que ça !
1. Mise en situation
Imaginons que nous avons un blog. Celui-ci a différents posts qui sont liés à des auteurs (chaque post est écrit par un seul auteur). Nous voulons avoir la possibilité d’afficher les posts par auteur. Ceux-ci doivent bien évidemment être munis d’un système de pagination.
Voici un petit extrait de la base de données que nous pourrions avoir :
Table Author :
- id int(11) – Un id unique auto-incrémenté.
- name varchar(255) – Le nom de l’auteur
- slug varchar(255) – Le slug (unique) permettant d’avoir une URL propre
Table Post :
- id int(11) – Un id unique auto-incrémenté.
- title varchar(255) – Titre du post
- content longtext – Le corps du post
- published tinyint(1) – 0 si non publié, 1 si publié
- created_at datetime – la date de création du post
- author_id int(11) – l’id de l’auteur ayant écrit le post
Il est maintenant grand temps d’attaquer le code !
2. Les routes
Dans notre fichier de routing, nous allons rajouter une seule et unique route que nous allons appeler blog_author.
blog_author:
path: /articles/author/{author}/{page}
defaults: { _controller: "AAMainBundle:Post:authorSort", page: 1 }
Décortiquons pas à pas ce code :
Cette route aura comme url « /articles/author/ suivi du slug de l’auteur et du numéro de page.
path: /articles/author/{author}/{page}
Dans le paramètre « defaults », on peut remarquer que la route fait appel à la fonctionauthorSort de notre controller « PostController.php » et par défaut sa page est la première, la page une.
defaults: { _controller: "AAMainBundle:Post:authorSort", page: 1 }
3. Le repository
Vous devriez avoir un repository dédié à l’entité Post (PostRepository.php). Si ce n’est pas le cas, créez-le. Puis rajoutez y le code suivant :
namespace AA\MainBundle\Entity;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\Tools\Pagination\Paginator;
class PostRepository extends \Doctrine\ORM\EntityRepository
{
public function getListAuthor($page=1, $maxperpage, $author)
{
$query = $this->createQueryBuilder('p')
->select('p')
->where('p.published = 1','p.author = :author')
->setParameter('author', $author)
->orderBy('p.createdAt', 'DESC')
;
$query->setFirstResult(($page-1) * $maxperpage)
->setMaxResults($maxperpage);
return new Paginator($query);
}
public function countTotalAuthor($author)
{
$query = $this->createQueryBuilder('p')
->select('COUNT(p)')
->where('p.published = 1','p.author = :author')
->setParameter('author', $author)
;
return $count = $query->getQuery()->getSingleScalarResult();
}
}
Décortiquons pas à pas ce code.
La première chose à faire et de rajouter l’appel à la classe Paginator :
use Doctrine\ORM\Tools\Pagination\Paginator;
La fonction getListAuthor va retourner nos posts en passant par la classe Paginator qui va s’occuper de segmenter nos posts en page. Elle prend en paramètre, le numéro de page, le maximum de posts qu’on veut afficher par page et bien évidemment l’auteur dont on veut afficher ses posts.
getListAuthor($page=1, $maxperpage, $author)
Nous allons créer une nouvelle query, celle-ci va sélectionner tous les posts de l’auteur concerné si ceux-ci sont publiés. Nous allons aussi les trier par ordre descendant au niveau de leur date de création pour que le dernier post créé soit affiché en haut de la page.
$query = $this->createQueryBuilder('p')
->select('p')
->where('p.published = 1','p.author = :author')
->setParameter('author', $author)
->orderBy('p.createdAt', 'DESC')
;
Il ne nous reste plus qu’à gérer le nombre de nombre de posts qu’on aimerait afficher par page.
$query->setFirstResult(($page-1) * $maxperpage)
->setMaxResults($maxperpage);
Et finalement, nous retournons le résultat sous la forme d’une instance de la classe Paginator (qu’on a chargé dans le repository à l’aide d’un use).
return new Paginator($query);
Notre deuxième fonction permet de compter le nombre de posts écrit par un auteur. Il n’est en effet pas possible de savoir de combien de pages nous avons besoin si nous n’avons pas cette information.
Nous allons créer une nouvelle query, pour laquelle il nous faut rajouter un « COUNT ». Puis, comme pour la fonction précédente, nous allons définir la condition where et le orderBy.
$query = $this->createQueryBuilder('p')
->select('COUNT(p)')
->where('p.published = 1','p.author = :author')
->setParameter('author', $author)
;
Il ne nous reste plus qu’à retourner le résultat.
return $count = $query->getQuery()->getSingleScalarResult();
4. Le controller
Comme défini précédemment dans le fichier de routing, nous allons travailler avec le controller dédié aux posts à savoir PostController.php. Dans celui-ci, nous allons rajouter la fonction authorSort appelée elle aussi dans la route créée au chapitre consacré au routing.
public function authorSortAction($author, $page)
{
$maxPosts ='10';
$em = $this->getDoctrine()->getManager();
$author =$em->getRepository('AAMainBundle:Author')->findOneBy(array('slug' => $author));
$post_count = $this->getDoctrine()
->getRepository('AAMainBundle:Post')
->countTotalAuthor($author);
$posts = $this->getDoctrine()->getRepository('AAMainBundle:Post')
->getListAuthor($page, $maxPosts, $author);
$pagination = array(
'page' => $page,
'route' => 'blog_author',
'pages_count' => ceil(posts_count / $maxPosts),
'route_params' => array()
);
return $this->render('AAMainBundle:Post:index_author.html.twig', array(
'author' => $author,
'posts_count'=>$posts_count,
'posts' => $posts,
'pagination'=> $pagination,
));
}
Décortiquons le code pas à pas.
Nous allons définir ici combien de posts nous voulons affichés par page. Je l’ai défini en dur, mais rien ne vous empêche de stocker cette valeur dans la base de données ou encore de la définir en tant que variable globale.
$maxPosts ='10';
Afin de pouvoir afficher le nom de l’auteur sur la page contenant ses articles, nous allons récupérer une instance d’un auteur grâce à son slug.
$author =$em->getRepository('AAMainBundle:Author')->findOneBy(array('slug' => $author));
Nous allons stocker le résultat de la fonction countTotalAuthor (créée précédemment dans le repository dédié aux posts).
$posts_count = $this->getDoctrine()->getRepository('AAMainsBundle:Post')->countTotalAuthor(array($author));
La variable $posts va contenir les posts d’un auteur.
$posts = $this->getDoctrine()->getRepository('AAMainBundle:Post')->getListAuthor($page, $maxPosts, $author);
La variable $pagination va contenir un tableau ayant diverses informations. Nous allons l’utiliser pour la création des numéros de pages afin que ceux-ci soient affichés correctement et qu’ils contiennent comme URL un chemin correct.
$pagination = array(
'page' => $page,
'route' => 'blog_author',
'pages_count' => ceil($posts_count / $maxPosts),
'route_params' => array()
);
Finalement nous allons retourner tout ce beau monde dans une vue nommée « index_author.html.twig et se trouvant dans le répertoire « Post ».
return $this->render('AAMainBundle:Post:index_author.html.twig', array(
'author' => $author,
'posts_count'=>$posts_count,
'posts' => $posts,
'pagination'=> $pagination,
));
5. La vue
Dans votre vue (index_author.html.twig), rajoutons le code suivant à l’endroit où vous voulez afficher les numéros de page (au-dessus du footer par exemple).
<div class="pagination">
<div class="pagination-buttons link_blog text-center">
{% if pagination.page>1 %}
<a href="{{ path(pagination.route, pagination.route_params|merge({'page': 1, 'author' : author.slug})) }}"><<</a>
<a href="{{ path(pagination.route, pagination.route_params|merge({'page': pagination.page-1, 'author' : author.slug})) }}"><<a>
{% endif %}
{% if posts_count!=0 %}
{% for p in range(max(pagination.page-2, 1), min(pagination.page+2, pagination.pages_count)) %}
<a{% if p == pagination.page %} class="current-page"{% endif %} href="{{ path(pagination.route, pagination.route_params|merge({'page': p, 'author' : author.slug})) }}">{{ p }}</a>
{% endfor %}
{% endif %}
{% if pagination.page<pagination.pages_count %}
<a href="{{ path(pagination.route, pagination.route_params|merge({'page': pagination.page+1,'author' : author.slug})) }}">></a>
<a href="{{ path(pagination.route, pagination.route_params|merge({'page': pagination.pages_count,'author' : author.slug})) }}">>></a>
{% endif %}
</div>
</div>
Vous avez maintenant une pagination fonctionnelle
Pour accéder à la page il vous suffit de rajouter un lien qui pointe sur le path blog_author et qui contient le slug de l’auteur dont nous voulons afficher les posts.
<a class="link-menu" href="{{ path('blog_author', { 'author': author.slug}) }}">Lire ses posts</a>
Il ne vous reste plus qu’à travailler sa mise en forme, ce qui fera office de dernier chapitre.
6. La mise en forme
Pour ce qui est de la mise en forme, voici un extrait de ma feuille de style que je vous propose à titre d'exemple.
.link_blog{
margin-top: 60px;
font-size: 1.8em;
}
.link_blog a:hover{
color:#59727D;
}
.current-page{
font-weight:bold;
}
Rien d’extraordinaire, mais le résultat est tout à faire correct. Cependant, je ne peux que vous conseiller de modifier ce CSS selon vos désirs et besoins.
Pour ce tutoriel, j’utilise le framework CSS Foundation et sa librairie d’icône. Pour remplacer les flèches « > » et « < » nous pouvons simplement ajouter une balise « i » comme celle-ci.
<i class="step fi-arrow-right size-40 picture_icon"></i>
Vous pouvez bien entendu utiliser d’autres librairies d’icônes comme FontAwesome, son fonctionnement reste très similaire.
Si vous utilisez Bootstrap, vous pouvez utiliser sa classe « pagination ». Pour cela, il vous suffit de faire votre petite cuisine dans la vue pour avoir un résultat similaire à celui-ci :
<ul class="pagination">
<li><a href="#">1</a></li>
<li><a href="#">2</a></li>
<li><a href="#">3</a></li>
<li><a href="#">4</a></li>
<li><a href="#">5</a></li>
</ul>
Conclusion
Le tutoriel est maintenant terminé et vous êtes capable de créer un système de pagination de toutes pièces ! Ce n'était pas si difficile ! Amusez-vous à le modifier, à l’adapter selon vos besoins :)