PrestaConcept
Nos réalisations
Nos métiers
  • Découvrez nos métiers
  • Développement web sur mesure

    Nous développons en méthode agile des back sous le framework PHP Symfony, des front en Angular.

  • Maintenance d'applications

    Maintien en condition opérationnelle de votre plateforme Symfony.

  • Expertise Symfony

    Coaching, formation, audit et conseil.

  • Hébergement et Infogérance

    Une expertise de l'hébergement depuis plus de 15 ans et l’infogérance de centaines de machines en production actuellement.

  • Qui sommes nous
  • Découvrez Prestaconcept
  • PrestaConcept

    Notre histoire, nos convictions, notre vision... Découvrez ce qui nous anime !

  • L'équipe

    Plus de 15 ans minimum d'expérience sur Symfony.

  • Nos engagements RSE

    Une société engagée pour un numérique responsable.

  • Nos convictions

    Co-construction, transparence.. Les principes qui guident nos collaborations !

  • Nous rejoindre

    Envie de nous rejoindre ? Consultez nos offres !

  • Blog
    J'ai un projet Nous contacter
    J'ai un projet Nous contacter Menu
    • Accueil
    • Blog
    • Tech
    • Librarie Open Source : YokaiEnumBundle

    Blog

    Librarie Open Source : YokaiEnumBundle

    bundle open source php symfony
    Yann Eugoné
    Yann Eugoné CTO
    Publié le vendredi 4 juin 2021

    Un bundle OpenSource qui apporte un système d'énumération totalement intégré à votre projet Symfony.

    Besoin

    Lorsque l'on créé une application il arrive fréquemment que nous cherchions à contraindre les valeurs possibles d'une propriété.

    Des propriétés pour lesquelles on souhaite ajouter une couche de validation et/ou pouvoir afficher à l'utilisateur un <select> (multiple ou non).

    Problématique

    Symfony ne propose pas de système tout prêt pour gérer ce cas d'usages (en tout cas, rien qui ne soit suffisamment simple à l'usage).

    On se retrouve donc souvent à dupliquer beaucoup de code, à grand coup de @Assert\Choice() et de $builder->add('property', ChoiceType::class, ['choices' => []]).

    Ce qui est assez dommage.

    Solution

    De ce constat, est né un bundle, qui se propose de packager les fonctionnalités du framework autour d'une nouvelle notion : les énumérations.

    Le contrat de création de ce bundle était le suivant :

    1. s'installer le plus simplement possible
    2. faciliter et centraliser la déclaration des énumérations
    3. réutiliser au maximum les fonctionnalités existantes de Symfony
    4. s'intégrer à SonataAdmin

    Vous pouvez sans attendre retrouver ce bundle sur GitHub et Packagist .

    Installation

    Ajoutez le bundle en dépendance de votre projet.

    composer require yokai/enum-bundle
    

    Si vous n'êtes pas encore passés sous symfony/flex (vous devriez y songer), ajoutez le bundle à votre kernel.

    new Yokai\EnumBundle\YokaiEnumBundle(),
    

    Voilà, c'est installé, reste à l'utiliser.

    Utilisation

    Cas d'usage

    Disons que votre application a des utilisateurs. Ces utilisateurs ont (entre autres) un statut et peuvent souscrire à des mailing lists.

    Note : On ne parle pas de persistance ici (pas de mapping Doctrine par exemple), car cela n'influe pas sur le bundle.

    <?php
    
    namespace App\Model;
    
    use Symfony\Component\Validator\Constraints as Assert;
    
    class User
    {
        public const STATUS_NEW = 'new';
        public const STATUS_ACTIVE = 'active';
        public const STATUS_DISABLED = 'disabled';
    
        public const SUBSCRIBE_NEWSLETTER = 'newsletter';
        public const SUBSCRIBE_COMMERCIAL = 'commercial';
    
        /**
         * @Assert\NotNull()
         * @Assert\Choice({User::STATUS_NEW, User::STATUS_ACTIVE, User::STATUS_DISABLED})
         */
        public string $status = self::STATUS_NEW;
    
        /**
         * @Assert\Choice({User::SUBSCRIBE_NEWSLETTER, User::SUBSCRIBE_COMMERCIAL}, multiple=true)
         */
        public array $subscriptions;
    }
    

    Les propriétés $status & $subscriptions ne sont pas des propriétés libres, la liste des valeurs possibles est restreinte. On utilise la contrainte Choice pour faire appliquer la validation. Les valeurs possibles pour ces propriétés sont fixées dans des constantes de classe, car c'est pratique si l'on souhaite faire des règles de gestion sur ces valeurs par la suite.

    Si l'on souhaite proposer un formulaire permettant d'éditer ces propriétés, on s'en sort avec le ChoiceType :

    <?php
    
    namespace App\Form\Type;
    
    use App\Model\User;
    use Symfony\Component\Form\AbstractType;
    use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
    use Symfony\Component\Form\FormBuilderInterface;
    use Symfony\Component\OptionsResolver\OptionsResolver;
    
    class UserType extends AbstractType
    {
        public function buildForm(FormBuilderInterface $builder, array $options): void
        {
            $builder
                ->add(
                    'status',
                    ChoiceType::class,
                    [
                        'required' => true,
                        'choices' => [
                            'user‧status‧new' => User::STATUS_NEW,
                            'user‧status‧active' => User::STATUS_ACTIVE,
                            'user‧status‧disabled' => User::STATUS_DISABLED,
                        ],
                    ]
                )
                ->add(
                    'subscriptions',
                    ChoiceType::class,
                    [
                        'required' => false,
                        'multiple' => true,
                        'choices' => [
                            'user‧subscription‧newsletter' => User::SUBSCRIBE_NEWSLETTER,
                            'user‧subscription.commercial' => User::SUBSCRIBE_COMMERCIAL,
                        ],
                    ]
                )
            ;
        }
    
        public function configureOptions(OptionsResolver $resolver): void
        {
            $resolver->setDefault('data_class', User::class);
        }
    }
    

    Enfin, si on souhaite afficher les valeurs d'un utilisateur existant dans un template Twig :

    <label>Status</label>
    <p>{{ ('user‧status.'~user‧status)|trans }}</p>
    <label>Subscriptions</label>
    {% for subscription in user‧subscriptions %}
        <p>{{ ('user‧subscription.'~subscription)|trans }}</p>
    {% endfor %}
    

    Problèmes

    Le problème principal, c'est la duplication du code. Evidemment, si vous n'avez qu'une seule énumération ce n'est pas très grave, mais quand votre application grossira, vous allez perdre du temps en maintenance et en évolutions.

    Mais même avec une seule énumération, nous avons aussi un problème de fragmentation :

    • les choix possibles sont définis à la fois dans la validation et dans le formulaire
    • la construction des labels est faite à la fois dans le formulaire et dans la vue

    Solution

    Nous allons déclarer notre énumération, en enregistrant un service

    services:
        enum‧user‧status:
            class: Yokai\EnumBundle\ConstantListTranslatedEnum
            arguments:
                $constantsPattern: 'App\Model\User::STATUS_*'
                $transPattern: 'user‧status.%s'
                $name: 'user‧status'
    
        enum‧user‧subscription:
            class: Yokai\EnumBundle\ConstantListTranslatedEnum
            arguments:
                $constantsPattern: 'App\Model\User::SUBSCRIPTION_*'
                $transPattern: 'user‧subscription.%s'
                $name: 'user‧subscription'
    

    Le bundle va ajouter automatiquement le tag yokai_enum‧enum sur ces 2 services, car ils implémentent l'interface Yokai\EnumBundle\EnumInterface, et enregistrer ces services auprès du "registre des énumérations" (Yokai\EnumBundle\EnumRegistry).

    note il existe plusieurs façons de déclarer une énumération. Une liste de constantes définies en configuration n'est qu'une des façons possibles.

    Maintenant, nous allons utiliser ces énumérations et modifier notre code.

    Dans le modèle, nous allons remplacer la contrainte Choice de Symfony par Enum, fournie par le bundle. Et pour chacune de ces annotations on va préciser le name de l'enum dont nous avons besoin.

    <?php
    
    namespace App\Model;
    
    use Symfony\Component\Validator\Constraints as Assert;
    + use Yokai\EnumBundle\Validator\Constraints\Enum;
    
    class User
    {
        public const STATUS_NEW = 'new';
        public const STATUS_ACTIVE = 'active';
        public const STATUS_DISABLED = 'disabled';
    
        public const SUBSCRIBE_NEWSLETTER = 'newsletter';
        public const SUBSCRIBE_COMMERCIAL = 'commercial';
    
        /**
         * @Assert\NotNull()
    -      * @Assert\Choice({User::STATUS_NEW, User::STATUS_ACTIVE, User::STATUS_DISABLED})
    +      * @Enum("user.status")
         */
        public string $status = self::STATUS_NEW;
    
        /**
    -      * @Assert\Choice({User::SUBSCRIBE_NEWSLETTER, User::SUBSCRIBE_COMMERCIAL}, multiple=true)
    +      * @Enum("user.subscription", multiple=true)
         */
        public array $subscriptions;
    }
    

    Dans le formulaire, nous allons tout retirer excepté l'ajout des champs. Le bundle se chargera pour nous de trouver le bon FormType à affecter à notre champ.

    <?php
    
    namespace App\Form\Type;
    
    use App\Model\User;
    use Symfony\Component\Form\AbstractType;
    - use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
    use Symfony\Component\Form\FormBuilderInterface;
    use Symfony\Component\OptionsResolver\OptionsResolver;
    
    final class UserType extends AbstractType
    {
        public function buildForm(FormBuilderInterface $builder, array $options): void
        {
            $builder
    -             ->add(
    -                 'status',
    -                 ChoiceType::class,
    -                 [
    -                     'required' => true,
    -                     'choices' => [
    -                         'user‧status‧new' => User::STATUS_NEW,
    -                         'user‧status‧active' => User::STATUS_ACTIVE,
    -                         'user‧status‧disabled' => User::STATUS_DISABLED,
    -                     ],
    -                 ]
    -             )
    +             ->add('status')
    -             ->add(
    -                 'subscriptions',
    -                 ChoiceType::class,
    -                 [
    -                     'required' => false,
    -                     'multiple' => true,
    -                     'choices' => [
    -                         'user‧subscription‧newsletter' => User::SUBSCRIBE_NEWSLETTER,
    -                         'user‧subscription.commercial' => User::SUBSCRIBE_COMMERCIAL,
    -                     ],
    -                 ]
    -             )
    +             ->add('subscriptions')
            ;
        }
    
        public function configureOptions(OptionsResolver $resolver): void
        {
            $resolver->setDefault('data_class', User::class);
        }
    }
    

    Dans le template, on va remplacer la génération dynamique de la clé de traduction par un filtre que propose le bundle.

    <label>Status</label>
    - <p>{{ ('user‧status.'~user‧status)|trans }}</p>
    + <p>{{ user‧status|enum_label('user‧status') }}</p>
    <label>Subscriptions</label>
    {% for subscription in user‧subscriptions %}
    -     <p>{{ ('user‧subscription.'~subscription)|trans }}</p>
    +     <p>{{ subscription|enum_label('user‧subscription') }}</p>
    {% endfor %}
    

    Voilà ! A partir de maintenant, les valeurs possibles et les labels associés sont centralisés dans un seul et même concept !

    Sous le capot

    En vérité, rien de plus que ce qui est décrit en préambule de l'article. Le bundle ré-utilise ce qui existe déjà dans Symfony. Il propose :

    • un FormType qui est une extension de ChoiceType
    • une Constraint qui est une extension de Choice
    • un Validator qui est une extension de ChoiceValidator
    • une TypeGuesser qui s'inspire grandement du ValidatorTypeGuesser
    • une TwigExtension sommaire pour afficher les labels

    Le tout est orchestré par une seule et unique classe : EnumRegistry auprès de qui sont enregistrés toutes les énumérations de votre application.

    Et demain

    Dans sa version actuelle (3.x), les énumérations ne peuvent avoir que des valeurs de type string.

    Avec l'arrivée de PHP 8.1 et de son support natif des énumérations, l'écosystème va naturellement s'orienter vers des énumérations dont les valeurs sont des objets.

    C'est pourquoi la prochaine version majeure du bundle (4.x) donnera la possibilité de définir des énumérations avec des valeurs très diverses : string, int, bool, ... et les énumérations natives !

    Cette version permettra également une intégration avec la, très populaire, librairie myclabs/php-enum.

    Blog

    Pour continuer votre lecture ...

    Tech

    Librarie Open Source : PrestaSitemapBundle

    Par Yann Eugoné 23/10/2020

    Le bundle OpenSource made by Prestaconcept qui génère le sitemap de votre projet Symfony

    Lire la suite
    Tech

    Des filtres enregistrés dans vos admins Sonata

    Par Benoit Jouhaud 07/12/2023

    Le PrestaSonataSavedFiltersBundle est un bundle Symfony qui permet de gérer des filtres enregistrés pouvant être appliqués aux listes des admins Sonata afin de les restaurer dans un état précis.
    Il s'intègre aux écrans de liste via un menu situé en haut à droite de l'écran, et dispose d'une admin p...

    Lire la suite
    Tech

    Librarie Open Source : PrestaImageBundle

    Par Benoit Jouhaud 03/03/2021

    Le bundle open source made by Prestaconcept qui permet à vos utilisateurs de redimensionner une image à l'upload.

    Lire la suite

    Vous avez un projet Laravel ?

    Nous sommes spécialisés en Symfony, et grâce à Web^ID, l’agence sœur du groupe Agile Invest, nous couvrons aussi toute l’expertise Laravel.

    Découvrir Web^ID

    Une question, un projet ?
    Planifiez un échange avec nous !

    Choisissez votre date
    PrestaConcept - Groupe Agile Invest
    5, imp. Morel, 69003 Lyon +33 (0)4 78 54 45 45
    Suivez-nous
    Ecoindex B

    Ce site internet est un site basse consommation. En savoir plus sur l'Ecoindex

    Nos réalisations

  • Logiciel de mise en conformité réglementaire
  • Application de suivi de production des centrales éoliennes
  • Outil d'aide à la décision
  • Portail client
  • Nos métiers

  • Développement sur-mesure
  • Reprise d'application Symfony
  • Expertise Symfony
  • Hébergement & Infogérance
  • Qui sommes-nous

  • PrestaConcept
  • Groupe Agile Invest
  • L'équipe
  • Engagement RSE
  • Blog

  • Tech
  • Méthodologie
  • PrestaConcept
  • RSE
  • © 2025 PrestaConcept
    Mentions légales Politique de confidentialité 🍪
    Retour en haut de page