set_error_handler 捕获不到 e_error,因为该类致命错误发生时脚本执行栈已损坏,php 直接终止而不进入错误分发流程;它仅能处理 e_warning、e_notice 等非终止错误。

set_error_handler 为什么捕获不到 E_ERROR
set_error_handler 默认不处理 E_ERROR、E_PARSE、E_CORE_ERROR 等致命错误。这不是 bug,是 PHP 的设计限制:这些错误发生时,脚本执行栈已损坏或中断,PHP 不再进入常规错误分发流程,而是直接终止 —— 所以你的回调函数根本不会被调用。
常见现象包括:Call to undefined function、Out of memory、Fatal error: Cannot redeclare,这些都属于 E_ERROR 级别,set_error_handler 完全无感。
-
set_error_handler只能捕获E_WARNING、E_NOTICE、E_DEPRECATED、E_USER_*等非终止类错误 - 即使你显式传入
E_ALL作为第二个参数(如set_error_handler($fn, E_ALL)),E_ERROR仍被自动过滤掉 -
@操作符能抑制E_WARNING等,但对E_ERROR无效 —— 它压根没机会走到错误处理器那一步
想捕获致命错误,得用 register_shutdown_function + error_get_last
真正能兜住 E_ERROR 的组合是:register_shutdown_function 配合 error_get_last()。前者保证脚本退出前必执行一次,后者能读取“最后发生的错误”(注意:仅限本次请求最后一次,且只在脚本自然结束或 exit() 后才稳定可用)。
典型写法:
register_shutdown_function(function () {
$error = error_get_last();
if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
error_log("Fatal: {$error['message']} in {$error['file']} on {$error['line']}");
// 这里可发告警、记录上下文、返回统一错误页
}
});
- 必须放在脚本早期(比如入口文件开头),否则可能错过某些致命错误
-
error_get_last()在set_error_handler被触发后仍有效,但它拿不到已被自定义处理器“吃掉”的非致命错误 - 如果脚本被
exit()或die()强制终止,register_shutdown_function仍会执行;但如果进程被 kill -9 杀死,则完全不触发
PHP 7+ 的 Exception 替代方案:Error 类
PHP 7 引入了 Error 类,它和 Exception 一样继承自 Throwable,因此能被 try-catch 捕获 —— 但前提是错误被“转换”为 Error 对象。这需要配合 set_error_handler 主动抛出异常来实现。
例如:
set_error_handler(function ($errno, $errstr, $errfile, $errline) {
if (error_reporting() & $errno) {
throw new Error($errstr, $errno);
}
return false;
});
- 这段代码只对当前作用域内可报告的错误生效(即未被
@抑制、且在error_reporting掩码内的错误) -
E_ERROR依然进不来这个函数 —— 所以它不能替代register_shutdown_function,只能把E_WARNING等转成可catch的Error - 实际项目中,常把这种转换逻辑和
set_exception_handler配合使用,统一处理Exception和Error
容易忽略的关键点
很多人以为只要开了 error_reporting(E_ALL) 就万事大吉,其实不是。错误能否被捕获,取决于三件事:错误类型本身是否可拦截、错误处理器是否已注册、以及错误是否发生在处理器生效之后。
-
parse error(语法错误)在文件加载阶段就报错,set_error_handler根本没机会注册,所以任何运行时设置都无效 - 在
set_error_handler注册前发生的错误(比如配置文件里的require失败),不会被它捕获 -
restore_error_handler()只影响后续错误,对已发生的错误无回溯能力 - 多线程 SAPI(如 PHP-FPM)中,
set_error_handler是 per-request 的,但register_shutdown_function也是 request 生命周期内有效
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











