PHP 异常捕获最佳实践
PHP 异常捕获最佳实践
一、为什么要规范异常处理
PHP 开发中,很多新手习惯使用 error_reporting 抑制报错、或者直接 die() 终止程序,缺少统一的异常捕获机制。线上接口一旦抛出未捕获异常,直接返回致命错误,不仅用户看到白屏,还会泄露敏感路径、数据库信息,同时日志缺失导致排查困难。现代 PHP 使用 try-catch 统一捕获异常,配合错误处理器,实现优雅报错、日志记录、业务回滚。
PHP 7+ 之后,大部分内部错误也会抛出 Error 对象,异常和错误统一继承 Throwable,捕获时优先使用 Throwable,可以同时覆盖 Exception 和 Error。
二、基础 try-catch 写法
<?php
try {
// 可能出错的业务逻辑
$db = new PDO("mysql:host=127.0.0.1;dbname=blog", "root", "123456");
$stmt = $db->query("SELECT * FROM article WHERE gid = ?");
$data = $stmt->fetch();
} catch (Throwable $e) {
// 捕获所有异常和致命错误
error_log("数据库异常:" . $e->getMessage() . " 文件:" . $e->getFile() . " 行号:" . $e->getLine());
echo json_encode(["code" => 500, "msg" => "服务繁忙,请稍后重试"]);
}
不要在 catch 里面直接打印 $e->getMessage() 给前端,生产环境必须屏蔽内部错误详情,只返回友好提示,原始错误写入日志。
三、多异常分级捕获
不同异常类型分开处理,精准反馈错误:
try {
// ...
} catch (PDOException $e) {
// 数据库连接、SQL 错误单独处理
error_log("数据库异常");
} catch (InvalidArgumentException $e) {
// 参数校验错误,返回 400
echo json_encode(["code" => 400, "msg" => $e->getMessage()]);
} catch (Throwable $e) {
// 兜底捕获
error_log("未知异常");
}
捕获顺序:子类异常放前面,父类 Throwable 放最后,否则后面的分支永远不会执行。
四、finally 清理资源
finally 块无论是否抛出异常都会执行,适合关闭文件句柄、释放数据库连接、解锁:
$fp = fopen("log.txt", "r");
try {
// 读取文件逻辑
} catch (Throwable $e) {
// 异常处理
} finally {
if (is_resource($fp)) fclose($fp);
}
避免出现异常之后资源句柄泄漏。
五、自定义业务异常
业务场景定义专属异常类,区分参数错误、权限不足、业务规则异常:
class PermissionException extends \Exception {}
class OrderException extends \Exception {}
抛出之后统一捕获,根据异常类型返回不同状态码,比单纯靠字符串判断错误更清晰。
六、生产环境配置
php.ini 生产环境设置:
display_errors = Off
log_errors = On
error_log = /var/log/php/error.log
禁止前端直接显示报错信息,所有错误写入日志文件。同时使用框架统一异常处理器,全局兜底捕获,避免任何未捕获异常直接暴露。
七、常见误区
- 捕获所有异常之后吞掉错误,不写日志,问题无法排查;
- 在循环内大量使用 try-catch,性能损耗高,大块业务块统一捕获;
- 用 Throwable 捕获之后继续抛出或者直接 exit,没有统一响应格式;
- 忽略 Error,只捕获 Exception,PHP 内部致命错误漏网。
八、总结
规范异常处理是 PHP 项目稳定性的基础。使用 try-catch 分级捕获,finally 释放资源,自定义业务异常分类,生产环境关闭错误展示、开启日志。线上系统做好全局兜底,既保证用户体验,又方便开发定位问题。