PHP 异常类设计

wen PHP项目 3

本文目录导读:

PHP 异常类设计

  1. 基础异常类设计
  2. 分类异常类
  3. 高级异常处理机制
  4. 使用示例
  5. 最佳实践

设计一个优秀的 PHP 异常类体系是构建健壮应用的关键,下面提供一个从基础到进阶的完整设计指南。

基础异常类设计

核心异常基类

<?php
namespace App\Exceptions;
use RuntimeException;
use Throwable;
/**
 * 应用基础异常类
 * 所有业务异常都应继承此类
 */
class AppException extends RuntimeException
{
    /**
     * 业务错误码
     * @var string|int
     */
    protected $errorCode;
    /**
     * 错误上下文数据
     * @var array
     */
    protected $context = [];
    /**
     * HTTP 状态码
     * @var int
     */
    protected $statusCode = 500;
    /**
     * 是否可重试
     * @var bool
     */
    protected $retryable = false;
    /**
     * 重试延迟(秒)
     * @var int|null
     */
    protected $retryAfter = null;
    /**
     * @param string $message 错误消息
     * @param string|int $errorCode 业务错误码
     * @param int $statusCode HTTP状态码
     * @param array $context 附加上下文
     * @param Throwable|null $previous 前一个异常
     */
    public function __construct(
        string $message = "",
        $errorCode = 'APP_ERROR',
        int $statusCode = 500,
        array $context = [],
        ?Throwable $previous = null
    ) {
        $this->errorCode = $errorCode;
        $this->statusCode = $statusCode;
        $this->context = $context;
        parent::__construct($message, $statusCode, $previous);
    }
    /**
     * 获取业务错误码
     */
    public function getErrorCode()
    {
        return $this->errorCode;
    }
    /**
     * 获取HTTP状态码
     */
    public function getStatusCode(): int
    {
        return $this->statusCode;
    }
    /**
     * 获取上下文数据
     */
    public function getContext(): array
    {
        return $this->context;
    }
    /**
     * 添加上下文数据(链式调用)
     */
    public function addContext(string $key, $value): self
    {
        $this->context[$key] = $value;
        return $this;
    }
    /**
     * 获取所有错误信息
     */
    public function toArray(): array
    {
        return [
            'message' => $this->getMessage(),
            'code' => $this->getErrorCode(),
            'status' => $this->getStatusCode(),
            'context' => $this->getContext(),
            'file' => $this->getFile(),
            'line' => $this->getLine(),
            'trace' => $this->getTraceAsString(),
        ];
    }
    /**
     * 设置是否可重试
     */
    public function setRetryable(bool $retryable, ?int $retryAfter = null): self
    {
        $this->retryable = $retryable;
        $this->retryAfter = $retryAfter;
        return $this;
    }
    /**
     * 是否可重试
     */
    public function isRetryable(): bool
    {
        return $this->retryable;
    }
    /**
     * 重试等待时间
     */
    public function getRetryAfter(): ?int
    {
        return $this->retryAfter;
    }
}

分类异常类

业务逻辑异常

<?php
namespace App\Exceptions;
/**
 * 业务逻辑异常
 * 用于处理业务规则不符的情况
 */
class BusinessException extends AppException
{
    /**
     * 业务规则名称
     */
    protected $rule;
    public function __construct(
        string $message,
        string $rule,
        $errorCode = 'BUSINESS_ERROR',
        array $context = [],
        ?Throwable $previous = null
    ) {
        $this->rule = $rule;
        parent::__construct($message, $errorCode, 400, $context, $previous);
    }
    public function getRule(): string
    {
        return $this->rule;
    }
}
/**
 * 资源未找到异常
 */
class ResourceNotFoundException extends AppException
{
    public function __construct(
        string $resource,
        $identifier = null,
        array $context = []
    ) {
        $message = "Resource '{$resource}'";
        if ($identifier !== null) {
            $message .= " with identifier '{$identifier}'";
        }
        $message .= " not found.";
        $context['resource'] = $resource;
        $context['identifier'] = $identifier;
        parent::__construct($message, 'RESOURCE_NOT_FOUND', 404, $context);
    }
}
/**
 * 权限不足异常
 */
class PermissionDeniedException extends AppException
{
    public function __construct(
        string $permission,
        array $context = []
    ) {
        $message = "Permission denied: {$permission}";
        $context['permission'] = $permission;
        parent::__construct($message, 'PERMISSION_DENIED', 403, $context);
    }
}

数据验证异常

<?php
namespace App\Exceptions;
/**
 * 数据验证异常
 * 用于处理输入验证失败
 */
