PHP无法用try-catch直接捕获所有致命错误,因解析错误(E_PARSE)等发生在脚本执行前或运行时环境已崩溃,导致try-catch机制失效;但可通过set_error_handler处理非致命错误,set_exception_handler捕获未捕获的异常(包括PHP7+的Error),结合register_shutdown_function在脚本终止时调用error_get_last()获取致命错误信息,实现全面的错误记录与响应。

PHP本身无法直接用
try-catch
E_PARSE
E_ERROR
set_error_handler()
set_exception_handler()
register_shutdown_function()
要全面捕获并处理PHP中的错误,你需要策略性地部署以下机制:
自定义错误处理函数 (set_error_handler
E_NOTICE
E_WARNING
E_USER_ERROR
E_RECOVERABLE_ERROR
set_error_handler
E_ERROR
E_PARSE
E_CORE_ERROR
E_COMPILE_ERROR
<?php
set_error_handler(function ($errno, $errstr, $errfile, $errline) {
// 对于一些错误类型,可以将其转换为异常抛出
// if (in_array($errno, [E_WARNING, E_NOTICE])) {
// throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
// }
// 或者直接记录日志
error_log("Error: [$errno] $errstr in $errfile on line $errline");
// 返回 false 让PHP继续执行默认的错误处理,或者返回 true 阻止PHP默认处理
return true;
});
?>自定义异常处理函数 (set_exception_handler
try-catch
Error
Throwable
set_exception_handler
<?php
set_exception_handler(function (Throwable $exception) {
error_log("Uncaught Exception: " . $exception->getMessage() . " in " . $exception->getFile() . " on line " . $exception->getLine());
// 在生产环境,通常会显示一个友好的错误页面
// echo "抱歉,系统出了点小问题,请稍后再试。";
});
?>注册关闭函数 (register_shutdown_function
E_ERROR
E_PARSE
register_shutdown_function
error_get_last()
<?php
register_shutdown_function(function () {
$lastError = error_get_last();
// 检查是否是致命错误类型
if ($lastError && in_array($lastError['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
error_log("Fatal Error: " . $lastError['message'] . " in " . $lastError['file'] . " on line " . $lastError['line']);
// 生产环境可以考虑发送通知
}
});
?>结合这三者,你就能构建一个较为全面的错误捕获与处理体系。
说实话,这确实是很多初学者甚至一些经验丰富的开发者都会疑惑的问题。在我看来,这主要源于PHP错误处理机制的历史演进和不同错误类型的本质差异。
try-catch
立即学习“PHP免费学习笔记(深入)”;
具体来说:
E_PARSE
try-catch
E_COMPILE_ERROR
E_CORE_ERROR
try-catch
E_ERROR
Error
try-catch
E_ERROR
try-catch
PHP 7引入了
Throwable
Exception
Error
Error
Throwable
E_ERROR
try-catch
new NonExistentClass()
Error
E_ERROR
try-catch
E_PARSE
try-catch
register_shutdown_function
它的工作原理是:你注册一个函数,这个函数会在PHP脚本执行完毕或被终止时自动调用。在你的关闭函数里,最关键的一步就是调用
error_get_last()
null
这是一个实际的例子:
<?php
// 首先,我们注册一个关闭函数
register_shutdown_function(function () {
$lastError = error_get_last(); // 获取最后一个错误信息
// 判断是否是致命错误类型
// E_ERROR (运行时致命错误)
// E_PARSE (解析错误)
// E_CORE_ERROR (PHP核心错误)
// E_COMPILE_ERROR (Zend引擎编译错误)
if ($lastError && in_array($lastError['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
$errorType = $lastError['type'];
$errorMessage = $lastError['message'];
$errorFile = $lastError['file'];
$errorLine = $lastError['line'];
// 这里就是你可以进行错误处理的地方了
// 比如,记录到日志文件:
$logMessage = sprintf(
"[%s] Fatal Error: Type %d (%s) - Message: %s in %s on line %d",
date('Y-m-d H:i:s'),
$errorType,
// 简单映射一下错误类型,方便阅读
match($errorType) {
E_ERROR => 'E_ERROR',
E_PARSE => 'E_PARSE',
E_CORE_ERROR => 'E_CORE_ERROR',
E_COMPILE_ERROR => 'E_COMPILE_ERROR',
default => 'UNKNOWN_FATAL_ERROR'
},
$errorMessage,
$errorFile,
$errorLine
);
error_log($logMessage, 3, '/var/log/php_fatal_errors.log'); // 写入到指定文件
// 在生产环境,你可能还会发送邮件、Slack通知,或者上报到Sentry/Bugsnag等错误监控服务
// send_notification_to_admin($logMessage);
// 为了用户体验,可以在页面上显示一个友好的错误提示,而不是直接暴露PHP错误信息
// 当然,这要确保在HTTP头发送之后才能输出
// if (!headers_sent()) {
// http_response_code(500);
// echo "<h1>服务器内部错误</h1><p>非常抱歉,我们遇到了一个问题,请稍后再试。</p>";
// }
}
});
// 制造一个运行时致命错误来测试
// 比如,调用一个不存在的函数(在PHP 5.x中会是E_ERROR,在PHP 7+中会是Error异常)
// 这里我们假设它会产生E_ERROR,或者一个未被捕获的Error异常最终导致脚本终止
// undefined_function_call();
// 制造一个内存耗尽的错误(这通常很难精确控制,但效果是类似的)
// ini_set('memory_limit', '8M'); // 临时设置一个很小的内存限制
// $largeArray = [];
// while (true) {
// $largeArray[] = str_repeat('A', 1024 * 1024); // 每次分配1MB
// }
// 制造一个真正的E_ERROR,例如:
// Class NonExistentClass {}
// $obj = new NonExistentClass(); // PHP 7+ 会抛出 Error,会被 set_exception_handler 捕获
// 如果是 PHP 5.x,这可能是 E_ERROR
// 为了演示 register_shutdown_function 捕获 E_ERROR,我们模拟一个更直接的场景
// 比如,尝试访问一个不存在的类的方法,且该类未被定义
// $object = null;
// $object->method(); // 这在 PHP 7+ 中通常会先抛出 TypeError,然后如果未捕获,则由 set_exception_handler 捕获。
// 如果是更底层的错误,或者发生在 set_exception_handler 自身出错,shutdown function 就会派上用场。
// 假设我们有一个语法错误的文件,require进来
// require 'syntax_error_file.php'; // 这会导致 E_PARSE 错误,shutdown function 可以捕获
// 正常执行的代码
echo "这段代码在致命错误发生前会执行。<br>";
// 故意制造一个会导致 E_ERROR 的情况(在PHP 7+中,很多 E_ERROR 变成了 Throwable 的 Error)
// 假设我们有一个资源句柄,但我们错误地把它当作对象来调用方法
$resource = fopen('php://memory', 'r');
// $resource->read(); // 这会导致 E_ERROR: Call to a member function read() on resource
// 对于 PHP 7+,这会抛出 TypeError,可以被 set_exception_handler 捕获。
// 所以,要真正演示 E_ERROR 被 shutdown function 捕获,需要一些更底层或者 set_exception_handler 自身失效的情况。
// 演示一个 PHP 7+ 中会被 set_exception_handler 捕获的 Error
// throw new Error("这是一个模拟的运行时致命错误,但现在是可捕获的Error");
// 为了确保 shutdown function 能捕获到一些“硬性”错误,
// 我们可以尝试在没有 set_exception_handler 的情况下,让一个 Error 浮出水面
// 或者模拟一个内存溢出,这通常是 E_ERROR
// ini_set('memory_limit', '16M');
// $bigString = str_repeat('A', 20 * 1024 * 1024); // 超过16M限制,会产生 E_ERROR
// echo "这段代码不会执行到";
// 一个更直接的 E_ERROR 例子:调用一个不存在的类的方法,如果该类未被定义,
// 并且这个错误没有被转换为 ErrorException 或被 try-catch 捕获
// 这在现代 PHP 中可能不容易直接产生 E_ERROR,因为很多都转成了 Error 异常。
// 但如果你的代码库里有老旧的逻辑,或者是在一些特定扩展里产生的底层错误,
// shutdown function 依然是最后的堡垒。
?>通过这种方式,即使脚本已经“死了”,你也能获取到它的“遗言”,这对于问题排查和系统稳定性至关重要。当然,它不能阻止脚本终止,但至少让你知道脚本为什么终止了,而不是一头雾水。
要构建一个真正健壮的错误报告系统,你需要将前面提到的所有机制有机地结合起来,形成一个多层次的防御体系。这不仅仅是技术上的堆砌,更是一种对系统稳定性和可维护性的深思熟虑。
在我看来,一个理想的错误处理流程是这样的:
最外层:register_shutdown_function
E_ERROR
E_PARSE
中间层:set_exception_handler
Error
try-catch
内层:set_error_handler
E_NOTICE
E_WARNING
ErrorException
try-catch
set_exception_handler
<?php
// 错误转异常的示例
set_error_handler(function ($errno, $errstr, $errfile, $errline) {
// 如果错误被 @ 符号抑制,则不抛出异常
if (!(error_reporting() & $errno)) {
return false; // 让PHP继续执行默认的错误处理
}
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
});
// 结合 set_exception_handler 和 register_shutdown_function
set_exception_handler(function (Throwable $e) {
// 记录所有未捕获的异常
error_log("Uncaught Exception/Error: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine() . "\n" . $e->getTraceAsString());
// 生产环境显示友好信息
if (!headers_sent()) {
http_response_code(500);
echo "<h1>系统内部错误</h1><p>请联系管理员或稍后再试。</p>";
}
});
register_shutdown_function(function () {
$lastError = error_get_last();
if ($lastError && in_array($lastError['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
error_log("Fatal Error (Shutdown): " . $lastError['message'] . " in " . $lastError['file'] . " on line " . $lastError['line']);
// 如果异常处理函数已经输出了错误页面,这里就不要重复输出了
// 否则,可以考虑再次输出一个通用错误页面
}
});
// 业务代码中,可以使用 try-catch 捕获预期异常和转换后的错误
try {
// 制造一个 E_WARNING
$file = fopen('non_existent_file.txt', 'r'); // 会产生 E_WARNING,被 set_error_handler 转换为 ErrorException
// 制造一个自定义异常
// throw new \Exception("这是一个自定义的业务异常");
// 制造一个 PHP 7+ 的 Error (例如类型错误)
// function test(string $s) {}
// test(123); // TypeError,会被 set_error_handler 捕获并转换为 ErrorException
} catch (ErrorException $e) {
// 捕获由 set_error_handler 转换而来的错误
error_log("Caught ErrorException: " . $e->getMessage());
// 可以根据错误类型进行更精细的处理
} catch (\Throwable $e) { // 捕获所有 Throwable,包括 Error 和 Exception以上就是PHP如何捕获致命错误_PHP中捕获并处理致命错误的机制的详细内容,更多请关注php中文网其它相关文章!
PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号