Symfony 4/ Sonata : Créer une interface imbriquée (nested interface)

Nous allons voir comment on construit une interface d’admin, composée de plusieurs tables qui ont des relations Many2Many.

Reprenons notre exemple d’interface many/many disponible ici
Nous avons une table zone, qui est composé de plusieurs éléments de la table départements. Sur ces départements, nous avons des agences.
Pour couronner le tout, et pour donner du sens à cette chaîne de données, nous ajoutons une table zx_credential, qui représente des commerciaux.
Voici notre chaîne de donnée : Commerciaux->Zones->Départements->Agences.

data model workbench

Pour notre projet, nous avons modélisé l’ensemble de la base via MysqWorkbench, et exporté le schéma dans MySql.

Il ne nous reste plus qu’à exporter les entity (avec getter et setters), et créer les CRUD Sonata

php bin/console doctrine:mapping:import "App\Entity" annotation --path=src/Entity
php bin/console make:entity --regenerate App
php bin/console make:sonata:admin App/Entity/ZxZone
php bin/console make:sonata:admin App/Entity/ZxCredential
php bin/console make:sonata:admin App/Entity/Agences
php bin/console make:sonata:admin App/Entity/Departement
php bin/console cache:clear

Pour la forme, nous renommons notre table « zx_credential » en « Accès commerciaux ».
Dans le fichier service.yaml il suffit de modifier l’argument « Label ».

    admin.zx_credential:
        class: App\Admin\ZxCredentialAdmin
        arguments: [~, App\Entity\ZxCredential, App\Controller\ZxCredentialAdminController]
        tags:
            - { name: sonata.admin, manager_type: orm, group: admin, label: "Accès commerciaux" }
        public: true
Sélection_131

Jusqu’ici, rien de plus classique. Mais ce que l’on souhaite c’est avoir une interface avec toute la chaîne de donnée liée entre elle, de sorte que lorsque l’on édite un commercial, on ait la possibilité d’aller directement dans la configuration de sa zone, puis ses départements, puis ses agences.
Pour cela, nous allons spécifier les liaisons des tables entre elles via un appel à la méthode « addChild » dans le service de notre CRUD. Et nous allons spécifié les enfants de chaques tables.
Pour que le process puisse fonctionner nous devons spécifier le service enfant, ainsi que le champs du parent utilisé pour faire la liaison.

Dans notre liaison ZxCredential->ZxZone, nous avons le champs suivant dans notre entity child (ZxZone) :

Sélection_133

C’est donc celui-ci qui est utilisé pour faire la liaison, et que nous allons utiliser dans notre configuration.

    admin.zx_credential:
        class: App\Admin\ZxCredentialAdmin
        arguments: [~, App\Entity\ZxCredential, App\Controller\ZxCredentialAdminController]
        calls:
            - [addChild, ["@admin.zx_zone", 'zxCredential']] 
        tags:
            - { name: sonata.admin, manager_type: orm, group: admin, label: "Accès commerciaux" }
        public: true

La configuration complète nous donne ceci :

   admin.departement:
        class: App\Admin\DepartementAdmin
        arguments: [~, App\Entity\Departement, ~]
        calls:
            - [addChild, ["@admin.agences","departement"]] 
        tags:
            - { name: sonata.admin, manager_type: orm, group: admin, label: Departement }
        public: true
        
    admin.zx_zone:
        class: App\Admin\ZxZoneAdmin
        arguments: [~, App\Entity\ZxZone, App\Controller\ZxZoneAdminController]
        calls:
            - [addChild, ["@admin.departement", "zxZone"]] 
        tags:
            - { name: sonata.admin, manager_type: orm, group: admin, label: Zones }
        public: true

    admin.zx_credential:
        class: App\Admin\ZxCredentialAdmin
        arguments: [~, App\Entity\ZxCredential, App\Controller\ZxCredentialAdminController]
        calls:
            - [addChild, ["@admin.zx_zone", 'zxCredential']] 
        tags:
            - { name: sonata.admin, manager_type: orm, group: admin, label: "Accès commerciaux" }
        public: true

    admin.agences:
        class: App\Admin\AgencesAdmin
        arguments: [~, App\Entity\Agences, ~]
        tags:
            - { name: sonata.admin, manager_type: orm, group: admin, label: Agences }
        public: true

A ce stade, toutes nos tables sont liées, mais il faudra configurer le menu pour naviguer entre elles.
Pour cela, nous allons ajouter la méthode configureSideMenu dans notre admin des commerciaux « ZxCredentialAdmin ».
Attention, c’est dans le point d’entré de l’interface qu’il faut configurer le menu. Donc toute la chaîne Commerciaux->Zones->Départements->Agences va se faire dans Commerciaux.

