不能仅用 set_error_handler,因为它不捕获 e_error 等致命错误;完整方案需同时注册 set_error_handler、set_exception_handler 和 register_shutdown_function。

为什么不能直接用 set_error_handler 就完事?
因为 set_error_handler 只捕获 E_WARNING、E_NOTICE 这类非终止错误,而 E_ERROR、E_PARSE 等致命错误它根本收不到——这些会直接中断脚本。真正“完整”的自定义错误处理,必须同时接管错误(set_error_handler)、异常(set_exception_handler)和致命错误(register_shutdown_function + error_get_last())。
常见错误现象:写了 set_error_handler 却发现 500 页面还是没日志、没友好的提示页,就是漏了致命错误兜底。
- PHP 7+ 中
E_ERROR不再触发set_error_handler,必须靠 shutdown 函数捞 -
set_exception_handler不处理错误,只处理未捕获的Exception和Error(PHP 7 的Error是 Throwable 子类) - 多个 handler 注册时后注册的会覆盖前一个,需确保只注册一次
怎么组织一个可复用的 ErrorHandler 类?
核心是把三类入口统一收敛到一个类里,用静态方法初始化,避免全局污染。关键不是“写得多”,而是“分得清”:哪些该记录,哪些该响应,哪些必须立刻中止。
示例骨架:
class ErrorHandler
{
public static function register(): void
{
set_error_handler([self::class, 'handleError']);
set_exception_handler([self::class, 'handleException']);
register_shutdown_function([self::class, 'handleShutdown']);
}
public static function handleError(int $level, string $message, string $file, int $line): bool
{
// 处理 E_WARNING/E_NOTICE 等
error_log("PHP Error: [$level] $message in $file:$line");
return false; // 返回 false 表示不屏蔽原生错误输出(开发环境可留,生产建议 true)
}
public static function handleException(Throwable $e): void
{
error_log("Uncaught Exception: " . $e->getMessage() . " in " . $e->getFile() . ":" . $e->getLine());
http_response_code(500);
echo "系统繁忙,请稍后再试";
}
public static function handleShutdown(): void
{
if ($error = error_get_last()) {
if (in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR])) {
error_log("Fatal Error: {$error['message']} in {$error['file']}:{$error['line']}");
http_response_code(500);
echo "系统繁忙,请稍后再试";
}
}
}
}
-
handleError返回false才会让 PHP 继续抛出原始错误(方便开发调试),生产环境建议返回true屏蔽 -
handleException必须接收Throwable,兼容Exception和 PHP 7+ 的Error -
handleShutdown里只处理致命错误类型,避免把exit()或正常结束误判为错误
如何让错误响应更可控?比如区分开发/生产环境
硬编码 echo 或 error_log 不够灵活。应该把“记录”和“响应”解耦,用配置驱动行为。
- 加一个
$isDev属性,开发环境显示详细错误(debug_backtrace()),生产环境只打日志+返回通用提示 - 把日志写入改为调用
error_log($msg, 3, $logFile),避免依赖 syslog 或第三方 - HTTP 响应状态码别写死:对
E_USER_DEPRECATED用 400,E_ERROR用 500,Exception根据类型判断(如InvalidArgumentException用 400) - 避免在 handler 中触发新错误(比如尝试写入不可写的日志目录),会导致二次崩溃
最容易被忽略的坑:错误处理自身的错误
handler 代码出错,比如日志路径不存在、echo 前已发送 header、或递归调用自身,会导致白屏且无任何线索。
- 所有 I/O 操作(文件写入、网络请求)必须包裹
try/catch,并用@抑制警告(仅此处合理) - 检查
headers_sent()再决定是否http_response_code()或echo - 不要在 handler 里调用可能触发新错误的函数,例如
json_encode()(大数组可能内存溢出)、file_get_contents() - shutdown handler 中调用
error_get_last()后,要立即重置,否则后续逻辑可能误读残留值
真正难的不是写满功能,而是保证错误处理器自己不会成为错误源头。上线前务必用 trigger_error('test', E_USER_ERROR)、throw new Exception()、eval('?>') 三类方式手动触发验证。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











