Skip to content

错误处理

介绍

当你开始一个新的 Laravel 项目时,错误和异常处理已经为你配置好了。App\Exceptions\Handler 类是所有应用程序抛出的异常被记录并呈现给用户的地方。我们将在本文档中深入探讨这个类。

配置

config/app.php 配置文件中的 debug 选项决定了向用户显示多少错误信息。默认情况下,此选项设置为遵循存储在 .env 文件中的 APP_DEBUG 环境变量的值。

在本地开发期间,你应该将 APP_DEBUG 环境变量设置为 true在生产环境中,此值应始终为 false。如果在生产中将此值设置为 true,你可能会将敏感的配置值暴露给应用程序的最终用户。

异常处理器

报告异常

所有异常都由 App\Exceptions\Handler 类处理。此类包含一个 register 方法,你可以在其中注册自定义异常报告和渲染回调。我们将详细检查这些概念。异常报告用于记录异常或将其发送到外部服务,如 FlareBugsnagSentry。默认情况下,异常将根据你的日志配置进行记录。不过,你可以随意记录异常。

例如,如果你需要以不同的方式报告不同类型的异常,可以使用 reportable 方法注册一个闭包,当需要报告给定类型的异常时应执行该闭包。Laravel 将通过检查闭包的类型提示来推断闭包报告的异常类型:

php
use App\Exceptions\InvalidOrderException;

/**
 * 为应用程序注册异常处理回调。
 *
 * @return void
 */
public function register()
{
    $this->reportable(function (InvalidOrderException $e) {
        //
    });
}

当你使用 reportable 方法注册自定义异常报告回调时,Laravel 仍将使用应用程序的默认日志配置记录异常。如果你希望阻止异常传播到默认日志堆栈,可以在定义报告回调时使用 stop 方法或从回调中返回 false

php
$this->reportable(function (InvalidOrderException $e) {
    //
})->stop();

$this->reportable(function (InvalidOrderException $e) {
    return false;
});
lightbulb

要自定义给定异常的异常报告,你还可以使用可报告异常

全局日志上下文

如果可用,Laravel 会自动将当前用户的 ID 作为上下文数据添加到每个异常的日志消息中。你可以通过覆盖应用程序的 App\Exceptions\Handler 类的 context 方法来定义自己的全局上下文数据。此信息将包含在应用程序写入的每个异常的日志消息中:

php
/**
 * 获取日志的默认上下文变量。
 *
 * @return array
 */
protected function context()
{
    return array_merge(parent::context(), [
        'foo' => 'bar',
    ]);
}

异常日志上下文

虽然为每个日志消息添加上下文可能很有用,但有时特定异常可能具有你希望在日志中包含的唯一上下文。通过在应用程序的自定义异常之一上定义 context 方法,你可以指定与该异常相关的任何数据,这些数据应添加到异常的日志条目中:

php
<?php

namespace App\Exceptions;

use Exception;

class InvalidOrderException extends Exception
{
    // ...

    /**
     * 获取异常的上下文信息。
     *
     * @return array
     */
    public function context()
    {
        return ['order_id' => $this->orderId];
    }
}

report 辅助函数

有时你可能需要报告异常,但继续处理当前请求。report 辅助函数允许你通过异常处理程序快速报告异常,而不向用户呈现错误页面:

php
public function isValid($value)
{
    try {
        // 验证值...
    } catch (Throwable $e) {
        report($e);

        return false;
    }
}

异常日志级别

当消息写入应用程序的日志时,消息是以指定的日志级别写入的,该级别指示记录消息的严重性或重要性。

如上所述,即使你使用 reportable 方法注册自定义异常报告回调,Laravel 仍将使用应用程序的默认日志配置记录异常;然而,由于日志级别有时会影响消息记录的通道,你可能希望配置某些异常记录的日志级别。

为此,你可以在应用程序的异常处理程序的 $levels 属性中定义异常类型及其关联日志级别的数组:

php
use PDOException;
use Psr\Log\LogLevel;

/**
 * 具有相应自定义日志级别的异常类型列表。
 *
 * @var array<class-string<\Throwable>, \Psr\Log\LogLevel::*>
 */
protected $levels = [
    PDOException::class => LogLevel::CRITICAL,
];

按类型忽略异常

在构建应用程序时,有些类型的异常你只想忽略而不报告。应用程序的异常处理程序包含一个 $dontReport 属性,该属性初始化为空数组。你添加到此属性的任何类都不会被报告;然而,它们可能仍然具有自定义渲染逻辑:

php
use App\Exceptions\InvalidOrderException;

