错误处理
简介
Laravel 默认已经为我们配置好了错误和异常处理,我们在 App\Exceptions\Handler
类中触发异常并将响应返回给用户。在本文档中我们将深入探讨这个类。
配置
配置文件 config/app.php
中的 debug
配置项控制浏览器显示的错误信息数量。默认情况下,该配置项通过 .env
文件中的环境变量 APP_DEBUG
进行设置。
对本地开发而言,你应该设置环境变量 APP_DEBUG
值为 true
。在生产环境,该值应该被设置为 false
。如果在生产环境被设置为 true
,就有可能将一些敏感的配置值暴露给终端用户。
异常处理器
报告异常
所有异常都由 App\Exceptions\Handler
类处理。这个类包含了一个 register
方法用于注册自定义的异常报告器和渲染器回调,接下来我们会详细介绍这些概念。我们可以通过异常报告记录异常或者将它们发送给外部服务,比如 Flare、Bugsnag 以及 Sentry。默认情况下,会基于日志配置记录异常,不过,你也可以按照自己期望的方式进行自定义。
例如,如果你需要以不同方式报告不同类型的异常,可以使用 reportable
方法注册一个闭包,该闭包会在给定类型异常需要被报告时执行。Laravel 会通过检查闭包的参数类型提示推断该闭包报告的异常类型:
use App\Exceptions\CustomException;
/**
* Register the exception handling callbacks for the application.
*/
public function register()
{
$this->reportable(function (CustomException $e) {
//
});
}
当你使用 reportable
方法注册自定义异常报告回调时,Laravel 仍然会使用应用默认的日志配置记录异常。如果你想要停止异常传播到默认的日志栈(不记录到日志系统),可以
在定义异常回调时使用 stop
方法或者从异常回调函数返回 false
:
$this->reportable(function (InvalidOrderException $e) {
// ...
})->stop();
$this->reportable(function (InvalidOrderException $e) {
return false;
});
可以考虑使用可报告的异常来取代在
reports
方法中进行大量的instanceof
检查。
全局日志上下文
如果当前用户 ID 可用的话,Laravel 会自动将其添加到每一条异常日志信息中作为上下文数据。你也可以通过重写 App\Exceptions\Handler
类的 context
方法来定义自己的全局上下文数据,该信息会被包含在应用写入的每一条异常日志信息中:
/**
* Get the default context variables for logging.
*/
protected function context()
{
return array_merge(parent::context(), [
'foo' => 'bar',
]);
}
异常日志上下文
在每个日志消息中添加上下文信息很有用,但有时特定的异常可能具有独特的上下文,您希望在日志中包含这些上下文。通过在应用程序的自定义异常上定义一个 context
方法,您可以指定与该异常相关的任何数据,这些数据应添加到异常的日志条目中:
<?php
namespace App\Exceptions;
use Exception;
class InvalidOrderException extends Exception
{
// ...
/**
* Get the exception's context information.
*
* @return array<string, mixed>
*/
public function context(): array
{
return ['order_id' => $this->orderId];
}
}
report
辅助函数
有时候你可能需要报告一个异常并继续处理当前请求。辅助函数 report
允许你使用异常处理器的 report
方法快速报告一个异常而不会渲染错误页:
public function isValid($value)
{
try {
// Validate the value...
} catch (Throwable $e) {
report($e);
return false;
}
}
异常日志级别
当日志消息被写入你的应用程序的日志文件中时,这些消息会被写入指定的日志级别,这指示了被记录的消息的严重性或重要性。
正如上面所提到的,即使你使用 reportable
方法注册了自定义异常报告回调,Laravel 仍然会使用应用程序的默认日志记录配置来记录异常。然而,由于日志级别有时会影响消息被记录的通道,你可能希望配置某些异常被记录的日志级别。
为了实现这一点,你可以在应用程序的异常处理器的 $levels
属性中定义一个异常类型及其关联的日志级别数组:
use PDOException;
use Psr\Log\LogLevel;
/**
* A list of exception types with their corresponding custom log levels.
*
* @var array<class-string<\Throwable>, \Psr\Log\LogLevel::*>
*/
protected $levels = [
PDOException::class => LogLevel::CRITICAL,
];
通过类型忽略异常
当构建应用程序时,可能有一些异常类型您希望忽略并且不会报告。您的应用程序异常处理程序包含一个 $dontReport
属性,该属性被初始化为空数组。您可以将要忽略报告的类添加到该属性中,这些类将不会被报告;但是,它们仍然可能具有自定义渲染逻辑:
use App\Exceptions\InvalidOrderException;
/**
* A list of the exception types that are not reported.
*
* @var array<int, class-string<\Throwable>>
*/
protected $dontReport = [
InvalidOrderException::class,
];
在内部,Laravel 已经为您忽略了一些类型的错误,例如由 404 HTTP 错误或由无效 CSRF 令牌生成的 419 HTTP 响应引发的异常。如果您想指示 Laravel 停止忽略某种类型的异常,可以在异常处理程序的 register
方法中调用 stopIgnoring
方法:
use Symfony\Component\HttpKernel\Exception\HttpException;
/**
* Register the exception handling callbacks for the application.
*/
public function register(): void
{
$this->stopIgnoring(HttpException::class);
// ...
}
渲染异常
默认情况下,Laravel 异常处理程序会将异常转换为 HTTP 响应。但是,您可以自由地为特定类型的异常注册自定义渲染闭包。您可以在异常处理程序中调用 renderable
方法来实现此功能。
传递给 renderable
方法的闭包应该返回一个 Illuminate\Http\Response
实例,该实例可以通过 response
辅助函数生成。Laravel 将通过检查闭包的类型提示来推断闭包渲染的异常类型:
use App\Exceptions\InvalidOrderException;
use Illuminate\Http\Request;
/**
* Register the exception handling callbacks for the application.
*/
public function register(): void
{
$this->renderable(function (InvalidOrderException $e, Request $request) {
return response()->view('errors.invalid-order', [], 500);
});
}
您还可以使用 renderable
方法来覆盖 Laravel 或 Symfony 内置异常(例如 NotFoundHttpException
)的渲染行为。如果传递给 renderable
方法的闭包没有返回值,将使用 Laravel 的默认异常渲染:
use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Register the exception handling callbacks for the application.
*/
public function register(): void
{
$this->renderable(function (NotFoundHttpException $e, Request $request) {
if ($request->is('api/*')) {
return response()->json([
'message' => 'Record not found.'
], 404);
}
});
}
可报告 & 可渲染的异常
除了在异常处理器的 report
和 render
方法中进行异常类型检查外,还可以在自定义异常中直接定义 report
和 render
方法。当异常中存在这些方法时,框架会自动调用它们:
<?php
namespace App\Exceptions;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class RenderException extends Exception
{
/**
* Report the exception.
*/
public function report(): void
{
//
}
/**
* Render the exception into an HTTP response.
*/
public function render(Request $request): Response
{
return response(...);
}
}
如果您的异常扩展了已经可以渲染的异常,例如内置的 Laravel 或 Symfony 异常,您可以在异常的 render 方法中返回 false,以渲染异常的默认 HTTP 响应:
/**
* Render the exception into an HTTP response.
*/
public function render(Request $request): Response|bool
{
if (/** Determine if the exception needs custom rendering */) {
return response(/* ... */);
}
return false;
}
如果您的异常包含了仅在特定条件下需要使用的自定义报告逻辑,可能需要指示 Laravel 在某些情况下使用默认异常处理配置报告异常。为了实现这一点,您可以在异常的 report
方法中返回 false
:
/**
* Report the exception.
*/
public function report(): bool
{
if (/** Determine if the exception needs custom reporting */) {
// ...
return true;
}
return false;
}
注:你可以在
report
方法中注入任何需要的依赖,它们会通过 Laravel 服务容器自动解析。
HTTP 异常
有些异常描述来自服务器的 HTTP 错误码,例如,这可能是一个“页面未找到”错误(404),“认证失败错误”(401)亦或是程序出错造成的 500 错误,为了在应用中生成这样的响应,可以使用 abort
辅助函数:
abort(404);
abort
辅助函数会立即引发一个会被异常处理器渲染的异常,此外,你还可以像这样提供响应描述:
abort(403, '未授权操作');
该方法可在请求生命周期的任何时间点使用。
自定义 HTTP 错误页面
在 Laravel 中,返回不同 HTTP 状态码的错误页面很简单,例如,如果你想要自定义 404 错误页面,创建一个 resources/views/errors/404.blade.php
文件,该视图文件用于渲染程序返回的所有 404 错误。需要注意的是,该目录下的视图命名应该和相应的 HTTP 状态码相匹配。abort
函数触发的 HttpException
异常会以 $exception
变量的方式传递给视图:
<h2>{{ $exception->getMessage() }}</h2>
你可以使用 Artisan 命令 vendor:publish
发布 Laravel 的错误页面模板:
php artisan vendor:publish --tag=laravel-errors
模板被发布后,就可以按照自己的喜好对其进行自定义。
兜底 HTTP 错误页面
您还可以为给定的一系列 HTTP 状态码定义一个“fallback”错误页面。如果没有针对特定 HTTP 状态码的对应页面,将呈现此页面。为了实现这一点,可以在您的应用程序的 resources/views/errors
目录中定义一个 4xx.blade.php
模板和一个 5xx.blade.php
模板。
No Comments