class ValidationException extends AppException
{
    /**
     * 验证错误集合
     * @var array
     */
    protected $errors = [];
    public function __construct(
        array $errors = [],
        string $message = "Validation failed.",
        ?Throwable $previous = null
    ) {
        $this->errors = $errors;
        parent::__construct($message, 'VALIDATION_ERROR', 422, ['errors' => $errors], $previous);
    }
    /**
     * 获取所有验证错误
     */
    public function getErrors(): array
    {
        return $this->errors;
    }
    /**
     * 获取指定字段的错误
     */
    public function getFieldErrors(string $field): array
    {
        return $this->errors[$field] ?? [];
    }
    /**
     * 添加字段错误
     */
    public function addFieldError(string $field, string $error): self
    {
        $this->errors[$field][] = $error;
        $this->context['errors'] = $this->errors;
        return $this;
    }
}

外部服务异常

<?php
namespace App\Exceptions;
use Throwable;
/**
 * 外部服务异常
 * 用于处理第三方服务调用失败
 */
class ExternalServiceException extends AppException
{
    const RETRYABLE = 'retryable';
    const NON_RETRYABLE = 'non_retryable';
    /**
     * 服务名称
     */
    protected $service;
    /**
     * 请求信息
     */
    protected $request;
    /**
     * 响应信息
     */
    protected $response;
    public function __construct(
        string $service,
        string $message = "External service error.",
        string $request = null,
        string $response = null,
        bool $retryable = false,
        int $statusCode = 502,
        ?Throwable $previous = null
    ) {
        $this->service = $service;
        $this->request = $request;
        $this->response = $response;
        parent::__construct(
            $message,
            'EXTERNAL_SERVICE_ERROR',
            $statusCode,
            [
                'service' => $service,
                'request' => $request,
                'response' => $response,
            ],
            $previous
        );
        if ($retryable) {
            $this->setRetryable(true);
        }
    }
    /**
     * 获取服务名称
     */
    public function getService(): string
    {
        return $this->service;
    }
    /**
     * 获取请求内容
     */
    public function getRequest(): ?string
    {
        return $this->request;
    }
    /**
     * 获取响应内容
     */
    public function getResponse(): ?string
    {
        return $this->response;
    }
}

高级异常处理机制

异常处理注册器

<?php
namespace App\Exceptions;
use Throwable;
/**
 * 异常处理注册器
 * 提供异常处理策略的注册和管理
 */
class ExceptionHandler
{
    /**
     * 异常处理策略集合
     * @var callable[]
     */
    protected $handlers = [];
    /**
     * 默认处理策略
     * @var callable
     */
    protected $defaultHandler;
    /**
     * 异常订阅者
     * @var array
     */
    protected $subscribers = [];
    /**
     * 注册处理策略
     */
    public function register(string $exceptionClass, callable $handler): self
    {
        $this->handlers[$exceptionClass] = $handler;
        return $this;
    }
    /**
     * 注册默认处理策略
     */
    public function setDefaultHandler(callable $handler): self
    {
        $this->defaultHandler = $handler;
        return $this;
    }
    /**
     * 注册异常订阅者
     */
    public function subscribe(callable $subscriber): self
    {
        $this->subscribers[] = $subscriber;
        return $this;
    }
    /**
     * 执行处理
     */
    public function handle(Throwable $exception)
    {
        // 寻找最匹配的处理策略
        $handler = $this->findHandler($exception);
        // 执行订阅者,通知异常发生
        foreach ($this->subscribers as $subscriber) {
            $subscriber($exception);
        }
        return $handler($exception);
    }
    /**
     * 寻找最合适的处理策略
     */
    protected function findHandler(Throwable $exception): callable
    {
        foreach ($this->handlers as $exceptionClass => $handler) {
            if ($exception instanceof $exceptionClass) {
                return $handler;
            }
        }
        return $this->defaultHandler ?? function ($e) {
            throw $e;
        };
    }
    /**
     * 批量注册常用异常处理
     */
    public function registerDefaults()
    {
        $this->register(ValidationException::class, function ($e) {
            // 返回422响应
            return [
                'status' => 422,
                'errors' => $e->getErrors(),
            ];
        });
        $this->register(ResourceNotFoundException::class, function ($e) {
            // 返回404响应
            return [
                'status' => 404,
                'error' => $e->getMessage(),
            ];
        });
        // 更多默认处理...
    }
}

重试机制

<?php
namespace App\Exceptions;
use Throwable;
/**
 * 异常重试执行器
 */
