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
    • Le composant workflow de Symfony

    Blog

    Le composant workflow de Symfony

    expression language php symfony workflow
    Alain Flaus
    Alain Flaus Développeur expert Symfony
    Publié le jeudi 11 janvier 2018

    Le composant Workflow est encore récent. Il n'est pas le plus connu, mais gagne à l'être.
    Petit aperçu des possibilités, exemples à la clé, et proposition d'améliorations.

    Présentation

    Un workflow est la définition du cycle de vie d'un objet/système qui change d'état lorsqu'on lui applique une action particulière (transition).

    Prenons pour exemple le cycle de vie d'une issue :

    • créée
    • affectée à un utilisateur
    • en cours de traitement
    • terminée
    • clôturée

    Voici une implémentation de notre issue:

    <?php
    
    namespace AppBundle\Entity;
    
    class Issue
    {
        /**
         * @var string
         *
         * @ORM\Id
         * @ORM\Column(type="string")
         */
        private $id;
    
        /**
         * @var string
         *
         * @ORM\Column(type="string", length=255)
         * @Assert\NotBlank()
         */
        private $title;
    
        /**
         * @var string
         *
         * @ORM\Column(type="string", nullable=true)
         */
        private $content;
    
        /**
         * @var string
         *
         * @ORM\Column(type="string", length=255)
         */
        private $state;
    
        /**
         * @var \DateTime
         *
         * @ORM\Column(type="datetime")
         * @Assert\DateTime()
         */
        private $createdAt;
    
        // ...
    }
    

    Configuration

    Le composant Workflow de Symfony permet de définir via la configuration les différents états et transitions de notre Issue.

    Le workflow single_state

    Reprenons l'exemple de notre issue :

    framework:
        workflows:
            issue:
                marking_store:
                    type: single_state
                    arguments:
                        - state
                supports: AppBundle\Entity\Issue
                initial_place: opened
                places:
                    - opened # créée
                    - affected # affectée à un utilisateur
                    - in_progress # en cours de traitement
                    - completed # terminée
                    - closed # clôturée
                transitions:
                    affect:
                        from: opened
                        to: affected
                    treat:
                        from: affected
                        to: in_progress
                    complete:
                        from: in_progress
                        to: completed
                    close:
                        from: completed
                        to: closed
    

    Chaque workflow porte un nom, ici "issue".

    • marking_store possède deux options :
      • type : qui peut prendre deux valeurs single_state s'il ne peut y avoir qu'un seul état à la fois, ou multiple_state si on gère plusieurs états en parallèle.
      • arguments : les arguments à ajouter au service marking_store du workflow. La première valeur représente le nom de la propriété qui stockera l'état (ici state).
    • supports : définit quel objet est concerné par ce workflow. Il est possible de définir une support_strategy mais pour l'instant seule la stratégie ClassInstanceSupportStrategy est implémentée.
    • initial_place : le statut initial de l'objet (attention il n'est pas affecté automatiquement)
    • places : liste les différents états possibles
    • transitions : liste les différentes actions qui déclenchent un changement d'état avec pour chacune un départ et une cible. L'objet doit valider l'état de départ pour que la transition soit applicable.

    Ci-dessous un schéma représentant le cycle de vie (workflow) de notre issue :

    Symfony Workflow Single State

    * les ronds représentent les états (bleu pour l'état initial) et les carrés des transitions

    Le workflow multiple_state

    Dans le cas d'un workflow de type multiple_state il est possible de définir plusieurs branches qui évolueront en simultané. Cela signifie aussi que certaines transitions ne seront applicables que si et seulement si tous les statuts de départ sont validés.

    Prenons pour exemple le cas d'une pull request.

    <?php
    
    namespace AppBundle\Entity;
    
    class PullRequest
    {
        /**
         * @var string
         *
         * @ORM\Id
         * @ORM\Column(type="string")
         */
        private $id;
    
        /**
         * @var string
         *
         * @ORM\Column(type="string", length=255)
         * @Assert\NotBlank()
         */
        private $title;
    
        /**
         * @var string
         *
         * @ORM\Column(type="string", nullable=true)
         */
        private $content;
    
        /**
         * @var array
         *
         * @ORM\Column(type="json_array")
         */
        private $states;
    
        /**
         * @var \DateTime
         *
         * @ORM\Column(type="datetime")
         * @Assert\DateTime()
         */
        private $createdAt;
    
        // ...
    }
    

    Scénario:

    • Un développeur ouvre la pull request
    • Il fait une demande de review
    • La PR doit être validée par 2 entités : Travis exécute les tests automatisés et un développeur relit le code produit. Ce qui correspond à deux statuts en simultané : travis_build et dev_review
    • Chacun des deux axes va évoluer de façon autonome Le build travis peut réussir ou échouer, de même que la relecture de code
    • À la fin, pour être mergée, la PR devra avoir passé avec succès le build Travis ainsi que la code review
    framework:
        workflows:
            pull_request:
                marking_store:
                    type: multiple_state
                    arguments:
                        - states
                supports: AppBundle\Entity\PullRequest
                initial_place: opened
                places:
                    - opened
                    - travis_build
                    - travis_build_ok
                    - travis_build_fail
                    - dev_review
                    - dev_review_ok
                    - dev_review_fail
                    - merged
                transitions:
                    review:
                        from: opened
                        to:   [travis_build, dev_review]
                    travis_build_success:
                        from: travis_build
                        to:   travis_build_ok
                    travis_build_failure:
                        from: travis_build
                        to:   travis_build_fail
                    dev_review_success:
                        from: dev_review
                        to:   dev_review_ok
                    dev_review_failure:
                        from: dev_review
                        to:   dev_review_fail
                    merge:
                        from: [travis_build_ok, dev_review_ok]
                        to:   merged
    

    Ci-dessous la représentation de ce nouveau workflow :

    Symfony Workflow Multiple State

    * dès lors que plusieurs flèches pointent sur une transition, chaque état d'origine doit être validé pour appliquer la transition.

    Utilisation

    Pour manipuler les workflows, Symfony implémente un registre qui permet de récupérer celui qui concerne notre objet. Si plusieurs workflows sont définis il faudra fournir explicitement le nom d'un workflow au registre.

    Initialisation

    Le workflow ne s'initialise pas tout seul, il faudra marquer notre objet lors de sa création. Ce qui revient dans notre exemple à donner le statut opened à notre PullRequest.

    <?php
    
    namespace AppBundle\Controller;
    
    use AppBundle\Entity\PullRequest;
    use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    
    class PullRequestController extends Controller
    {
        public function newAction()
        {
            $pullRequest = new PullRequest();
    
            $workflow = $this->get('workflow.registry')->get($pullRequest);
            $workflow->getMarking($pullRequest); // getMarking est appelé afin de forcer l'initialisation de l'état de départ (initial_place)
    
            //...
        }
    }
    

    Pour modifier le statut de notre pull request, il faut lui appliquer une transition. Les conditions de départ doivent être validées. En cas d'erreur la methode apply lèvera une LogicException.

    <?php
    
    namespace AppBundle\Controller;
    
    use AppBundle\Entity\PullRequest;
    use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    use Symfony\Component\Workflow\Exception\LogicException;
    
    class PullRequestController extends Controller
    {
        public function switchAction(PullRequest $pullRequest, $transition)
        {
            $workflow = $this->get('workflow.registry')->get($pullRequest);
    
            try {
                $workflow->apply($pullRequest, $transition);
                $this->getEntityManager()->flush();
    
            } catch (LogicException $e) {
                $this->getSession()->getFlashBag()->add(
                    'error',
                    sprintf('Can not execute transition %s for PR: %d', $transition, $pullRequest->getId())
                );
            }
    
            //...
       }
    }
    

    Debug

    Les représentations graphiques des workflows présents dans ce billet ont été générées à l'aide de deux outils:

    • La commande workflow:dump qui permet de générer un fichier .dot décrivant le workflow.
    • dot (graphiz) qui permet de générer une image à partir du fichier .dot.
    php bin/console workflow:dump pull_request > pull_request.dot
    dot -Tpng pull_request.dot -o pull_request.png
    

    Twig

    Le composant Workflow vient avec une sa propre extension Twig.

    Elle permet par exemple de tester si une transition peut être appliquée à l'objet afin d'afficher ou non un bouton d'action :

    {% if workflow_can(pull_request, 'review') %}
        <a href="...">Review</a>
    {% endif %}
    

    Mais aussi afficher la listes des actions disponibles :

    {% for transition in workflow_transitions(pull_request) %}
        <a href="{{ path('pull_request_switch', {id: pull_request.id, transition: transition.name}) }}">{{ transition.name }}</a>
    {% else %}
        No actions available.
    {% endfor %}
    

    La documentation officielle contient la liste exhaustive de toutes les fonctions implémentées par cette extension.

    Events

    Le composant Workflow fournit des événements pour chaque état/transition du cycle de vie (en prenant la transition review de notre Workflow pull_request pour exemple):

    • Lorsqu'il est question de vérifier la possibilité d'appliquer une transition :
      • workflow.guard
      • workflow.pull_request.guard
      • workflow.pull_request.guard.review
    • Lorsqu'un objet quitte un état :
      • workflow.leave
      • workflow.pull_request.leave
      • workflow.pull_request.leave.opened
    • Lorsqu'une transition est appliquée :
      • workflow.transition
      • workflow.pull_request.transition
      • workflow.pull_request.transition.review
    • Lorsqu'un objet s'apprête à entrer dans un état :
      • workflow.enter
      • workflow.pull_request.enter
      • workflow.pull_request.enter.travis_build
      • workflow.pull_request.enter.dev_review
    • Lorsqu'un objet vient d'entrer dans un état :
      • workflow.entered
      • workflow.pull_request.entered
      • workflow.pull_request.entered.travis_build
      • workflow.pull_request.entered.dev_review
    • Lorsqu'il est question d'annoncer les états disponibles :
      • workflow.announce
      • workflow.pull_request.announce
      • workflow.pull_request.announce.travis_build
      • workflow.pull_request.announce.travis_build_success
      • workflow.pull_request.announce.travis_build_failure
      • workflow.pull_request.announce.dev_review_success
      • workflow.pull_request.announce.dev_review_failure

    Il est possible d'utiliser ces événements pour logger chaque changement d'état par exemple.

    La documentation officielle contient la liste exhaustive de tous les événements propagés.

    Guard

    Il est possible d'appliquer des restrictions aux transitions pour vérifier (par exemple) que l'utilisateur est bien authentifié ou qu'il possède les droits nécessaires.

    Pour cela, la définition de chaque transition supporte une option guard. Cette option utilise le composant expression language pour faire appel à des fonctions telles que is_fully_authenticated() ou is_granted(), et même accèder aux attributs de l'objet concerné : subject (notre pull request).

    Exemple de configuration:

    framework:
        workflows:
            pull_request:
                transitions:
                    dev_review_success:
                        guard: "is_fully_authenticated() and has_role('ROLE_REVIEWER') and is_granted('ACCEPT', subject)"
                        from: dev_review
                        to:   dev_review_ok
                    dev_review_failure:
                        guard: "is_fully_authenticated() and has_role('ROLE_REVIEWER') and is_granted('REJECT', subject)"
                        from: dev_review
                        to:   dev_review_fail
                    merge:
                        guard: "is_fully_authenticated() and has_role('ROLE_MERGER') and is_granted('MERGE', subject)"
                        from: [travis_build_ok, dev_review_ok]
                        to:   merged
    

    Nouvelle feature

    Les fonctionnalités de l'option guard sont pour l'instant assez limitées.

    Nous avons eu l'idée de l'enrichir en lui ajoutant le support d'une nouvelle méthode is_valid(). Cette fonction attend en paramètre l'objet à valider ainsi que des groupes de validation (optionnellement).

    Au même titre que is_granted(), celle-ci est parsée par le composant expression language. Elle fait appel au composant de validation de Symfony.

    Une pull request pour cette nouvelle feature a été créée.

    Blog

    Pour continuer votre lecture ...

    Tech

    Retour sur le SymfonyLive Online French Edition 2021

    Par Benoit Jouhaud, Matthieu Crinquand, Alain Flaus et Benoit Leveque 15/04/2021

    Croissant virtuel et chacun son café ! C'est ainsi que démarre l'édition 2021 du Symfony Live Online.

    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

    Utilisation de Stopwatch et WebProfiler dans Symfony

    Par Yann Eugoné 09/11/2023

    Comment utiliser le composant Stopwatch et le WebProfilerBundle pour détecter les lenteurs de vos applications.

    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