Passo 19: Prendere decisioni con un Workflow
Prendere decisioni con un Workflow¶
Avere uno stato per un modello è abbastanza comune. Lo stato del commento è determinato solo dallo spam checker. E se aggiungessimo altri fattori sui quali prendere delle decisioni?
Potremmo fare in modo che sia l’amministratore del sito a moderare tutti i commenti dopo il controllo dello spam. Il tutto si tradurrebbe in qualcosa di simile:
- Iniziamo con l’assegnare un stato
submitted
quando un commento viene inviato da un utente; - Lasciamo che lo spam checker analizzi il commento e modifichi lo stato su
potential_spam
,ham
oppurerejected
; - Se il commento non viene rifiutato dallo spam checker, aspettiamo che sia l’amministratore del sito a decidere se sia buono o meno, modificando lo stato in
published
oppurerejected
.
L’implementazione di questa logica è abbastanza semplice, ma come si può immaginare, all’aumentare del numero delle regole la complessità aumenterà. Invece di codificare noi stessi la logica, possiamo usare il componente Workflow di Symfony:
1 | $ symfony composer req workflow
|
Descrivere i workflow¶
Il workflow dei commenti può essere descritto nel file 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
|
Per convalidare il workflow, generiamo una rappresentazione visiva:
1 | $ symfony console workflow:dump comment | dot -Tpng -o workflow.png
|

Nota
Il comando dot
fa parte dell’utility Graphviz.
Utilizzare un workflow¶
Sostituire la logica corrente nel gestore dei messaggi con il workflow:
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');
- }
- $this->entityManager->flush();
+ 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->bus->dispatch($message);
+ } elseif ($this->logger) {
+ $this->logger->debug('Dropping comment message', ['comment' => $comment->getId(), 'state' => $comment->getState()]);
+ }
}
}
|
La nuova logica è la seguente:
- Se la transizione
accept
è disponibile per il commento nel messaggio, controllare la presenza di spam; - A seconda del risultato, scegliere la transizione giusta da applicare;
- Chiamare
apply()
per aggiornare il commento tramite una chiamata al metodosetState()
; - Chiamare
flush()
per salvare le modifiche nel database; - Inviare nuovamente il messaggio per avviare la transizione del workflow.
Poiché non abbiamo implementato la validazione da parte dell’amministratore, la prossima volta che il messaggio sarà consumato, verrà generato il seguente messaggio di log: «Dropping comment message».
Implementiamo un’auto-validazione fino al prossimo capitolo:
1 2 3 4 5 6 7 8 9 10 11 12 | --- a/src/MessageHandler/CommentMessageHandler.php
+++ b/src/MessageHandler/CommentMessageHandler.php
@@ -50,6 +50,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()]);
}
|
Eseguire symfony server:log
e aggiungere un commento nel frontend per vedere tutte le transizioni che si susseguono una dopo l’altra.
- « Previous Passo 18: Esecuzione asincrona
- Next » Passo 20: Invio di e-mail agli amministratori
This work, including the code samples, is licensed under a Creative Commons BY-NC-SA 4.0 license.