set_error_handler无法捕获fatal error,唯一通用兜底方式是register_shutdown_function配合error_get_last()判断e_error等致命错误类型并处理。

fatal error发生时,set_error_handler完全不触发
PHP的set_error_handler只捕获E_ERROR及以下级别的错误(如E_WARNING、E_NOTICE),但致命错误(Fatal Error)属于E_ERROR的子类,却**被刻意排除在外**——这是设计使然,不是配置问题。你写再全的set_error_handler回调,遇到Fatal error: Uncaught TypeError或Call to undefined function xxx,它就是不会进你的函数。
真正能兜住致命错误的,只有register_shutdown_function配合error_get_last():
- 在脚本终止前强制执行一次回调,无论是否异常退出
-
error_get_last()只在致命错误后返回非null,且类型必为E_ERROR、E_PARSE、E_COMPILE_ERROR等 - 注意:它也会在正常结束时触发,所以必须先判断
error_get_last()是否返回有效错误
用register_shutdown_function捕获并区分致命错误类型
直接读error_get_last()['type']比匹配错误消息字符串更可靠。常见致命错误类型有:
-
E_ERROR:运行时致命错误(如调用未定义函数、内存耗尽) -
E_PARSE:语法解析错误(通常出现在require/include时,但PHP 8+已限制在编译期报错) -
E_COMPILE_ERROR:编译时致命错误(如eval()里语法错) -
E_CORE_ERROR:PHP启动时核心错误(极少见)
示例逻辑:
register_shutdown_function(function () {
$error = error_get_last();
if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_COMPILE_ERROR, E_CORE_ERROR])) {
// 记录日志、发告警、清理资源
error_log("FATAL: {$error['message']} in {$error['file']}:{$error['line']}");
}
});
PHP 7+ 的Throwable机制不能替代shutdown监听
很多人误以为try/catch加Throwable能捕获所有致命错误——不能。Throwable只覆盖Exception和Error子类,而大多数致命错误(如Parse error、Fatal error: Class 'XXX' not found)发生在字节码生成或执行前,根本不会进入try作用域。
-
Error类实例(如TypeError、ParseError)确实可被catch (Throwable $e)捕获,但仅限于“运行中抛出”的错误 -
ParseError在include时是Fatal error,不是ParseError异常;只有eval("bad syntax")才会抛出ParseError对象 - 所以
shutdown仍是唯一通用兜底方式,try/catch只能处理部分可预见的运行时Error
线上环境别依赖display_errors=On来“看到”致命错误
开发机开display_errors只是方便调试,线上必须关——否则Fatal error信息会直接输出到HTTP响应体,可能泄露路径、变量名甚至数据库配置。
- 致命错误一旦发生,脚本立即终止,
ob_start()等输出控制也救不了页面内容泄露 - 正确做法:确保
log_errors=On,error_log指向可控文件或syslog,并用register_shutdown_function补充记录上下文(如请求URI、POST数据摘要) - 注意
error_log文件权限,避免web进程可读敏感日志
最常被忽略的一点:register_shutdown_function本身也可能出错(比如回调里又触发致命错误),导致二次崩溃而丢失原始错误。上线前务必测试该回调的健壮性——比如把它包在最外层try/catch里,或至少做is_callable检查。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











