8min.

Administrer une entité custom dans un back-office Sylius

Lorsqu’on utilise le formulaire de contact par défaut de Sylius, celui-ci se contente d’envoyer un e-mail sans enregistrer les informations en base de données.

Dans le cadre d’un projet, j’ai eu besoin d’aller plus loin en sauvegardant les contacts du site pour un suivi ultérieur. Pour cela, j’ai créé une entité personnalisée dédiée aux contacts, ainsi qu’une administration pour gérer ces données directement depuis le back-office de Sylius.

Dans cet article, je vais vous détailler chaque étape de ce processus et partager quelques erreurs que vous pourriez rencontrer en chemin.

Section intitulée 1-creer-l-entite-et-le-repository1. Créer l’entité et le repository

Pour cela, rien de plus simple avec MakerBundle :

bin/console make:entity

 Class name of the entity to create or update (e.g. GrumpyGnome):
 > Contact\Contact

 Mark this class as an API Platform resource (expose a CRUD API for it) (yes/no) [no]:
 > yes

 created: src/Entity/Contact/Contact.php
 created: src/Repository/Contact/ContactRepository.php

 Entity generated! Now let's add some fields!
 You can always add more fields later manually or by re-running this command.

L’entité Contact et son repository ont ainsi été créées, rangées dans le dossier Contact pour rester bien organisées. J’ai ensuite ajouté les champs nécessaires (nom, email, message, date de création, etc.) à l’entité et mis en place un constructeur pour initialiser automatiquement la date de création lors de l’instanciation.

Section intitulée 2-mettre-a-jour-la-base-de-donnees2. Mettre à jour la base de données

Pour enregistrer cette nouvelle entité dans la base de données, il faut d’abord générer un fichier de migration, puis l’appliquer :

bin/console make:migration
bin/console doctrine:migrations:migrate

À ce stade, l’entité est fonctionnelle et permet l’enregistrement de nouveaux objets en base de données. Bien que cela nécessite également la création d’un FormType, d’un template et d’un contrôleur, ainsi que d’autres fichiers pour la partie front-end, je ne détaillerai pas cette partie dans cet article. L’étape suivante consiste à rendre cette entité accessible via l’interface d’administration.

Section intitulée 3-enregistrer-la-ressource-dans-la-config-sylius3. Enregistrer la ressource dans la config sylius

Il est maintenant nécessaire d’enregistrer l’entité dans la configuration Sylius pour qu’elle puisse être gérée en tant que ressource :

# config/packages/sylius_resource.yml
sylius_resource:
    resources:
        app.contact: # alias
  	driver: doctrine/orm
  	classes:
    	    model: App\Entity\Contact\Contact
    	    repository: App\Repository\ContactRepository

app.contact correspond à l’alias de la ressource et sera utilisé pour la référencer dans les configurations à venir.

Section intitulée 4-configurer-les-routes-dans-l-admin4. Configurer les routes dans l’admin

Pour afficher les contacts dans le back-office, il faut d’abord configurer les routes nécessaires :

# config/routes.yaml
app_admin_contact:
    resource: |
        alias: app.contact
        section: admin
        templates: "@SyliusAdmin\\Crud"
        vars:
            all:
                header: admin.ui.contact.header
                subheader: admin.ui.contact.subheader
                breadcrumb: admin.ui.contact.header
    type: sylius.resource
    prefix: /admin

Dans alias, on va indiquer l’alias de la ressource qu’on a précédemment configurée. admin.ui.contact.header et admin.ui.contact.subheader sont des clés de traduction utilisées dans les templates.

Avec cette configuration, Sylius va créer les routes suivantes :

app_admin_contact_index    	/admin/contacts/
app_admin_contact_create   	/admin/contacts/new
app_admin_contact_update   	/admin/contacts/{id}/edit
app_admin_contact_show     	/admin/contacts/{id}
app_admin_contact_bulk_delete   /admin/contacts/bulk-delete
app_admin_contact_delete   	/admin/contacts/{id}

En allant sur /admin/contacts/, on obtient maintenant une erreur 500 avec l’exception :

Neither the property "definition" nor one of the methods "definition()", "getdefinition()"/"isdefinition()"/"hasdefinition()" or "__call()" exist and have public access in class "Pagerfanta\Pagerfanta".

Cela est dû au fait que l’on n’a pas encore indiqué de grid à cette nouvelle configuration. Nous allons donc en créer une.

Section intitulée 5-creer-une-grid-make-grid5. Créer une grid (make:grid)

Une grid est un système de gestion des données d’une entité, permettant de définir la manière dont les informations seront présentées dans le tableau de listing de l’interface d’administration. Elle facilite l’affichage et la manipulation des données, en organisant leur présentation selon des critères que vous pouvez personnaliser pour répondre à vos besoins spécifiques.

On va de nouveau utiliser le MakerBundle de Symfony afin de générer cette grid :

php bin/console make:grid

