DevFusionMakerBundle : Générer du code scrud pour vos application Symfony.

Installation

Avec le Symfony website skeleton 4 / 5 exécutez cette commande pour installer et intégrer le DevFusionMakerBundle dans votre application:

composer require devfusion/maker-bundle --dev

Le code généré par cet outil peut utiliser des filtres de twig/intl-extra.

Il est donc conseillé d’installer les extensions de twig/extra-bundle pour les configurer.

composer require twig/intl-extra
composer require twig/extra-bundle

Le code généré utilise également Bootstrap 4, jQuery 3 et Font Awesome 5.

Vous pouvez utiliser Webpack Encore pour les installer.

Ou vous pouvez cloner un projet de base pour tester rapidement :

git clone --single-branch --branch user-area-with-back-office git@github.com:official-dev-fusion/dev-fusion-skeleton-user.git

C’est tout, vous êtes maintenant prêt pour générer votre premier code scrud.

Votre premier code scrud

La génération de votre premier code scrud vous prendra moins de 30 secondes si votre application Symfony définit déjà certaines entités ORM Doctrine.

Sinon, créez des entités avant de continuer (vous pouvez utiliser la commande make: entity de Symfony MakerBundle pour les générer rapidement).

Pour l’exemple, considérons que l’application définit une entité Gnome et un repository GnomeRepository :

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="App\Repository\GnomeRepository")
 */
class Gnome
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $name;

    /**
     * @ORM\Column(type="date")
     */
    private $birthdate;

    /**
     * @ORM\Column(type="decimal", precision=3, scale=2)
     */
    private $size;

    /**
     * @ORM\Column(type="decimal", precision=5, scale=2)
     */
    private $weight;

    /**
     * @ORM\Column(type="text")
     */
    private $biography;
    
    public function __toString(): string
    {
        return $this->name;
    }
    
    public function getId(): ?int
    {
        return $this->id;
    }
    
    // ...
}

Il faut d’abord générer un fichier de configuration :

php bin/console df:scrud:config Gnome --level=0

OK
 Next: Check your new SCRUD configuration file by going to config/dev_fusion/scrud/gnome.yaml

Le fichier de configuration généré :

entities:
    Gnome:
        class: App\Entity\Gnome

C’est la configuration minimale pour générer un code scrud.

Vous pouvez générer plusieurs codes scrud liés à un ou plusieurs entités avec un fichier de configuration.

Les entités sont configurées en tant qu’éléments sous la clé entities. Le nom des éléments est utilisé comme clé YAML. Ces noms doivent être uniques dans le code et il est recommandé d’utiliser la syntaxe CamelCase (par exemple PostCategory et non post_category ou postCategory).

La seule option de configuration requise est la clé YAML class et la valeur de cette dernière définit le nom complet de la classe de l’entité Doctrine.

Il est conseillé d’ajouter vos fichiers modifiés dans la zone de staging git avant de lancer la génération du code. De cette manière, vous allez pouvoir revenir en arrière avec la commande git clean -df si vous avez un résultat inattendu.

git add .

Vous pouvez lancer la génération du code :

php bin/console df:scrud:exec gnome.yaml

 updated: src/Repository/Gnomeitory.php
 updated: translations/messages.en.yaml
 updated: translations/messages.fr.yaml
 created: src/Form/GnomeType.php
 created: src/Form/GnomeFilterType.php
 created: src/Manager/GnomeManager.php
 created: src/Form/GnomeBatchType.php
 created: src/Controller/GnomeController.php
 created: templates/gnome/search/_filter.html.twig
 created: templates/gnome/search/index.html.twig
 created: templates/gnome/search/_list.html.twig
 created: templates/gnome/search/_pagination.html.twig
 created: templates/gnome/create.html.twig
 created: templates/gnome/read.html.twig
 created: templates/gnome/update.html.twig
 created: templates/gnome/delete.html.twig


  Success!


 Next: Check your new SCRUD by going to /gnome/search/

C’est tout, vous pouvez parcourir le code généré et constater le résultat.

