workflow.yaml 必须位于 config/packages/workflow.yaml,顶层为 framework: → workflows:,每个 workflow 显式声明 type: state_machine,marking_store 字段名需与实体属性或 getstate() 方法返回值严格一致,状态值字符串须全程统一。

怎么配 workflow.yaml 才算生效
配错路径或顶层结构,workflow 服务根本不会注册,后续所有调用都会报 Class "Workflow" not found 或 The workflow "xxx" has no type defined。不是“写了就跑”,而是容器里压根没这个服务。
- 文件必须叫
config/packages/workflow.yaml,写成workflows.yaml、workflow.php或放错目录都不行 - 顶层键必须是
framework:→workflows:,不能顶格写workflows: - 每个 workflow 必须显式声明
type: state_machine,漏掉就默认走workflow类型(支持多状态并存),和订单/文章这类单状态业务直觉不符 -
marking_store的字段名要和实体属性完全一致,比如配置了arguments: ['status'],实体就必须有public string $status = 'draft';或提供getStatus()方法
实体状态字段怎么对得上
Workflow 不会自动猜你的状态存在哪——它要么读公共属性,要么调 getState(),否则静默失败,不报错也不更新。
- 最稳妥做法:在实体里加
public function getState(): string { return $this->status; },不管字段叫$status还是$currentState - 如果不想改实体,就在 YAML 里指定
marking_store.type: method并写明方法名:arguments: ['getState'] - 状态值必须是字符串,且和
places列表里的每一项严格相等——'shipped'和'Shipped'是两个状态,'paid '(带空格)也匹配不上paid - 别手动赋值:
$order->status = 'shipped';会绕过全部校验,can()失效、事件不触发、审计断档
transition 触发失败的常见原因
Transition "ship" does not exist 这类报错,90% 不是代码写错了,而是当前对象的 state 值不在 ship 的 from 列表里。
- 先查实体当前状态:
var_dump($order->getState());,再翻workflow.yaml看ship的from是否包含这个值 - 多个 workflow 共存时,别用
$registry->get($order),它只返回第一个匹配的;显式传 name:$registry->get($order, 'order_main_workflow') - guard 回调返回
false会静默拦截,不抛异常也不打日志——建议开头加error_log("guard for {$order->getId()}");确认是否执行 -
can()返回true不代表apply()一定成功:guard 可能中途变卦,事件监听器也可能抛异常,apply()后还得手动$entityManager->flush()
为什么 apply() 后数据库没更新
Workflow 组件只负责校验和变更内存中的状态标记,它不碰数据库。这是设计使然,不是 bug。
-
$workflow->apply($order, 'ship');成功后,$order->getState()已变成'shipped',但数据库仍是旧值 - 必须紧接着调用:
$entityManager->persist($order); $entityManager->flush(); - 如果用了 Doctrine,注意不要在 guard 或事件监听里做耗时操作(如远程 API 调用),它们运行在
apply()同步流程中,会阻塞整个事务 - 复杂流程(如含退款、补发子状态)建议拆成多个独立 workflow,避免一个大配置难维护、难测试
最容易被忽略的是:状态值的字符串一致性贯穿全程——YAML 里的 places、实体 getState() 返回值、数据库字段存储值,三者必须字面完全相同。差一个空格、大小写或下划线,状态机就卡住,且不报错。











