在 PHP 面试中,异常与错误处理是一个高频且容易拉开差距的考点。很多候选人对 try...catch 和 throw 倒背如流,但一旦被追问“PHP 7 之前和之后错误处理有什么本质区别”“set_error_handler 到底能捕获哪些错误”“Error 和 Exception 是什么关系”,往往就答得含糊。本文从面试实战角度出发,系统梳理 PHP 异常与错误处理体系,并深入讲解 set_error_handler 的实践细节。
一、PHP 7 是错误处理的分水岭
在 PHP 7 之前,PHP 的错误(Error)和异常(Exception)是两套几乎平行的体系:
- 错误:由引擎触发,如
E_NOTICE、E_WARNING、E_ERROR,通常无法被try...catch捕获(E_ERROR等致命错误直接终止脚本)。 - 异常:由代码
throw抛出,必须被catch或导致 Fatal error。
PHP 7 引入了 Throwable 接口,并定义了 Error 类,让大部分引擎级错误也以异常形式抛出。从此:
Throwable
├── Error
│ ├── TypeError
│ ├── ValueError
│ ├── ParseError
│ ├── ArithmeticError
│ └── ...
└── Exception
├── RuntimeException
├── LogicException
└── ...
Exception 和 Error 都实现了 Throwable,所以 catch (Throwable $e) 可以同时捕获两者。这是面试中非常爱问的一点。
二、错误级别与常见常量
PHP 的错误级别众多,面试中至少要记住几个关键级别及其特性:
| 级别 | 含义 | 是否可被 set_error_handler 捕获 |
|---|---|---|
| E_ERROR | 致命错误 | 否 |
| E_WARNING | 警告 | 是 |
| E_NOTICE | 通知 | 是 |
| E_DEPRECATED | 弃用提示 | 是 |
| E_USER_ERROR | 用户触发致命错误 | 是 |
| E_USER_WARNING | 用户触发警告 | 是 |
| E_USER_NOTICE | 用户触发通知 | 是 |
| E_STRICT | 编码规范建议 | 是 |
关键结论:set_error_handler 无法处理 E_ERROR、E_PARSE、E_CORE_ERROR、E_COMPILE_ERROR 等引擎级致命错误,这些错误只能通过 register_shutdown_function 配合 error_get_last() 来兜底。
三、set_error_handler 的实践用法
set_error_handler 允许你自定义错误处理函数,把传统错误转换为异常,从而实现统一处理。
set_error_handler(function ($errno, $errstr, $errfile, $errline) {
// 不处理被 @ 抑制的错误(error_reporting() 返回 0)
if (!(error_reporting() & $errno)) {
return false;
}
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
});
这样处理后,E_WARNING、E_NOTICE 等都会被转换成 ErrorException,可以被 try...catch 捕获:
try {
$result = 1 / 0; // PHP 8 会抛 DivisionByZeroError
file_get_contents('/not/exist'); // 触发 E_WARNING
} catch (Throwable $e) {
echo "捕获到: " . $e->getMessage();
}
实践中的几个坑
- 返回 false 表示交给 PHP 原生处理:如果自定义处理器返回
false,PHP 会继续执行默认的错误处理流程;返回true则完全由你接管。 @抑制符的影响:使用@时error_reporting()在 PHP 8 之前会返回 0,因此要判断error_reporting() & $errno,否则会破坏抑制语义。- 不要忘记恢复:
set_error_handler返回旧的处理器,必要时用restore_error_handler()还原,避免污染全局。 - 不能捕获致命错误:
E_ERROR仍会终止脚本,需用register_shutdown_function兜底:
register_shutdown_function(function () {
$error = error_get_last();
if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
// 记录日志或返回 500
}
});
四、异常处理的最佳实践
面试中回答“你如何设计异常处理”时,可以围绕以下几点展开:
- 分层捕获:先捕获具体异常(如
PDOException),再捕获Throwable兜底。 - 自定义异常:按业务域定义异常类,便于区分和统一处理。
finally释放资源:无论是否异常,都确保关闭文件、数据库连接等。- 全局异常处理器:用
set_exception_handler处理未被捕获的异常,避免白屏。
set_exception_handler(function (Throwable $e) {
error_log($e->getMessage());
http_response_code(500);
echo "服务器内部错误";
});
五、面试高频问题速答
Q:Error 和 Exception 有什么区别?
A:两者都实现 Throwable。Exception 是传统异常,用于业务逻辑;Error 是 PHP 7 引入的引擎级错误,如类型错误、调用未定义函数。catch (Exception) 无法捕获 Error,必须用 Throwable 或 Error。
Q:set_error_handler 能捕获 E_ERROR 吗?
A:不能。它只能处理 E_WARNING、E_NOTICE、E_USER_* 等可恢复错误,致命错误需用 register_shutdown_function + error_get_last()。
Q:为什么要把错误转成异常?
A:统一处理路径,让所有问题都能用 try...catch 和 finally 管理,便于日志、回滚和向上抛出。
Q:@ 抑制符和自定义错误处理器会冲突吗?
A:会。PHP 8 之前 @ 会让 error_reporting() 返回 0,处理器需判断后返回 false 才能保留抑制语义;PHP 8 中 @ 改为将错误级别设为特定掩码,处理方式更明确。
结语
掌握 PHP 异常与错误处理体系,核心在于理解 Throwable 的统一模型、区分“可捕获”与“不可捕获”的错误级别,并能用 set_error_handler 把传统错误转换为异常。面试中,如果能进一步讲清 @ 抑制符、register_shutdown_function 兜底以及 PHP 7/8 的差异,基本就能体现出你对 PHP 底层机制的深入理解。建议在项目中实践一套统一的异常处理中间件,这比背诵概念更有说服力。
未经允许不得转载:任鹏个人博客 » PHP 面试精讲:PHP 异常与错误处理体系及 set_error_handler 实践

