Skip to content
  • About
    • What is Symfony?
    • Community
    • News
    • Contributing
    • Support
  • Documentation
    • Symfony Docs
    • Symfony Book
    • Screencasts
    • Symfony Bundles
    • Symfony Cloud
    • Training
  • Services
    • Platform.sh for Symfony Best platform to deploy Symfony apps
    • SymfonyInsight Automatic quality checks for your apps
    • Symfony Certification Prove your knowledge and boost your career
    • SensioLabs Professional services to help you with Symfony
    • Blackfire Profile and monitor performance of your apps
  • Other
  • Blog
  • Download
sponsored by
  1. Home
  2. Documentation
  3. Symfony: The Fast Track
  4. Italian
  5. 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 oppure rejected;
  • 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 oppure rejected.

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:

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

Note

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
--- a/src/MessageHandler/CommentMessageHandler.php
+++ b/src/MessageHandler/CommentMessageHandler.php
@@ -6,7 +6,10 @@ use App\Message\CommentMessage;
 use App\Repository\CommentRepository;
 use App\SpamChecker;
 use Doctrine\ORM\EntityManagerInterface;
+use Psr\Log\LoggerInterface;
 use Symfony\Component\Messenger\Attribute\AsMessageHandler;
+use Symfony\Component\Messenger\MessageBusInterface;
+use Symfony\Component\Workflow\WorkflowInterface;

 #[AsMessageHandler]
 class CommentMessageHandler
@@ -15,6 +18,9 @@ class CommentMessageHandler
         private EntityManagerInterface $entityManager,
         private SpamChecker $spamChecker,
         private CommentRepository $commentRepository,
+        private MessageBusInterface $bus,
+        private WorkflowInterface $commentStateMachine,
+        private ?LoggerInterface $logger = null,
     ) {
     }

@@ -25,12 +31,18 @@ class CommentMessageHandler
             return;
         }

-        if (2 === $this->spamChecker->getSpamScore($comment, $message->getContext())) {
-            $comment->setState('spam');
-        } else {
-            $comment->setState('published');
+        if ($this->commentStateMachine->can($comment, 'accept')) {
+            $score = $this->spamChecker->getSpamScore($comment, $message->getContext());
+            $transition = match ($score) {
+                2 => 'reject_spam',
+                1 => 'might_be_spam',
+                default => 'accept',
+            };
+            $this->commentStateMachine->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()]);
         }
-
-        $this->entityManager->flush();
     }
 }

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 metodo setState();
  • 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
@@ -41,6 +41,9 @@ class CommentMessageHandler
             $this->commentStateMachine->apply($comment, $transition);
             $this->entityManager->flush();
             $this->bus->dispatch($message);
+        } elseif ($this->commentStateMachine->can($comment, 'publish') || $this->commentStateMachine->can($comment, 'publish_ham')) {
+            $this->commentStateMachine->apply($comment, $this->commentStateMachine->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.

Trovare servizi nel Dependency Injection Container

Quando si utilizza la dependency injection, otteniamo i servizi dal dependency injection container attraverso il type hinting di un'interfaccia, oppure alcune volte attraverso il nome di una classe che rappresenta una implementazione concreta dell'interfaccia. Ma quando un'interfaccia ha più di una implementazione concreta, Symfony non può indovinare quale implementazione utilizzare. Abbiamo bisogno di un modo per essere espliciti.

Ci siamo appena imbattuti, nella sezione precedente, in un esempio in cui abbiamo iniettato l'interfaccia WorkflowInterface.

Sicome stiamo iniettando l'interfaccia generica WorkflowInterface nel costruttore, come può Symfony indovinare quale implementazione del workflow utilizzare? Symfony usa una convenzione basata sul nome dell'argomento: $commentStateMachine si riferisce alla variabile di configurazione del workflow comment (il cui tipo è state_machine). Provate con un altro argomento e fallirà.

Se non si ricorda la convezione, usare il comando debug::container. Cerchiamo tutti i servizi che contengono "workflow":

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ symfony console debug:container workflow

 Select one of the following services to display its information:
  [0] console.command.workflow_dump
  [1] workflow.abstract
  [2] workflow.marking_store.method
  [3] workflow.registry
  [4] workflow.security.expression_language
  [5] workflow.twig_extension
  [6] monolog.logger.workflow
  [7] Symfony\Component\Workflow\Registry
  [8] Symfony\Component\Workflow\WorkflowInterface $commentStateMachine
  [9] Psr\Log\LoggerInterface $workflowLogger
 >

Si noti la scelta 8, Symfony\Component\Workflow\WorkflowInterface $commentStateMachine, che dice che l'uso di $commentStateMachine come nome di parametro ha un significato speciale.

Note

Avremmo potuto usare il comando debug:autowiring come visto in un capitolo precedente:

1
$ symfony console debug:autowiring workflow

Andare oltre

  • Documentazione su Workflows e State Machine e quando scegliere una o l'altra;
  • Documentazione su Symfony Workflow.
Previous page Esecuzione asincrona
Next page Invio di e-mail agli amministratori
This work, including the code samples, is licensed under a Creative Commons BY-NC-SA 4.0 license.
TOC
    Version

    Symfony 6.4 is backed by

    Check Code Performance in Dev, Test, Staging & Production

    Check Code Performance in Dev, Test, Staging & Production

    Put the code quality back at the heart of your project

    Put the code quality back at the heart of your project

    Version:
    Locale:
    ebook

    This book is backed by:

    see all backers

    Symfony footer

    Avatar of zenmate, a Symfony contributor

    Thanks zenmate for being a Symfony contributor

    2 commits • 186 lines changed

    View all contributors that help us make Symfony

    Become a Symfony contributor

    Be an active part of the community and contribute ideas, code and bug fixes. Both experts and newcomers are welcome.

    Learn how to contribute

    Symfony™ is a trademark of Symfony SAS. All rights reserved.

    • What is Symfony?

      • What is Symfony?
      • Symfony at a Glance
      • Symfony Components
      • Symfony Releases
      • Security Policy
      • Logo & Screenshots
      • Trademark & Licenses
      • symfony1 Legacy
    • Learn Symfony

      • Symfony Docs
      • Symfony Book
      • Reference
      • Bundles
      • Best Practices
      • Training
      • eLearning Platform
      • Certification
    • Screencasts

      • Learn Symfony
      • Learn PHP
      • Learn JavaScript
      • Learn Drupal
      • Learn RESTful APIs
    • Community

      • Symfony Community
      • SymfonyConnect
      • Events & Meetups
      • Projects using Symfony
      • Contributors
      • Symfony Jobs
      • Backers
      • Code of Conduct
      • Downloads Stats
      • Support
    • Blog

      • All Blog Posts
      • A Week of Symfony
      • Case Studies
      • Cloud
      • Community
      • Conferences
      • Diversity
      • Living on the edge
      • Releases
      • Security Advisories
      • Symfony Insight
      • Twig
      • SensioLabs Blog
    • Services

      • SensioLabs services
      • Train developers
      • Manage your project quality
      • Improve your project performance
      • Host Symfony projects

      Powered by

    Follow Symfony