1iere étape, on ajoute les use dans toutes nos interfaces d’admin, comme ça c’est bouclé.

<?php

declare(strict_types=1);

namespace App\Admin;

use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Show\ShowMapper;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;

/*gestion de nos interfaces imbriquées*/
use Knp\Menu\ItemInterface as MenuItemInterface;
use Sonata\AdminBundle\Admin\AdminInterface;
use Sonata\AdminBundle\Route\RouteCollection;

Ensuite on ajoute notre premier lien vers la gestion des zones, depuis l’interface Zx_Credential.

   protected function configureSideMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null): void
    {
        if (!$childAdmin && !\in_array($action, ['edit'], true)) {
            return;
        }
         
        $admin = $this->isChild() ? $this->getParent() : $this;
        $id = $admin->getRequest()->get('id');
        $label=$this->hasSubject() && null !== $this->getSubject()->getLabel() ? $this->getSubject()->getLabel():null;
        

    
        $menu->addChild(
            'Configuration de l\'accès commercial : '.$label,
            $admin->generateMenuUrl('edit', ['id' => $id])
             
            );
    
        $child=$menu->addChild( 'Listes des zones',
            [
                'uri' => $admin->generateUrl('admin.zx_zone.list', ['id' => $id])
            ]);
        
        
        
    }
    

En gros, ce qu’il faut retenir, et qui n’est pas clair dans la documentation, c’est la route à configurer. Il nous faut le nom de la route, puis l’id.
Pour l’id, c’est simple, pour un premier niveaux, c’est toujours :

$id = $admin->getRequest()->get('id');

Et pour la route, c’est toujours la même chose, c’est le nom du service suivit du type de la vue. Donc ici admin.zx_zone.list
Ce qui nous donne :

$child=$menu->addChild( 'Listes des zones',
            [
                'uri' => $admin->generateUrl('admin.zx_zone.list', ['id' => $id])
            ]);
commercial_zone-1

A ce stade, nous avons notre première interface imbriquée. Pour les suivantes, nous allons répéter la configuration.
Ce qui suit n’est pas expliqué dans la documentation, car c’est d’une logique implacable. Mais vous pourrez chercher longtemps si vous n’avez pas compris le fonctionnement de l’imbrication des interfaces.
Ce qu’il faut bien comprendre c’est que tout se fait à partir de la première interface. L’inclusion des boutons du menu et la construction des routes.
La premier, puis la première+la seconde, puis la premier+la seconde+ la troisième.

L’interface doit se construire toujours dans notre première interface, pour chaque étapes. Pour cela nous avons besoin de récupérer l’id à chaque étapes, et construire la route.
Et pour récupérer les clés, le truc c’est de scruter les routes qui sont générées par symfony, via la commande debug:router

