PHP 错误与异常处理
AI 辅助生成本文由 AI 起草并经人工核查,示例均标明软件版本;发现错误请以官方文档为准。
PHP 有两套机制:**错误(error)**用于引擎层面的问题,**异常(exception)**用于代码主动抛出的问题。分清两者,才能决定该捕获还是该修复。
错误等级
常见等级(PHP 8):
| 等级 | 场景 | 是否中断 |
|---|---|---|
E_ERROR |
致命错误,如调用未定义函数 | 是 |
E_WARNING |
运行时警告,如 include 失败 | 否 |
E_NOTICE |
提示,如访问未定义变量或数组键 | 否 |
E_DEPRECATED |
使用了将来会移除的特性 | 否 |
E_PARSE |
语法错误 | 是 |
PHP 8 把很多历史错误升级成了 Error 异常(如 TypeError、DivisionByZeroError),所以现在大部分问题都能用 try / catch 处理。
异常体系
Throwable
├── Exception (常规异常,业务代码抛这个)
│ ├── RuntimeException
│ ├── LogicException
│ └── ...
└── Error (引擎级错误,PHP 7+)
├── TypeError
├── ValueError
├── DivisionByZeroError
└── ParseError
捕获时用 Throwable 能同时覆盖两者;但业务代码通常只应捕获 Exception,引擎级 Error 一般代表代码有 bug,让它暴露出来更好修。
try / catch / finally
try {
$data = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
error_log("解析失败:" . $e->getMessage());
$data = [];
} finally {
// 总会执行,用于清理:关闭句柄、恢复状态
}
finally 无论是否发生异常、是否在 catch 里 return,都会执行。
多个 catch 从具体到宽泛排列:
try {
process($input);
} catch (InvalidArgumentException $e) {
echo "参数不合法:" . $e->getMessage();
} catch (RuntimeException $e) {
echo "运行时出错:" . $e->getMessage();
} catch (Throwable $e) {
error_log($e);
echo "服务器内部错误";
}
Exception 的常用方法
try {
throw new RuntimeException("连接超时");
} catch (RuntimeException $e) {
$e->getMessage(); // "连接超时"
$e->getCode(); // 错误码,默认 0
$e->getFile(); // 抛出位置的文件
$e->getLine(); // 抛出的行号
$e->getTraceAsString(); // 调用栈
}
抛异常
function withdraw(float $balance, float $amount): float
{
if ($amount <= 0) {
throw new InvalidArgumentException("取款金额必须大于 0");
}
if ($amount > $balance) {
throw new RuntimeException("余额不足", 1001);
}
return $balance - $amount;
}
选哪个异常类:
InvalidArgumentException:调用方传参错误(属于LogicException,是代码 bug)。RuntimeException:运行时外部条件不满足(余额不足、文件被删、网络超时)。DomainException:值不在业务允许的范围内。
自定义异常
业务复杂时用自定义类型区分错误,比读 message 字符串可靠:
class ValidationException extends RuntimeException
{
/** @param array<string, string> $errors 字段 => 错误信息 */
public function __construct(public array $errors)
{
parent::__construct("输入校验未通过");
}
}
try {
$errors = validate($_POST);
if ($errors !== []) {
throw new ValidationException($errors);
}
} catch (ValidationException $e) {
foreach ($e->errors as $field => $message) {
echo htmlspecialchars($field, ENT_QUOTES, "UTF-8") . ":"
. htmlspecialchars($message, ENT_QUOTES, "UTF-8") . "<br>";
}
}
PHP 8 的构造器属性提升(public array $errors)省掉了属性声明与赋值。
让 PHP 函数抛异常而不是返回 false
很多老函数失败时返回 false,配合 JSON_THROW_ON_ERROR 这类标志可以改成抛异常:
// 默认:失败返回 null,需要 json_last_error() 查原因
$data = json_decode($raw, true);
// PHP 7.3+:失败直接抛 JsonException
$data = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
类似的还有 PDO::ERRMODE_EXCEPTION(数据库)、filter_var 仍需自行判断。
全局处理器:兜底最后一道
未被捕获的异常会导致致命错误,注册处理器可以做统一日志与友好页面:
set_exception_handler(function (Throwable $e): void {
error_log(sprintf(
"[%s] %s in %s:%d\n%s",
date("c"),
$e->getMessage(),
$e->getFile(),
$e->getLine(),
$e->getTraceAsString()
));
http_response_code(500);
echo "服务器开小差了,请稍后再试";
});
放在应用入口(如 index.php)的最前面。注意它只处理未被捕获的异常。
错误报告配置
开发环境:显示全部错误。
; php.ini 开发环境
display_errors = On
error_reporting = E_ALL
log_errors = On
生产环境:记录到日志,不显示给用户(错误信息里可能含路径、SQL、凭据)。
; php.ini 生产环境
display_errors = Off
error_reporting = E_ALL
log_errors = On
error_log = /var/log/php/error.log
也可以在脚本里临时设置:
error_reporting(E_ALL);
ini_set("display_errors", "0");
常见错误与注意事项
- 空的
catch (Exception $e) {}:吞掉异常,问题被隐藏。至少要error_log。 - 捕获
Throwable后一律返回默认值:会把引擎级 bug 也一起掩盖,只捕获你预期的类型。 - 用
@抑制错误:错误信息消失,但代码仍在错误状态下继续运行。 - 生产环境开着
display_errors:泄露路径、SQL 和配置信息。 - 在
finally里return:会覆盖try/catch的返回值,几乎都不是本意。 - 异常消息直接输出给用户:应记录到日志,给用户通用提示。
小结
Exception用于业务问题,Error用于引擎级问题,Throwable是两者的父接口。- 抛异常时选对类型:
InvalidArgumentException(参数)vsRuntimeException(外部条件)。 json_decode等函数可用JSON_THROW_ON_ERROR改成抛异常。- 入口注册
set_exception_handler做统一兜底。 - 生产环境
display_errors = Off+log_errors = On。