Skip to content

Error 及其子类

概述

PHP 7.0 引入了 Error 类作为 Throwable 接口的实现之一,用于表示 PHP 运行时的内部错误。在 PHP 7.0 之前,许多此类情况会导致致命错误(Fatal Error),脚本直接终止。PHP 7.0+ 将它们转换为可通过 try/catch 捕获的 Error 异常。

PHP 版本说明

  • Error 基类自 PHP 7.0 起可用
  • TypeErrorArgumentCountErrorArithmeticErrorDivisionByZeroError 自 PHP 7.0 起
  • ParseError 自 PHP 7.0 起
  • CompileError 自 PHP 7.3 起从 ParseError 中独立
  • ValueError 自 PHP 8.0 起引入

基础概念

Error 与 Exception 的区别

特性ErrorException
来源PHP 引擎内部错误用户代码抛出
可预防通常可通过正确编码避免业务逻辑中的预期错误
继承ThrowableErrorThrowableException
用户可继承不建议(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
<?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
<?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
<?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 - 代码解析错误

ParseErroreval() 调用的代码包含语法错误,或使用 include/require 加载的文件有语法错误时抛出。

php
<?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
<?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+
DivisionByZeroErrorintdiv()% 除零7.0+
ParseErroreval()/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 将 CompileErrorParseError 中独立出来:

  • ParseError:仅处理语法解析错误
  • CompileError:处理更广泛的编译错误,ParseError 是其子类
php
<?php

declare(strict_types=1);

// CompileError 可以捕获 ParseError(因为继承关系)
try {
    eval('function () { }'); // 无效的函数声明
} catch (\CompileError $e) {
    echo 'CompileError 捕获: ' . $e->getMessage();
}

实战示例

严格模式下的类型安全包装

php
<?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();
}

注意事项

  1. strict_types=1 影响类型检查严格度:开启严格模式后,标量类型不会自动隐式转换,更容易触发 TypeError

  2. ArgumentCountError 不适用于可变参数:当函数有 ...$args 参数时,传入任意数量的参数都不会触发此错误。

  3. DivisionByZeroError 仅限整数运算10 / 0 返回 INF(浮点数),不会抛出异常;只有 intdiv(10, 0)10 % 0 才会抛出。

  4. ParseError 不捕获主文件语法错误:如果 PHP 主文件本身有语法错误,PHP 引擎在编译阶段就会失败,不会进入 try/catch

  5. ValueError 是 PHP 8.0+ 新增:在 PHP 7.x 中不存在此类,如果需要兼容 PHP 7.x,不要捕获 ValueError

最佳实践

推荐做法

  1. 区分 Error 和 ExceptionError 通常意味着代码有 Bug,而 Exception 意味着运行时遇到了可处理的异常情况
  2. 开发环境捕获 Error 详细输出catch (\Error $e) 并打印完整堆栈,帮助快速定位问题
  3. 生产环境将 Error 转为友好信息:不要向用户暴露 TypeError 等内部错误细节
  4. 严格模式下编写防御性代码:使用 strict_types=1,主动验证输入类型
  5. 利用 ArgumentCountError 进行接口验证:在 API 调用层捕获以提供更好的错误提示

进阶用法

调试与测试技巧

php
<?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
<?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
<?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 修正
性能下降索引缺失/数据量大添加索引,优化查询
数据不一致并发冲突/事务残留使用锁机制和事务
内存溢出大数据集/未释放资源增大内存限制,分批处理

故障排除步骤

  1. 检查错误日志和异常信息
  2. 确认配置和环境是否正确
  3. 使用调试工具逐步排查
  4. 参考官方文档查找已知问题

版本兼容性说明

功能最低版本说明
基础功能PHP 8.1本文档基准版本
只读属性PHP 8.1public readonly 修饰符
枚举类型PHP 8.1enum 类型和 match 表达式
FiberPHP 8.1协程/轻量级并发
命名参数PHP 8.0foo(arg_name: value)
联合类型PHP 8.0`int
Null 安全运算符PHP 8.0$obj?->method()
析构器 promotionPHP 8.0__construct(public $x)
php
<?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');

参考链接