Le DevFusionMakerBundle a généré par défaut :

  • Des fichiers de traduction;
    • Toute les chaînes de caractères de chaque page sont traduites;
    • Un fichier de traduction pour l’anglais;
    • Un fichier de traduction correspondant à la locale par défaut de l’application;
  • Une page de recherche;
    • Un filtre pouvant chercher dans les éléments texte de l’entité;
    • Une pagination dont le nombre d’éléments par page est modifiable;
    • Une sélection multiple permettant de supprimer plusieurs éléments à la fois;
    • Une case à cocher pour sélectionner tous les éléments de la page;
    • Une modale jQuery pour confirmer la suppression d’un élément;
  • Une page de création;
  • Une page de lecture pour voir les détails d’un élément;
  • Une page de modification;
  • Une page de confirmation pour la multi suppression;

Ce n’est pas le nombre de pages et de fonctionnalités généré automatiquement qui fait la force du DevFusionMakerBundle.

C’est plutôt la possibilité de paramétrer précisément ce que vous voulez et la structure maintenable et réutilisable du code généré.

Vous pouvez activer ou désactiver chacune des fonctionnalités, influencer la structure du code, choisir les éléments de l’entités qui seront affiché dans chaque pages, ajouter des filtres twig et configurer les formulaires.

Pour avoir une meilleure idée des options de configuration vous pouvez utiliser la commande df:scrud:config avec trois niveau différent 0, 1 et 2 avec l’option –level.

Ex :

php bin/console df:scrud:config --level=2

Générer plusieurs pages de recherche pour la même entité

Dans certains cas, une application doit afficher plusieurs pages de recherche pour le même type d’élément.

Vous pourriez avoir besoin d’afficher une page pour les petits gnomes et une page pour les grands gnomes.

Ces pages n’auraient pas besoin d’être liées à une page de création, de modification et de suppression.

Voici à quoi le fichier de configuration pourrait ressembler :

entities:
    SmallGnome:
        class: App\Entity\Gnome
        search:
            dql_filter: 'entity.size <= 1.5'
            pagination: true
            multi_select: false
            filter_view:
                activate: false
        create:
            activate: false
        read:
            activate: false
        update:
            activate: false
            multi_select: false
        delete:
            activate: false
            multi_select: false
    GreatGnome:
        class: App\Entity\Gnome
        search:
            dql_filter: 'entity.size > 1.5'
            pagination: true
            multi_select: false
            filter_view:
                activate: false
        create:
            activate: false
        read:
            activate: false
        update:
            activate: false
            multi_select: false
        delete:
            activate: false
            multi_select: false
  • dql_filter: ‘entity.size <= 1.5’ pour les petits gnomes;
  • dql_filter: ‘entity.size > 1.5’ pour les grands gnomes;
  • Activation de la pagination;
  • Désactivation de toutes les autres fonctionnalités;

Vous pouvez tester la génération :

php bin/console df:scrud:exec gnome.yaml

 updated: src/Repository/GnomeRepository.php
 updated: translations/messages.en.yaml
 updated: translations/messages.fr.yaml
 created: src/Controller/SmallGnomeController.php
 created: templates/small_gnome/search/index.html.twig
 created: templates/small_gnome/search/_list.html.twig
 created: templates/small_gnome/search/_pagination.html.twig


  Success!


 Next: Check your new SCRUD by going to /small/gnome/search/
 
 updated: src/Repository/GnomeRepository.php
 updated: translations/messages.en.yaml
 updated: translations/messages.fr.yaml
 created: src/Controller/GreatGnomeController.php
 created: templates/great_gnome/search/index.html.twig
 created: templates/great_gnome/search/_list.html.twig
 created: templates/great_gnome/search/_pagination.html.twig


  Success!


 Next: Check your new SCRUD by going to /great/gnome/search/

N’oubliez pas de faire un cache:clear pour voir la traduction.

Le manager est généré seulement si le filter_view ou le multi_select est activé.

Configuration de base

Changer l’URI du contrôleur

Vous pouvez préfixer l’URI d’un contrôleur.

Par exemple :

  • /gnome/search
  • /gnome/create
  • /gnome/read/{id}
  • /gnome/update/{id}
  • /gnome/delete?ids[0]={id}

