资讯详情

资讯详情

FastAdmin Hook 机制与插件错误透传:`$result` 之外的信封

FastAdmin Hook 机制与插件错误透传$result之外的信封基于 FastAdminThinkPHP 5.0 PHP 7.3 的真实项目经验。本文以短信网关发送失败时拿到底层错误信息为例拆解Hook::listen的引用传参机制以及如何利用 Model 的ArrayAccess实现错误信息的 in/out 信封传递最后讨论多层日志的冗余治理。0. 从一个困惑说起给短信网关加错误透传时遇到一段看不懂的代码$sms\app\common\model\Sms::create([...]);$resultHook::listen(sms_send,$sms,null,true);if(!$result){// 为什么错误信息从 $sms 上读而不是从 $result 读$errorisset($sms[error])?$sms[error]:未知错误;}两个疑问$sms明明是个模型对象插件发短信的失败信息怎么跑到它身上去了失败结果不应该在$result里吗答案藏在 TP5 的Hook机制里。把 [thinkphp/library/think/Hook.php](file:///E:/www/hnnonggou/thinkphp/library/think/Hook.php) 拆开看就明白了。1. Hook::listen 源码拆解TP5 的 Hook 是标签 → 行为的事件机制。FastAdmin 的插件addons就是通过它挂载的sms_send这个标签上挂着 alisms 插件的smsSend方法。1.1 入口listenpublicstaticfunctionlisten($tag,$paramsnull,$extranull,$oncefalse){$results[];foreach(static::get($tag)as$key$name){$results[$key]self::exec($name,$tag,$params,$extra);// 如果返回 false或者仅获取一个有效返回则中断行为执行if(false$results[$key]||(!is_null($results[$key])$once)){break;}}return$once?end($results):$results;}三个关键点$params是引用参数——这是整个信封机制的地基$once true时只执行第一个行为并返回其返回值所以$result只有插件的 return 值返回false会中断后续行为失败短路语义1.2 分发execpublicstaticfunctionexec($class,$tag,$paramsnull,$extranull){$methodLoader::parseName($tag,1,false);// sms_send → smsSend// ...$objnew$class();$result$obj-$method($params,$extra);// 调用插件方法return$result;}exec的$params同样是引用一路原样传给插件的smsSend($params)。2. 核心知识点一引用传参 共享同一个对象调用链路SmsExt::send $sms Sms::create(...) // 模型对象落库 $result Hook::listen(sms_send, $sms, null, true) └→ exec(addons\alisms\Alisms, sms_send, $sms) └→ Alisms::smsSend($params) // $params 与 $sms 是同一个对象PHP 的对象变量存的是句柄$params让插件拿到的句柄与调用方的$sms指向同一份内存。插件里写publicfunctionsmsSend($params){$alismsnew\addons\alisms\library\Alisms();$result$alisms-mobile($params[mobile])-template(...)-send();if(!$result){$params[error]$alisms-getError();// 写进的是调用方那个对象}return$result;}插件执行完SmsExt::send里的$sms就多出了一个error属性。3. 核心知识点二Model 是天然信封ArrayAccess为什么能往模型上$params[error] xxx这样赋值因为 TP5 的Model实现了ArrayAccess接口offsetSet→ 写入模型内存中的data数组offsetExists/offsetGet→ 从data数组读关键这只改内存不会自动写库。只有调用save()才会落库。所以模型对象天然就是一个信封——插件往里塞附加信息调用方原样取回数据库表结构完全不用动。4. 为什么错误不在 $result 里$result是listen的返回值它只取插件方法的return值而插件约定只返回成败布尔returnfalse;// 失败 → $result falsereturntrue;// 成功 → $result true布尔里装不下阿里云为什么拒绝你签名错误模板不存在余额不足。所以通道携带什么用途return值$result成败布尔流程控制if 判断$params引用错误详情 Message排查、审计、透传给上层$result告诉你成没成$sms[error]告诉你为什么没成。这是 FastAdmin 插件机制的既有约定——插件从$params[mobile]取输入入参信封也可以往$params写输出出参信封in/out 两用。5. 实战让错误穿透三层完整的错误透传链路阿里云返回 {Code: isv.xxx, Message: InvalidResult} → alisms 插件$params[error] $alisms-getError() [源头捕获] → SmsExt::sendself::$lastError $sms[error] [库层缓存] → 网关控制器Smslib::getLastError() [业务读取] ├→ \think\Log::record(sms_send_fail app... error...) └→ fa_sms_gateway_log 表 fail_reason 字段 [审计流水]各层职责插件层只负责捕获 回写是错误的最源头库层SmsExt只负责缓存 透传通过getLastError()暴露网关层决定记到哪里——文件日志给运维 grep数据库流水给审计6. 日志分层与冗余治理链路里一度有三层文件日志 一层数据库流水层日志内容用途alisms 插件alisms send fail mobile... event... error...源头覆盖后台测试短信等非网关场景SmsExt 库sms_send fail mobile... event... error...与插件层内容几乎完全重复网关控制器sms_send_fail app... scene... error...带 app_id/scene 业务维度运维排查fa_sms_gateway_logfail_reason 字段审计留痕业务可查可统计治理结论SmsExt 中间层是冗余——同一失败写两遍日志文件且没有增量信息app_id/scene 在网关层才有。库层应该只透传错误不记日志记什么由调用方决定。插件层保留——它是唯一覆盖非网关直接调插件场景的日志且是错误源头。网关日志 DB 流水不算冗余——文件日志用于运维排查/告警数据库流水用于审计追溯载体和用途不同是生产级的正常分层。7. 要点回顾Hook::listen($tag, $params, ...)的第二个参数是引用传参插件和调用方共享同一个对象TP5 Model 的ArrayAccess让模型对象天然可作为 in/out信封写入只进内存data数组不落库$result只携带成败布尔详情走信封成败走返回值——这是 FastAdmin 插件的既有约定错误透传的分层原则源头捕获、库层透传、调用方决定如何记录多层日志要按增量信息审视内容重复且无新维度的中间层日志就是冗余
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →