步骤 19: 用 Workflow 进行决策
5.0 version
Maintained
用 Workflow 进行决策¶
让模型拥有状态是很常见的。目前评论的状态只由垃圾信息检查器来决定。如果我们要加入更多的决策因素,那该怎么做?
在垃圾信息检查器判别之后,我们可能想要让网站管理员来管理所有评论。这个流程看上去可能是这样的:
- 当用户提交评论时,我们把它的初始状态设为
submitted
; - 然后垃圾信息检查器来分析这条评论,把它的状态转为
potential_spam
、ham
或rejected
中的一个; - 如果评论不是
rejected
状态,那需要等待管理员根据它的内容质量来决定,是把它切换到published
还是rejected
。
实现这个逻辑并不复杂,但你可以想到,不断增加类似的规则会大幅提高复杂度。我们可以使用 Symfony 的 Workflow 组件,而不是自己实现它。
1 | $ symfony composer req workflow
|
描述工作流(Workflow)¶
可以在 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
|
为了验证这个流程,可以生成一个示意图:
1 | $ symfony console workflow:dump comment | dot -Tpng -o workflow.png
|

注解
dot
命令是 Graphviz 工具的一部分。
使用工作流¶
在消息处理器里用工作流来代替当前逻辑:
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');
- }
- $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()]);
+ }
}
}
|
新的逻辑是这样:
- 如果消息里的评论可以进行名为
accept
的状态迁移,就检查是否为垃圾信息; - 根据结果来选择要应用的状态迁移;
- 调用
apply()
方法来更新评论,它会调用评论的setState()
方法。 - 调用
flush()
方法来把更新保存到数据库; - 重新派发信息来让工作流再次迁移状态。
因为我们还没有实现管理后台的验证,下次消费信息时,日志中会记录 “Dropping comment message”。
在下一章里我们会实现一个自动验证:
patch_file¶
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()]);
}
|
执行 symfony server:log
,然后在前端页面添加一个评论,看一下输出的一个个状态迁移。
- « Previous 步骤 18: 使用异步
- Next » 步骤 20: 向管理员发送邮件
This work, including the code samples, is licensed under a Creative Commons BY-NC-SA 4.0 license.