Cyril Souillard
CTO chez MyLittleBox.
Introduit avec Symfony 3.2, le composant Workflow permet de gérer simplement les différents états et transitions au sein de vos objets. Découvrons ensemble l’utilisation de ce composant avec un exemple simple de l’e-commerce et bien connu de tous : le colis.
Un colis ne peut avoir qu’un état à la fois. Son état peut prendre les valeurs « new », « preparing », « prepared », « shipped », « delivered », « returned », « lost ».
Grâce au composant Workflow, nous pouvons très simplement définir ces différents états et les transitions entre chacun d’entre eux. Pour cela, éditons le fichier config/packages/workflows.yml ainsi :
framework:
workflows:
delivery:
marking_store:
type: single_state
arguments:
- state
supports:
- App\Entity\Packet
places:
- new
- preparing
- packed
- shipped
- delivered
- returned
- lost
transitions:
prepare:
from: new
to: preparing
pack:
from: preparing
to: packed
send:
from: packed
to: shipped
deliver:
from: shipped
to: delivered
return:
from: [delivered, shipped]
to: returned
lose:
from: [shipped, delivered]
to: lost
marking_store
: définit la façon de stocker l’information d’état au sein de votre objet. Ici nous souhaitons que notre objet ne stocke qu’un seul état à la fois d’où le type single_state
. Aussi nous souhaitons que la valeur de notre état soit stockée dans la propriété $state
de l’objet.supports
: définit les objets sur lesquels le workflow s’applique.places
: définit les différents états possibles pour notre objet.transitions
: définit la liste des transitions entre chaque état. Ici un objet en état "shipped" pourra seulement suivre les transitions “deliver", “return" et “lose", mais aucune autre.Une fois la configuration créée, vous pouvez simplement créer un schéma de votre workflow en utilisant Graphviz :
php bin/console workflow:dump delivery | dot -Tpng -o graph.png
Ce qui génèrera l’image suivante :
Doc ? Qui a dit doc ?
Maintenant, vous pouvez très simplement récupérer les transitions possibles pour votre objet.
$packet = new Packet();
$workflow = $this->get('workflow.delivery');
$workflow->getEnabledTransitions($packet);
Tester si une transition est possible et l’appliquer
if ($workflow->can($packet, 'lose')) {
$workflow->apply($packet, 'lose');
}
Facile, on a défini tous nos états et transitions mais nous n’avons aucune règle de gestion. Imaginons, par exemple qu’un colis ne peut être considéré comme perdu que 15 jours après avoir été expédié.
Actuellement, rien ne limite cette contrainte dans notre modèle heureusement, c’est possible de l’implémenter via les évènements déclenchés par le composant.
Dans notre cas, voici ce que cela pourra donner :
use Symfony\Component\Workflow\Event\GuardEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class PacketLostListener implements EventSubscriberInterface
{
public function guardLost(GuardEvent $event)
{
/** @var \App\Entity\Packet $packet */
$packet = $event->getSubject();
$shippedAt = $packet->getShippedAt();
$now = new \DateTime();
if ($now->diff($shippedAt)->format('%a') < 15) {
$event->setBlocked(true);
}
}
public static function getSubscribedEvents()
{
return array(
'workflow.blogpost.guard.lost' => array('guardLost'),
);
}
}
Simple à utiliser, ce composant vous permettra de décorréler votre workflow de votre modèle. Simple à mettre en place, mangez-en matin, midi et soir.
Cet example a été créé sous Symfony 4, le composant a été introduit par Symfony 3.2, mais si d'aventure vous étiez encore sur une version antérieure, sachez qu'il existe un bundle pour intégrer ce composant à partir de Symfony 2.3. Plus d'excuse ;).