1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SymfonyのWorkflow Componentを使ってみた!

Last updated at Posted at 2023-12-11

\ Symfonyアドベントカレンダー11日目です /

気になってたWorkflow Componentを使ってみたので、そのメモです。

今回、The Fast Track - 基礎から最速で学ぶ Symfony 入門ワークフローを使って判定するを参考に進めました。

作るものは、カンファレンスに対してコメントを投稿されると、spamチェックをしステータスを管理する機能です。

概要

準備

ここがメインではないので使ったコマンドとDB概要のみ記載します。

ConferenceエンティティとCommentエンティティを作成し、関連づける

$ composer require symfony/maker-bundle --dev
$ composer require orm
$ symfony console make:entity Comment
$ symfony console make:entity Conference
mysql> desc conference;
+------------------+--------------+------+-----+---------+----------------+
| Field            | Type         | Null | Key | Default | Extra          |
+------------------+--------------+------+-----+---------+----------------+
| id               | int(11)      | NO   | PRI | NULL    | auto_increment |
| city             | varchar(255) | NO   |     | NULL    |                |
| year             | varchar(4)   | NO   |     | NULL    |                |
| is_international | tinyint(1)   | NO   |     | NULL    |                |
+------------------+--------------+------+-----+---------+----------------+
4 rows in set (0.02 sec)

mysql> desc comment;
+---------------+--------------+------+-----+-----------+----------------+
| Field         | Type         | Null | Key | Default   | Extra          |
+---------------+--------------+------+-----+-----------+----------------+
| id            | int(11)      | NO   | PRI | NULL      | auto_increment |
| conference_id | int(11)      | NO   | MUL | NULL      |                |
| author        | varchar(255) | NO   |     | NULL      |                |
| text          | longtext     | NO   |     | NULL      |                |
| email         | varchar(255) | NO   |     | NULL      |                |
| created_at    | datetime     | NO   |     | NULL      |                |
| state         | varchar(255) | NO   |     | submitted |                |
+---------------+--------------+------+-----+-----------+----------------+
7 rows in set (0.00 sec)

ConferenceControllerを作成する

$ symfony console make:controller ConferenceController
$ composer req symfony/asset twig/intl-extra:^3
$ composer require form validator

非同期で簡易的なspamチェックをするようにする

$ symfony composer req doctrine-messenger
$ symfony console messenger:consume async -vv

参考:

ワークフローを使ってステータスを管理するようにする

symfony/workflowをインストールする

$ composer require symfony/workflow

ワークフローを作成する(yaml)

config/packages/workflow.yaml

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
                - spam
                - rejected
                - published
            transitions:
                accept:
                    from: submitted
                    to:   ham
                reject_spam:
                    from: submitted
                    to:   spam
                publish_ham:
                    from: ham
                    to:   published
                reject_ham:
                    from: ham
                    to:   rejected

ワークフロー図を生成する

symfony console workflow:dump comment | dot -Tpng -o workflow.png

こんな図を作成できます :sunglasses:
workflow.png

ワークフローを設定する

ConferenceController.php

class ConferenceController extends AbstractController
{

