Skip to content

异常类层次结构

概述

PHP 的异常体系基于 Throwable 接口构建,分为 ErrorException 两大分支。Error 表示 PHP 引擎层面的错误(通常不可恢复),Exception 表示应用层面的异常(通常可恢复)。

理解异常层次结构有助于正确选择异常类型、编写精确的 catch 语句,以及在合适的位置放置自定义异常。

版本说明

  • PHP 7.0 引入 Throwable、Error 及其子类
  • PHP 8.0 引入 ValueError、UnhandledMatchError
  • PHP 8.1 引入 FiberError

基础概念

完整层次结构树

Throwable (接口)
├── Error
│   ├── ArithmeticError
│   │   ├── DivisionByZeroError
│   │   └── AssertionError  (PHP 8.0+)
│   ├── AssertionError
│   ├── CompileError
│   │   └── ParseError
│   ├── TypeError
│   ├── ValueError
│   ├── ArgumentCountError
│   ├── UnhandledMatchError
│   ├── FiberError
│   └── ...

└── Exception
    ├── ErrorException
    ├── LogicException
    │   ├── BadFunctionCallException
    │   │   └── BadMethodCallException
    │   ├── DomainException
    │   ├── InvalidArgumentException
    │   ├── LengthException
    │   ├── RangeException
    │   └── OverflowException
    │       └── UnderflowException
    └── RuntimeException
        ├── OutOfBoundsException
        │   ├── OutOfRangeException
        │   └── InfiniteLoopException
        ├── OverflowException
        ├── UnexpectedValueException
        ├── RuntimeException 子类...
        └── ...

Error 分支 vs Exception 分支

特性ErrorException
用途PHP 内部错误应用层异常
可恢复性通常不可恢复通常可恢复
用户自定义不推荐(继承 Error)推荐(继承 Exception)
try/catch可以可以
示例TypeError, ParseErrorRuntimeException, InvalidArgumentException

语法与代码

Error 分支

php
<?php

declare(strict_types=1);

// TypeError — 类型不匹配
function expectString(string $value): string
{
    return $value;
}

try {
    expectString(42);  // TypeError
} catch (TypeError $e) {
    echo "TypeError: " . $e->getMessage();
}

// ValueError — 值不正确(PHP 8.0+)
try {
    $result = intdiv(PHP_INT_MIN, -1);  // 算术溢出
} catch (ValueError|ArithmeticError $e) {
    echo get_class($e) . ": " . $e->getMessage();
}

// ArgumentCountError — 参数数量错误
function twoParams(int $a, int $b): void {}

try {
    twoParams(1);  // ArgumentCountError
} catch (ArgumentCountError $e) {
    echo "参数数量错误: " . $e->getMessage();
}

// ParseError — 语法错误
try {
    eval('invalid code ===');
} catch (ParseError $e) {
    echo "语法错误: " . $e->getMessage();
}

Exception 分支 — LogicException 系列

php
<?php

declare(strict_types=1);

// LogicException — 编程逻辑错误(应该在编码阶段发现)
// DomainException — 值不在有效域内
function setAge(int $age): void
{
    if ($age < 0 || $age > 200) {
        throw new DomainException("年龄 {$age} 不在有效范围内");
    }
}

// InvalidArgumentException — 参数无效
function setName(string $name): void
{
    if (strlen(trim($name)) === 0) {
        throw new InvalidArgumentException('名称不能为空');
    }
}

// BadMethodCallException — 调用了不存在的方法
class Proxy
{
    public function __call(string $method, array $args): mixed
    {
        throw new BadMethodCallException("方法 {$method} 不存在");
    }
}

// LengthException — 长度超出范围
function addToBuffer(string $item, array $buffer, int $maxSize): void
{
    if (count($buffer) >= $maxSize) {
        throw new LengthException("缓冲区已满 (最大: {$maxSize})");
    }
    $buffer[] = $item;
}

Exception 分支 — RuntimeException 系列

php
<?php

declare(strict_types=1);

// RuntimeException — 运行时异常(不可预测的错误)
// OutOfBoundsException — 索引越界
function getFromList(array $list, int $index): mixed
{
    if (!isset($list[$index])) {
        throw new OutOfBoundsException("索引 {$index} 越界");
    }
    return $list[$index];
}

// UnexpectedValueException — 值不符合预期
function parseConfigValue(mixed $value): string
{
    if (!is_string($value)) {
        throw new UnexpectedValueException('配置值必须是字符串');
    }
    return $value;
}

// OverflowException — 数值溢出
function safeAdd(int $a, int $b): int
{
    $result = $a + $b;
    if ($result > PHP_INT_MAX) {
        throw new OverflowException('加法结果溢出');
    }
    return $result;
}

详细说明

自定义异常的位置

php
<?php

declare(strict_types=1);

// 自定义异常应继承 Exception(不是 Error)
// 选择继承哪个基类取决于异常的性质

// 1. 业务逻辑异常 → 继承 Exception 或 LogicException
class InvalidOrderException extends LogicException
{
    public function __construct(string $message = '', int $code = 0, ?Throwable $previous = null)
    {
        parent::__construct($message, $code, $previous);
    }
}

// 2. 运行时异常 → 继承 RuntimeException
class DatabaseConnectionException extends RuntimeException
{
    public function __construct(string $message = '', int $code = 0, ?Throwable $previous = null)
    {
        parent::__construct($message, $code, $previous);
    }
}

