PHP 中 Error 与 Exception 的区别及异常处理最佳实践
Error 表示引擎级严重错误,Exception 表示业务级可恢复异常,二者共同实现 Throwable 接口。
原理:PHP 7 的异常体系重构
PHP 7 之前,引擎级错误(如类型错误、除零)通过 E_ERROR、E_WARNING 等错误级别触发,无法用 try-catch 捕获,只能通过 set_error_handler() 处理部分非致命错误。致命错误会直接终止脚本,开发者束手无策。
机制:Error vs Exception 的核心区别
| 特性 | Error | Exception |
|---|---|---|
| 含义 | 引擎/编译级严重错误 | 业务逻辑可恢复异常 |
| 常见子类 | TypeError、ParseError、DivisionByZeroError |
RuntimeException、LogicException、InvalidArgumentException |
| 是否应该捕获 | 通常不应在业务层捕获(说明代码有 bug) | 应该按场景捕获并处理 |
| 公共接口 | Throwable |
Throwable |
| 可以 catch | PHP 7+ 可以 | 一直可以 |
三种错误处理机制的关系
try-catch-finally:捕获Throwable(含 Error 和 Exception)。finally块无论是否抛出异常都会执行。set_error_handler():捕获传统错误级别(E_WARNING、E_NOTICE等)。不能捕获E_ERROR。set_exception_handler():兜底处理未被catch的异常/错误,通常用于全局异常日志记录。
结论:对开发的直接影响
- 全局异常处理器(
set_exception_handler)是生产环境的安全网,应在项目入口注册。 - 业务层
catch应只捕获预期的Exception子类,不要无脑catch (\Throwable $e)吞掉所有错误。 TypeError等 Error 说明代码本身有缺陷(传参类型错误),应修复代码而非捕获 Error。
- 验证候选人是否理解 PHP 7+ 的异常体系结构,而非停留在 PHP 5 的认知
- 考察对 Error 和 Exception 设计意图的区分——是否知道"什么该捕获、什么不该捕获"
- 判断候选人是否具备工程化的异常处理习惯(自定义异常、全局兜底、日志记录)
-
为什么不应该在业务代码中
catch (\Throwable $e)或catch (\Error $e)?简答:
Error通常意味着代码 bug(如类型错误、调用不存在的方法),应该暴露并修复,而非捕获后静默处理。无差别捕获Throwable会掩盖真实问题,导致 bug 难以排查。业务层应只catch具体的Exception子类。 -
set_error_handler()能捕获所有传统错误吗?简答:不能。
set_error_handler()能捕获E_WARNING、E_NOTICE、E_USER_*等,但不能捕获E_ERROR、E_PARSE、E_CORE_ERROR、E_COMPILE_ERROR。致命错误只能通过register_shutdown_function()+error_get_last()做最后的日志记录。 -
finally块在什么场景下必须使用?简答:需要释放资源的场景:关闭文件句柄、数据库连接、释放锁等。无论
try块是否抛出异常,finally都会执行,确保资源不泄漏。即使try或catch中有return,finally仍然会执行。 -
如何设计项目的自定义异常体系?
简答:建议按业务领域划分异常层级。定义一个项目根异常(如
AppException extends RuntimeException),再按模块细分(OrderException、PaymentException)。每个异常携带错误码($code)和上下文数据。API 项目通过全局异常处理器将异常转换为统一的错误响应格式。