本文目录导读:

在PHP中设计异常处理层级,主要是为了将不同的错误类型进行逻辑分组,从而能在不同的层级(如控制器、服务层、全局中间件)进行差异化处理(比如返回不同的HTTP状态码、不同的错误消息格式)。
以下是一套经过实践检验的、结构清晰的异常处理层级设计方案,适用于现代PHP框架(如Laravel、Symfony或ThinkPHP)。
基础架构:三层核心异常
建议将异常分为三个核心层级,每个层级都有明确的职责:
// 1. 顶层:系统/框架级异常 (不应被业务代码捕获,应由框架处理) // 如:PDOException, TypeError, ParseError // --> 交给全局异常处理器记录日志并返回500 // 2. 中层:业务逻辑异常 (最核心) // 如:用户不存在、订单已支付、库存不足 // --> 根据子类类型返回不同HTTP状态码 // 3. 底层:基础设施异常 (第三方服务、数据库、缓存) // 如:Redis连接失败、API调用超时、文件写入失败 // --> 通常需要重试或降级,返回503
具体类结构设计
创建一个继承自 \Exception 的 BaseException,然后在此之下建立子层级。
<?php
namespace App\Exceptions;
use Exception;
use Throwable;
/**
* 应用基础异常(所有自定义异常的父类)
*/
class BaseException extends Exception
{
// 期望的HTTP状态码
protected int $statusCode = 500;
// 错误码(用于前端或API识别具体错误,非HTTP状态码)
protected int $errorCode = 0;
// 错误数据(可选,用于携带验证失败等详细信息)
protected ?array $data = null;
public function __construct(
string $message = "",
int $errorCode = 0,
?array $data = null,
int $statusCode = 500,
?Throwable $previous = null
) {
parent::__construct($message, $errorCode, $previous);
$this->errorCode = $errorCode;
$this->data = $data;
$this->statusCode = $statusCode;
}
public function getStatusCode(): int
{
return $this->statusCode;
}
public function getData(): ?array
{
return $this->data;
}
}
业务层级细分(最常用)
<?php
namespace App\Exceptions\Business;
use App\Exceptions\BaseException;
// ---- 业务通用 ----
class BusinessException extends BaseException
{
public function __construct(string $message = "业务处理失败", int $errorCode = 1000, ?array $data = null, ?Throwable $previous = null)
{
parent::__construct($message, $errorCode, $data, 400, $previous);
}
}
// ---- 资源相关 ----
class ResourceNotFoundException extends BaseException
{
public function __construct(string $message = "资源未找到", int $errorCode = 1001, ?array $data = null, ?Throwable $previous = null)
{
parent::__construct($message, $errorCode, $data, 404, $previous);
}
}
// ---- 权限相关 ----
class UnauthorizedException extends BaseException
{
public function __construct(string $message = "未授权", int $errorCode = 1002, ?array $data = null, ?Throwable $previous = null)
{
parent::__construct($message, $errorCode, $data, 401, $previous);
}
}
class ForbiddenException extends BaseException
{
public function __construct(string $message = "无权限访问", int $errorCode = 1003, ?array $data = null, ?Throwable $previous = null)
{
parent::__construct($message, $errorCode, $data, 403, $previous);
}
}
// ---- 验证错误 ----
class ValidationException extends BaseException
{
public function __construct(string $message = "数据验证失败", int $errorCode = 1004, ?array $data = null, ?Throwable $previous = null)
{
parent::__construct($message, $errorCode, $data, 422, $previous);
}
}
// ---- 并发/状态冲突 ----
class ConflictException extends BaseException
{
public function __construct(string $message = "资源冲突", int $errorCode = 1005, ?array $data = null, ?Throwable $previous = null)
{
parent::__construct($message, $errorCode, $data, 409, $previous);
}
}
// ---- 限流/频率限制 ----
class TooManyRequestsException extends BaseException
{
public function __construct(string $message = "请求过于频繁", int $errorCode = 1006, ?array $data = null, ?Throwable $previous = null)
{
parent::__construct($message, $errorCode, $data, 429, $previous);
}
}
基础设施层级(可选)
<?php
namespace App\Exceptions\Infrastructure;
use App\Exceptions\BaseException;
// 数据库连接故障
class DatabaseConnectionException extends BaseException
{
public function __construct(string $message = "数据库服务不可用")
{
parent::__construct($message, 2001, null, 503);
}
}
// 第三方API调用失败
class ExternalServiceException extends BaseException
{
public function __construct(string $message = "外部服务调用失败")
{
parent::__construct($message, 2002, null, 502);
}
}
// 缓存服务异常
class CacheException extends BaseException
{
public function __construct(string $message = "缓存服务异常")
{
parent::__construct($message, 2003, null, 503);
}
}
全局异常处理(以Laravel为例)
这是整个体系发挥作用的关键环节,在 App\Exceptions\Handler 中注册渲染逻辑:
<?php
namespace App\Exceptions;
use App\Exceptions\Business\{ValidationException, ResourceNotFoundException};
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
class Handler extends ExceptionHandler
{
protected $dontReport = [
// 不需要记录到日志的异常(如验证错误、404)
ValidationException::class,
ResourceNotFoundException::class,
// ...
];
public function register()
{
$this->reportable(function (Throwable $e) {
// 可以在这里加入Sentry或日志记录逻辑
// 但注意不要重复记录 $dontReport 里的异常
});
$this->renderable(function (\App\Exceptions\BaseException $e, $request) {
// 处理所有继承了 BaseException 的自定义异常
if ($request->expectsJson() || $request->is('api/*')) {
return response()->json([
'code' => $e->getCode() ?: $e->getStatusCode(), // 自定义业务码
'message' => $e->getMessage(),
'data' => $e->getData(), // 比如验证错误的详细字段
], $e->getStatusCode());
}
// 如果是Web请求,可以跳转到错误页面
return response()->view("errors.{$e->getStatusCode()}", [
'message' => $e->getMessage()
], $e->getStatusCode());
});
// 兜底:处理所有未捕获的PHP异常(比如500错误)
$this->renderable(function (Throwable $e, $request) {
if ($request->expectsJson() || $request->is('api/*')) {
return response()->json([
'code' => 500,
'message' => '服务器内部错误',
'data' => null,
], 500);
}
return response()->view('errors.500', [], 500);
});
}
}
优雅的“熔断”机制:避免多层嵌套Try-Catch
设计完层级后,不要在业务代码里写很多 try { ... } catch (SpecificException $e) { ... }。
利用 异常冒泡 设计:
- Service层:如果遇到问题,只管
throw(throw new ResourceNotFoundException())。 - Controller层:绝大多数情况下不需要catch,异常自动冒泡到全局处理器。
- 事务层:仅在需要回滚数据库事务的地方 catch,然后重新抛出(
throw $e)。
// Service层的推荐写法 —— 不捕获
public function getUser(int $id): User
{
$user = User::find($id);
if (!$user) {
throw new ResourceNotFoundException("用户ID:{$id} 不存在");
}
return $user;
}
// Controller层 —— 也不需要写try-catch
public function show(int $id)
{
$user = $this->userService->getUser($id);
return response()->json($user);
}
// 异常直接冒泡到全局Handler,返回 404 JSON
设计原则总结
| 层级 | 代表异常 | HTTP状态码 | 处理方式 | 日志记录 |
|---|---|---|---|---|
| 业务层 | ValidationException, ResourceNotFoundException |
4xx | 全局Handler直接返回错误信息 | 可选(低优先级) |
| 业务层 | ConflictException, BusinessException |
409/400 | 全局Handler返回错误信息 | 可选 |
| 基础设施层 | DatabaseConnectionException |
503 | 全局Handler返回错误并记录日志 | 必须 |
| 系统层(原生) | PDOException, Error |
500 | 全局Handler记录日志并返回通用错误 | 必须 |
一些额外建议
- 不要使用Exception作为业务控制流:即不要用
try { ... } catch (\Exception $e) { return 'something'; }来代替if-else逻辑判断。 - 错误码保持一致性:建议在
config/errors.php中集中管理errorCode的意义,1000=通用业务错误,1001=资源未找到,1002=未授权。 - 区分“可恢复”与“不可恢复”异常:基础设施异常(如数据库断连)通常是不可恢复的,应该直接上报运维;而业务异常(如用户输入错误)是预期的,直接返回给前端即可。
- 利用IDE提示:将所有的业务异常声明在
@throws注解中,@throws ResourceNotFoundException,便于团队成员理解方法风险。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播