PHP 预定义异常及使用场景
PHP 里所有可 throw 的对象都属于 Throwable。官方预定义分成两大类:
Throwable
├── Exception // 业务、逻辑、可预期异常(用户代码逻辑中应该捕获并处理的异常)
└── Error // PHP 运行时错误(如语法错误、类型错误)
Error 主线
这是 PHP 7+ 最重要的预定义异常体
Error
├── ArithmeticError
│ └── DivisionByZeroError
├── AssertionError
├── CompileError
│ └── ParseError
├── TypeError
│ └── ArgumentCountError
├── ValueError
├── UnhandledMatchError
├── FiberError
└── RequestParseBodyException
1)TypeError
表示:类型不符合声明
参数类型错误
function add(int $a, int $b) {}
add("1", []);
返回值类型错误
function getName(): string
{
return 123;
}
属性类型错误
class User {
public int $age;
}
$user->age = "18";
本质: 值存在,但类型错了
2)ArgumentCountError
它是 TypeError 子类。
表示:参数数量不对
strlen();
3)ValueError
PHP 8 很重要的新成员。
表示:类型正确,但值非法
示例
str_repeat("a", -1);类型没错:
-1 // int
但值无效。
和 TypeError 的本质区别
TypeError
strlen([]);
类型错。
ValueError
str_repeat("a", -1);类型对,值错。
4)ArithmeticError
表示:算术计算错误父类
它最常见子类是:
DivisionByZeroError
5)DivisionByZeroError
intdiv(10, 0);
注意
普通 / 有时只是 warning:
10 / 0
而:
intdiv()
一定抛异常。
6)AssertionError
来自:
assert()
assert($age > 0);
失败后抛它。
用途
调试
开发阶段契约验证
测试辅助
7)CompileError
表示:编译阶段错误
子类:
ParseError
8)ParseError
语法解析失败。
典型场景
eval('$a = ;');适用范围
eval动态模板
DSL
动态生成 PHP 代码
9)UnhandledMatchError
PHP 8 match 专属。
$status = match($code) {
200 => 'ok',
};
传:
404
就会抛。因为 match 是:严格穷举表达式, 不像 switch 。
10)FiberError
PHP Fiber(协程)相关异常。
触发场景
非法 Fiber 状态切换:
$fiber->resume();
$fiber->resume(); // 非法
主要用于
协程框架
异步 IO
Swoole / ReactPHP 风格系统
11)RequestParseBodyException
较新的请求体解析异常。
通常出现在:
multipart/form-data
request body parse
上传解析
属于底层 HTTP 请求解析异常。
Exception 主线
Exception
├── ErrorException
├── ClosedGeneratorException
├── LogicException
│ ├── BadFunctionCallException
│ ├── BadMethodCallException
│ ├── DomainException
│ ├── InvalidArgumentException
│ ├── LengthException
│ └── OutOfRangeException
├── RuntimeException
│ ├── OutOfBoundsException
│ ├── OverflowException
│ ├── RangeException
│ ├── UnderflowException
│ └── UnexpectedValueException
├── DOMException
├── PharException
├── ReflectionException
├── JsonException
├── Random\RandomException
└── 扩展模块自己的异常
SPL 标准库子类
这是日常开发最常用的一棵树。
Exception
├── LogicException
│ ├── BadFunctionCallException
│ │ └── BadMethodCallException
│ ├── DomainException
│ ├── InvalidArgumentException
│ ├── LengthException
│ └── OutOfRangeException
│
└── RuntimeException
├── OutOfBoundsException
├── OverflowException
├── RangeException
├── UnderflowException
└── UnexpectedValueException
逐个解释这些子类
1)ErrorException
class ErrorException extends Exception
用途:把 warning / notice 转成异常
set_error_handler(function ($severity, $message) {
throw new ErrorException($message);
});
2)ClosedGeneratorException
生成器关闭后继续访问时抛出。
$gen = (function () {
yield 1;
})();
$gen->next();
$gen->current(); // 可能触发相关问题
3) LogicException 系:代码调用方式有问题
只要修代码,问题就消失, 不是环境问题,也不是网络问题,而是:
调用者传错参数
方法调用顺序不对
值超定义域
程序进入“不该到达”的状态
1. InvalidArgumentException
参数不合法, 参数不符合预期类型/约束。
最常见场景:
function setAge(int $age): void
{
if ($age < 0) {
throw new InvalidArgumentException('Age cannot be negative');
}
}
函数入口参数检查失败,例如:
年龄不能小于 0
URL 不能为空
用户 ID 必须大于 0
页码不能是负数
2. DomainException
值不在允许集合里, 值不符合已定义的数据域。
和参数合法不同,它强调:值超出了业务定义域
$status = 'archived';
if (!in_array($status, ['draft', 'published'])) {
throw new DomainException('Invalid status');
}
典型场景
枚举值非法
状态机状态非法
不支持的类型
switch/match 不允许的分支
3. LengthException
长度违规
if (strlen($username) > 20) {
throw new LengthException('Username too long');
}
典型场景
用户名长度限制
token 长度固定
数组元素数量超限制
上传文件名长度限制
4. OutOfRangeException
逻辑范围错误(偏“业务规则”)
$page = 0;
if ($page < 1) {
throw new OutOfRangeException('Page starts from 1');
}
典型场景
分页页码从 1 开始
月份必须 1~12
星级评分只能 1~5
游戏等级不能超过 max level
5. BadFunctionCallException
函数调用顺序错误
if (!$this->initialized) {
throw new BadFunctionCallException('Call init() first');
}
场景理解
调用顺序有前置条件:
connect()
query()
close()
如果没 connect() 就 query()。
6. BadMethodCallException
和上面一样,只是强调:对象方法调用错误
$user->save();
但对象未初始化。
4) RuntimeException 系:运行过程出问题
代码没问题,但运行时失败
1. RuntimeException
场景:最通用运行失败
if (!file_exists($file)) {
throw new RuntimeException('File missing');
}
最典型场景
文件不存在
网络超时
Redis 连不上
API 请求失败
权限不足
配置文件缺失
2. UnexpectedValueException
结果和预期不一致, 返回值不在预期集合中。
$data = json_decode($json, true);
if (!isset($data['id'])) {
throw new UnexpectedValueException('Missing id');
}
典型场景
API 返回字段缺失
配置值不是预期格式
第三方返回状态异常
解析结果结构不对
3. OutOfBoundsException
访问不存在的 key/index
$list = ['a', 'b'];
if (!isset($list[10])) {
throw new OutOfBoundsException('Index not found');
}
常见场景
数组 key 不存在
集合类 offset 不存在
DTO 字段缺失
Map 查不到 key
4. OverflowException
容器满了常见场景if ($queue->count() >= $max) {
throw new OverflowException('Queue full');
}
固定长度队列
缓冲区写满
线程池满
连接池满
5. UnderflowException
容器空了
if ($stack->isEmpty()) {
throw new UnderflowException('Stack empty');
}
常见场景
空栈 pop
空队列 dequeue
缓冲区读空
连接池无空闲资源
6. RangeException
运行时数值超范围
和 OutOfRangeException 很像,但偏运行结果。
if ($temperature > $maxSensorValue) {
throw new RangeException('Sensor overflow');
}
场景区别
OutOfRangeException: 输入参数超业务范围
setMonth(13)
RangeException: 程序运行结果超允许范围
$calculated = 999999999999;
5)JsonException
JSON 解析失败。
json_decode($json, true, 512, JSON_THROW_ON_ERROR);
失败就抛:
JsonException
6)PDOException
数据库异常(继承 RuntimeException)。
$pdo->query("BAD SQL");7)ReflectionException
反射失败。
new ReflectionClass('NotExist');8)DOMException
DOM/XML 操作失败。
$dom->appendChild($wrongNode);
9)PharException
phar 包操作失败。