Peut devenir :

  • /admin/gnome/search
  • /admin/gnome/create
  • /admin/gnome/read/{id}
  • /admin/gnome/update/{id}
  • /admin/gnome/delete?ids[0]={id}

En utilisant la configuration :

entities:
    Gnome:
        class: App\Entity\Gnome
        prefix_route: /admin

Générer le code dans un sous-dossier

La structure des dossiers doit parfois être modifié si vous avez par exemple un backend et un frontend.

Vous pourriez avoir besoin de lister les produits dans le frontend pour les présenter au client du site et les lister dans le backend pour permettre aux administrateurs de les gérer.

Pour simplifier le code, il peut être bon d’avoir deux ProductControlleur. Un dans le dossier Back et l’autre dans le dossier Front…

Controller -> Back -> Front

Form -> Back -> Front

templates -> back -> front

# config/dev_fusion/scrud/back_gnome.yaml
entities:
    Gnome:
        class: App\Entity\Gnome
        prefix_route: back
        prefix_directory: back
php bin/console df:scrud:exec back_gnome.yaml

 updated: src/Repository/GnomeRepository.php
 created: translations/back_messages.en.yaml
 created: translations/back_messages.fr.yaml
 created: src/Form/Back/GnomeType.php
 created: src/Form/Back/GnomeFilterType.php
 created: src/Manager/Back/GnomeManager.php
 created: src/Form/Back/GnomeBatchType.php
 created: src/Controller/Back/GnomeController.php
 created: templates/back/gnome/search/_filter.html.twig
 created: templates/back/gnome/search/index.html.twig
 created: templates/back/gnome/search/_list.html.twig
 created: templates/back/gnome/search/_pagination.html.twig
 created: templates/back/gnome/create.html.twig
 created: templates/back/gnome/read.html.twig
 created: templates/back/gnome/update.html.twig
 created: templates/back/gnome/delete.html.twig


  Success!


 Next: Check your new SCRUD by going to /back/gnome/search

# config/dev_fusion/scrud/front_gnome.yaml
entities:
    Gnome:
        class: App\Entity\Gnome
        prefix_directory: front
        search:
            pagination: true
            multi_select: false
            filter_view:
                activate: false
        create:
            activate: false
        read:
            activate: false
        update:
            activate: false
            multi_select: false
        delete:
            activate: false
            multi_select: false
php bin/console df:scrud:exec front_gnome.yaml
----
 updated: src/Repository/GnomeRepository.php
 created: translations/front_messages.en.yaml
 created: translations/front_messages.fr.yaml
 created: src/Controller/Front/GnomeController.php
 created: templates/front/gnome/search/index.html.twig
 created: templates/front/gnome/search/_list.html.twig
 created: templates/front/gnome/search/_pagination.html.twig


  Success!


 Next: Check your new SCRUD by going to /gnome/search
----

Générer un voter pour restreindre l’accès aux ressources

Pour utiliser les voters, vous devez comprendre comment Symfony travaille avec eux :

Tous les voters sont appelés chaque fois que vous utilisez la méthode denyAccessUnlessGranted() dans un contrôleur.

Les voters sont très utiles si la condition d’accès à l’action du contrôleur est dynamique. C’est-à-dire que vous n’avez pas seulement à vérifier si l’utilisateur courant a un rôle en particulier.

Vous pourriez par exemple autoriser un utilisateur à modifier un projet seulement s’il fait partie de l’équipe de celui-ci et qu’il a le rôle manager.

Symfony prend les réponses de tous les voters et prend la décision finale (autoriser ou refuser l’accès à la ressource) selon la stratégie définie dans l’application.

Le DevFusionMakerBundle utilise une clé pour préciser quel voter doit prendre la décision.

Chacun des voters vérifie s’il a la clé dans sa liste et renvoie faux sinon.

Pour qu’une requête à une ressource puisse être autorisée, il faut au moins un voter qui renvoie vrai.

Pour que la clé soit unique, le nom de la route de l’action est utilisé.

