Projet de hack de Sylius : SyliusUiBundle

La page à la fin de la première étape.

Suite du premier live

Ce projet a démarré un vendredi soir par un live sur Twitter. Le but est de monter un site utilisant un maximum de bundle Sylius sans la partie e-commerce. Je ne veux pas d’un Sylius amputer de ses fonctionnalités alors qu’elles sont dans le projet.

Si vous ne comprenez pas de quoi je parle, c’est que vous n’avez peut-être pas lu le premier billet sur l’initialisation du projet de hack de Sylius. Prenez le temps de le lire pour avant de continuer pour vous permettre de comprendre la suite. Nous allons rechercher le meilleur bundle à utiliser pour refaire l’apparence de l’administration Sylius rapidement, mais également tout le CSS et JavaScript nécessaire.

Outils utilisés

Déterminer le bundle à installer

Dans cette étape, je veux obtenir une page web sur mon projet qui ressemble à l’administration de Sylius.

En parcourant les bundles installés dans le dossier vendor/sylius du projet Sylius, je remarque AdminBundle. Cela semble une bonne piste, mais en regardant son contenu, je m’aperçois qu’il contient des éléments liés à la partie e-commerce de Sylius. Selon les règles de mon projet, je ne peux pas l’utiliser.

En regardant les dépendances de l’AdminBundle, je me rends compte qu’il y a un UiBundle. Il contient des éléments de base de l’interface utilisés par AdminBundle, mais rien de spécifique au e-commerce.

Ce bundle semble très bien, mais a un défaut majeur, la documentation est celle de Sylius. Il n’y a donc aucune information pour l’utiliser séparément. Ce n’est pas vraiment un problème, car en ayant le code source de Sylius, il est possible de le lire pour comprendre son comportement.

Installer SyliusUiBundle

Ce bundle fait partie intégrante de Sylius. Il dispose cependant de son dépôt de code source ce qui me permet de l’installer comme n’importe quelle dépendance dans mon projet grâce à la commande suivante : composer require sylius/ui-bundle

L’installation ajoute le bundle et les dépendances. Mais aucune configuration ni aide pour savoir quelle est la prochaine étape.
Comment l’utiliser ?

Intégration de l’UiBundle dans le projet

Dans tout projets Symfony utilisant Twig comme moteur de rendu, il existe un fichier de base Twig nommé base.html.twig ou layout.html.twig ou comme bon vous semble…

Pour comprendre, comment fonctionne l’UiBundle, la meilleure façon de faire est de regarder comment l’AdminBundle l’utilise. Le fichier de base de l’AdminBundle est donc vendor/sylius/sylius/src/Sylius/Bundle/AdminBundle/Resources/views/layout.html.twig.

Je copie donc le contenu du fichier dans mon fichier themes/MyTheme/templates/base.html.twig. Je replace les balises (pour les CSS et le JS) et blocs (body) que je souhaite conserver.

Mon nouveau template de base

J’ajoute quelques balises pour afficher quelques informations par-ci par-là. Essentiellement pour repérer les emplacements.

Et je tente d’afficher la page après avoir démarré le serveur web avec la commande symfony serve -d du client Symfony. La page s’affiche et c’est vraiment très moche. Ce n’est pas du tout le résultat attendu. Mais il y a un début de mise en page qui se dessine.

La page Home avec le nouveau template.

Il manque clairement les CSS pour la mise en page.

Ajout des assets

Depuis Symfony 5, il est nécessaire d’utiliser les outils de développement JS pour gérer les assets CSS et JS. La documentation de Symfony recommande d’utiliser le bundle WebpackEncore pour ajouter dans Symfony une passerelle entre les outils JS du front et Symfony (back).

Pour l’installer, c’est extrêmement simple grâce au plug-in Flex pour Composer. La commande composer req encore installera tout le nécessaire et indiquera même la prochaine étape (ça change de l’UiBundle)

Suivons le guide et exécutons la commande yarn install pour installer les dépendances puis yarn dev pour compiler.

Maintenant que la compilation fonctionne, comment fait Sylius pour compiler les assets de l’administration ?

Retournons sur le projet Sylius et commençons à suivre la piste en partant du fichier de configuration webpack.config.js présent à la racine du projet.