/**
 * 不报告的异常类型列表。
 *
 * @var array<int, class-string<\Throwable>>
 */
protected $dontReport = [
    InvalidOrderException::class,
];
lightbulb

在后台,Laravel 已经为你忽略了一些类型的错误,例如由 404 HTTP "未找到" 错误或由无效 CSRF 令牌生成的 419 HTTP 响应导致的异常。

渲染异常

默认情况下,Laravel 异常处理程序将为你将异常转换为 HTTP 响应。不过,你可以自由地为给定类型的异常注册自定义渲染闭包。你可以通过异常处理程序的 renderable 方法来实现这一点。

传递给 renderable 方法的闭包应返回 Illuminate\Http\Response 的实例,该实例可以通过 response 辅助函数生成。Laravel 将通过检查闭包的类型提示来推断闭包渲染的异常类型:

php
use App\Exceptions\InvalidOrderException;

/**
 * 为应用程序注册异常处理回调。
 *
 * @return void
 */
public function register()
{
    $this->renderable(function (InvalidOrderException $e, $request) {
        return response()->view('errors.invalid-order', [], 500);
    });
}

你还可以使用 renderable 方法覆盖内置 Laravel 或 Symfony 异常(如 NotFoundHttpException)的渲染行为。如果传递给 renderable 方法的闭包不返回值,将使用 Laravel 的默认异常渲染:

php
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

/**
 * 为应用程序注册异常处理回调。
 *
 * @return void
 */
public function register()
{
    $this->renderable(function (NotFoundHttpException $e, $request) {
        if ($request->is('api/*')) {
            return response()->json([
                'message' => '记录未找到。'
            ], 404);
        }
    });
}

可报告和可渲染的异常

你可以直接在自定义异常上定义 reportrender 方法,而不是在异常处理程序的 register 方法中进行类型检查。当这些方法存在时,框架将自动调用它们:

php
<?php

namespace App\Exceptions;

use Exception;

class InvalidOrderException extends Exception
{
    /**
     * 报告异常。
     *
     * @return bool|null
     */
    public function report()
    {
        //
    }

    /**
     * 将异常渲染为 HTTP 响应。
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function render($request)
    {
        return response(/* ... */);
    }
}

如果你的异常扩展了已经可渲染的异常,例如内置的 Laravel 或 Symfony 异常,你可以从异常的 render 方法返回 false 以渲染异常的默认 HTTP 响应:

php
/**
 * 将异常渲染为 HTTP 响应。
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Http\Response
 */
public function render($request)
{
    // 确定异常是否需要自定义渲染...

    return false;
}

如果你的异常包含仅在满足某些条件时才需要的自定义报告逻辑,你可能需要指示 Laravel 有时使用默认异常处理配置报告异常。为此,你可以从异常的 report 方法返回 false

php
/**
 * 报告异常。
 *
 * @return bool|null
 */
public function report()
{
    // 确定异常是否需要自定义报告...

    return false;
}
lightbulb

你可以在 report 方法中类型提示任何所需的依赖项,它们将由 Laravel 的服务容器自动注入到方法中。

HTTP 异常

某些异常描述来自服务器的 HTTP 错误代码。例如,这可能是一个 "页面未找到" 错误(404)、一个 "未授权错误"(401)或甚至是开发人员生成的 500 错误。为了从应用程序的任何地方生成这样的响应,你可以使用 abort 辅助函数:

php
abort(404);

自定义 HTTP 错误页面

Laravel 使得为各种 HTTP 状态代码显示自定义错误页面变得容易。例如,如果你希望自定义 404 HTTP 状态代码的错误页面,请创建一个 resources/views/errors/404.blade.php 视图模板。此视图将在应用程序生成的所有 404 错误上呈现。此目录中的视图应命名为与它们对应的 HTTP 状态代码相匹配。abort 函数引发的 Symfony\Component\HttpKernel\Exception\HttpException 实例将作为 $exception 变量传递给视图:

php
<h2>{{ $exception->getMessage() }}</h2>

你可以使用 vendor:publish Artisan 命令发布 Laravel 的默认错误页面模板。模板发布后,你可以根据需要自定义它们:

shell
php artisan vendor:publish --tag=laravel-errors

回退 HTTP 错误页面

你还可以为给定系列的 HTTP 状态代码定义一个 "回退" 错误页面。如果没有发生的特定 HTTP 状态代码的相应页面,将呈现此页面。为此,在应用程序的 resources/views/errors 目录中定义一个 4xx.blade.php 模板和一个 5xx.blade.php 模板。