entities:
    Gnome:
        class: App\Entity\Gnome
        voter: true
  • Si vous passez le voter à true, vous devez avoir une entité User;
  • Par défaut, un utilisateur non connecté n’a pas l’accès aux ressources;
  • Par défaut, le rôle admin a le droit de tout faire;
  • Par défaut, tous les autres rôles ne peuvent pas créer, modifier et supprimer, mais ils peuvent rechercher et lire;
  • Une méthode can est créée pour chacune des actions du contrôleur;

Vous pouvez personnaliser la logique d’accès dans la classe du voter :

<?php

namespace App\Security\Voter;

use App\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Security;

class GnomeVoter extends Voter
{
    
    const SEARCH = 'gnome_search';
    const CREATE = 'gnome_create';
    const READ = 'gnome_read';
    const UPDATE = 'gnome_update';
    const DELETE = 'gnome_delete';
    
    /**
     * @var Security
     */
    private $security;
    
    /** 
     * @param Security $security
     */
    public function __construct(Security $security)
    {
        $this->security = $security;
    }
    
    protected function supports($attribute, $subject)
    {        
        return in_array($attribute, [
            self::SEARCH,
            self::CREATE,
            self::READ,
            self::UPDATE,
            self::DELETE
        ]);
    }

    protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
    {
        $user = $token->getUser();
        // if the user is anonymous, do not grant access
        if (!$user instanceof UserInterface) {
            return false;
        }
        
        if ($this->security->isGranted('ROLE_ADMIN')) {
            return true;
        }

        // ... (check conditions and return true to grant permission) ...
        switch ($attribute) {
            case self::SEARCH:
                return $this->canSearch($subject, $user);
            case self::CREATE:
                return $this->canCreate($subject, $user);
            case self::READ:
                return $this->canRead($subject, $user);
            case self::UPDATE:
                return $this->canUpdate($subject, $user);
            case self::DELETE:
                return $this->canDelete($subject, $user);
        }
        throw new \LogicException('This code should not be reached!');
    }
    
    private function canSearch($subject, User $user)
    {
        return true;
    }

    private function canCreate($subject, User $user)
    {
        return true;
    }

    private function canRead($subject, User $user)
    {
        return true;
    }

    private function canUpdate($subject, User $user)
    {
        return false;
    }

    private function canDelete($subject, User $user)
    {
        return false;
    }
}

Redéfinir le squelette du code généré

Le DevFusionMakerBundle donne la possibilité de réécrire les squelettes de génération.

Vous pouvez copier des fichiers de vendor/devfusion/maker-bundle/Resources/skeleton/scrud_bootstrap_4/ vers templates/bundles/DFMakerBundle/scrud_bootstrap_4/ et les modifier.

Vous pouvez même modifier le nom du dossier scrud_bootstrap_4/ par un autre nom ce qui permet d’avoir plusieurs squelettes de génération.

Par exemple :

entities:
    Gnome:
        class: App\Entity\Gnome
        skeleton: back_scrud_bootstrap_4 # Default scrud_bootstrap_4
        prefix_directory: back
        # ...
entities:
    Gnome:
        class: App\Entity\Gnome
        skeleton: front_scrud_bootstrap_4 # Default: scrud_bootstrap_4
        prefix_directory: front
        # ...

Recherche et lecture

Choisir et trier les propriétés

Par défaut, les vues recherche et lecture affichent toutes les propriétés de l’entité.

Pour décider quelles propriétés générer, utilisez l’option fields pour les définir explicitement.

entities:
    Gnome:
        class: App\Entity\Gnome
        fields: [ name, birthdate, biography ]

Cette option est également utile pour réorganiser les propriétés, car par défaut, elles sont affichées dans le même ordre que celui défini dans l’entité.

Si la vue de recherche doit être différente de la vue lecture, l’option fields peut être redéfinie dans les sous-clés yaml search et read.

entities:
    Gnome:
        class: App\Entity\Gnome
        search:
            fields: [ name, birthdate ]
        read:
            activate: true
            fields: [ name, birthdate, biography ]

Personnaliser la génération des propriétés

