Event 负责广播通知,Behavior 负责接管响应;Event 是无人认领的通知,Behavior 是可插拔的能力契约;二者协同实现清晰解耦,避免硬依赖与竞态冲突。

Behavior 和 Event 在 Yii2 里不是“配套使用”的可选项,而是解耦架构的两层齿轮——Event 负责“通知发生了什么”,Behavior 负责“我来接管这件事”。真正硬核的解耦,不靠写得少,而靠分得清谁该听、谁该动、谁该藏。
事件是广播站,不是快递员
Event 的本质不是“调用某个函数”,而是“发一个无人认领的通知”。组件触发 beforeSave,它不关心有没有人监听,也不管监听者是谁、做了什么。它只做三件事:生成 $event 对象、带上 sender(自己)、广播出去。
-
别在 trigger 里传业务逻辑:不要把验证规则、日志内容塞进
$event->data当参数传;那是 handler 的职责,不是广播站的义务。 -
善用 $event->handled:比如权限检查行为先响应
beforeAction,发现未登录就设$event->handled = true,后续中间件或控制器方法可主动跳过——这不是拦截,是共识退出。 -
避免全局事件名冲突:用命名空间前缀,如
'app.order.beforeConfirm',而不是裸名'beforeConfirm'。
行为是插件,不是继承补丁
Behavior 不是“给类加几个方法”,而是让组件临时获得一套可插拔的能力契约。它不改父类、不碰原逻辑,只通过 attach() 注入生命周期钩子。
-
events() 返回的是委托关系,不是执行指令:返回
[ActiveRecord::EVENT_BEFORE_VALIDATE => 'validatePhone'],意思是“当模型要校验时,请调我的validatePhone方法”——这个绑定发生在 attach 阶段,不是每次 trigger 时才查表。 -
owner 是桥梁,不是傀儡:行为内可通过
$this->owner访问宿主,但绝不应直接改宿主私有属性或绕过其方法。正确做法是调用$this->owner->setAttribute()或触发宿主事件,保持语义清晰。 -
静态配置优先于动态 attach:在
behaviors()中声明行为,框架会自动 ensureBehaviors() 并完成事件注册;动态 attach 适合条件性增强(如仅对特定用户启用审计行为),但需手动管理 detach,易漏。
组合技:用 Behavior 封装 Event 的使用模式
单独写 $model->on('afterSave', [...]) 是散装解耦;把相同逻辑抽成 Behavior,才是可复用的解耦单元。例如时间戳行为:
- 定义
TimestampBehavior,重写events()返回[EVENT_BEFORE_INSERT => 'touchCreatedAt', EVENT_BEFORE_UPDATE => 'touchUpdatedAt']; - 每个 handler 方法只做一件事:
touchCreatedAt()设置created_at,touchUpdatedAt()设置updated_at; - 所有 ActiveRecord 子类只需声明该 behavior,无需重复 on/trigger,也无需在每个模型里重写 beforeSave。
警惕耦合回流的三个信号
当你发现解耦正在失效,往往有这些征兆:
- Behavior 里 new 了一个具体 Service 类:说明它偷偷引入了硬依赖,应改为通过 DI 容器获取或接收接口实例。
-
多个 Behavior 同时监听同一个事件并修改同一字段:比如两个行为都设
updated_at,谁后执行谁胜出——这不是协作,是竞态,应合并或明确优先级。 -
在 Controller 里手动
$model->trigger('customEvent'):事件应由组件自身生命周期驱动,人为 trigger 容易脱离上下文,变成隐藏调用链。


















