




PHP致命错误无法用set_error_handler捕获,必须用register_shutdown_function+error_get_last()兜底隐屏,并关闭display_errors、开启log_errors写入有权限的日志文件。
PHP 发生致命错误(Fatal error)时默认会直接输出错误信息到页面,暴露路径、函数名甚至数据库配置,不仅影响用户体验,更存在安全风险。要隐屏,不能只靠 error_reporting(0) 或关掉 display_errors —— 这些对致命错误基本无效。
set_error_handler() 拦不住 Fatal error
因为 PHP 的错误处理机制中,Fatal error(如 Call to undefined function、Class not found、Maximum execution time exceeded)属于“不可捕获的致命错误”,它发生在脚本执行中途且无法恢复,set_error_handler() 根本不会被调用。
set_error_handler() 只能捕获 E_WARNING、E_NOTICE 等非致命错误register_shutdown_function() 是唯一能在致命错误后执行的钩子error_get_last() 判断是否真发生了致命错误register_shutdown_function() + error_get_last() 实现隐屏这是生产环境最可靠的做法:在脚本终止前检查最后错误,如果是致命类,就清空输出缓冲、返回自定义响应(如 500 页面或空白响应),避免泄漏。
register_shutdown_function(function () {
if ($error = error_get_last()) {
$fatalErrors = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR];
if (in_array($error['type'], $fatalErrors)) {
// 清除可能已输出的内容
if (ob_get_level()) ob_end_clean();
// 返回 HTTP 500 状态(可选)
http_response_code(500);
// 输出统一提示(或直接 exit,不输出任何内容)
echo 'Service unavailable';
exit;
}
}
});
ob_end_clean() 很关键:防止前面已有
display_errors 和 log_errors 的正确组合隐屏 ≠ 不记录。线上必须关闭 display_errors,但务必开启 log_errors 并指定有效日志路径,否则你连问题都发现不了。
display_errors = Off,log_errors = On,error_log = /var/log/php/error.log
error_log 路径需 Web 服务器用户(如 www-data)有写权限,否则日志静默丢失ini_set() 动态设置,需确保在出错前执行;但 display_errors 对致命错误的屏蔽效果不稳定,不能依赖真正可靠的隐屏不是靠关掉一个开关,而是靠 shutdown 钩子兜底 + 日志闭环 + 权限与路径校验。最容易被忽略的是 ob_end_clean() 缺失导致 headers already sent 报错,或者日志目录不可写却以为“已经记了”。