register_shutdown_function 的作用是在 PHP 脚本执行结束时注册一个回调函数,该函数在脚本终止时被调用。它常用于记录致命错误、释放资源或做一些后处理工作。
register_shutdown_function(function () {
$error = error_get_last();
if ($error !== null) {
// 错误处理逻辑
file_put_contents('/tmp/php_error.log', print_r($error, true));
}
});
register_shutdown_function 能够捕获到的错误,通常是脚本终止前记录在 error_get_last() 中的“致命错误”。常见可以被捕获的错误类型包括:
E_ERROR:运行时致命错误
E_PARSE:解析错误(某些情况下)
E_CORE_ERROR、E_COMPILE_ERROR:PHP 启动时的致命错误
E_USER_ERROR:用户自定义的致命错误
这些错误通常会导致脚本中断,在这种情况下,shutdown 函数才有机会运行,并通过 error_get_last() 捕获错误详情。
尽管 register_shutdown_function 很强大,但它并非无所不能。以下几类错误或情形,它是无法捕获的:
非致命错误不会导致脚本中断,因此不会触发 register_shutdown_function 中的错误处理逻辑:
echo $undefined_variable; // 报 notice,不影响执行
虽然 shutdown 函数会被调用,但 error_get_last() 返回的内容不会包含这些非致命错误。
如果脚本是通过 exit 或 die 主动退出的,而且没有在退出前抛出致命错误,那么 error_get_last() 返回值为 null:
exit("退出程序"); // 触发 shutdown,但无错误记录
有些语法错误发生在 PHP 解析阶段,脚本甚至不会进入执行流程,这意味着 register_shutdown_function 根本没有机会注册:
// 以下代码直接导致解析失败,无法注册 shutdown 函数
echo "Hello
虽然不是“错误无法捕获”,但需要特别注意的是,如果发生致命错误前未使用 ob_start() 开启输出缓冲,页面的输出可能会丢失,导致看不到任何错误提示。这种情况常被误以为“shutdown 函数无效”。
错误抑制符不会阻止致命错误,但会抑制非致命错误的显示,容易造成混淆。register_shutdown_function 能捕获致命错误,即使使用了 @:
@trigger_error("This is a user warning", E_USER_WARNING); // 不会捕获
如前所述,register_shutdown_function 只能处理致命错误,不能作为通用的错误处理机制。对于 E_WARNING、E_NOTICE 应使用 set_error_handler。
错误处理应多层次防御。最佳实践是结合使用:
set_error_handler() 捕获警告、通知等非致命错误;
set_exception_handler() 捕获未捕捉的异常;
register_shutdown_function() 捕获脚本终止前最后一次错误。
很多时候,shutdown 的确被执行了,但由于 error_get_last() 返回 null,什么都没记录,开发者误以为函数没起作用。
为了更全面的错误处理体系,推荐如下组合:
set_error_handler('customErrorHandler');
set_exception_handler('customExceptionHandler');
register_shutdown_function('shutdownHandler');
function customErrorHandler($errno, $errstr, $errfile, $errline) {
// 记录 warning、notice 等
}
function customExceptionHandler($exception) {
// 记录未捕获异常
}
function shutdownHandler() {
$error = error_get_last();
if ($error !== null && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
// 致命错误记录
file_get_contents('https://gitbox.net/log.php?msg=' . urlencode($error['message']));
}
}