Aura.Payload 不是事件总线,仅作用例层与响应层间的数据容器;领域事件须用 Symfony EventDispatcher 配合实现 Event 接口的显式事件类(如 OrderCreated)来分发,确保解耦、类型安全与语义清晰。

为什么不用 Aura.Payload 直接传领域事件
Aura.Payload 本质是个数据容器,不是事件总线。它没有订阅、分发、监听机制,只负责结构化携带状态(比如 $payload->setStatus('SUCCESS')->setOutput($data))。想用它“传递领域事件”,等于拿水杯去接闪电——杯子能装东西,但不负责引雷也不触发反应。
常见错误现象:setInput() 塞了个订单创建参数,接着调 setOutput() 放了个“OrderCreated”对象,就以为完成了事件通知;结果下游完全没收到,因为没人监听这个 payload。
- 它不触发任何钩子,也不调用事件分发器
- 它的
getOutput()返回值只是普通对象,不是可被事件系统识别的事件实例 - 若强行把事件对象塞进
setOutput(),后续还得手动提取、再交给EventDispatcher,多此一举
正确封装领域事件:用 symfony/event-dispatcher + 自定义事件类
PHP 领域事件的核心是「解耦」和「显式契约」。你需要一个真正的事件类(如 OrderCreated),带明确属性和构造逻辑,再由标准分发器广播出去。
使用场景:订单创建成功后,需同时发邮件、更新库存、写日志——这些动作不该在用例层硬编码,而应由事件驱动。
立即学习“PHP免费学习笔记(深入)”;
- 事件类必须实现
Symfony\Contracts\EventDispatcher\Event(或继承Event基类) - 属性应为
readonly或私有+只读 getter,防止外部篡改业务语义 - 不要在事件里放
Aura.Payload实例,直接放原始领域对象(如Order $order)或其 ID 和关键字段
示例:
final class OrderCreated implements Event
{
public function __construct(
public readonly OrderId $id,
public readonly Money $total,
public readonly \DateTimeImmutable $occurredAt,
) {}
}
Aura.Payload 在事件流程中该在哪用
它唯一合理的位置,是作为**用例层(Use Case)与控制器/响应层之间的数据桥接器**,而不是跨领域通信载体。比如你用 CQRS 模式,命令处理器返回一个 Payload,里面含状态和可能的事件对象,但事件本身仍要单独 dispatch。
参数差异很关键:Aura.Payload::setOutput() 是任意值,EventDispatcher::dispatch() 要求是实现了 Event 接口的对象。
- 别把
$payload->getOutput()当作事件源直接 dispatch —— 类型不匹配会报错或静默失败 - 如果用例返回了 payload,应先判断
if ($payload->getStatus() === PayloadStatus::SUCCESS),再从 payload 提取领域对象,构建并 dispatch 真实事件 - 性能上无影响,但混淆职责会让测试变重:你得 mock payload 还得 mock dispatcher,本可各测各的
容易漏掉的兼容性细节
PHP 8.2+ 的只读类、属性提升构造函数和 Event 接口约束,会让旧写法立刻报错。比如用 public $orderId 定义事件属性,在 PHP 8.2 后无法通过类型检查,因为 Event 接口不强制属性可见性,但框架内部反射逻辑依赖明确声明。
- 别用
array或stdClass代替事件类——序列化、调试、IDE 支持全丢 - 事件类名必须以动宾结构命名(
OrderShipped,不是OrderShippingEvent),这是 DDD 社区事实标准 - 如果项目用了
spiral/roadrunner或 Swoole,注意事件监听器不能带单例状态,否则跨请求污染
真正难的不是怎么 dispatch,而是决定哪些状态变化值得升格为事件、谁该监听、以及事件是否需要幂等处理。这些没法靠工具自动解决,得翻着领域模型一页页对。











