Article écrit par Benoit Delesalle
Quand on doit mettre en place un workflow dans une application web, on peut rapidement se perdre lorsqu’il s’agit de le manipuler. Dans cet article, nous allons voir comment se simplifier la vie en utilisant le composant Workflow dans une application Symfony.
Mais dis-moi Jamy, c’est quoi un workflow ?
Un workflow, c’est tout simplement un ensemble de règles qui régit un processus de notre application. Prenons un exemple pour mieux comprendre.
Imaginons que notre application soit un site e-commerce et que nous devons gérer l’état d’une commande. Pour cet exemple, on peut se dire que les états possibles d’une commande sont les suivants :
- created : lorsqu’un client ajoute des produits à son panier.
- waiting_for_payment : lorsque le client a validé son panier mais qu’il n’a pas encore payé sa commande.
- awaiting_delivery : lorsque le client a payé sa commande mais qu’elle n’a pas encore été prise en compte par la société de livraison.
- in_delivering : lorsque la commande a été prise en charge par la société de livraison.
- delivered : lorsque la commande est bien arrivée à destination.
- canceled : lorsque le client a annulé sa commande ou a abandonné son panier.
Maintenant que nous avons nos états, nous devons nous demander comment passer de l’un à l’autre. Dans notre exemple, nous ne pouvons pas passer de l’état « waiting_for_payment » à l’état « in_delivering » sans avoir reçu le paiement en question. Nous allons donc devoir définir les règles de changement d’état. Notre workflow pourrait donc ressembler à ceci.
Maintenant imaginons ce workflow dans notre application. Pour chaque changement d’état, il va falloir vérifier si ce changement est possible ou non, cela risque de nous procurer des heures de développement à se tirer les cheveux. Si seulement il existait une solution pour nous simplifier la vie. Mais attendez ! Elle existe cette solution, c’est le composant Workflow de Symfony.
Le composant Workflow
Pour cet article, nous supposons que notre application Symfony est en version 4.3. Dans un premier temps, installons le composant avec composer
.
$ composer install symfony/workflow
Symfony vous crée un fichier de configuration de base qui ressemble à ceci :
# config/packages/workflow.yaml framework : workflows : null
C’est dans ce fichier de configuration que nous allons définir tous nos workflows, leurs états possibles et comment passer d’un état à un autre. Ajoutons donc notre workflow concernant nos commandes.
# config/packages/workflow.yaml framework : workflows : order : type : 'state_machine' marking_store : type : 'method' property : 'state' supports : - AppEntityOrder
Arrêtons-nous sur cette configuration. Nous avons donc créé un workflow qui se nomme order
et qui est de type state_machine
. Il en existe deux types :
state_machine
workflow
En soi, un state_machine
est un workflow
avec quelques différences :
- un objet dans un
state_machine
ne peut avoir qu’un seul état à la fois contrairement à unworkflow
. - le
state_machine
est dit cyclique, c’est-à-dire que notre objet peut revenir dans un état qu’il a déjà connu, ce que ne peut pas faire unworkflow
.
Cela sous-entend également qu’un objet dans un workflow
doit avoir connu tous les états précédents avant de passer à un nouvel état alors que le state_machine
doit être dans au moins un de ces états.
Ensuite, nous avons défini avec l’option marking_store
la manière dont nous voulons stocker l’état actuel de notre objet. Ici, cela veut simplement dire que nous allons stocker l’état dans un attribut state
de notre objet en utilisant une méthode setState
.
Finalement, nous avons indiqué les objets qui suivent ce workflow avec l’option supports
, ici il n’y a que notre entité AppEntityOrder
.
Tout cela est bien beau, mais comment définir les états possibles ? C’est très simple, allons voir cela de ce pas.
# config/packages/workflow.yaml framework : workflows : order : type : 'state_machine' marking_store : type : 'method' property : 'state' supports : - AppEntityOrder initial_marking : created places : - created - waiting_for_payment - awaiting_delivery - in_delivering - delivered - canceled
Dans places
, nous avons listé tous les états que peut prendre notre commande et nous avons spécifié dans initial_marking
l’état de départ de notre objet.
Il ne nous reste plus qu’à préciser nos transitions, c’est-à-dire les règles de mouvement entre chaque état.
# config/packages/workflow.yaml framework : workflows : order : type : 'state_machine' marking_store : type : 'method' property : 'state' supports : - AppEntityOrder initial_marking : created places : - created - waiting_for_payment - awaiting_delivery - in_delivering - delivered - canceled transitions : add_product_to_cart : from : created to : created confirm_order : from : created to : waiting_for_payment pay_order : from : waiting_for_payment to : awaiting_delivery accept_delivery : from : awaiting_delivery to : in_delivering delivery_done : from : in_delivering to : delivered cancel_order : from : [created, waiting_for_payment] to : canceled
Avec ces règles, il me sera désormais impossible de passer à un état in_delivering
sans passer par la case paiement.
Vous pouvez désormais utiliser votre workflow dans votre application en injectant directement le Registry
du composant Workflow dans votre constructeur et en récupérant votre workflow par son nom en lui passant l’objet sur lequel vous souhaitez l’appliquer.
<?php // src/Controller/OrderController.php namespace AppController ; use AppEntityCommande ; use SymfonyBundleFrameworkBundleControllerAbstractController ; use SymfonyComponentRoutingAnnotationRoute ; use SymfonyComponentWorkflowRegistry ; class OrderController extends AbstractController { /** * @var Registry */ private $workflows ; public function __construct(Registry $workflows) { $this->workflows = $workflows ; } /** * @Route("/order", name="order") */ public function order() { $order = new Order(); $workflow = $this->workflows->get($order, 'order'); } }
Grâce à ce workflow, vous pouvez désormais vérifier simplement si votre commande peut passer à un état particulier et lui appliquer cet état en procédant comme ceci :
<?php if($workflow->can($order, 'pay_order')) { // votre code $workflow->apply($order, 'pay_order'); }
Vous pouvez également utiliser ces conditions dans vos templates twig.
{% if workflow_can(order, 'pay_order') %} <a href="...">Payer la commande</a> {% endif %}
Voilà, vous savez désormais comment vous faciliter la vie pour vos workflows.
Dans un prochain article, nous verrons comment aller plus loin en utilisant les événements liés aux workflows pour vraiment profiter de la puissance de ce composant.