Laravel 爲用戶提供了一個基礎的全局異常攔截處理器App\Exceptions\Hander
。若是沒有全局的異常錯誤攔截器,那咱們在每一個可能發生錯誤異常的業務邏輯分支中,都要使用 try ... catch,而後將執行結果返回 Controller層,再由其根據結果來構造相應的 Response,那代碼冗餘的會至關能夠。php
全局異常錯誤處理,是每一個框架都應該具有的,此次咱們就經過簡析 Laravel 的源碼和執行流程,來看一下此模式是如何被運用的。laravel
laravel/laravel
腳手架中有一個預約義好的異常處理器:web
app/Exceptions/Handler.php
json
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
bootstrap
// 綁定 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
app
// 使用 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::class
的 handle
方法對請求作一次處理,若是沒有異常則分發路由,若是有異常則調用 reportException
和 renderException
方法記錄
&渲染
異常。框架
具體處理者則是咱們在 bootstrap/app.php
中註冊綁定的異常處理服務 Illuminate\Contracts\Debug\ExceptionHandler::class
的 report
& render
,具體的服務即綁定的 App\Exceptions\Handler::class
。ide
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 有提供 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); }