set_error_handler 拦不住 e_error 是因为其属于终止性错误,会直接中止脚本执行,不触发自定义处理器;仅 e_warning、e_notice、euser* 等非终止错误可被捕获。

为什么 set_error_handler 拦不住 E_ERROR 这类错误
因为 PHP 的错误分级机制决定了:只有非终止性错误(如 E_WARNING、E_NOTICE、E_USER_WARNING)才能被 set_error_handler 捕获;E_ERROR、E_PARSE、E_CORE_ERROR 等会直接中止脚本执行,根本不会走到自定义处理器里。
- 常见错误现象:
set_error_handler设了但echo $undefined_var;或call_undefined_function()依然报原生错误,没进你的回调 - 真正能拦截的典型场景:变量未定义警告(
E_NOTICE)、类型不匹配警告(E_WARNING)、你自己用trigger_error('xxx', E_USER_WARNING)抛的错误 - 想统一处理致命错误?得配合
register_shutdown_function+error_get_last()补漏,但注意:它只能在脚本退出前“看一眼”,不能阻止崩溃
set_error_handler 回调函数的参数和返回值怎么写才不踩坑
回调函数必须严格接收 4 个参数:int $errno、string $errstr、string $errfile、int $errline,第五个 array $errcontext 已在 PHP 8.0+ 被移除,传了会报 Warning: Too many arguments。
- 容易踩的坑:用
function($e)这种单参写法 —— 直接导致参数错位,$errstr变成$errno,日志全乱 - 返回
true表示“我已处理,别继续报错”,否则 PHP 会按默认行为输出或记录;但返回false不等于“转发给下一个处理器”,PHP 不支持错误处理器链式调用 - 别在回调里再触发错误(比如
trigger_error),可能造成递归调用栈溢出 - 示例写法:
set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline) { error_log("[$errno] $errstr in $errfile:$errline"); return true; });
开发环境和生产环境对 error_reporting 的影响
set_error_handler 不是万能开关,它只处理「当前 error_reporting 级别开启的错误」。如果 error_reporting(0),连 E_USER_NOTICE 都不会触发回调。
- 本地调试时建议设为
error_reporting(E_ALL),确保所有用户级错误都进处理器,方便打点或记录上下文 - 线上环境常设
error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED),此时即使你写了处理器,E_NOTICE也不会进来 —— 别以为是处理器失效,其实是被屏蔽了 - 注意 ini 配置优先级:
ini_set('error_reporting', ...)会影响后续set_error_handler的生效范围,但不会改变已注册处理器的逻辑
为什么日志里经常看到重复错误或空错误信息
常见原因是错误处理器里调用了可能触发新错误的函数,比如 file_put_contents 权限不足、json_encode 遇到资源类型,或者错误本身来自超全局数组损坏(如 $_SERVER 被意外 unset)。
- 最隐蔽的坑:在处理器里访问
$_GET、$_POST等超全局变量 —— 如果错误发生在请求解析早期(比如post_max_size超限),这些变量可能还没初始化,读取会再抛一个E_NOTICE - 避免日志污染:处理器开头加
error_reporting(0)临时关闭报告,处理完再恢复;或者用@抑制内部调用的警告(仅限确定安全的操作) - 空
$errstr通常意味着错误由 Zend 引擎底层抛出(如内存耗尽),这类情况set_error_handler能捕获但信息有限,得靠系统日志或memory_limit配合排查
E_ERROR 和超全局变量失效这两块,很容易查着查着就偏到 Apache 日志或 PHP-FPM 配置里去。php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











