ThinkPHP6事件订阅实战:我把订单通知从"到处乱挂"重构到"一处监听"的踩坑记录

小助手
小助手 版主圣羽星庭 勋望元宿志愿先锋
社区管理
站长杂谈 74 浏览 0 回复

之前项目里的业务通知写得那叫一个随心所欲——控制器里直接调模型,模型里又塞邮件发送,支付回调里还硬编码了短信逻辑。上周改个通知文案,翻了五六个文件才找全,当场血压拉满。周末干脆把整个事件机制捋了一遍,重构完清爽多了,记录一下。

一、先分清:钩子、事件、中间件别再用混

刚开始我也蒙,ThinkPHP6里这几个概念长得太像。实际用下来我的理解是:

• 中间件:管请求进出的,偏HTTP层,比如鉴权、日志

• 事件(Event):业务状态变了要广播出去,谁爱听谁听

• 钩子(Hook):更老派的叫法,TP6里基本统一成事件系统了,但文档偶尔混用

我这次重构的核心就是把"订单状态变更"这件事抽成事件,至于要发邮件、推微信、写日志,那是监听者的事,订单模型只管抛事件。

二、注册监听:三种方式我踩了两个坑

TP6注册事件监听有三种路子,我全试了一遍:

1. 事件类+监听类(最规范,但我一开始写反了)

我傻乎乎把业务逻辑写进了Event类,后来才搞懂:Event类是"发生了啥",携带数据;Listener类才是"要干啥",处理逻辑。比如:

```php // app/event/OrderPaid.php —— 这是"消息信封" class OrderPaid { public $order; public function __construct($order) { $this->order = $order; } } // app/listener/SendSmsNotice.php —— 这是"处理人" class SendSmsNotice implements \think\event\ListenerInterface { public function handle($event) { // $event->order 拿数据 SmsService::send($event->order->mobile, '您的订单已支付...'); } } ```

2. 闭包监听(适合临时调试,生产慎用)

我在event.php配置里写过这个,方便是方便,但代码分散不好追踪:

```php Event::listen('OrderPaid', function($event) { Log::info('订单支付事件触发:' . $event->order->order_no); }); ```

3. 事件订阅器(最终采用的方案)

一个类管多个事件的监听,适合业务聚合。我的OrderSubscriber长这样:

```php class OrderSubscriber { public function subscribe(Event $event) { $event->listen('OrderPaid', [$this, 'onPaidSendSms']); $event->listen('OrderPaid', [$this, 'onPaidUpdateUserStats']); $event->listen('OrderShipped', [$this, 'onShippedNotifyExpress']); } public function onPaidSendSms($event) { ... } public function onPaidUpdateUserStats($event) { ... } } ```

然后在event.php的subscribe数组里挂上这个类,一处注册,到处生效。

三、调试事件:我加了这三招才定位到监听没触发

重构完测试,有个监听死活不执行,排查了半小时。总结几个调试技巧:

1. 先看事件有没有真的触发

控制器里用```Event::trigger('OrderPaid', new OrderPaid($order));```之后,立刻加一行Log::info确认执行到了。我那次问题就是前面有个return漏了,事件代码根本没跑到。

2. 监听类名大小写坑

TP6的事件名默认是字符串匹配,我配置里写的```'orderPaid'```,触发用的```'OrderPaid'```,Linux环境下直接不匹配。统一改成驼峰后解决,本地Windows反而不报错,坑。

3. 加个全局调试中间件

临时在event.php里塞了个万能监听,排查阶段很有用:

```php Event::listen('*', function($event, $params) { trace('事件触发:' . $event . ',参数类型:' . get_class($params[0] ?? new \stdClass())); }); ```

TP6不支持真正的通配符,这个'*'会被当成普通字符串事件名,但配合手动trigger调试用还行。更靠谱的是直接看runtime/log里的日志,或者Xdebug跟进去。

四、生命周期挂钩:我目前这么分层

现在项目里的事件挂载点,基本按这个节奏来:

阶段事件示例监听典型操作
数据写入前OrderCreating参数校验、防重复提交
数据写入后OrderCreated初始化关联数据
状态变更时OrderPaid / OrderShipped通知、统计、积分
异步队列OrderPaidQueue大任务延后处理

有个细节:支付回调这种第三方入口,我直接在回调控制器里trigger事件,然后立刻返回success,具体通知走队列异步发。之前同步发邮件,超时了微信重复通知三笔,血亏。

五、还没搞懂的点

文档里说事件可以走队列,但```Event::trigger```好像没有直接支持,我是手动在监听里```Queue::push```的。有没有老哥试过```event.php```里配置```'queue' => true```或者别的姿势?另外多个监听器的执行顺序,目前看是注册顺序,但有没有优先级控制我没找到,求指点。

重构完代码量其实多了,但控制器薄了一半,改需求不用翻七八个文件了。这周末打算把用户注册、退款也迁过来,希望别翻车。

评论0
回复 · 0
还没有回复
微信客服 微信客服