php bin/console debug:router
---------------------------------------------------------- ---------- -------- ------ ------------------------------------------------------------------------------------------------------------- 
  Name                                                       Method     Scheme   Host   Path                                                                                                         
 ---------------------------------------------------------- ---------- -------- ------ ------------------------------------------------------------------------------------------------------------- 
  _preview_error                                             ANY        ANY      ANY    /_error/{code}.{_format}                                                                                     
  _wdt                                                       ANY        ANY      ANY    /_wdt/{token}                                                                                                
  _profiler_home                                             ANY        ANY      ANY    /_profiler/                                                                                                  
  _profiler_search                                           ANY        ANY      ANY    /_profiler/search                                                                                            
  _profiler_search_bar                                       ANY        ANY      ANY    /_profiler/search_bar                                                                                        
  _profiler_phpinfo                                          ANY        ANY      ANY    /_profiler/phpinfo                                                                                           
  _profiler_search_results                                   ANY        ANY      ANY    /_profiler/{token}/search/results                                                                            
  _profiler_open_file                                        ANY        ANY      ANY    /_profiler/open                                                                                              
  _profiler                                                  ANY        ANY      ANY    /_profiler/{token}                                                                                           
  _profiler_router                                           ANY        ANY      ANY    /_profiler/{token}/router                                                                                    
  _profiler_exception                                        ANY        ANY      ANY    /_profiler/{token}/exception                                                                                 
  _profiler_exception_css                                    ANY        ANY      ANY    /_profiler/{token}/exception.css   
  admin_app_departement_list                                 ANY        ANY      ANY    /admin/app/departement/list                                                                                  
  admin_app_departement_create                               ANY        ANY      ANY    /admin/app/departement/create                                                                                
  admin_app_departement_batch                                ANY        ANY      ANY    /admin/app/departement/batch                                                                                 
  admin_app_departement_edit                                 ANY        ANY      ANY    /admin/app/departement/{id}/edit                                                                             
  admin_app_departement_delete                               ANY        ANY      ANY    /admin/app/departement/{id}/delete                                                                           
  admin_app_departement_show                                 ANY        ANY      ANY    /admin/app/departement/{id}/show                                                                             
  admin_app_departement_export                               ANY        ANY      ANY    /admin/app/departement/export                                                                                
  admin_app_departement_agences_list                         ANY        ANY      ANY    /admin/app/departement/{id}/agences/list                                                                     
  admin_app_departement_agences_create                       ANY        ANY      ANY    /admin/app/departement/{id}/agences/create                                                                   
  admin_app_departement_agences_batch                        ANY        ANY      ANY    /admin/app/departement/{id}/agences/batch                                                                    
  admin_app_departement_agences_edit                         ANY        ANY      ANY    /admin/app/departement/{id}/agences/{childId}/edit                                                           
  admin_app_departement_agences_delete                       ANY        ANY      ANY    /admin/app/departement/{id}/agences/{childId}/delete                                                         
  admin_app_departement_agences_show                         ANY        ANY      ANY    /admin/app/departement/{id}/agences/{childId}/show                                                           
  admin_app_departement_agences_export                       ANY        ANY      ANY    /admin/app/departement/{id}/agences/export                                                                   
  admin_app_zxcredential_list                                ANY        ANY      ANY    /admin/app/zxcredential/list                                                                                 
  admin_app_zxcredential_create                              ANY        ANY      ANY    /admin/app/zxcredential/create                                                                               
  admin_app_zxcredential_batch                               ANY        ANY      ANY    /admin/app/zxcredential/batch                                                                                
  admin_app_zxcredential_edit                                ANY        ANY      ANY    /admin/app/zxcredential/{id}/edit                                                                            
  admin_app_zxcredential_delete                              ANY        ANY      ANY    /admin/app/zxcredential/{id}/delete                                                                          
  admin_app_zxcredential_show                                ANY        ANY      ANY    /admin/app/zxcredential/{id}/show                                                                            
  admin_app_zxcredential_export                              ANY        ANY      ANY    /admin/app/zxcredential/export                                                                               
  admin_app_zxcredential_zxzone_list                         ANY        ANY      ANY    /admin/app/zxcredential/{id}/zxzone/list                                                                     
  admin_app_zxcredential_zxzone_create                       ANY        ANY      ANY    /admin/app/zxcredential/{id}/zxzone/create                                                                   
  admin_app_zxcredential_zxzone_batch                        ANY        ANY      ANY    /admin/app/zxcredential/{id}/zxzone/batch                                                                    
  admin_app_zxcredential_zxzone_edit                         ANY        ANY      ANY    /admin/app/zxcredential/{id}/zxzone/{childId}/edit                                                           
  admin_app_zxcredential_zxzone_delete                       ANY        ANY      ANY    /admin/app/zxcredential/{id}/zxzone/{childId}/delete                                                         
  admin_app_zxcredential_zxzone_show                         ANY        ANY      ANY    /admin/app/zxcredential/{id}/zxzone/{childId}/show                                                           
  admin_app_zxcredential_zxzone_export                       ANY        ANY      ANY    /admin/app/zxcredential/{id}/zxzone/export                                                                   
  admin_app_zxcredential_zxzone_departement_list             ANY        ANY      ANY    /admin/app/zxcredential/{id}/zxzone/{childId}/departement/list                                               
  admin_app_zxcredential_zxzone_departement_create           ANY        ANY      ANY    /admin/app/zxcredential/{id}/zxzone/{childId}/departement/create                                             
  admin_app_zxcredential_zxzone_departement_batch            ANY        ANY      ANY    /admin/app/zxcredential/{id}/zxzone/{childId}/departement/batch                                              
  admin_app_zxcredential_zxzone_departement_edit             ANY        ANY      ANY    /admin/app/zxcredential/{id}/zxzone/{childId}/departement/{childChildId}/edit                                
  admin_app_zxcredential_zxzone_departement_delete           ANY        ANY      ANY    /admin/app/zxcredential/{id}/zxzone/{childId}/departement/{childChildId}/delete                              
  admin_app_zxcredential_zxzone_departement_show             ANY        ANY      ANY    /admin/app/zxcredential/{id}/zxzone/{childId}/departement/{childChildId}/show                                
  admin_app_zxcredential_zxzone_departement_export           ANY        ANY      ANY    /admin/app/zxcredential/{id}/zxzone/{childId}/departement/export                                             
  admin_app_zxcredential_zxzone_departement_agences_list     ANY        ANY      ANY    /admin/app/zxcredential/{id}/zxzone/{childId}/departement/{childChildId}/agences/list                        
  admin_app_zxcredential_zxzone_departement_agences_create   ANY        ANY      ANY    /admin/app/zxcredential/{id}/zxzone/{childId}/departement/{childChildId}/agences/create                      
  admin_app_zxcredential_zxzone_departement_agences_batch    ANY        ANY      ANY    /admin/app/zxcredential/{id}/zxzone/{childId}/departement/{childChildId}/agences/batch                       
  admin_app_zxcredential_zxzone_departement_agences_edit     ANY        ANY      ANY    /admin/app/zxcredential/{id}/zxzone/{childId}/departement/{childChildId}/agences/{childChildChildId}/edit    
  admin_app_zxcredential_zxzone_departement_agences_delete   ANY        ANY      ANY    /admin/app/zxcredential/{id}/zxzone/{childId}/departement/{childChildId}/agences/{childChildChildId}/delete  
  admin_app_zxcredential_zxzone_departement_agences_show     ANY        ANY      ANY    /admin/app/zxcredential/{id}/zxzone/{childId}/departement/{childChildId}/agences/{childChildChildId}/show    
  admin_app_zxcredential_zxzone_departement_agences_export   ANY        ANY      ANY    /admin/app/zxcredential/{id}/zxzone/{childId}/departement/{childChildId}/agences/export                      
  admin_app_agences_list                                     ANY        ANY      ANY    /admin/app/agences/list                                                                                      
  admin_app_agences_create                                   ANY        ANY      ANY    /admin/app/agences/create                                                                                    
  admin_app_agences_batch                                    ANY        ANY      ANY    /admin/app/agences/batch                                                                                     
  admin_app_agences_edit                                     ANY        ANY      ANY    /admin/app/agences/{id}/edit                                                                                 
  admin_app_agences_delete                                   ANY        ANY      ANY    /admin/app/agences/{id}/delete                                                                               
  admin_app_agences_show                                     ANY        ANY      ANY    /admin/app/agences/{id}/show    