    #[Route('/conference/{id}', name: 'conference')]
    public function show(Request $request, Environment $twig, Conference $conference, CommentRepository $commentRepository): Response
    {
        $comment = new Comment();
        $form = $this->createForm(CommentType::class, $comment);
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            $comment->setConference($conference);

            $this->entityManager->persist($comment);
            $this->entityManager->flush();

            $this->bus->dispatch(new CommentMessage($comment->getId()));
            ...

CommentMessageHandler.php

<?php
declare(strict_types=1);

namespace App\MessageHandler;

use App\Message\CommentMessage;
use App\Repository\CommentRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Workflow\WorkflowInterface;

#[AsMessageHandler]
class CommentMessageHandler
{
    public function __construct(
        private readonly EntityManagerInterface $entityManager,
        private readonly CommentRepository      $commentRepository,
        private readonly MessageBusInterface    $bus,
        private readonly WorkflowInterface      $commentStateMachine,
    )
    {
    }

    public function __invoke(CommentMessage $message): void
    {
        $comment = $this->commentRepository->find($message->getId());
        if (!$comment) {
            return;
        }

        if ($this->commentStateMachine->can($comment, 'accept')) {
            $transition = 'accept';
            // 超簡易的なspamチェック
            if (str_contains($comment->getText(), 'test')) {
                $transition = 'reject_spam';
            }

            $this->commentStateMachine->apply($comment, $transition);
            $this->entityManager->flush();
            $this->bus->dispatch($message);
        } elseif ($this->commentStateMachine->can($comment, 'publish_ham')) {
            $this->commentStateMachine->apply($comment, 'publish_ham');
            $this->entityManager->flush();
        }
    }
}

こんな感じで、コメント内容に「test」が含まれている場合はstatusがspamに遷移し、
それ以外の場合はham->publishedに遷移しました。

mysql> select * from comment;
+----+---------------+--------+---------+------------------+---------------------+-----------+
| id | conference_id | author | text    | email            | created_at          | state     |
+----+---------------+--------+---------+------------------+---------------------+-----------+
...
|  9 |             2 | taro   | test    | taro@example.com | 2018-01-01 00:00:00 | spam      |
| 10 |             2 | jiro   | hey     | jiro@example.com | 2018-01-01 00:00:00 | published |
+----+---------------+--------+---------+------------------+---------------------+-----------+

$ symfony console messenger:consume async -vv

                                                                                                                        
 [OK] Consuming messages from transport "async".                                                                        
                                                                                                                        

 // The worker will automatically exit once it has received a stop signal via the messenger:stop-workers command.       

 // Quit the worker with CONTROL-C.                                                                                     

[info] Received message App\Message\CommentMessage
[info] Leaving "submitted" for subject of class "App\Entity\Comment" in workflow "comment".
[info] Transition "reject_spam" for subject of class "App\Entity\Comment" in workflow "comment".
[info] Entering "spam" for subject of class "App\Entity\Comment" in workflow "comment".
[info] Sending message App\Message\CommentMessage with async sender using Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineTransport
[info] Message App\Message\CommentMessage handled by App\MessageHandler\CommentMessageHandler::__invoke
[info] App\Message\CommentMessage was handled successfully (acknowledging to transport).

[info] Received message App\Message\CommentMessage
[info] Leaving "submitted" for subject of class "App\Entity\Comment" in workflow "comment".
[info] Transition "accept" for subject of class "App\Entity\Comment" in workflow "comment".
[info] Entering "ham" for subject of class "App\Entity\Comment" in workflow "comment".
[info] Sending message App\Message\CommentMessage with async sender using Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineTransport
[info] Message App\Message\CommentMessage handled by App\MessageHandler\CommentMessageHandler::__invoke
[info] App\Message\CommentMessage was handled successfully (acknowledging to transport).
[info] Received message App\Message\CommentMessage
[info] Leaving "ham" for subject of class "App\Entity\Comment" in workflow "comment".
[info] Transition "publish_ham" for subject of class "App\Entity\Comment" in workflow "comment".
[info] Entering "published" for subject of class "App\Entity\Comment" in workflow "comment".
[info] Message App\Message\CommentMessage handled by App\MessageHandler\CommentMessageHandler::__invoke
[info] App\Message\CommentMessage was handled successfully (acknowledging to transport).

番外編: Symfony ProfilerにWorkflowが追加されました :tada: ※Symfony6.4以上

スクリーンショット 2023-12-11 16.46.14.png

さいごに

yamlにルールを記載できたり、ワークフロー図を生成できたり超便利だなと思いました。

今回、初めてだったのでThe Fast Trackを参考にコメントのステータス管理で試してみました。
メジャーな機能だとTODOリストのステータス管理などに使えそうだなと思いました!

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?