因为set_error_handler仅捕获E_WARNING等可恢复错误,对Exception、Error(如FatalError)完全无效;PHP 7+需协同set_exception_handler和register_shutdown_function三者兜底,且告警逻辑硬编码导致扩展难、耦合高。

为什么直接用 set_error_handler 不够用
它只能捕获传统错误(E_WARNING、E_NOTICE 等),对 Exception 和 Error(如 FatalError)完全无效。PHP 7+ 的 Throwable 体系把错误和异常统一为可捕获对象,但 set_error_handler 还是老一套——你得额外配 set_exception_handler 和 register_shutdown_function 捕获致命错误,三套逻辑并行,容易漏掉某类问题。
更麻烦的是:所有告警逻辑(发邮件、推 Slack、写日志)都挤在同一个回调函数里,一改全动,加个新渠道就得改主流程,违反开闭原则。
- 只靠
set_error_handler会漏掉未捕获的Exception和FatalError - 所有通知逻辑硬编码,新增钉钉告警要改核心函数,易出错
- 无法按错误类型分流:比如
E_ERROR立刻电话告警,E_DEPRECATED只进日报表
Observer 接口怎么对接 Throwable
观察者模式的关键是“被观察者”能统一发出通知,而 PHP 的错误/异常入口分散。你需要一个中心化的“错误事件分发器”,它不处理具体逻辑,只负责把 Throwable 实例广播给所有注册的观察者。
这个分发器本身不是 Observer,而是 Observerd(被观察者)。它需在三个地方注册钩子:
-
set_exception_handler:捕获所有未 catch 的Exception/Error -
set_error_handler:把传统错误包装成Error子类再抛出(例如ErrorException) -
register_shutdown_function:检查error_get_last(),若存在FatalError就构造FatalError对象并通知
这样,所有错误最终都变成 Throwable 实例,进入同一通知管道。
邮件、Slack、Sentry 观察者怎么写才不耦合
每个观察者只关心“自己该做什么”,不依赖其他观察者,也不修改原始错误对象。接口定义只需一个 update(Throwable $e) 方法。
示例:Slack 观察者只做三件事——格式化消息、调用 Slack webhook、记录发送状态。它不关心这错误是否也发了邮件,也不管 Sentry 是否已上报。
- 邮件观察者:
mail()或 SwiftMailer 发送,附带$e->getTraceAsString() - Slack 观察者:用
curlPOST 到 webhook URL,body 包含$e->getMessage()和环境标识(如$_SERVER['APP_ENV']) - Sentry 观察者:调用
Sentry\captureException($e),不自行重试或降级
关键点:所有观察者构造时接收配置(如 webhook 地址、邮箱列表),而不是从全局变量读取——方便单元测试和多环境切换。
性能与顺序问题怎么避坑
观察者执行是同步的,如果某个观察者卡住(比如 Slack webhook 超时 30 秒),整个错误处理就阻塞,可能拖垮请求生命周期。必须加超时和隔离。
- 每个观察者用
try/catch包裹,内部异常不能外泄(否则可能触发二次错误通知) - 网络类观察者(Slack、邮件)设
curl_setopt($ch, CURLOPT_TIMEOUT, 2),失败就跳过,记一条“告警渠道不可用”日志 - 不要让观察者互相依赖:比如“先发邮件,再等邮件返回成功才发 Slack”——这违背观察者解耦本质
- 顺序有语义:把日志类观察者(写本地文件)放最前,确保即使后续告警失败,错误至少落盘
最易忽略的一点:观察者列表本身要是线程安全的。PHP-FPM 是多进程模型,但单个请求内观察者注册是单线程的;真正要注意的是 CLI 场景下多个 Worker 并发注册——用 spl_object_hash($observer) 去重比用数组键名更可靠。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











