错误处理
介绍
当你开始一个新的 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\InvalidOrderException;
/**
* 为应用程序注册异常处理回调
*/
public function register(): void
{
$this->reportable(function (InvalidOrderException $e) {
// ...
});
}
reportable
方法注册一个自定义异常报告回调时, Laravel 依然会使用默认的日志配置记录下应用异常。 如果您想要在默认的日志堆栈中停止这个行为,您可以在定义报告回调时使用 stop 方法或者从回调函数中返回 false
:$this->reportable(function (InvalidOrderException $e) {
// ...
})->stop();
$this->reportable(function (InvalidOrderException $e) {
return false;
});
技巧
要为给定的异常自定义异常报告,您可以使用 可报告异常.
全局日志上下文
App\Exceptions\Handler
类中的 context
方法来定义您自己的全局上下文数据(环境变量)。此后,每一条异常日志信息都将包含这个信息:/**
* 获取默认日志的上下文变量。
*
* @return array<string, mixed>
*/
protected function context(): array
{
return array_merge(parent::context(), [
'foo' => 'bar',
]);
}
异常日志上下文
尽管将上下文添加到每个日志消息中可能很有用,但有时特定的异常可能具有您想要包含在日志中的唯一上下文。通过在应用程序的自定义异常中定义context
方法,您可以指定与该异常相关的任何数据,应将其添加到异常的日志条目中:
<?php
namespace App\Exceptions;
use Exception;
class InvalidOrderException extends Exception
{
// ...
/**
* 获取异常上下文信息
*
* @return array<string, mixed>
*/
public function context(): array
{
return ['order_id' => $this->orderId];
}
}
report
助手
有时,您可能需要报告异常,但继续处理当前请求。report
助手函数允许您通过异常处理程序快速报告异常,而无需向用户呈现错误页面:
public function isValid(string $value): bool
{
try {
// Validate the value...
} catch (Throwable $e) {
report($e);
return false;
}
}
异常日志级别
当消息被写入应用程序的日志时,消息将以指定的日志级别写入,该级别指示正在记录的消息的严重性或重要性。
如上所述,即使使用reportable
方法注册自定义异常报告回调,Laravel仍将使用应用程序的默认日志记录配置记录异常;但是,由于日志级别有时会影响消息记录的通道,因此您可能希望配置某些异常记录的日志级别。
为了实现这个目标,您可以在应用程序的异常处理程序的$levels
属性中定义一个异常类型数组以及它们关联的日志级别:
use PDOException;
use Psr\Log\LogLevel;
/**
* 包含其对应自定义日志级别的异常类型列表。
*
* @var array<class-string<\Throwable>, \Psr\Log\LogLevel::*>
*/
protected $levels = [
PDOException::class => LogLevel::CRITICAL,
];
按类型忽略异常
在构建应用程序时,您可能希望忽略某些类型的异常并永远不报告它们。应用程序的异常处理程序包含一个 $dontReport
属性,该属性初始化为空数组。您添加到此属性的任何类都将不会被报告;但是它们仍然可能具有自定义渲染逻辑:
use App\Exceptions\InvalidOrderException;
/**
* 不会被报告的异常类型列表。
*
* @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;
/**
* 为应用程序注册异常处理回调函数。
*/
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);
}
});
}
Reportable & Renderable 异常
您可以直接在自定义异常类中定义 report
和 render
方法,而不是在异常处理程序的 register
方法中定义自定义报告和呈现行为。当存在这些方法时,框架将自动调用它们:
<?php
namespace App\Exceptions;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class InvalidOrderException 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 (/** 确定异常是否需要自定义报告 */) {
// ...
return true;
}
return false;
}
注意
你可以在report
方法中类型提示任何所需的依赖项,它们将自动被 Laravel 的服务容器注入该方法中。
HTTP 异常
有些异常描述了服务器返回的 HTTP 错误代码。例如,这可能是一个 "页面未找到" 错误(404),一个 "未经授权错误"(401)或甚至是一个由开发者生成的 500 错误。为了从应用程序的任何地方生成这样的响应,你可以使用 abort
帮助函数:
abort(404);
自定义 HTTP 错误页面
Laravel 使得为各种 HTTP 状态码显示自定义错误页面变得很容易。例如,如果你想自定义 404 HTTP 状态码的错误页面,请创建一个 resources/views/errors/404.blade.php
视图模板。这个视图将会被渲染在应用程序生成的所有 404 错误上。这个目录中的视图应该被命名为它们对应的 HTTP 状态码。abort
函数引发的 Symfony\Component\HttpKernel\Exception\HttpException
实例将会以 $exception
变量的形式传递给视图:
<h2>{{ $exception->getMessage() }}</h2>
你可以使用 vendor:publish
Artisan 命令发布 Laravel 的默认错误页面模板。一旦模板被发布,你可以根据自己的喜好进行自定义:
php artisan vendor:publish --tag=laravel-errors
回退 HTTP 错误页面
你也可以为给定系列的 HTTP 状态码定义一个“回退”错误页面。如果没有针对发生的具体 HTTP 状态码相应的页面,就会呈现此页面。为了实现这一点,在你应用程序的 resources/views/errors
目录中定义一个 4xx.blade.php
模板和一个 5xx.blade.php
模板。