Lorsque les propriétés d’entité ne sont pas configurées, le DevFusionMakerBundle les génère de la manière la plus appropriée en fonction de leurs types de données et leurs noms. Si vous préférez contrôler d’avantage le code généré, vous pouvez utiliser la configuration de champ étendue.

Au lieu d’utiliser une chaîne pour définir la propriété (par exemple ‘email’), vous devez définir les options avec le nom de la propriété ({property: ‘email’}) et les options que vous souhaitez définir.

Options disponibles :

  • ‘type’: Les types de base Doctrine, Selon le type un filtre twig peut être ajouté par défaut;
  • ‘label_key_trans’: La clé yaml de traduction;
  • ‘label’: Titre de la propriété;
  • ‘twig_filters’: un tableau de filtre twig;

Ex :

entities:
    Gnome:
        class: App\Entity\Gnome
        fields:
            - name
            - birthdate
            - { property: biography, twig_filters: [ nl2br ] }

Les propriétés virtuelles

Parfois, il est utile d’afficher des valeurs qui ne sont pas des propriétés d’entité. Par exemple, si votre entité Client définit les propriétés firstName et lastName, vous souhaiterez peut-être afficher une colonne appelée Nom avec les deux valeurs fusionnées. Celles-ci sont appelées propriétés virtuelles car elles n’existent pas vraiment en tant que propriétés d’entité Doctrine.

Ajoutez d’abord la propriété virtuelle à la configuration d’entité comme toute autre propriété:

Les sous-propriétés

Parfois, il est utile d’afficher des propriétés d’une relation OneToOne ou ManyToOne.

Vous pourriez par exemple afficher le contact d’un gnome :

entities:
    Gnome:
        class: App\Entity\Gnome
        fields: [ name, contact.email, contact.phone ]

Tri des listes d’entités

Par défaut, la vue de recherche est triée dans l’ordre décroissant en fonction de la valeur de la clé primaire. Vous pouvez trier par n’importe quelle autre propriété d’entité à l’aide de l’option de configuration de tri :

entities:
    Gnome:
        class: App\Entity\Gnome
        search:
            order:
                - { by: entity.id, direction: DESC }

Afficher les actions dans la page lecture

Vous pouvez ajouter le lien des actions modifier et supprimer dans la page de lecture.

entities:
    Gnome:
        class: App\Entity\Gnome
        read:
            activate: true
            action_up: true
            action_down: true

Créer et modifier

Choisir et trier les champs de formulaire

Comme pour les propriétés, vous pouvez modifier l’ordre et le nombre de champs de formulaire dans la page de création et de modification.

entities:
    Gnome:
        class: App\Entity\Gnome
        forms: [ name, birthdate, biography ]

Si le formulaire de création doit être différent du formulaire de modification l’option forms peut être redéfinie dans les sous-clés yaml create et update.

entities:
    Gnome:
        class: App\Entity\Gnome
        create:
            activate: true
            forms: [ name, birthdate ]
        update:
            activate: true
            forms: [ biography ]

Personnaliser la génération des formulaires

Lorsque les formulaires ne sont pas configurés, le DevFusionMakerBundle les génère de la manière la plus appropriée en fonction du type de données de l’entité. Si vous préférez contrôler d’avantage leur génération, vous pouvez utiliser la configuration de formulaire étendue.

Au lieu d’utiliser une chaîne pour définir la propriété (par exemple ‘email’), vous devez définir les options avec le nom de la propriété ({property: ‘email’}) et les options que vous souhaitez définir.

Options disponibles :

  • ‘type’: Le type du formulaire;
  • ‘type_options’: Les options du formulaire;
  • ‘type_class’: Le nom complet de la classe du form type;
  • ‘label_key_trans’: La clé yaml de traduction;
  • ‘label’: Le label du formulaire;

Ex (FOSCKEditorBundle doit être installé) :

entities:
    Gnome:
        class: App\Entity\Gnome
        forms:
            - name
            - { property: 'contact', type: 'ContactType', type_class: null }
            - birthdate
            - size
            - weight
            - { property: 'biography', type: 'CKEditorType', type_class: 'FOS\CKEditorBundle\Form\Type\CKEditorType' }