Et notre attention particuliere va se porter sur notre route admin_app_zxcredential_zxzone_departement_agences_list qui comporte toute notre chaine de donnée.
Elle est composée de nos tables avec le nom des id que nous allons récupéré pour construire les routes de nos interfaces.
/admin/app/zxcredential/{id}/zxzone/{childId}/departement/{childChildId}/agences/list
zxcredential : id
zxzone : childId
departement : childChildId

Donc en testant la présence de ces variables dans nos urls entrantes, nous allons pouvoir définir la profondeur de notre interface et construire les menus en conséquences.

Pour les routes c’est simple, pour chaques étapes, on rajoute la route courrante séparé par un pipe | :
zxcredential : admin.zx_zone.list
zxzone : admin.zx_zone|admin.departement.list
departement : admin.zx_zone|admin.departement|admin.agences.list

Ce qui nous donnes :

   protected function configureSideMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null): void
    {
        if (!$childAdmin && !\in_array($action, ['edit'], true)) {
            return;
        }
         
        $admin = $this->isChild() ? $this->getParent() : $this;
        $id = $admin->getRequest()->get('id');
        $label=$this->hasSubject() && null !== $this->getSubject()->getLabel() ? $this->getSubject()->getLabel():null;
        

    
        $menu->addChild(
            'Configuration de l\'accès commercial : '.$label,
            $admin->generateMenuUrl('edit', ['id' => $id])
             
            );
    
        $child=$menu->addChild( 'Listes des zones',
            [
                'uri' => $admin->generateUrl('admin.zx_zone.list', [
                    'id' => $id
                    
                ])
            ]);
        
        if(!empty($admin->getRequest()->get('childId'))){
            $child=$menu->addChild( 'Listes des departements',
                [
                    'uri' => $admin->generateUrl('admin.zx_zone|admin.departement.list', [
                        'id' => $id, 
                        'childId' => $admin->getRequest()->get('childId')
                        
                    ])
                ]);
        }
        if(!empty($admin->getRequest()->get('childChildId'))){
            $child=$menu->addChild( 'Listes des agences',
                [
                    'uri' => $admin->generateUrl('admin.zx_zone|admin.departement|admin.agences.list', [
                        'id' => $id, 
                        'childId' => $admin->getRequest()->get('childId'),
                        'childChildId' => $admin->getRequest()->get('childChildId')
                    ])
                ]);
        }

    }

Notre interface est maintenant complète :

commercial_to_agence