Là, on doit choisir pour quelle entité on souhaite créer cette grid parmi la liste des entités de notre projet.

Cela générera un fichier PHP dans src/Grid/ContactsGrid.php. La méthode getName() retourne le nom de la grid, ici app_contact. Il suffit ensuite de l’ajouter à la configuration précédente :

# config/routes.yaml
 app_admin_contact:
     resource: |
         alias: app.contact
         section: admin
         templates: "@SyliusAdmin\\Crud"
+        grid: app_contact
         vars:
             all:
                 header: admin.ui.contact.header
                 subheader: admin.ui.contact.subheader
                 breadcrumb: admin.ui.contact.header
     type: sylius.resource
     prefix: /admin

Votre page de listing des contacts dans le back-office est maintenant fonctionnelle. Il ne reste plus qu’à intégrer cette page dans le menu. Pour ajouter l’entrée « Contacts » dans le menu de l’administration, il faut créer un MenuListener.

Section intitulée 6-configurer-le-menulistener6. Configurer le MenuListener

Le menu fonctionne en évènements. Nous allons donc créer une classe que je vais nommer ContactMenuListener mais que vous pouvez appeler comme vous le souhaitez. Afin que Sylius prenne en compte ce listener dans la création du menu, l’important est de le lier à l’évènement sylius.menu.admin.main dans la configuration de l’attribute. Le fichier application/src/Menu/ContactMenuListener.php ressemblera à ceci :

<?php

namespace App\Menu;

use Sylius\Bundle\UiBundle\Menu\Event\MenuBuilderEvent;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;

#[AsEventListener(event: 'sylius.menu.admin.main', method: 'addContactMenuItems')]
final class ContactMenuListener
{
	public function addContactMenuItems(MenuBuilderEvent $event): void
	{
    	$menu = $event->getMenu();

    	$newSubmenu = $menu
        	// sous-menu
        	->addChild('app_admin_users')
        	->setLabel('Données utilisateurs')
    	;

    	$newSubmenu
        	// élément du sous-menu
        	->addChild('app_admin_users_contact', ['route' => 'app_admin_contact_index'])
        	->setLabel('Contacts')
        	->setLabelAttribute('icon', 'envelope outline')
    	;
	}
}

La méthode addChild() correspond à l’identifiant qu’on donne à l’item. Pour les icônes, Sylius utilise semantic ui donc vous pouvez choisir l’icône souhaitée.

Ça donne ceci : Screen menu sylius

Et voici notre listing :

Screen admin sylius index contact cud

Mais voilà, il s’agit d’un listing de contacts, on ne veut ni les modifier, ni les supprimer, ni les créer. On souhaite uniquement les voir. On va devoir revenir sur notre grid.

Section intitulée 7-personnaliser-la-grid-et-les-actions-disponibles7. Personnaliser la grid et les actions disponibles

Dans le fichier généré, nous avons :

->addActionGroup(
            	MainActionGroup::create(
                	CreateAction::create(),
            	)
        	)
        	->addActionGroup(
            	ItemActionGroup::create(
                	// ShowAction::create(),
                	UpdateAction::create(),
                	DeleteAction::create()
            	)
        	)
        	->addActionGroup(
            	BulkActionGroup::create(
                	DeleteAction::create()
            	)
        	)
  • MainActionGroup est le bouton qu’on a en vert tout en haut, le bouton global ;
  • ItemActionGroup actions possibles pour chaque item ;
  • BulkActionGroup est l’action globale qu’on souhaite faire sur les items qu’on aura sélectionné.

Il suffit de modifier les actions de la grid comme suit :

-        	->addActionGroup(
-            	MainActionGroup::create(
-                	CreateAction::create(),
-            	)
-        	)
         	->addActionGroup(
             	ItemActionGroup::create(
-            	// ShowAction::create(),
-                	UpdateAction::create(),
-                	DeleteAction::create()
-            	)
-        	)
-        	->addActionGroup(
-            	BulkActionGroup::create(
-                	DeleteAction::create()
+                	ShowAction::create(),
             	)
         	)

Cela supprime les actions de modification et suppression, et conserve uniquement l’action « afficher » :

Screen admin sylius index contact show

Si nous cliquons sur « Afficher », nous obtenons alors :

Unable to find template "@SyliusAdmin/Crud/show.html.twig" (looked into: /var/www/application/templates/bundles/SyliusAdminBundle, /var/www/application/vendor/sylius/sylius/src/Sylius/Bundle/AdminBundle/Resources/views).

Sylius n’a pas de template configuré pour l’action show (on comprend mieux pourquoi l’action est commentée par défaut dans la génération de la grid).

Note : Le template a été ajouté dans la version 2.0 de Sylius 🎉

Section intitulée 8-creer-un-template-d-affichage-show8. Créer un template d’affichage (show)

Pour gérer l’affichage des contacts dans le back-office, il faut créer un template spécifique. Voici un exemple de fichier application/templates/Admin/Crud/Contact/show.html.twig :

