Pas 19: Luarea deciziilor cu Workflow

5.0 version
Maintained

Luarea deciziilor cu Workflow

A avea o stare pentru un model este destul de comun. Starea comentariilor este determinată doar de verificatorul de spam. Ce se întâmplă dacă adăugăm mai mulți factori de decizie?

Am putea dori să lăsăm administratorul site-ului să modereze toate comentariile după verificatorul de spam. Procesul ar fi ceva similar:

  • Începe cu o stare submitted când un comentariu este trimis de un utilizator;
  • Lasă verificatorul de spam să analizeze comentariul și schimbă starea pe potential_spam, ham sau respins;
  • Dacă nu este respins, așteptă ca administratorul site-ului să decidă dacă comentariul este suficient de bun, comutând statul la published sau rejected.

Implementarea acestei logici nu este prea complexă, însă adăugarea mai multor reguli ar crește mult complexitatea. În loc să codificăm logica, putem folosi componenta Symfony Workflow:

1
$ symfony composer req workflow

Descrierea fluxurilor de lucru

Fluxul de lucru pentru comentarii poate fi descris în fișierul 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

Pentru a valida fluxul de lucru, generează o reprezentare vizuală:

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

Notă

Comanda dot face parte din utilitarul Graphviz.

Utilizarea unui flux de lucru

Înlocuește logica curentă din manipulatorul de mesaje cu fluxul de lucru:

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()]);
+        }
     }
 }

Logică nouă are următoarea structură:

  • Dacă tranziția accept este disponibilă pentru comentariul din mesaj, verifică dacă nu există spam;
  • În funcție de rezultat, alege tranziția potrivită pentru a o aplica;
  • Apelează apply() pentru a actualiza comentariul printr-un apel la metoda setState();
  • Apelează flush() pentru a salva modificările în baza de date;
  • Reexpediază mesajul pentru a permite fluxului de lucru să tranziteze din nou.

Deoarece nu am implementat validarea admin, la următoarea consumare a mesajului va fi logat mesajul „Dropping comment message”.

Să implementăm o validare automată până la următorul capitol:

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()]);
         }

Execută server symfony:log și adaugă un comentariu în frontend pentru a vedea toate tranzițiile care se petrec una după alta.


  • « Previous Pas 18: Procesând asincron
  • Next » Pas 20: Expedierea e-mail-urilor administratorilor

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