La configuration Webpack qui gère l’admin fait référence à un seul point d’entrée (EntryPoint) assets/admin/entry.js et le contenu de ce fichier nous renvoie au fichier sylius/bundle/AdminBundle/Resources/private/entry.

La définition des chemins des dossiers contenant les assets de Sylius.

Je n’ai aucun dossier sylius/bundle dans mon dossier asset. En retournant dans la configuration de Webpack, il y a une référence en haut et en bas vers une configuration d’un alias pour sylius/bundle.

L’ajout des alias dans la configuration Webpack.

En suivant les chemins (de l’alias puis du fichier), je trouve le fichier souhaité. Je copie le contenu du fichier pour l’ajouter dans mon fichier assets/app.js.

Mon fichier app.js

En regardant la correspondance dans la configuration Webpack, je supprime toutes les lignes qui font référence à AdminBundle.

Dans la configuration Webpak de mon projet, j’ajoute les alias pour sylius/bundle et sylius/ui-resources en me basant sur le projet Sylius et en personnalisant pour mon projet.

Lors des tentatives successives de compilation des assets, j’ai des erreurs liées au manque de librairies Sementic UI et au loader Sass pour gérer les fichiers css à compiler.

Pour ajouter le framework CSS Sementic UI, j’exécute la commande yarn add --dev semantic-ui-css puis pour le loader Sass cette commande yarn add sass-loader@^12.0.0 sass --dev.

Malgré l’ajout de Sass, la compilation ne fonctionne pas, car il est nécessaire de l’activer dans la configuration de WebPack. La compilation fonctionne.

Activation de Sass dans la configuration de WebPack

Dans le fichier base.html.twig de mon thème, j’active l’exécution des fonctions encore_entry_link_tags et encore_entry_script_tags pour ajouter les fichiers CSS et JS au chargement des pages.

Le rendu de la page est bien plus agréable.

Le menu de gauche

Il est maintenant temps d’ajouter des éléments dans le menu de gauche. Dans Sylius, le KnpMenuBundle est utilisé pour gérer tous les menus. Il est très pratique, car tout est géré dans PHP et une fonction Twig suffit pour obtenir le rendu du menu.

KnpMenuBundle dispose de sa propre documentation ce qui aide à l’ajouter dans le projet.

Nous avons besoin d’une classe qui définira le menu. Elle sera écrite dans le fichier src/Menu/AppMainMenu.php il n’est pas nécessaire d’étendre une classe abstraite ou implémentée une interface. Nous avons besoin d’une méthode. Ça sera generateMenu et elle retournera un objet ItemInterface de Knp.

Voici le contenu de la classe:

<?php
/**
 * @copyright Macintoshplus (c) 2021
 * Added by : Macintoshplus at 08/10/2021 22:35
 */

declare(strict_types=1);

namespace AppMenu;

use KnpMenuItemInterface;
use KnpMenuMenuFactory;
use SyliusBundleUiBundleMenuEventMenuBuilderEvent;
use SymfonyContractsEventDispatcherEventDispatcherInterface;

final class AppMainMenu
{
    public const EVENT_NAME = 'app.menu.main';
    /**
     * @var MenuFactory
     */
    private $menuFactory;
    /**
     * @var EventDispatcherInterface
     */
    private $eventDispatcher;

    public function __construct(MenuFactory $menuFactory, EventDispatcherInterface $eventDispatcher)
    {
        $this->menuFactory = $menuFactory;
        $this->eventDispatcher = $eventDispatcher;
    }

    public function generateMenu(array $options): ItemInterface
    {
        $menu = $this->menuFactory->createItem('app.main');

        $admin = $menu->addChild('admin')->setLabel('Admin');
        $admin->addChild('Item1')->setLabel('Item 1')->setLabelAttribute('icon', 'folder')
        ;
        $admin->addChild('Item2')->setLabel('Item 2')->setLabelAttribute('icon', 'folder')
        ;
        $admin->addChild('Item3')->setLabel('Item 3')->setLabelAttribute('icon', 'folder')
        ;

        $this->eventDispatcher->dispatch(new MenuBuilderEvent($this->menuFactory, $menu), self::EVENT_NAME);

        return $menu;
    }
}

