Commentaires
Utilisez APYDataGridBundle pour des listings impeccables
Introduction
APYDataGridBundle fait partie des utilitaires qui permettent de gagner du temps sur les projets de type Application Web.
Grâce à celui-ci, et à cet article, vous obtiendrez des listes, filtrables, triables, exportables, avec pagination et gestion des relations entre vos entités. Vous pourrez également gérer des actions sur chacune des lignes, ou mettre en place des traitements spécifiques sur chaque colonne.
Bref : vous allez normalement être en mesure de gérer tous les cas de figures auxquels vous pourriez être confrontés sur vos projets.
Un grand merci à Yoann Petit, qui gère et maintient le Bundle, que l'on peut retrouver sur Github.
Utiliser un framework tel que Symfony et l'écosystème de Bundle développés et mis à disposition par la communauté offre de nombreux avantages, dont celui non négligeable de faire gagner du temps sur les projets. C'est une des raisons pour lesquelles il ne faut pas hésiter à contribuer au code source de Symfony ou de vos Bundles favoris.
Il existe bien entendu tout un tas de solutions permettant de mettre en place des fonctionnalités identiques à APYDataGridBundle, allant de la plus simple à la plus complexe :
- Un bon vieux PHPMyAdmin pourra combler les cas les plus simples, surtout si le besoin est interne.
- SensioGeneratorBundle, couplé à la possibilité de surcharger les templates utilisés lors de la génération, est un outil intéressant.
- SonataAdminBundle est un outil également puissant pour mettre en place des interfaces de gestion des entités.
- KunstmaanAdminBundle semble prometteur, mais je n'ai personnellement jamais réussi à le faire fonctionner.
Mise en place d'APYDataGridBundle
Exemple concret : Calendrier de l'Avent
Pour illustrer cet article, nous nous placerons dans le contexte d'un listing d'articles. Pour cela, nous reposerons sur un modèle assez simple, composé d'articles, d'auteurs et de commentaires, dont les relations sont illustrées dans le schéma ci-dessous :
Nous génèrerons ces entités à travers la commande Symfony doctrine:generate:entity
dans un Bundle appelé ACSEOCalendrierBundle. Il est possible de choisir n'importe quel format de mapping (annotation, yml, xml) proposé.
> php app/console doctrine:generate:entity
Installation du Bundle APYDataGrid
Pour disposer d'une fonctionnalité de génération en ligne de commande non encore disponible sur le Bundle d'origine (Pull Request en cours), il convient de mettre à jour votre fichier composer.json
avec les informations ci-dessous :
{
"repositories": [
{
"type": "vcs",
"url": "https://github.com/acseo/APYDataGridBundle.git"
}
],
}
Puis :
{
"require": {
...
"apy/datagrid-bundle": "dev-master"
...
},
}
Lancez l'installation du Bundle grâce à Composer :
> php composer.phar update
Pour finir, mettez à jour votre fichier app/AppKernel.php
:
<?php
// app/AppKernel.php
class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = array(
//...
new APY\DataGridBundle\APYDataGridBundle(),
//...
);
// ...
}
}
Ca y est, APYDataGrid est installé ! Maintenant, voyons ce que nous pouvons en faire.
Générer une grille
Maintenant que nous disposons de nos entités ainsi que d'APYDataGrid, il est temps de relier les deux. Pour nous faciliter la tâche, APYDataGrid dispose d'une commande permettant d'inspecter l'entité choisie pour générer un modèle de grille par défaut, qu'il nous suffira de personnaliser. Commençons par les articles.
Générer une grille par défaut :
Il suffit d'exécuter la commande suivante :
php app/console apydatagrid:generate:grid ACSEOCalendrierBundle:Article default
- Le premier argument (
ACSEOCalendrierBundle:Article
) permet de préciser l'entité sur laquelle nous travaillons.
- Le deuxième argument (
default
) permet de nommer la grille. Cela pourra être utilisé pour créer des grilles reposant sur la même entité mais avec des paramétrages différents.
Cette commande génère le fichier ACSEOCalendrierBundle/Resources/grid/Article.default.grid.yml
avec le contenu suivant :
ACSEO\Bundle\CalendrierBundle\Entity\Article:
Source:
columns:
id: ~
title: ~
content: ~
publishedDate: ~
writer: ~
comments: ~
groupBy: ~
filterable: ~
Columns:
id:
filterable: true
visible: true
id: id
title: id
title:
filterable: true
visible: true
id: title
title: title
content:
filterable: true
visible: true
id: content
title: content
publishedDate:
filterable: true
visible: true
id: publishedDate
title: publishedDate
writer:
filterable: true
visible: true
id: writer
title: writer
comments:
filterable: true
visible: true
id: comments
title: comments
Par défaut, tous les champs sont définis comme visibles et triables. Les relations avec les entités (Writer
, Comments
) ne sont pas encore paramétrées. Nous verrons plus tard comment nous y prendre.
Affichage d'une grille.
Maintenant que ce fichier est généré, faisons en sorte d'afficher notre grille. Pour cela, nous devrons intervenir au niveau du contrôleur puis de la vue.
Contrôleur ArticleController.php
:
<?php
namespace ACSEO\Bundle\CalendrierBundle\Controller;
use APY\DataGridBundle\Grid\Source\Entity;
/**
* Article controller.
*
* @Route("/article")
*/
class ArticleController extends Controller
{
/**
* Affiche la liste des Articles
*
* @Route("/", name="article")
* @Method("GET", "POST")
* @Template()
*/
public function indexAction()
{
// Initialisation de la source de données
$source = new Entity('ACSEOArticleBundle:Article');
// Récupération du service Grid
$grid = $this->container->get('grid');
// Affectation de la source
$grid->setSource($source);
// Renvoie une réponse
return $grid->getGridResponse('ACSEOCalendrierBundle:Article:index.html.twig');
}
Vue Resources/views/Article/index.html.twig
Maintenant que notre contrôleur fait appel à APYDataGrid, il ne nous reste plus qu'à déclencher l'affichage au niveau de notre vue, auquel nous avons ajouté une petite mise en forme CSS proposée dans la documentation du Bundle:
{# Resources/views/Article/index.html.twig #}
{% extends '::base.html.twig' %}
{% block body -%}
<h1>Liste des articles</h1>
{{ grid_search(grid) }}
{{ grid(grid) }}
{% endblock %}
Et voilà !
Vous avez sous les yeux un premier affichage de vos articles, sur lequel vous pouvez lister, trier, filter et utiliser la pagination. Bien entendu, l'affichage peut être optimisé, mais c'est un bon début.
Au final, mettre en place un tel affichage nécessite peu de temps et de code :
- Définition de votre entité, par exemple avec
app/console doctrine:generate:entity
.
- Création de la grille à travers la commande
app/console apydatagrid:generate:grid
.
- Mise en place du contrôleur et de la vue (qui peut être générée en utilisant
SensioGeneratorBundle
) et la possibilité de surchager les templates de base.
Vous pouvez bien sûr vous plonger dans la documentation du Bundle qui présente beaucoup de fonctionnalités.
La deuxième partie de l'article présente une utilisation plus avancée d'APYDataGrid.
Aller plus loin avec APYDataGrid
Avoir un affichage de base, c'est bien. Avoir un affichage optimisé avec plus de fonctionnalités, c'est mieux. Nous allons améliorer l'affichage de notre listing, étape par étape.
Modification de la grille, par paramétrage
De nombreuses choses sont possibles uniquement par paramétrage. Ainsi, nous allons pouvoir changer l'affichage, les filtres, le format des filtres et d'autres contenus uniquement en modifiant le fichier Article.default.grid.yml
.
#ACSEO\Bundle\CalendrierBundle\Entity\Article:
Source:
columns:
id: ~
title: ~
content: ~
publishedDate: ~
# On active ici la liasion entre Article et Writer
writer.firstName: ~
writer.lastName: ~
writer.companyName: ~
comments: ~
groupBy: ~
filterable: ~
Columns:
id:
# On n'affiche pas l'id dans le tableau ni dans les filtres
filterable: false
visible: false
id: id
title: id
title:
filterable: true
visible: true
id: title
# Modification du libellé affiché dans les filtres et dans le tableu
title: Titre de l'article
# On désactive l'utilisation d'opérateurs sur le filtre de recherche
operatorsVisible: false
publishedDate:
filterable: false
visible: true
id: publishedDate
title: Date de publication
writer.firstName:
filterable: true
visible: true
id: writer.firstName
# Définition de la colonne : champ firstName de l'entité Writer
field: writer.firstName
source: true
title: Prénom de l'auteur
# Utilisation des filtres "like" ou "not like" pour la recherche
operators: ["like", "nlike"]
writer.lastName:
filterable: true
visible: true
id: writer.lastName
field: writer.lastName
source: true
title: Nom de l'auteur
operatorsVisible: false
# Utilisation d'un menu déroulant pour le filtre plutôt que d'un champ texte
filter: select
writer.companyName:
filterable: true
visible: true
id: writer.companyName
field: writer.companyName
source: true
title: Société
operatorsVisible: false
filter: select
# On autorise le filtre à travers des cases à cocher
selectMulti: true
selectExpanded: true
Moyennant un peu de CSS, l'affichage correspond désormais à l'écran ci-dessous :
Modification de la grille, via le contrôleur
Maintenant que nous avons une grille présentable, nous pouvons utiliser notre contrôleur pour manipuler encore plus notre grille. Nous pourrions par exemple:
- Fusionner les champs Prénom et Nom,
- Ajouter des boutons d'action permettant de voir ou de supprimer un article,
- Ajouter une colonne affichant le nombre de commentaires par article,
- Donner la possibilité d'effectuer un export CSV de notre liste.
Manipulation d'une colonne
Fusionner les champs Prénom et Nom revient à manipuler le contenu d'une colonne, en exploitant les données de la ligne à laquelle elle est rattachée.
Pour cela, rien de plus simple, grâce à la fonction manipulateRow()
:
# déclaration de la colonne author dans Article.default.grid.yml
ACSEO\Bundle\CalendrierBundle\Entity\Article:
Source:
columns:
#...
author: ~
#...
Columns:
#...
author:
filterable: false
visible: true
id: author
title: Auteur
operatorsVisible: false
<?php
//src/ACSEO/Bundle/CalendrierBundle/Controller/ArticleController.php
class ArticleController extends Controller
{
//..
public function indexAction()
{
//..
// Manipulation des données pour renseigner la colonne Auteur
$source->manipulateRow(
function ($row) {
$row->setField('author', $row->getField('writer.firstName').' '.$row->getField('writer.lastName'));
return $row;
}
);
//..
Dans ce cas, la méthode manipulateRow()
travaille sur des données disponibles dans chacune des lignes. Nous pourrions également implémenter une fonction getFullName
au sein de l'entité Writer
et appeler celle-ci grâce au code : $row->getEntity()->getFullName()
.
Ajout de colonnes d'action
Nous pouvons ajouter très simplement, des boutons d'actions pour chacune des lignes via le contrôleur :
<?php
use APY\DataGridBundle\Grid\Action\RowAction;
// ...
class ArticleController extends Controller
{
//..
public function indexAction()
{
//...
$rowAction = new RowAction("Voir l'article", 'article_show');
$grid->addRowAction($rowAction);
$rowAction = new RowAction("Supprimer l'article", 'article_delete', true, '_self');
$rowAction->setConfirmMessage('Etes vous sur de vouloir supprimer cet article ?');
$grid->addRowAction($rowAction);
//...
Le rendu devrait être identique au suivant à ce stade :
Ajout d'une colonne virtuelle et modification de son rendu
Pour ajouter une colonne permettant d'afficher le nombre de commentaires, rien de plus simple. Nous en profiterons en plus pour faire afficher un commentaire conditionnel et ajouter des attributs sur nos lignes :
<?php
//..
use APY\DataGridBundle\Grid\Column\BlankColumn;
//..
class ArticleController extends Controller
{
//..
public function indexAction()
{
//..
$nbCommentsColumn = new BlankColumn(array(
'id' => 'nbComments',
'title' => 'Commentaires',
));
$grid->addColumn($nbCommentsColumn, 8);
// Manipulation des données
$source->manipulateRow(
function ($row) {
$row->setField('author', $row->getField('writer.firstName').' '.$row->getField('writer.lastName'));
// Commentaire en fonction du nombre de commentaires
$nbComments = $row->getEntity()->getNbComments();
$comment = 'Amazing !';
if ($nbComments == 0) {
$comment = '';
} elseif ($nbComments < 100 ) {
$comment = 'Not Bad !';
}
$row->setField('nbComments', sprintf('%u. %s', $nbComments, $comment));
// Ajout d'une couleur sur les lignes dont les articles ne sont pas publiés
$publishedDate = $row->getField('publishedDate');
if ($publishedDate > new \Datetime()) {
$row->setColor('#CCCCCC');
}
return $row;
}
);
//..
Grâce à cela, nous devrions avoir l'affichage ci-dessous :
Ajout de fonctions d'export
L'ajout d'un export de la liste, prenant en compte les filtres actifs, est un jeu d'enfant :
<?php
namespace ACSEO\Bundle\CalendrierBundle\Controller;
//..
use APY\DataGridBundle\Grid\Export\CSVExport;
use APY\DataGridBundle\Grid\Export\ExcelExport;
//..
class ArticleController extends Controller
{
//..
public function indexAction()
{
//..
$grid->addExport(new CSVExport('Exporter au format CSV'));
$grid->addExport(new ExcelExport('Exporter au format Excel'));
On obient ensuite la sélection de l'export sur notre grille :
Conclusion
À travers cet exemple, vous avez compris comment il était simple de mettre en place une solution satisfaisante de présentation de vos données sous forme de listing et à quel point APYDataGridBundle
offrait une grande liberté dans la manipulation des données.
Cet article ne donne qu'un aperçu de ce que le bundle peut proposer. N'hésitez pas à vous rendre sur le dépôt de code et sa documentation pour aller plus loin dans l'utilisation de celui-ci.
Vous pourrez retrouver les exemples utilisés dans cet article sur ce dépôt Github. Fiez vous aux commits pour retrouver les différentes étapes présentées ci-dessus.