异常处理篇之底层源码剖析

前面我们介绍 Laravel 中 HTTP 请求生命周期和中间件底层处理逻辑的时候,都涉及到了 vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php 这个文件,对应的 Illuminate\Foundation\Http\Kernel 类是我们 Laravel 应用 HTTP Kernel 类 App\Http\Kernel 类的基类,该类中有一个用于处理 HTTP 请求的 handle 方法,这段代码也是应用核心入口处理逻辑所在:

public function handle($request)
{
    try {
        $request->enableHttpMethodParameterOverride();

        $response = $this->sendRequestThroughRouter($request);
    } catch (Exception $e) {
        $this->reportException($e);

        $response = $this->renderException($request, $e);
    } catch (Throwable $e) {
        $this->reportException($e = new FatalThrowableError($e));

        $response = $this->renderException($request, $e);
    }

    $this->app['events']->dispatch(
        new Events\RequestHandled($request, $response)
    );

    return $response;
}

在将用户请求实例传入 sendRequestThroughRouter 方法进行处理并返回响应的整个过程中,都有外层包裹的 try...catch 代码块对处理过程中的任何异常进行捕获和处理:

$this->reportException($e);

$response = $this->renderException($request, $e);

正如你所看到的,请求处理过程中的所有未处理异常都会被捕获并按照以下两步进行处理:

  • 根据应用配置向不同的渠道报告异常;
  • 将异常转化为可渲染的格式并以响应的方式返回给终端用户。

以上两个步骤底层执行逻辑分别对应异常处理器的 report 方法render 方法

Laravel 默认的异常处理器位于 app/Exceptions/Handler.php,对应的处理器类继承自基类 Illuminate\Foundation\Exceptions\Handler,如果你需要对异常处理逻辑进行自定义的话,可以在这里覆盖父类的 report 方法和 render 方法实现,关于异常报告和渲染的具体细节,我们放到下一篇单独讲,这里我们先对 Laravel 异常处理的整体思路有一个大致的了解。

熟悉 PHP 异常处理逻辑的同学可能知道,PHP 提供了设置错误报告级别函数 error_reporting 以及默认的异常处理器函数 set_exception_handler,不太熟悉的同学可以先参考这篇教程。Laravel 也是遵循这一思路来实现异常处理的,对应的初始化逻辑位于 Illuminate\Foundation\Bootstrap\HandleExceptions 里面的 bootstrap 方法中:

public function bootstrap(Application $app)
{
    $this->app = $app;

    error_reporting(-1);

    set_error_handler([$this, 'handleError']);

    set_exception_handler([$this, 'handleException']);

    register_shutdown_function([$this, 'handleShutdown']);

    if (! $app->environment('testing')) {
        ini_set('display_errors', 'Off');
    }
}

Laravel 默认设置的错误报告级别是 -1,即尽可能显示所有 PHP 错误,包括将来 PHP 可能加入的新的错误级别和常量,默认的异常错误处理器是当前实例的 handleError 方法,默认的异常处理器是当前实例的 handleException 方法:

public function handleException($e)
{
    if (! $e instanceof Exception) {
        $e = new FatalThrowableError($e);
    }

    try {
        $this->getExceptionHandler()->report($e);
    } catch (Exception $e) {
        //
    }

    if ($this->app->runningInConsole()) {
        $this->renderForConsole($e);
    } else {
        $this->renderHttpResponse($e);
    }
}

仍然是首先调用 App\Exceptions\Handlerreport 方法报告异常,然后根据控制台应用还是 Web 应用分别调用对应的渲染方法渲染异常。

以上 HandleExceptions 实例的 bootstrap 方法会在调用 sendRequestThroughRouter 方法时在应用启动期间调用:

public function bootstrap()
{
    if (! $this->app->hasBeenBootstrapped()) {
        $this->app->bootstrapWith($this->bootstrappers());
    }
}

这样,在处理中间件和请求业务逻辑时就可以通过注册的异常处理器来处理系统异常了。当然,如果你对 Laravel 提供的默认异常处理器处理逻辑不太满意,或者不符合你的需求,可以通过在 bootstrap/app.php 中指定绑定自定义的、实现了 Illuminate\Contracts\Debug\ExceptionHandler 接口的异常处理器到容器来完成自定义系统默认异常处理器的设置工作:

$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);

当前默认的异常处理器是我们前面提到的 App\Exceptions\Handler 类。有关 Laravel 异常处理器异常报告和渲染的更多细节和自定义实现,学院君接下来分两篇的篇幅分别探讨。

另外,Laravel 控制台应用异常处理逻辑和上面介绍的 HTTP 应用是一样的,你可以自行从入口文件 app/Console/Kernel.phphandle 方法开始去分析。

上一篇: Laravel 路由匹配和执行底层源码剖析

下一篇: 异常处理篇之异常信息报告、渲染及自定义处理