Error 及其子类
概述
PHP 7.0 引入了 Error 类作为 Throwable 接口的实现之一,用于表示 PHP 运行时的内部错误。在 PHP 7.0 之前,许多此类情况会导致致命错误(Fatal Error),脚本直接终止。PHP 7.0+ 将它们转换为可通过 try/catch 捕获的 Error 异常。
PHP 版本说明
Error基类自 PHP 7.0 起可用TypeError、ArgumentCountError、ArithmeticError、DivisionByZeroError自 PHP 7.0 起ParseError自 PHP 7.0 起CompileError自 PHP 7.3 起从ParseError中独立ValueError自 PHP 8.0 起引入
基础概念
Error 与 Exception 的区别
| 特性 | Error | Exception |
|---|---|---|
| 来源 | PHP 引擎内部错误 | 用户代码抛出 |
| 可预防 | 通常可通过正确编码避免 | 业务逻辑中的预期错误 |
| 继承 | Throwable → Error | Throwable → Exception |
| 用户可继承 | 不建议(PHP 内部使用) | 推荐用户继承 |
| 典型场景 | 类型错误、语法错误 | 业务验证失败、资源不可用 |
Error 类层次结构
Throwable (接口)
├── Exception
│ ├── ...
└── Error
├── TypeError ← 类型不匹配
├── ValueError ← PHP 8.0+,值不正确
├── ArgumentCountError ← 参数数量错误
├── ArithmeticError ← 算术运算错误
│ └── DivisionByZeroError ← 除零错误
├── CompileError ← PHP 7.3+,编译错误
│ └── ParseError ← 解析错误
├── UnhandledMatchError ← PHP 8.0+,match 无匹配
└── FiberError ← PHP 8.1+,Fiber 操作错误语法与代码
TypeError - 类型不匹配
TypeError 在参数或返回值类型声明不匹配时抛出。
<?php
declare(strict_types=1);
function parseInt(string $value): int
{
return (int) $value;
}
// 参数类型不匹配
try {
parseInt(123); // 传入 int,期望 string
} catch (\TypeError $e) {
echo $e->getMessage();
// "parseInt(): Argument #1 ($value) must be of type string, int given"
}
// 返回值类型不匹配
function alwaysString(): string
{
return 42; // 返回 int,期望 string
}
try {
alwaysString();
} catch (\TypeError $e) {
echo $e->getMessage();
// "alwaysString(): Return value must be of type string, int returned"
}ArgumentCountError - 参数数量错误
ArgumentCountError 继承自 TypeError,在调用函数时传入参数数量不足或过多时抛出。
<?php
declare(strict_types=1);
function createUser(string $name, string $email, int $age): void
{
echo "创建用户: {$name}";
}
try {
createUser('Alice', 'alice@example.com');
// 缺少第 3 个参数 $age
} catch (\ArgumentCountError $e) {
echo $e->getMessage();
// "createUser() expects exactly 3 arguments, 2 given"
}
// ArgumentCountError 是 TypeError 的子类
try {
createUser('Alice');
} catch (\TypeError $e) {
// ArgumentCountError 会被 TypeError 的 catch 捕获
echo '被 TypeError 捕获: ' . get_class($e);
}ArithmeticError 与 DivisionByZeroError
ArithmeticError 在算术运算无法完成时抛出。DivisionByZeroError 是其子类,专门处理除零错误。
<?php
declare(strict_types=1);
// PHP_INT_MIN 取反溢出
try {
$result = -PHP_INT_MIN; // 整数溢出
} catch (\ArithmeticError $e) {
echo $e->getMessage();
// "Integer overflow during negation of minimum integer value"
}
// 整数除零
try {
$result = intdiv(10, 0);
} catch (\DivisionByZeroError $e) {
echo $e->getMessage();
// "Division by zero"
}
// 普通除法对浮点数不会抛出 DivisionByZeroError
// 10 / 0 的结果是 INF(float)
// 只有 intdiv() 和 % 运算符才会抛出 DivisionByZeroError
try {
$result = 10 % 0;
} catch (\DivisionByZeroError $e) {
echo '取模除零: ' . $e->getMessage();
}ParseError - 代码解析错误
ParseError 在 eval() 调用的代码包含语法错误,或使用 include/require 加载的文件有语法错误时抛出。
<?php
declare(strict_types=1);
try {
eval('echo "Hello";;'); // 多余的分号
} catch (\ParseError $e) {
echo $e->getMessage();
// "syntax error, unexpected ';'"
}
// 动态包含有语法错误的文件
try {
include '/path/to/broken-file.php';
} catch (\ParseError $e) {
echo "文件语法错误: {$e->getMessage()}";
echo "文件: {$e->getFile()}:{$e->getLine()}";
}ValueError - PHP 8.0+
ValueError 在函数接收到了正确类型但值不合法时抛出(PHP 8.0+)。
<?php
declare(strict_types=1);
// array_chunk 第二个参数必须 >= 1
try {
array_chunk([1, 2, 3], 0);
} catch (\ValueError $e) {
echo $e->getMessage();
// "array_chunk(): Argument #2 ($length) must be at least 1"
}
// JSON 编码深度溢出
try {
json_encode(['data' => 'value'], JSON_THROW_ON_ERROR, 0);
} catch (\ValueError $e) {
echo $e->getMessage();
}详细说明
各 Error 子类触发场景汇总
| Error 子类 | 触发场景 | PHP 版本 |
|---|---|---|
TypeError | 参数/返回值类型声明不匹配 | 7.0+ |
ArgumentCountError | 函数参数数量不足或过多 | 7.0+ |
ArithmeticError | 整数溢出(如 -PHP_INT_MIN)、位位移操作数非法 | 7.0+ |
DivisionByZeroError | intdiv() 或 % 除零 | 7.0+ |
ParseError | eval()/include 代码有语法错误 | 7.0+ |
CompileError | 编译时错误(PHP 7.3+ 独立于 ParseError) | 7.3+ |
ValueError | 参数类型正确但值不合法 | 8.0+ |
为什么不应该继承 Error
Error 类设计给 PHP 引擎内部使用,代表程序本身的错误(如语法错误、类型错误)。用户业务逻辑错误应使用 Exception 或其子类。
重要区别
用户代码中 throw new Error() 虽然语法合法,但违反了异常的设计意图:
Error= "代码写得有问题"Exception= "运行时遇到了预期外的情况"
CompileError vs ParseError
PHP 7.3 将 CompileError 从 ParseError 中独立出来:
ParseError:仅处理语法解析错误CompileError:处理更广泛的编译错误,ParseError是其子类
<?php
declare(strict_types=1);
// CompileError 可以捕获 ParseError(因为继承关系)
try {
eval('function () { }'); // 无效的函数声明
} catch (\CompileError $e) {
echo 'CompileError 捕获: ' . $e->getMessage();
}实战示例
严格模式下的类型安全包装
<?php
declare(strict_types=1);
class SafeCaller
{
/**
* 安全调用函数,将 Error 转为友好的错误消息
*/
public static function call(callable $callback, mixed ...$args): mixed
{
try {
return $callback(...$args);
} catch (\TypeError $e) {
throw new \InvalidArgumentException(
message: '参数类型错误: ' . self::simplifyMessage($e->getMessage()),
code: 400,
previous: $e
);
} catch (\ValueError $e) {
throw new \InvalidArgumentException(
message: '参数值无效: ' . self::simplifyMessage($e->getMessage()),
code: 400,
previous: $e
);
} catch (\ArgumentCountError $e) {
throw new \InvalidArgumentException(
message: '参数数量错误',
code: 400,
previous: $e
);
} catch (\Error $e) {
throw new \RuntimeException(
message: '内部错误: ' . $e->getMessage(),
code: 500,
previous: $e
);
}
}
private static function simplifyMessage(string $message): string
{
// 提取函数名部分
if (preg_match('/^(\w+)\(\)/', $message, $matches)) {
return $matches[1] . ' ' . substr($message, strlen($matches[0]));
}
return $message;
}
}
// 使用示例
try {
$result = SafeCaller::call('intval', 'abc');
echo $result;
} catch (\InvalidArgumentException $e) {
echo '用户友好错误: ' . $e->getMessage();
}注意事项
strict_types=1影响类型检查严格度:开启严格模式后,标量类型不会自动隐式转换,更容易触发TypeError。ArgumentCountError不适用于可变参数:当函数有...$args参数时,传入任意数量的参数都不会触发此错误。DivisionByZeroError仅限整数运算:10 / 0返回INF(浮点数),不会抛出异常;只有intdiv(10, 0)和10 % 0才会抛出。ParseError不捕获主文件语法错误:如果 PHP 主文件本身有语法错误,PHP 引擎在编译阶段就会失败,不会进入try/catch。ValueError是 PHP 8.0+ 新增:在 PHP 7.x 中不存在此类,如果需要兼容 PHP 7.x,不要捕获ValueError。
最佳实践
推荐做法
- 区分 Error 和 Exception:
Error通常意味着代码有 Bug,而Exception意味着运行时遇到了可处理的异常情况 - 开发环境捕获 Error 详细输出:
catch (\Error $e)并打印完整堆栈,帮助快速定位问题 - 生产环境将 Error 转为友好信息:不要向用户暴露
TypeError等内部错误细节 - 严格模式下编写防御性代码:使用
strict_types=1,主动验证输入类型 - 利用 ArgumentCountError 进行接口验证:在 API 调用层捕获以提供更好的错误提示
进阶用法
调试与测试技巧
<?php
declare(strict_types=1);
// 单元测试辅助函数
function createTestResource(): mixed
{
return match (true) {
default => new stdClass(),
};
}
// 调试输出函数
function debugOutput(mixed , string = ''): void
{
= ? ": " : '';
.= print_r(, true);
fwrite(STDERR, . "\n");
}
// 性能基准测试
function benchmark(callable , int = 1000): float
{
= hrtime(true);
for ($i = 0; $i < $iterations; $i++) {
$fn();
}
return (hrtime(true) - $start) / 1e9;
}日志记录实践
<?php
declare(strict_types=1);
/**
* 简易日志记录器
*/
class SimpleLogger
{
private string $logFile;
private string $level = 'INFO';
public function __construct(string $logFile)
{
$this->logFile = $logFile;
}
public function info(string $message, array $context = []): void
{
$this->log('INFO', $message, $context);
}
public function warning(string $message, array $context = []): void
{
$this->log('WARNING', $message, $context);
}
public function error(string $message, array $context = []): void
{
$this->log('ERROR', $message, $context);
}
private function log(string $level, string $message, array $context): void
{
$timestamp = date('Y-m-d H:i:s');
$contextStr = $context ? ' ' . json_encode($context, JSON_UNESCAPED_UNICODE) : '';
$line = "[{$timestamp}] [{$level}] {$message}{$contextStr}\n";
file_put_contents($this->logFile, $line, FILE_APPEND | LOCK_EX);
}
}配置与环境检测
<?php
declare(strict_types=1);
// 环境检测工具
class EnvironmentChecker
{
public static function checkRequirements(array $requirements): array
{
$results = [];
foreach ($requirements as $name => $check) {
$results[$name] = is_callable($check) ? $check() : false;
}
return $results;
}
public static function getSystemInfo(): array
{
return [
'php_version' => PHP_VERSION,
'os' => PHP_OS,
'sapi' => PHP_SAPI,
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'loaded_extensions' => get_loaded_extensions(),
];
}
}常见问题排查
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 连接超时 | 网络问题/配置错误 | 检查配置,增加超时时间 |
| 权限不足 | 文件/目录权限 | 使用 chmod/chown 修正 |
| 性能下降 | 索引缺失/数据量大 | 添加索引,优化查询 |
| 数据不一致 | 并发冲突/事务残留 | 使用锁机制和事务 |
| 内存溢出 | 大数据集/未释放资源 | 增大内存限制,分批处理 |
故障排除步骤
- 检查错误日志和异常信息
- 确认配置和环境是否正确
- 使用调试工具逐步排查
- 参考官方文档查找已知问题
版本兼容性说明
| 功能 | 最低版本 | 说明 |
|---|---|---|
| 基础功能 | PHP 8.1 | 本文档基准版本 |
| 只读属性 | PHP 8.1 | public readonly 修饰符 |
| 枚举类型 | PHP 8.1 | enum 类型和 match 表达式 |
| Fiber | PHP 8.1 | 协程/轻量级并发 |
| 命名参数 | PHP 8.0 | foo(arg_name: value) |
| 联合类型 | PHP 8.0 | `int |
| Null 安全运算符 | PHP 8.0 | $obj?->method() |
| 析构器 promotion | PHP 8.0 | __construct(public $x) |
<?php
declare(strict_types=1);
// 版本兼容性检测
function ensureVersion(string $minVersion): void
{
if (version_compare(PHP_VERSION, $minVersion, '<')) {
throw new RuntimeException(
sprintf('需要 PHP %s+, 当前版本: %s', $minVersion, PHP_VERSION)
);
}
}
ensureVersion('8.1.0');