Baptiste Adrien
Web développeur chez JoliCode ❤️
Le back office (ou arrière-guichet✌️) est cette zone secrète de notre site, astucieusement dissimulée sous l'url /admin
. Une fois la porte poussée s'ouvre alors un monde de formulaires, de processus et de règles de gestion, excitant ! Non pas trop.
Pour que cette porte ne soit pas semblable à celle de Rodin, jetons-nous corps et âmes dans le bundle EasyAdmin afin que votre back office soit aussi cool qu'un speakeasy ! 🍻
EasyAdmin est un bundle permettant de mettre en place un back-office d'administration. Il est assez jeune au regard de Symfony, janvier 2015 marque sa première release.
C'est aujourd'hui le bundle d'admin mis en avant par Symfony :
Il mise sur la simplicité : il ne vous faudra que quelques lignes de code pour avoir une interface d'administration de vos entités (gestion CRUD, recherche, pagination, template responsive) clé en main.
Cette simplicité n'est pas dû au hasard : c'est une vraie volonté insufflée par @javiereguiluz, créateur du bundle et accessoirement membre de la Core Team Symfony. Javier pèse soigneusement l'intérêt des évolutions proposées et n'hésite pas à les refuser si elles sortent d'un cas d'utilisation général.
Pendant longtemps Sonata Admin a été le bundle de référence au sein des projets Symfony, à juste titre car il était le plus complet et documenté de l'écosystème. Cependant Sonata Admin est complexe, il comporte de nombreuses dépendances rendant les mises à jour laborieuses. Sur ma dernière migration (admin peu complexe) de Sonata vers EasyAdmin, le gain de code a été assez significatif :
Avec en prime une cure d'amincissement de bundles :
+ new EasyCorp\Bundle\EasyAdminBundle\EasyAdminBundle(),
- new Sonata\CoreBundle\SonataCoreBundle(),
- new Sonata\BlockBundle\SonataBlockBundle(),
- new Sonata\AdminBundle\SonataAdminBundle(),
- new Sonata\DoctrineORMAdminBundle\SonataDoctrineORMAdminBundle(),
- new Knp\Bundle\MenuBundle\KnpMenuBundle(),
- new App\AdminBundle\AdminBundle(),
La courbe d'apprentissage de Sonata Admin est également assez élevée : difficile d'être autonome sans avoir sa documentation sous la main, le bundle a une logique propre et demande une bonne expérience pour être à l'aise dessus. N'étant pas expert Sonata, je me suis trop souvent retrouvé en train de batailler pour implémenter une feature pourtant simple.
À l'inverse, EasyAdmin repose sur des concepts standards de Symfony, et c'est pour moi l'un de ses principaux atouts : pour ajouter une fonctionnalité (CMS, gestion de média…), il suffit de créer un contrôleur et passer sur du code Symfony standard.
Ceci dit, EasyAdmin apporte moins de fonctionnalités que Sonata, il ne gère pas la sécurité (filtre selon les rôles), ni une gestion des médias et gère moins bien les relations complexes entre entités. Mais c'est le postulat d'EasyAdmin qui se veut le MVP du back office et offre une grande flexibilité pour développer des fonctionnalités plus complexes.
À l'utilisation, il existe une grosse différence au niveau de la configuration des bundles :
Vous l'avez peut-être vu passer : la 4ème version de Symfony a été publiée hier ! L'occasion parfaite pour voir ensemble l'intégration du bundle avec Symfony 4. Je n'entrerai pas dans le détail des nouveautés : d'autres articles du calendrier s'en chargeront dans les jours à venir.
⚠️ Dans cet article, les exemples et configs sont donnés au format Symfony 4, la structure a sensiblement changé !
Commencez par récupérer la base de votre projet Symfony grâce au nouveau dépôt skeleton :
composer create-project symfony/skeleton beerfactory
Ajoutez-y votre bundle d'admin préféré (spoil : EasyAdminBundle) grâce aux recettes apportée par Flex :
composer req admin
Flex a automatiquement ajouté EasyAdminBundle (configuré sur l'alias admin) et créé les fichiers de configuration nécessaires :
# easy_admin:
# entities:
# # List the entity class name you want to manage
# - App\Entity\Product
# - App\Entity\Category
# - App\Entity\User
C'est dans ce fichier que vous allez renseigner les entités gérées par EasyAdmin.
Récupérez le bundle Maker pour pouvoir générer vos entités (bye bye SensioGeneratorBundle 💋) :
composer req maker
Créez votre entité :
bin/console make:entity Beer
Renseignez son namespace complet dans le fichier de config config/packages/easy_admin.yaml
, afin de l'activer dans EasyAdmin :
easy_admin:
entities:
- App\Entity\Beer
On dit également bye bye au parameters.yml
dans Symfony 4 💋, on utilise désormais les variables d'environnement, plus standards. Modifiez la variable DATABASE_URL
avec les infos de votre base de données dans votre fichier .env
(situé à la racine du projet), puis créez votre base de données :
bin/console doctrine:database:create --if-not-exists
bin/console doctrine:schema:update --force
Il ne vous reste plus qu'à lancer le serveur et à vous rendre sur http://127.0.0.1:8000/admin ✨
php -S 127.0.0.1:8000 -t public
Hello EasyAdminBundle, easy peasy ! 🏄
Par défaut, EasyAdminBundle offre une configuration CRUD assez basique, vous allez devoir l'affiner pour répondre au mieux à votre besoin métier. Voici les types de développement que vous allez rencontrer :
Plutôt qu'expliquer étape par étape la configuration du bundle (la documentation du bundle est très complète), je vais vous donner quelques astuces liées à EasyAdminBundle qui je l'espère vous seront utiles une fois les bases assimilées.
Comme évoqué précédemment, la configuration du bundle repose essentiellement sur des fichiers YAML. Afin de retrouver vos petits, évitez de tout mettre dans le fichier config/package/easy_admin.yaml
: il deviendra vite énorme et les conflits git seront récurrents.
Créez plutôt un dossier admin
avec des sous fichiers :
config/
├── package/
│ ├── easy_admin.yaml
│ ├── admin/
│ ├── menu.yaml
│ ├── config.yaml
│ ├── entities/
│ ├── beer.yaml
│ └── category.yaml
puis importez le tout depuis easy_admin.yaml
:
imports:
# Depuis Symfony 2.8
- { resource: admin/ }
Avec une administration complexe, vous allez sûrement devoir appeler des constantes PHP depuis votre YAML, c'est possible depuis la version 3.2 de Symfony :
!php/const AppBundle\Entity\Beer::ONLINE_STATE
Vous pourriez avoir besoin de séparer une entité en plusieurs sous-entités reflétant mieux votre métier. Dans mon cas, je veux séparer les bières organiques (naturelles, sans ajout d'ingrédients superflus) des autres, je peux utiliser l'option dql_filter
dans laquelle je peux mettre une expression DQL :
easy_admin:
entities:
Beer:
class: App\Entity\Beer
list:
dql_filter: "entity.isOrganic = false"
fields:
- { property: 'name' }
- { property: 'description' }
- { property: 'labelThumbnail' }
OrganicBeer:
class: App\Entity\Beer
list:
dql_filter: "entity.isOrganic = true"
fields:
- { property: 'name' }
- { property: 'description' }
- { property: 'labelThumbnail' }
Vous pouvez aussi injecter vos variables d'environnement :
dql_filter: "entity.roles LIKE '%%env(ROLE_ADMIN)%%'"
Dans cet exemple, on liste les utilisateurs avec le rôle %ROLE_ADMIN% (on utilise LIKE ainsi que les % en début et fin pour rechercher dans le tableau sérialisé).
Dans l'exemple précédent nous avons dupliqué la configuration pour la liste, elle est ici assez concise, mais cela peut devenir problématique sur des configurations plus complexes. Ainsi, vous pouvez utiliser les ancres YAML &
et *
pour mutualiser :
easy_admin:
entities:
Beer:
class: App\Entity\Beer
list:
dql_filter: "entity.isOrganic = false"
fields: &beerListFields
- { property: 'name' }
- { property: 'description' }
- { property: 'labelThumbnail' }
OrganicBeer:
class: App\Entity\Beer
list:
dql_filter: "entity.isOrganic = true"
fields: *beerListFields
C'est mieux ! Mais si l'on veut ajouter un champ uniquement pour les bières organiques ? Nous pouvons utiliser l'opérateur <
:
easy_admin:
entities:
Beer:
class: App\Entity\Beer
list:
dql_filter: "entity.isOrganic = false"
fields: &beerListFields
- { property: 'name' }
- { property: 'description' }
- { property: 'labelThumbnail' }
OrganicBeer:
class: App\Entity\Beer
list:
dql_filter: "entity.isOrganic = true"
fields:
<<: *beerListFields
<<: { property: 'abv' } # alcohol by volume
Vous voilà YAML master ✌️
Vous l'avez remarqué, EasyAdmin affiche un élégant switch dans la liste lorsque la propriété affichée est une booléenne. Mais que faire si vous avez une propriété non booléenne status
qui peut avoir comme état ONLINE
et OFFLINE
? L'astuce est de passer par une propriété virtuelle dans votre entité :
public function getIsOnline()
{
return $this->status === self::ONLINE_STATUS;
}
public function setIsOnline($isOnline)
{
$this->setStatus($isOnline ? self::ONLINE_STATUS : self::OFFLINE_STATUS);
}
puis dans votre config utilisez le type toggle
sur votre propriété virtuelle :
- { property: 'isOnline', type: 'toggle' }
Le menu est le point central de votre admin : soignez-le ! Vous pouvez le rendre clair en utilisant des séparateurs, des sous-menus et des icônes (http://fontawesome.io/icons/) :
# config/packages/admin/menu.yaml
easy_admin:
design:
menu:
- label: 'Manager'
- label: 'Beers'
icon: 'beer'
children:
- { entity: 'Beer', icon: 'thermometer-2', label: 'Classic beers'}
- { entity: 'OrganicBeer', icon: 'leaf', label: 'Organic beers'}
- { entity: 'Category', icon: 'th-list', label: 'Categories' }
- label: 'Env: %env(APP_ENV)%'
Vous pouvez aussi afficher vos variables d'environnement avec %env(APP_ENV)%
dans vos labels.
EasyAdminBundle utilise le thème AdminLTE en coulisse, vous pouvez donc utiliser la majorité du markup des widgets présent dans la démo, le css étant déjà embarqué dans EasyAdmin. Cela peut être utile pour vos pages personnalisées au sein de votre admin.
Par défaut, la page d'accueil est la première entité configurée : généralement nous avons besoin d'un dashboard servant de page d'accueil. Vous pouvez en ajouter une en créant un contrôleur étendant celui d'EasyAdmin :
bin/console make:controller AdminController
namespace App\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AdminController as BaseAdminController;
class AdminController extends BaseAdminController
{
/**
* @Route("/dashboard", name="admin_dashboard")
*/
public function dashboard()
{
return $this->render('admin/dashboard.html.twig');
}
}
Créez le template étendant celui d'EasyAdmin :
% extends '@EasyAdmin/default/layout.html.twig' %}
{% block main %}
Dashboard
{% endblock %}
Modifiez votre route dans config/routes/easy_admin.yaml
pour passer par votre contrôleur:
easy_admin_bundle:
resource: 'Controller/AdminController.php'
prefix: /admin
type: annotation
Enfin, ajoutez à votre menu le dashboard avec la propriété default: true
easy_admin:
design:
menu:
- label: Dashboard
icon: dashboard
route: admin_dashboard
default: true
Il ne vous manque plus qu'à le construire (pensez au widget du thème AdminLTE). Généralement, vous procéderez toujours de cette manière pour ajouter des sections personnalisées dans votre admin.
Un besoin récurrent est d'avoir un template personnalisé pour un champ (au niveau de la vue liste). Dans notre exemple, nous voulons afficher l'indice IBU (International Bitterness Unit) de nos bières sous forme de barre de pourcentage.
Commencez par créer un template dans templates/EasyAdmin/Beer/fields/ibu.html.twig
:
<div class="progress-group">
<span class="progress-text">IBU</span>
<span class="progress-number"><b>{{ value }}</b>/120</span>
<div class="progress sm">
<div class="progress-bar progress-bar-aqua" style="width: {{ (100 * value / 120) }}%"></div>
</div>
</div>
Au sein du template, vous pouvez accéder à la valeur courante grâce à value
et à l'entité avec item
. Comme évoqué précédemment le markup de la barre de progression vient d'AdminLTE : pas besoin d'ajouter du CSS !
Enfin, indiquez quel template doit utiliser EasyAdmin dans la config de votre entité Beer :
- { property: 'ibu', template: 'EasyAdmin/Beer/fields/ibu.html.twig' }
Il existe 3 écrans principaux dans EasyAdmin : view
, edit
et list
. Vous pouvez les surcharger en créant un template avec le nom de l'action dans le répertoire portant le nom de l'entité. Pour surcharger le template d'édition d'une bière, nous allons donc créer le fichier templates/easy_admin/Beer/edit.html.twig
:
{% extends '@EasyAdmin/default/edit.html.twig' %}
{% block main %}
{# Ajouter votre logique ici #}
{% block entity_form %}
{{ form(form) }}
{% endblock entity_form %}
{% endblock %}
De manière générale, vous pouvez surcharger tous les templates du bundle listés ici (paginator, menu, etc…).
Comme évoqué en début d'article, EasyAdmin ne permet pas de filtrer des actions en fonction des rôles de l'utilisateur courant. Je ne vais pas détailler comment l'implémenter mais plutôt vous rediriger vers cet article l'expliquant très bien :
EasyAdmin fait une recherche "full text" dans toutes les propriétés de votre entité mais il ne prend pas en compte les relations. Dans mon cas, j'ai une entité Beer
liée à une entité Category
, par défaut, il est impossible de remonter une bière en faisant une recherche sur le nom de catégorie.
Pour y remédier, vous pouvez modifier la requête DQL afin d'ajouter des champs. Dans votre AdminController
(cf. tips 8), surchargez la méthode createSearchQueryBuilder()
:
protected function createSearchQueryBuilder($entityClass, $searchQuery, array $searchableFields, $sortField = null, $sortDirection = null, $dqlFilter = null)
{
// Récupération du query builder parent
$qb = parent::createSearchQueryBuilder($entityClass, $searchQuery, $searchableFields, $sortField, $sortDirection, $dqlFilter);
// Si entité Beer, prise en charge du nom de la catégorie
if ($entityClass === Beer::class) {
$qb->innerJoin('entity.category', 'c')
->orWhere('LOWER(c.name) LIKE :category_name')
->setParameter('category_name', '%'.$searchQuery.'%')
;
}
return $qb;
}
Au premier regard, EasyAdminBundle semble être limité pour développer des back offices complexes : il offre par défaut des fonctionnalités réduites au regard de Sonata.
En réalité, il est très facile de développer et d'intégrer des fonctionnalités avancées au sein d'EasyAdminBundle. Ce dernier s'apparente à une belle pierre d'argile facilement façonnable permettant d'intégrer au mieux ses processus métiers. Chez JoliCode, nous l'utilisons désormais sur de nombreux projets, nous n'avons pas eu de mal à intégrer un CMS complexe. N'hésitez pas à sauter le pas !
EasyAdmin est un bundle qui évolue assez vite avec des releases fréquentes. Voici quelques fonctionnalités qui verront sûrement le jour dans les prochaines releases :
Vous pouvez retrouver le suivi de ces fonctionnalités dans la board "Future Features" du projet GitHub. Encore une fois, je ne peux que vous recommander de lire la documentation 🤓 pour connaitre chaque recoin du bundle.
À vos arrières-guichets ! 🚀🚀