本文目录导读:

设计一个优秀的 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);
}
最佳实践
异常类设计原则
- 层次化设计 - 异常类应该有清晰的继承层次
- 语义清晰 - 异常名称应该准确描述问题
- 携带上下文 - 异常应该包含足够的调试信息
- 一致性 - 错误码和格式应该统一
- 可预测性 - 避免在构造中抛出异常
错误处理策略
- 快速失败:验证失败立即抛出异常
- 优雅降级:外部服务失败时提供降级方案
- 重试机制:对可恢复错误提供重试支持
- 日志记录:所有异常都应该有完整的日志记录
- 监控告警:关键异常应该触发告警
性能考虑
- 避免使用异常作为控制流
- 在异常中只包含必要的信息
- 考虑使用异常池或复用对象
- 评估异常处理性能开销
这个设计提供了一个全面、健壮的异常处理体系,能够满足从简单到复杂的大部分应用需求,核心优势在于:
- 清晰的分层结构 - 容易扩展和维护
- 丰富的上下文信息 - 便于调试和日志记录
- 灵活的处理机制 - 支持自定义处理和重试策略
- 良好的可测试性 - 易于单元测试和集成测试