错误处理
DuxLite 提供了完整的错误处理机制,基于 SlimPHP 的错误处理系统,支持多种响应格式、自定义错误页面和详细的错误日志记录。
设计理念
DuxLite 错误处理理念:显式且用户友好的错误处理
- 分层异常设计:业务异常、验证异常、系统异常分类处理
- 智能渲染:根据请求类型自动选择 HTML、JSON、XML 响应格式
- 国际化支持:错误信息支持多语言翻译
- 开发友好:调试模式下显示详细错误信息
- 生产安全:生产环境下隐藏敏感信息
异常类型
1. 基础异常类
php
// 源码位置:src/Handlers/Exception.php
use Core\Handlers\Exception;
// 框架基础异常类,继承自 RuntimeException
class Exception extends \RuntimeException {}
2. 业务异常
用于处理业务逻辑错误,如权限不足、数据不存在等:
php
// 源码位置:src/Handlers/ExceptionBusiness.php
use Core\Handlers\ExceptionBusiness;
// 抛出业务异常
throw new ExceptionBusiness('用户不存在', 404);
throw new ExceptionBusiness('权限不足', 403);
throw new ExceptionBusiness('操作失败', 500);
// 在控制器中使用
class UserController
{
public function show(int $id): ResponseInterface
{
$user = User::find($id);
if (!$user) {
throw new ExceptionBusiness('用户不存在', 404);
}
return response()->json($user);
}
}
3. 国际化业务异常
支持多语言的业务异常:
php
// 源码位置:src/Handlers/ExceptionBusinessLang.php
use Core\Handlers\ExceptionBusinessLang;
// 使用语言包中的错误信息
throw new ExceptionBusinessLang('error.notFound'); // 页面不存在
throw new ExceptionBusinessLang('message.emptyData'); // 空数据
throw new ExceptionBusinessLang('validate.placeholder', '用户名'); // 请输入用户名
// 语言包配置(common.zh-CN.toml)
[error]
notFound = "页面不存在"
notFoundMessage = "您访问的页面不存在或者已被删除"
[validate]
placeholder = "请输入%name%"
4. 验证异常
用于表单验证错误:
php
// 源码位置:src/Handlers/ExceptionValidator.php
use Core\Handlers\ExceptionValidator;
// 验证器自动抛出
$data = Validator::parser($request->getParsedBody(), [
'name' => ['required', '姓名不能为空'],
'email' => ['required|email', '请输入有效的邮箱地址']
]);
// 手动抛出验证异常
throw new ExceptionValidator([
'name' => ['姓名不能为空'],
'email' => ['邮箱格式不正确']
]);
5. 404 异常
专门处理资源不存在的情况:
php
// 源码位置:src/Handlers/ExceptionNotFound.php
use Core\Handlers\ExceptionNotFound;
// 自动设置 404 状态码
throw new ExceptionNotFound();
// 在路由中使用
$app->get('/users/{id}', function ($request, $response, $args) {
$user = User::find($args['id']);
if (!$user) {
throw new ExceptionNotFound();
}
return response()->json($user);
});
6. 数据异常
携带额外数据的异常:
php
// 源码位置:src/Handlers/ExceptionData.php
use Core\Handlers\ExceptionData;
// 创建数据异常实例
$exception = new ExceptionData('操作失败', 422);
$exception->data = [
'errors' => ['field1' => '错误信息1'],
'metadata' => ['timestamp' => time()]
];
throw $exception;
7. 内部异常
系统内部错误,不对外暴露:
php
// 源码位置:src/Handlers/ExceptionInternal.php
use Core\Handlers\ExceptionInternal;
// 用于框架内部错误
throw new ExceptionInternal('内部系统错误');
错误处理器
ErrorHandler 核心功能
DuxLite 的错误处理器扩展了 SlimPHP 的基础功能:
php
// 源码位置:src/Handlers/ErrorHandler.php
use Core\Handlers\ErrorHandler;
class ErrorHandler extends slimErrorHandler
{
// 智能状态码判断
protected function determineStatusCode(): int
{
if ($this->method === 'OPTIONS') {
return 200; // OPTIONS 请求返回 200
}
if ($this->exception instanceof HttpException || $this->exception instanceof Exception) {
return $this->exception->getCode() ?: 500;
}
return 500;
}
// 智能错误日志记录
protected function logError(string $error): void
{
// 不记录这些类型的错误日志
if (
$this->statusCode == 404 || // 404 错误
$this->exception instanceof HttpSpecializedException || // HTTP 专用异常
$this->exception instanceof LogicException || // 逻辑异常
$this->exception instanceof Exception // 框架异常
) {
return;
}
// 记录详细错误信息
App::log()->error($this->exception->getMessage(), [
'uri' => $this->request->getUri(),
'query' => $this->request->getQueryParams(),
'file' => [
'file' => $this->exception->getFile(),
'line' => $this->exception->getLine()
],
'trace' => $this->exception->getTrace()
]);
}
}
错误渲染器
DuxLite 支持多种响应格式的错误渲染:
1. HTML 渲染器
用于 Web 页面的错误显示:
php
// 源码位置:src/Handlers/ErrorHtmlRenderer.php
use Core\Handlers\ErrorHtmlRenderer;
// 自动选择模板
// 404 错误 -> 404.latte 模板
// 其他错误 -> error.latte 模板
// 模板变量
[
"code" => 404, // 错误状态码
"title" => "页面不存在", // 错误标题
"message" => "页面已被删除" // 错误描述
]
2. JSON 渲染器
用于 API 接口的错误响应:
php
// 源码位置:src/Handlers/ErrorJsonRenderer.php
use Core\Handlers\ErrorJsonRenderer;
// JSON 响应格式
{
"code": 404,
"message": "用户不存在",
"data": []
}
// 验证错误的响应格式
{
"code": 422,
"message": "验证失败",
"data": {
"name": ["姓名不能为空"],
"email": ["邮箱格式不正确"]
}
}
3. XML 和纯文本渲染器
php
// XML 渲染器
use Core\Handlers\ErrorXmlRenderer;
// 纯文本渲染器
use Core\Handlers\ErrorPlainRenderer;
自定义错误页面
1. 默认模板
框架提供了美观的默认错误页面:
html
<!-- 404 页面模板:src/Tpl/404.latte -->
<!DOCTYPE html>
<html>
<head>
<title>{__('error.notFound', 'common')}</title>
<!-- Tailwind CSS 样式 -->
</head>
<body>
<main class="grid h-[100vh] place-items-center">
<div class="text-center">
<p class="text-4xl font-semibold text-blue-600">{$code}</p>
<h1 class="mt-4 text-4xl font-bold">{__('error.notFound', 'common')}</h1>
<p class="mt-6 text-base">{__('error.notFoundMessage', 'common')}</p>
<div class="mt-10 flex gap-x-6">
<a href="/" class="btn-primary">{__('error.home', 'common')}</a>
<a href="#" onclick="history.go(-1)">{__('error.back', 'common')}</a>
</div>
</div>
</main>
</body>
</html>
2. 自定义错误模板
php
// 注册自定义模板
App::di()->set('tpl.404', '/path/to/custom/404.latte');
App::di()->set('tpl.error', '/path/to/custom/error.latte');
// 在 Bootstrap.php 中配置
public static function loadApp(Slim\App $app): void
{
// 自定义 404 页面
App::di()->set('tpl.404', __DIR__ . '/templates/errors/404.latte');
// 自定义错误页面
App::di()->set('tpl.error', __DIR__ . '/templates/errors/error.latte');
}
3. 模板变量
错误模板可以使用以下变量:
php
// 可用变量
$code // 错误状态码:404, 500, 422
$title // 错误标题
$message // 错误描述信息
// 使用示例
{$code} // 404
{$title} // 页面不存在
{$message} // 您访问的页面不存在
{__('error.home', 'common')} // 回到首页(多语言)
错误信息特性
ErrorRendererTrait
提供智能的错误信息处理:
php
// 源码位置:src/Handlers/ErrorRendererTrait.php
trait ErrorRendererTrait
{
// 获取错误标题
protected function getErrorTitle(Throwable $exception): string
{
// 调试模式或特定异常:显示真实错误信息
if (App::$debug || $exception instanceof HttpException || $exception instanceof Exception) {
return $exception->getMessage();
}
// 生产模式:显示通用错误信息
return __('error.errorTitle', 'common'); // "应用错误"
}
// 获取错误描述
protected function getErrorDescription(Throwable $exception): string
{
if ($exception instanceof HttpException) {
return $exception->getDescription();
}
return __('error.errorMessage', 'common'); // "操作遇到了错误,请稍后再试"
}
}
实际使用示例
1. 控制器中的错误处理
php
class UserController
{
public function show(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
{
try {
$id = (int) $args['id'];
// 参数验证
if ($id <= 0) {
throw new ExceptionBusiness('无效的用户ID', 400);
}
// 查询用户
$user = User::find($id);
if (!$user) {
throw new ExceptionNotFound(); // 自动 404
}
// 权限检查
if (!$this->canViewUser($request, $user)) {
throw new ExceptionBusiness('权限不足', 403);
}
return send($response, $user);
} catch (ExceptionBusiness $e) {
// 业务异常会被自动处理和渲染
throw $e;
} catch (\Exception $e) {
// 系统异常转换为内部错误
throw new ExceptionInternal('用户查询失败');
}
}
public function store(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
{
$data = $request->getParsedBody();
// 验证器会自动抛出 ExceptionValidator
$validData = Validator::parser($data, [
'name' => ['required', '姓名不能为空'],
'email' => ['required|email', '请输入有效的邮箱地址'],
'phone' => ['regex:/^1[3-9]\d{9}$/', '请输入有效的手机号']
]);
// 业务逻辑
if (User::where('email', $validData->email)->exists()) {
throw new ExceptionBusiness('邮箱已被使用', 422);
}
$user = User::create((array) $validData);
return send($response, $user, '用户创建成功');
}
}
2. 中间件中的错误处理
php
class AuthMiddleware
{
public function __invoke(Request $request, RequestHandler $handler): Response
{
$token = $request->getHeaderLine('Authorization');
if (!$token) {
throw new ExceptionBusiness('缺少认证令牌', 401);
}
try {
$user = $this->validateToken($token);
$request = $request->withAttribute('user', $user);
} catch (TokenExpiredException $e) {
throw new ExceptionBusiness('认证令牌已过期', 401);
} catch (InvalidTokenException $e) {
throw new ExceptionBusiness('无效的认证令牌', 401);
}
return $handler->handle($request);
}
}
3. 全局异常处理
php
// 在 Bootstrap.php 中配置错误处理
public static function loadApp(Slim\App $app): void
{
// 设置错误处理器
$errorHandler = new ErrorHandler(
$app->getCallableResolver(),
$app->getResponseFactory()
);
// 配置错误渲染器
$errorHandler->registerErrorRenderer('text/html', ErrorHtmlRenderer::class);
$errorHandler->registerErrorRenderer('application/json', ErrorJsonRenderer::class);
$errorHandler->registerErrorRenderer('application/xml', ErrorXmlRenderer::class);
$errorHandler->registerErrorRenderer('text/plain', ErrorPlainRenderer::class);
$app->addErrorMiddleware(App::$debug, true, true)
->setDefaultErrorHandler($errorHandler);
}
调试模式
开发环境配置
toml
# config/use.toml
[app]
debug = true # 开启调试模式
# 调试模式特性
# 1. 显示详细错误信息和堆栈跟踪
# 2. 错误页面显示异常类名和文件位置
# 3. API 响应包含调试信息
生产环境配置
toml
# config/use.toml
[app]
debug = false # 关闭调试模式
# 生产模式特性
# 1. 隐藏敏感错误信息
# 2. 显示用户友好的错误页面
# 3. 详细错误信息仅记录到日志
最佳实践
1. 异常分类使用
php
// ✅ 正确的异常使用
throw new ExceptionBusiness('用户不存在', 404); // 业务逻辑错误
throw new ExceptionValidator($errors); // 验证错误
throw new ExceptionNotFound(); // 资源不存在
throw new ExceptionInternal('数据库连接失败'); // 系统内部错误
// ❌ 错误的异常使用
throw new \Exception('用户不存在'); // 不要使用通用异常
throw new ExceptionBusiness($e->getMessage()); // 不要暴露系统异常信息
2. 错误信息国际化
php
// ✅ 使用语言包
throw new ExceptionBusinessLang('message.emptyData');
throw new ExceptionBusinessLang('validate.placeholder', '用户名');
// ❌ 硬编码错误信息
throw new ExceptionBusiness('数据为空');
3. 错误日志记录
php
// ✅ 合理的日志记录
try {
$result = $this->externalApiCall();
} catch (\Exception $e) {
// 记录详细错误信息供调试
App::log()->error('外部API调用失败', [
'api' => 'getUserInfo',
'params' => $params,
'error' => $e->getMessage()
]);
// 抛出用户友好的错误
throw new ExceptionBusiness('获取用户信息失败,请稍后重试', 500);
}
4. 统一错误响应格式
php
// API 响应统一格式
{
"code": 404,
"message": "用户不存在",
"data": []
}
// 验证错误格式
{
"code": 422,
"message": "验证失败",
"data": {
"name": ["姓名不能为空"],
"email": ["邮箱格式不正确"]
}
}
通过 DuxLite 的错误处理系统,你可以构建健壮、用户友好且易于调试的应用程序。