Laravel 全局异常错误处理源码解析及使用场景

栏目: PHP · 发布时间: 5年前

内容简介:如果没有全局的异常错误拦截器,那我们在每个可能发生错误异常的业务逻辑分支中,都要使用 try ... catch,然后将执行结果返回 Controller层,再由其根据结果来构造相应的 Response,那代码冗余的会相当可以。全局异常错误处理,是每个框架都应该具备的,这次我们就通过简析 Laravel 的源码和执行流程,来看一下此模式是如何被运用的。

如果没有全局的异常错误拦截器,那我们在每个可能发生错误异常的业务逻辑分支中,都要使用 try ... catch,然后将执行结果返回 Controller层,再由其根据结果来构造相应的 Response,那代码冗余的会相当可以。

全局异常错误处理,是每个框架都应该具备的,这次我们就通过简析 Laravel 的源码和执行流程,来看一下此模式是如何被运用的。

源码解析

laravel/laravel 脚手架中有一个预定义好的异常处理器:

app/Exceptions/Handler.php

namespace App\Exceptions;

use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;

class Handler extends ExceptionHandler
{
    // 不被处理的异常错误
    protected $dontReport = [];

    // 认证异常时不被flashed的数据
    protected $dontFlash = [
        'password',
        'password_confirmation',
    ];
    // 上报异常至错误driver,如日志文件(storage/logs/laravel.log),第三方日志存储分析平台
    public function report(Exception $exception)
    {
        parent::report($exception);
    }
    // 将异常信息响应给客户端
    public function render($request, Exception $exception)
    {
        return parent::render($request, $exception);
    }
}

当 Laravel 处理一次请求时,在启动文件中注册了以下服务:

bootstrap/app.php

// 绑定 http 服务提供者
$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);
// 绑定 cli 服务提供者
$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);
// 这里将异常处理器的服务提供者绑定到了 `App\Exceptions\Handler::class`
$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);

而后进入请求捕获,处理阶段:

public/index.php

// 使用 http 服务处理请求
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
// http 服务处理捕获的请求 $requeset
$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

Illuminate\Contracts\Http\Kernel::class 具体提供者是 App\Http\Kernel::class 继承至 Illuminate\Foundation\Http\Kernel::class ,我们去其中看 http 服务 的 handle 方法是如何处理请求的。

请求处理阶段:

Illuminate\Foundation\Http\Kernel::classhandle 方法对请求做一次处理,如果没有异常则分发路由,如果有异常则调用 reportExceptionrenderException 方法 记录 & 渲染 异常。

具体处理者则是我们在 bootstrap/app.php 中注册绑定的 异常处理服务 Illuminate\Contracts\Debug\ExceptionHandler::classreport & render ,具体的服务即绑定的 App\Exceptions\Handler::class

public function handle($request)
{
    try {
        // 没有异常 则进入路由分发
        $request->enableHttpMethodParameterOverride();
        $response = $this->sendRequestThroughRouter($request);
    } catch (Exception $e) {
        // 捕获异常 则 report & render
        $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;
}

//Report the exception to the exception handler.
protected function reportException(Exception $e)
{
    // 服务`Illuminate\Contracts\Debug\ExceptionHandler::class` 的 report 方法
    $this->app[ExceptionHandler::class]->report($e);
}
//Render the exception to a response.
protected function renderException($request, Exception $e)
{
    // 服务`Illuminate\Contracts\Debug\ExceptionHandler::class` 的 render 方法
    return $this->app[ExceptionHandler::class]->render($request, $e);
}

handler 方法作为请求处理的入口,后续的路由分发,用户业务调用(controller, model)等执行的上下文依然在此方法中,故异常也能在这一层被捕获。

然后我们就可以在业务中通过 throw new CustomException($code, "错误异常描述"); 的方式将控制流程转交给全局异常处理器,由其解析异常并构建响应实体给客户端,这一模式在 Api服务 的开发中是效率极高的。

laravel 的依赖中有 symfony 这个超级棒的组件库,symfony 为我们提供了详细的 Http 异常库,我们可以直接借用这些异常类(当然也可以自定义)

Laravel 全局异常错误处理源码解析及使用场景

laravel 有提供 abort 助手函数来实现创建一个异常错误,但主要面向 web 网站(因为laravel主要就是用来开发后台的嘛)的,对 Api

不太友好,而且看源码发现只顾及了 404 这货。

/**
 * abort(401, "你需要登录")
 * abort(403, "你登录了也白搭")
 * abort(404, "页面找不到了")
 * Throw an HttpException with the given data.
 *
 * @param  int     $code
 * @param  string  $message
 * @param  array   $headers
 * @return void
 *
 * @throws \Symfony\Component\HttpKernel\Exception\HttpException
 */
public function abort($code, $message = '', array $headers = [])
{
    if ($code == 404) {
        throw new NotFoundHttpException($message);
    }

    throw new HttpException($code, $message, null, $headers);
}

即只有 404 用了具体的异常类去抛出,其他的状态码都一股脑的归为 HttpException,这样就不太方便我们在全局异常处理器的 render 中根据 Exception 的具体类型来分而治之了,但 abort 也的确是为了方便你调用具体的错误页面的 resources/views/errors/{statusCode.blade.php} 的,需要对 Api 友好自己改写吧。

使用场景

// 业务代码 不满足直接抛出异常即可
if ("" = trim($username)) {
    throw new BadRequestHttpException("用户名必须");
}
// 全局处理器
public function render($request, Exception $exception)
{
    if ($exception instanceof BadRequestHttpException) {
        return response()->json([
            "err" => 400,
            "msg" => $exception->getMessage()
        ]);
    }
    
    if ($exception instanceof AccessDeniedHttpException) {
        return response()->json([
            "err" => 403,
            "msg" => "unauthorized"
        ]);
    }
    
    if ($exception instanceof NotFoundHttpException) {
        return response()->json([
            "err" => 403,
            "msg" => "forbidden"
        ]);
    }
    
    if ($exception instanceof NotFoundHttpException) {
        return response()->json([
            "err" => 404,
            "msg" => "not found"
        ]);
    }
    
    if ($exception instanceof MethodNotAllowedHttpException) {
        return response()->json([
            "err" => 405,
            "msg" => "method not allowed"
        ]);
    }
    
    if ($exception instanceof MethodNotAllowedHttpException) {
        return response()->json([
            "err" => 406,
            "msg" => "你想要的数据类型我特么给不了啊"
        ]);
    }
    
    if ($exception instanceof TooManyRequestsHttpException) {
        return response()->json([
            "err" => 429,
            "msg" => "to many request"
        ]);
    }
    
    return parent::render($request, $exception);
}

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Java Message Service API Tutorial and Reference

Java Message Service API Tutorial and Reference

Hapner, Mark; Burridge, Rich; Sharma, Rahul / 2002-2 / $ 56.49

Java Message Service (JMS) represents a powerful solution for communicating between Java enterprise applications, software components, and legacy systems. In this authoritative tutorial and comprehens......一起来看看 《Java Message Service API Tutorial and Reference》 这本书的介绍吧!

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具