Étape 19: Prendre des décisions avec un workflow

5.0 version
Maintained

Prendre des décisions avec un workflow

Avoir un état pour un modèle est assez commun. L’état du commentaire n’est déterminé que par le vérificateur de spam. Et si on ajoutait d’autres critères de décision ?

Nous pourrions laisser l’admin du site modérer tous les commentaires après le vérificateur de spam. Le processus serait quelque chose comme :

  • Commencez par un état submitted lorsqu’un commentaire est soumis par un internaute ;
  • Laissez le vérificateur de spam analyser le commentaire et changer l’état en potential_spam, ham ou rejected
  • S’il n’est pas rejeté, attendez que l’admin du site décide si le commentaire est suffisamment utile en changeant l’état pour published ou rejected.

La mise en œuvre de cette logique n’est pas trop complexe, mais vous pouvez imaginer que l’ajout de règles supplémentaires augmenterait considérablement la complexité. Au lieu de coder la logique nous-mêmes, nous pouvons utiliser le composant Symfony Workflow :

1
$ symfony composer req workflow

Décrire des workflows

Le workflow de commentaires peut être décrit dans le fichier config/packages/workflow.yaml :

config/packages/workflow.yaml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
framework:
    workflows:
        comment:
            type: state_machine
            audit_trail:
                enabled: "%kernel.debug%"
            marking_store:
                type: 'method'
                property: 'state'
            supports:
                - App\Entity\Comment
            initial_marking: submitted
            places:
                - submitted
                - ham
                - potential_spam
                - spam
                - rejected
                - published
            transitions:
                accept:
                    from: submitted
                    to:   ham
                might_be_spam:
                    from: submitted
                    to:   potential_spam
                reject_spam:
                    from: submitted
                    to:   spam
                publish:
                    from: potential_spam
                    to:   published
                reject:
                    from: potential_spam
                    to:   rejected
                publish_ham:
                    from: ham
                    to:   published
                reject_ham:
                    from: ham
                    to:   rejected

Pour valider le workflow, générez une représentation visuelle :

1
$ symfony console workflow:dump comment | dot -Tpng -o workflow.png
../_images/workflow.png

Note

La commande dot fait partie de l’utilitaire Graphviz.

Utiliser un workflow

Remplacez la logique actuelle dans le gestionnaire de messages par le workflow :

patch_file
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
--- a/src/MessageHandler/CommentMessageHandler.php
+++ b/src/MessageHandler/CommentMessageHandler.php
@@ -6,19 +6,28 @@ use App\Message\CommentMessage;
 use App\Repository\CommentRepository;
 use App\SpamChecker;
 use Doctrine\ORM\EntityManagerInterface;
+use Psr\Log\LoggerInterface;
 use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
+use Symfony\Component\Messenger\MessageBusInterface;
+use Symfony\Component\Workflow\WorkflowInterface;

 class CommentMessageHandler implements MessageHandlerInterface
 {
     private $spamChecker;
     private $entityManager;
     private $commentRepository;
+    private $bus;
+    private $workflow;
+    private $logger;

-    public function __construct(EntityManagerInterface $entityManager, SpamChecker $spamChecker, CommentRepository $commentRepository)
+    public function __construct(EntityManagerInterface $entityManager, SpamChecker $spamChecker, CommentRepository $commentRepository, MessageBusInterface $bus, WorkflowInterface $commentStateMachine, LoggerInterface $logger = null)
     {
         $this->entityManager = $entityManager;
         $this->spamChecker = $spamChecker;
         $this->commentRepository = $commentRepository;
+        $this->bus = $bus;
+        $this->workflow = $commentStateMachine;
+        $this->logger = $logger;
     }

     public function __invoke(CommentMessage $message)
@@ -28,12 +37,21 @@ class CommentMessageHandler implements MessageHandlerInterface
             return;
         }

-        if (2 === $this->spamChecker->getSpamScore($comment, $message->getContext())) {
-            $comment->setState('spam');
-        } else {
-            $comment->setState('published');
-        }
+
+        if ($this->workflow->can($comment, 'accept')) {
+            $score = $this->spamChecker->getSpamScore($comment, $message->getContext());
+            $transition = 'accept';
+            if (2 === $score) {
+                $transition = 'reject_spam';
+            } elseif (1 === $score) {
+                $transition = 'might_be_spam';
+            }
+            $this->workflow->apply($comment, $transition);
+            $this->entityManager->flush();

-        $this->entityManager->flush();
+            $this->bus->dispatch($message);
+        } elseif ($this->logger) {
+            $this->logger->debug('Dropping comment message', ['comment' => $comment->getId(), 'state' => $comment->getState()]);
+        }
     }
 }

La nouvelle logique se lit comme ceci :

  • Si la transition accept est disponible pour le commentaire dans le message, vérifiez si c’est un spam ;
  • Selon le résultat, choisissez la bonne transition à appliquer ;
  • Appellez apply() pour mettre à jour le Comment via un appel à la méthode setState() ;
  • Appelez flush() pour valider les changements dans la base de données ;
  • Réexpédiez le message pour permettre au workflow d’effectuer une nouvelle transition.

Comme nous n’avons pas implémenté la fonctionnalité de validation par l’admin, la prochaine fois que le message sera consommé, le message « Dropping comment message » sera enregistré.

Mettons en place une validation automatique en attendant le prochain chapitre :

patch_file
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
--- a/src/MessageHandler/CommentMessageHandler.php
+++ b/src/MessageHandler/CommentMessageHandler.php
@@ -47,6 +47,9 @@ class CommentMessageHandler implements MessageHandlerInterface
             $this->entityManager->flush();

             $this->bus->dispatch($message);
+        } elseif ($this->workflow->can($comment, 'publish') || $this->workflow->can($comment, 'publish_ham')) {
+            $this->workflow->apply($comment, $this->workflow->can($comment, 'publish') ? 'publish' : 'publish_ham');
+            $this->entityManager->flush();
         } elseif ($this->logger) {
             $this->logger->debug('Dropping comment message', ['comment' => $comment->getId(), 'state' => $comment->getState()]);
         }

Exécutez symfony server:log et ajoutez un commentaire sur le site pour voir toutes les transitions se produire les unes après les autres.


  • « Previous Étape 18: Faire de l’asynchrone
  • Next » Étape 20: Envoyer des emails aux admins

This work, including the code samples, is licensed under a Creative Commons BY-NC-SA 4.0 license.