// 3. 领域特定异常 → 继承具体的 SPL 异常
class EntityNotFoundException extends OutOfBoundsException
{
    private string $entityType;
    private string $entityId;

    public function __construct(string $entityType, string $entityId)
    {
        $this->entityType = $entityType;
        $this->entityId = $entityId;
        parent::__construct("{$entityType}({$entityId}) 未找到");
    }

    public function entityType(): string { return $this->entityType; }
    public function entityId(): string { return $this->entityId; }
}

异常类的通用属性

php
<?php

declare(strict_types=1);

// 所有 Throwable 都有这些方法
class AppException extends Exception
{
    public function __construct(
        string $message = '',
        int $code = 0,
        ?Throwable $previous = null
    ) {
        parent::__construct($message, $code, $previous);
    }
}

$e = new AppException('测试异常', 1001);

// Throwable 接口的方法
$e->getMessage();         // 异常消息
$e->getCode();            // 异常码
$e->getFile();            // 抛出位置文件
$e->getLine();            // 抛出位置行号
$e->getTrace();           // 堆栈跟踪数组
$e->getTraceAsString();   // 堆栈跟踪字符串
$e->getPrevious();        // 前一个异常
$e->__toString();         // 格式化字符串(Stringable)

异常捕获的最佳顺序

php
<?php

declare(strict_types=1);

// 正确的 catch 顺序
function handleErrors(): void
{
    try {
        riskyOperation();
    } catch (InvalidArgumentException $e) {
        // 最具体的异常(子类)
    } catch (RuntimeException $e) {
        // 较通用的异常(父类)
    } catch (Throwable $e) {
        // 最通用的兜底
    }
}

// 层次结构决定了 catch 的匹配:
// InvalidArgumentException extends LogicException extends Exception implements Throwable
// 如果抛出 InvalidArgumentException,会被第一个 catch 捕获
// 如果抛出 RuntimeException,会被第二个 catch 捕获
// 如果抛出 TypeError(Error 分支),会被第三个 catch 捕获

实战示例

领域异常体系

php
<?php

declare(strict_types=1);

// 基础异常
abstract class AppBaseException extends Exception {}

// 业务异常
class BusinessException extends AppBaseException {}

class ValidationException extends BusinessException
{
    /** @var array<string, string> */
    private array $errors;

    /**
     * @param array<string, string> $errors
     */
    public function __construct(array $errors, ?Throwable $previous = null)
    {
        $this->errors = $errors;
        parent::__construct('数据验证失败', 422, $previous);
    }

    /**
     * @return array<string, string>
     */
    public function errors(): array
    {
        return $this->errors;
    }
}

class EntityNotFoundException extends BusinessException
{
    public function __construct(string $entity, string $id, ?Throwable $previous = null)
    {
        parent::__construct("实体 {$entity}(ID: {$id}) 未找到", 404, $previous);
    }
}

class DuplicateEntityException extends BusinessException
{
    public function __construct(string $entity, string $key, ?Throwable $previous = null)
    {
        parent::__construct("实体 {$entity}({$key}) 已存在", 409, $previous);
    }
}

// 基础设施异常
class InfrastructureException extends AppBaseException {}

class DatabaseException extends InfrastructureException
{
    public function __construct(string $message, ?Throwable $previous = null)
    {
        parent::__construct($message, 500, $previous);
    }
}

class CacheException extends InfrastructureException
{
    public function __construct(string $message, ?Throwable $previous = null)
    {
        parent::__construct($message, 500, $previous);
    }
}

注意事项

不要捕获 Error 除非必要

php
<?php

declare(strict_types=1);

// 反面示例:捕获所有 Throwable 包括 Error
try {
    riskyOperation();
} catch (Throwable $e) {
    // 可能捕获了 OutOfMemoryError、TypeError 等不应忽略的错误
    return null;
}

// 正面示例:只捕获特定异常
try {
    riskyOperation();
} catch (BusinessException $e) {
    // 处理业务异常
    return fallbackValue($e);
}
// Error 类型不捕获,让它向上传播到全局处理器

异常码的约定

php
<?php

declare(strict_types=1);

// 常用的异常码约定
class AppExceptionCodes
{
    // 业务异常码
    public const VALIDATION_FAILED = 422;
    public const NOT_FOUND = 404;
    public const CONFLICT = 409;
    public const FORBIDDEN = 403;
    public const UNAUTHORIZED = 401;

    // 系统异常码
    public const INTERNAL_ERROR = 500;
    public const SERVICE_UNAVAILABLE = 503;
    public const DB_ERROR = 500;
}

// 使用
throw new EntityNotFoundException('User', '123');
// code = 404(在构造函数中设定)

最佳实践

  1. 自定义异常继承 Exception:不要继承 Error(除非创建自定义内部错误)
  2. 异常体系与业务领域对齐:创建有意义的异常子类
  3. 使用异常码:便于 API 返回和日志分析
  4. 保持异常消息信息丰富:包含足够上下文但不含敏感信息
  5. 合理的层次深度:2~3 层继承即可
php
<?php

declare(strict_types=1);

// 推荐的异常层次
// AppException (基类)
// ├── DomainException (领域异常)
// │   ├── UserNotFoundException
// │   └── InvalidStateException
// ├── ServiceException (服务异常)
// │   ├── PaymentFailedException
// │   └── ExternalApiException
// └── InfrastructureException (基础设施)
//     ├── DatabaseException
//     └── CacheException

参考链接