第 2.7 章:错误处理
学习时间: 40 分钟
1. 为什么标准错误很糟糕?
如果您的 Laravel 应用程序发生错误(例如,在数据库中找不到记录),而您没有进行任何处理,用户将会看到一个包含调试信息的巨大 HTML 页面,或者一条不具信息量的“服务器错误”消息。
对于 API 来说,这是一场灾难。您的前端应用程序期望接收 JSON,而不是 HTML。我们的任务是拦截任何错误并将其转换为结构化的 JSON 响应。
2. 错误中央调度器:bootstrap/app.php
在旧版 Laravel 中,有一个笨重的 App\Exceptions\Handler.php
文件。在 Laravel 11/12 中,一切都变得更加简单和优雅。错误管理中心现在直接位于您的应用程序配置文件中 — bootstrap/app.php
。
打开 bootstrap/app.php
。在最底部,您会看到一个 .withExceptions(...)
块。这就是我们的“中央调度器”。
<?php
// bootstrap/app.php
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
// ...
})
->withExceptions(function (Exceptions $exceptions) {
// <-- 我们将在这里工作
})->create();
3. 处理最常见的错误:“未找到” (404)
API 中最常见的错误是用户请求不存在的资源(例如,GET /api/planets/999
)。在这种情况下,Laravel 会生成 ModelNotFoundException
或 NotFoundHttpException
异常。让我们来拦截它们。
将以下代码添加到 .withExceptions(...)
内部:
<?php
// bootstrap/app.php
->withExceptions(function (Exceptions $exceptions) {
// 当在数据库中找不到模型时,拦截异常
$exceptions->render(function (ModelNotFoundException $e, Request $request) {
// 检查请求是否来自我们的 API
if ($request->is('api/*')) {
return response()->json([
'message' => '在我们的银河系中未找到所请求的资源。'
], 404);
}
});
// 当路由本身未找到时,拦截异常
$exceptions->render(function (NotFoundHttpException $e, Request $request) {
if ($request->is('api/*')) {
return response()->json([
'message' => '不存在这样的宇宙路线。'
], 404);
}
});
})->create();
$exceptions->render(...)
— 我们注册了一个“处理程序”。它表示:“如果发生ModelNotFoundException
类型的异常,则执行此代码。”if ($request->is('api/*'))
— 这是一个重要的检查。它确保我们漂亮的 JSON 响应仅发送给 API 请求,而不影响普通的网页。return response()->json(...)
— 我们创建并返回带有 404 代码的标准化 JSON 响应。
现在,如果您请求一个不存在的星球,您将获得一个整洁的 JSON,而不是丑陋的 HTML 页面。
4. 自定义异常:创建我们自己的“警报信号”
有时标准异常是不够的。假设我们有一个业务规则:“不能删除‘地球’这个星球”。如果有人试图这样做,我们必须返回一个有意义的错误。
步骤 1:创建我们自己的异常类 在终端中执行:
步骤 2:在控制器中使用它
打开 PlanetController.php
并修改 destroy
方法:
<?php
// app/Http/Controllers/PlanetController.php
use App\Exceptions\CannotDeleteEarthException; // <-- 导入我们的异常
use App\Models\Planet;
public function destroy(Planet $planet)
{
// 我们的新业务规则
if (strtolower($planet->name) === 'земля') {
throw new CannotDeleteEarthException('根据银河法典,禁止删除地球。');
}
$planet->delete();
return response()->json(null, 204);
}
DELETE /api/planets/1
(其中 1 是地球的 ID),我们的代码将抛出 CannotDeleteEarthException
异常。
步骤 3:教 Laravel 优雅地处理我们的“警报”
回到 bootstrap/app.php
并为我们的异常添加一个新的处理程序。
<?php
// bootstrap/app.php
->withExceptions(function (Exceptions $exceptions) {
// 我们的新处理程序
$exceptions->render(function (CannotDeleteEarthException $e, Request $request) {
return response()->json([
'message' => '操作被禁止。',
'details' => $e->getMessage() // 获取我们通过 throw 传递的消息
], 403); // 403 Forbidden - “禁止访问”
});
// ... (404 的其他处理程序)
})->create();
5. 处理所有其他故障 (500 Internal Server Error)
如何处理所有其他意外错误?例如,如果数据库崩溃或代码中存在语法错误。为此,我们可以为最常见的错误类型 — Throwable
— 注册一个“通用”处理程序。
重要提示: 这个处理程序必须是最后一个,以免拦截我们上面定义的更具体的异常。
<?php
// bootstrap/app.php
->withExceptions(function (Exceptions $exceptions) {
// ... (CannotDeleteEarthException 和 404 的处理程序)
// 通用处理程序(在最末尾)
$exceptions->render(function (Throwable $e, Request $request) {
if ($request->is('api/*')) {
// 在调试模式下,可以显示真实的错误消息
$message = config('app.debug')
? '发生错误:' . $e->getMessage()
: '船上发生了意外错误。工程师已被召集。';
return response()->json(['message' => $message], 500);
}
});
})->create();
现在,任何“未知”异常都将被巧妙地拦截并转换为带有 500 代码的 JSON,而不会破坏您的 API 或向用户显示多余的信息。
6. 错误日志记录:宇宙飞船的黑匣子
config/logging.php
中的日志配置:
<?php
'channels' => [
'space_api' => [
'driver' => 'daily',
'path' => storage_path('logs/space_api.log'),
'level' => 'error',
'days' => 14,
],
],
添加日志记录:
<?php
try {
// 有错误风险的代码
} catch (Exception $e) {
Log::channel('space_api')->error('访问星球时出错', [
'exception' => $e,
'request' => request()->all(),
'user_id' => auth()->id()
]);
throw $e;
}
巩固测验
🚀 本章总结:
您为您的 API 配备了可靠的救援系统: - 🛟 全局捕获标准错误 - 🪐 带有可理解代码的自定义异常 - 📝 所有错误统一使用 JSON 格式 - 🔍 记录事件详情日志 - 📡 与监控系统集成
宇宙飞船已准备好应对紧急情况! 在本章的最后一节,我们将测试所有系统。
📌 检查:
- 创建
PlanetNotFoundException
异常- 在
->withExceptions
中添加 404 错误处理- 测试对不存在行星的请求
⚠️ 如果错误未被捕获:
- 确保
is('api/*')
与您的路由匹配- 检查
register()
中的处理程序顺序- 对于自定义异常,请使用
throw new