class RetryExecutor
{
    /**
     * @var int 最大重试次数
     */
    protected int $maxAttempts;
    /**
     * @var int 基础延迟(秒)
     */
    protected int $baseDelay;
    /**
     * @var int 最大延迟(秒)
     */
    protected int $maxDelay;
    /**
     * @var bool 是否指数退避
     */
    protected bool $exponentialBackoff;
    /**
     * @var callable 重试判断回调
     */
    protected $shouldRetryCallback;
    public function __construct(
        int $maxAttempts = 3,
        int $baseDelay = 1,
        int $maxDelay = 10,
        bool $exponentialBackoff = true
    ) {
        $this->maxAttempts = $maxAttempts;
        $this->baseDelay = $baseDelay;
        $this->maxDelay = $maxDelay;
        $this->exponentialBackoff = $exponentialBackoff;
    }
    /**
     * 执行带重试机制的操作
     */
    public function execute(callable $operation)
    {
        $attempts = 0;
        while ($attempts < $this->maxAttempts) {
            try {
                return $operation();
            } catch (AppException $exception) {
                $attempts++;
                // 判断是否应该重试
                if (!$this->shouldRetry($exception, $attempts)) {
                    throw $exception;
                }
                $this->sleep($this->getDelay($attempts));
            }
        }
        throw new \RuntimeException("Operation failed after {$this->maxAttempts} attempts.");
    }
    /**
     * 判断是否需要重试
     */
    protected function shouldRetry(AppException $exception, int $attempts): bool
    {
        if ($this->shouldRetryCallback) {
            return call_user_func($this->shouldRetryCallback, $exception, $attempts);
        }
        return $exception->isRetryable() && $attempts < $this->maxAttempts;
    }
    /**
     * 设置自定义重试判断函数
     */
    public function setShouldRetryCallback(callable $callback): self
    {
        $this->shouldRetryCallback = $callback;
        return $this;
    }
    /**
     * 获取延迟时间
     */
    protected function getDelay(int $attempts): int
    {
        $delay = $this->baseDelay;
        if ($this->exponentialBackoff) {
            $delay = min(
                $this->maxDelay,
                $this->baseDelay * pow(2, $attempts - 1)
            );
        }
        return $delay;
    }
    /**
     * 延迟执行,支持纳秒级精度
     */
    protected function sleep(int $seconds): void
    {
        usleep($seconds * 1000000);
    }
    /**
     * 带抖动的延迟执行
     */
    protected function sleepWithJitter(int $seconds): void
    {
        $jitter = random_int(0, $seconds * 100) / 100;
        usleep(($seconds + $jitter) * 1000000);
    }
}

使用示例

<?php
// 创建自定义业务异常
class UserNotFoundException extends ResourceNotFoundException
{
    public function __construct(int $userId)
    {
        parent::__construct('user', $userId);
    }
}
// 在业务逻辑中使用
class UserService
{
    public function getUser(int $id): User
    {
        try {
            $user = $this->userRepository->find($id);
            if (!$user) {
                throw new UserNotFoundException($id);
            }
            if (!$user->isActive()) {
                throw new BusinessException(
                    'User is not active.',
                    'user.inactive'
                );
            }
            return $user;
        } catch (UserNotFoundException $e) {
            // 记录日志,可能重新包装异常
            throw $e->addContext('operation', 'getUser');
        } catch (\Exception $e) {
            // 处理意外异常
            throw $e;
        }
    }
    public function updateUser(int $id, array $data): User
    {
        // 验证数据
        $validator = new Validator();
        if (!$validator->validate($data)) {
            throw new ValidationException($validator->getErrors());
        }
        // ...
    }
    public function callExternalApi()
    {
        // 使用重试机制
        $executor = new RetryExecutor(3, 1, 10);
        return $executor->execute(function () {
            try {
                $this->externalApi->request();
            } catch (ExternalServiceException $e) {
                // 设置可重试标志
                throw $e->setRetryable(true, 5);
            }
        });
    }
}
// 应用入口处理
$handler = new ExceptionHandler();
$handler->registerDefaults();
try {
    $app->run();
} catch (Throwable $e) {
    $handler->handle($e);
}

最佳实践

异常类设计原则

  1. 层次化设计 - 异常类应该有清晰的继承层次
  2. 语义清晰 - 异常名称应该准确描述问题
  3. 携带上下文 - 异常应该包含足够的调试信息
  4. 一致性 - 错误码和格式应该统一
  5. 可预测性 - 避免在构造中抛出异常

错误处理策略

  • 快速失败:验证失败立即抛出异常
  • 优雅降级:外部服务失败时提供降级方案
  • 重试机制:对可恢复错误提供重试支持
  • 日志记录:所有异常都应该有完整的日志记录
  • 监控告警:关键异常应该触发告警

性能考虑

  • 避免使用异常作为控制流
  • 在异常中只包含必要的信息
  • 考虑使用异常池或复用对象
  • 评估异常处理性能开销

这个设计提供了一个全面、健壮的异常处理体系,能够满足从简单到复杂的大部分应用需求,核心优势在于:

  1. 清晰的分层结构 - 容易扩展和维护
  2. 丰富的上下文信息 - 便于调试和日志记录
  3. 灵活的处理机制 - 支持自定义处理和重试策略
  4. 良好的可测试性 - 易于单元测试和集成测试

抱歉,评论功能暂时关闭!