{% extends '@SyliusAdmin/layout.html.twig' %}

{% set header = configuration.vars.header|default(metadata.applicationName~'.ui.show_'~metadata.name) %}
{% set event_prefix = metadata.applicationName ~ '.admin.' ~ metadata.name ~ '.show' %}
{% set index_url = path(
	configuration.vars.index.route.name|default(configuration.getRouteName('index')),
	configuration.vars.index.route.parameters|default(configuration.vars.route.parameters|default({}))
)
%}

{% block title %}{{ header|trans }} {{ parent() }}{% endblock %}

{% block content %}
	<div class="ui stackable two column grid">
    	<div class="column">
        	{# Deux fichiers que j'ai créés #}
        	{% include 'Admin/Crud/Contact/_header.html.twig' %}
        	{% include 'Admin/Crud/Contact/_breadcrumb.html.twig' %}
    	</div>
	</div>

	<div class="ui segment">
    	<div class="ui stackable grid">
        	<div class="three column">
            	<div class="ui segment sylius-grid-table-wrapper">
                	<table class="ui very basic celled table">
                    	<tbody>
                    	<tr class="item">
                        	<td class="two wide column">{{ 'admin.ui.show_contact.form.date'|trans}}</td>
                        	<td>{{ resource.createdAt|date('d-m-Y à H:m') }}</td>
                    	</tr>
                    	<tr class="item">
                        	<td>{{ 'admin.ui.show_contact.form.firstname'|trans}}</td>
                        	<td>{{ resource.firstname }}</td>
                    	</tr>
                    	<tr class="item">
                        	<td>{{ 'admin.ui.show_contact.form.lastname'|trans}}</td>
                        	<td>{{ resource.lastname }}</td>
                    	</tr>
                    	<tr class="item">
                        	<td>{{ 'admin.ui.show_contact.form.email'|trans}}</td>
                        	<td>{{ resource.email }}</td>
                    	</tr>
                    	<tr class="item">
                        	<td>{{ 'admin.ui.show_contact.form.phone'|trans}}</td>
                        	<td>{{ resource.phone }}</td>
                    	</tr>
                    	<tr class="item">
                        	<td>{{ 'admin.ui.show_contact.form.message'|trans}}</td>
                        	<td>{{ resource.message }}</td>
                    	</tr>

                    	</tbody>
                	</table>
            	</div>
        	</div>
    	</div>
	</div>

{% endblock %}

Pour ce fichier, je me suis appuyée sur application/vendor/sylius/sylius/src/Sylius/Bundle/AdminBundle/Resources/views/Crud/update.html.twig.

Alors, maintenant, comment on indique qu’il faut utiliser ce template ?

On va devoir créer une nouvelle route

# config/routes.yaml
app_admin_contact_show:
    path: /admin/contacts/{id}/show
    methods: [GET]
    defaults:
        _controller: App\Controller\Admin\ContactController::showAction
        _sylius:
            section: admin
            permission: true
            redirect: referer
            template: "/Admin/Crud/Contact/show.html.twig"
            vars:
                icon: eye
                header: admin.ui.show_contact.header # override key message
                breadcrumb: admin.ui.contact.header

On en profite pour supprimer les routes inutiles sur la configuration précédente avec l’option except :

app_admin_contact:
    resource: |
        alias: app.contact
        section: admin
        templates: "@SyliusAdmin\\Crud"
        grid: app_contact
        except: ['create', 'show', 'update', 'delete']
        vars:
            all:
                header: admin.ui.contact.header
                subheader: admin.ui.contact.subheader
                breadcrumb: admin.ui.contact.header
    type: sylius.resource
    prefix: /admin

Et dans la route de l’action show, on doit déclarer un nouveau contrôleur. Rien besoin de mettre dans cette classe à part qu’il étend ResourceController et on l’ajoute à la config de la ressource :

sylius_resource:
    resources:
        app.contact:  # alias
            driver: doctrine/orm
            classes:
                controller: App\Controller\Admin\ContactController
                model: App\Entity\Contact\Contact
                repository: App\Repository\ContactRepository

Et voilà :

Screen admin sylius contact show

Section intitulée conclusionConclusion

En suivant ces étapes, vous avez créé une entité personnalisée « Contact », et l’avez intégrée au back-office de Sylius avec une gestion complète via une grid, des routes et une entrée dans le menu d’administration. Cette méthode est assez flexible, vous permettant d’ajouter facilement de nouvelles entités à votre projet Sylius. L’intégration des entités custom dans le back-office de Sylius peut sembler complexe au premier abord, mais grâce à la modularité de Sylius et l’utilisation de bundles comme le SyliusGridBundle, le processus est un peu plus rapide. Vous pouvez désormais étendre votre back-office avec de nouvelles entités en toute simplicité !

Commentaires et discussions

Ces clients ont profité de notre expertise