Cet article est publié sous licence CC BY-NC-SA
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 :
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.
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:
- App\Entity\Order
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 :
state_machine
ne peut avoir qu’un seul état à la fois contrairement à un workflow
.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 un workflow
.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é App\Entity\Order
.
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:
- App\Entity\Order
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:
- App\Entity\Order
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 App\Controller;
use App\Entity\Commande;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Workflow\Registry;
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.
<a href="...">Payer la commande</a>
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.
L’équipe Synbioz.
Libres d’être ensemble.
Nos conseils et ressources pour vos développements produit.