EasyAdminBundle

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 ! 🍻

Hello EasyAdminBundle πŸ‘‹

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.

Sonata ou EasyAdmin ? πŸ€”

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 :

PR EasyAdminBundle

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 :

  • Sonata privilégie une configuration par classes/services assez verbeuse;
  • EasyAdmin offre une configuration par fichier YAML, verbeuse aussi mais plus simple et human-friendly.

EasyAdminBundle, Symfony 4 et Flex ✨

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é !

Installation de Symfony 4

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

Ajout d'EasyAdminBundle

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 :

  • config/packages/routes/easy_admin.yaml
  • config/packages/easy_admin.yaml :
# 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.

Ajout des entités

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

Branchement de la base de données

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

EasyAdminBundle screen

Hello EasyAdminBundle, easy peasy ! πŸ„

Affiner son admin

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 :

  • La phase simple qui consiste à modifier la config YAML pour affiner l'affichage (champs, actions, menu, design…);
  • La phase plus complexe pour ajouter de la logique personnalisée (en étendant le contrôleur d'EasyAdminBundle ou se brancher sur les événements)

 

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.

12 tips sur EasyAdminBundle 🍭

1 - Bien gérer sa configuration d'admin

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/ }

2 - Utiliser des constantes en YAML

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

3 - Utiliser des filtres DQL

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' }

EasyAdminBundle screen

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é).

4 - Mutualiser sa configuration YAML

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 ✌️

5 - Activer le toggle pour des propriétés non booléennes

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' }

6 - Bien gérer son menu

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 screen

7 - Utiliser le markup du thème AdminLTE

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.

EasyAdminBundle screen

8 - Ajouter un dashboard

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.

9 - Surcharger le template d'un champ

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' }

EasyAdminBundle screen

10 - Surcharger le template d'édition d'une entité

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…).

11 - Filtrer les actions selon les rôles utilisateurs

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 :

https://leanpub.com/practicalsymfony3/read#leanpub-auto-adding-simple-access-control-to-easyadminbundle

12 - Étendre la recherche

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;
}

EasyAdminBundle screen

Pour conclure πŸ’‹

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 :

  • Recherche par filtres
  • Actions de masse
  • Export en CSV, JSON, Excel et PDF
  • Meilleur gestion de la sécurité

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.