Avant de retourner le menu, la classe émet un évènement. Cela permet à quiconque de venir manipuler le menu avant qu’il ne soit affiché (grâce à un Listener ou Subscriber). J’ai repris ici le fonctionnement de Sylius, car je le trouve intéressant et que la classe nécessaire est présente dans l’UiBundle.

L’autoconfiguration et l’autowirring de Symfony ont besoin d’aide pour ce service. J’ajoute donc la configuration suivante dans le fichier config/services.yaml:

    AppMenuAppMainMenu:
        arguments:
            $menuFactory: '@knp_menu.factory'
        tags:
            - { name: knp_menu.menu_builder, method: 'generateMenu', alias: 'app.main'}

Le tag ajouté dans la configuration indique au MenuBundle comment se nomme le menu (alias) et quelle méthode appelés sur le service pour obtenir les items du menu.

Maintenant dans Twig, il reste à appeler la fonction knp_menu_render. Ici aussi, je vais voir comment c’est réalisé dans l’AdminBundle.

Dans le bloc sidebar, le contenu est maigre avec cette ligne {{ sylius_template_event('sylius.admin.layout.sidebar') }}. Après quelques recherches, je comprends que c’est un système d’évènement lié au rendu des templates de Sylius. Ils permettent de personnaliser le contenu facilement grâce à des blocs.

Pour connaitre les blocs affichés lors d’un évènement de template, il est nécessaire de savoir comment en ajouter un. Le document de Sylius nous apprend donc que c’est sous la clé de configuration sylius_ui.events que tous sont défini.

Pour afficher la configuration d’un bundle Symfony, j’utilise la commande de débug bin/console debug:configuration sylius_ui events et il m’affiche toute la configuration. Je cherche donc dans le résultat le nom de l’événement sylius.admin.layout.sidebar pour en suite aller chercher les fichiers.

Je finis par arriver dans le fichier vendor/sylius/sylius/src/Sylius/Bundle/AdminBundle/Resources/views/Layout/_menu.html.twig qui contient le champ pour le moteur de recherche et l’appel de la fonction twig knp_menu_render. Je copie le contenu du fichier dans le bloc sidebar de mon template de base.html.twig (je n’utilise pas les événements des templates dans mon projet pour l’instant).

Le code de la sidebar

Lors de l’actualisation de la page, le menu apparaît avec les icônes à droite du titre du menu.

Le résultat est déjà satisfaisant mais les icones des items ne sont pas au bon endroit.

Mais le rendu est déjà très bien. Comme je souhaite avoir une bonne ressemblance avec Sylius, j’ajoute le code HTML pour le logo au-dessus du champ de recherche.

Pour placer les icônes du menu à gauche, je retourne donc dans l’AdminBundle pour trouver les instructions CSS nécessaires que j’ajoute dans un nouveau fichier assets/styles/_ui.scss. Je ne manque pas de l’ajouter dans le fichier assets/app.js pour qu’il soit pris en compte lors de la compilation des assets.

Après compilation des assets et rechargement de la page, bingo ! L’apparence est conforme à ce que j’attendais.

Résultat final

Conclusion

L’objectif a été atteint en une heure de travail. C’est très rapide ! Ce que montre cette première étape est qu’il est important de savoir lire le code et surtout ne pas hésiter à aller dans le code source des outils utilisés pour les décortiquer.

Que faire maintenant ? Le projet n’est pas terminé, j’aimerais arriver à utiliser d’autre bundle dans ce projet.

Par exemple avec l’UiBundle est venu le SyliusGridBundle qui permet d’afficher des données sous forme de tableau, d’ajouter une pagination, un filtre, des actions globales, des actions de masse (bulk) et des actions pour chaque ligne de données.

Il y a également, le SyliusRessourceBundle qui permet de gérer des entités Doctrine comme des ressources Sylius et faciliter leurs utilisations dans l’administration de Sylius. Mais pourrais-je les utiliser indépendamment ? Ou uniquement ensemble ?

La suite de l’aventure est disponible avec l’utilisation du GridBundle sans le ResourceBundle (qui arrivera plus tard)

Le projet de Hack de Sylius sur GitHub