如何重寫Laravel異常處理類詳解
現(xiàn)在開發(fā)前后端分離變得越來越流行了,后端只提供接口返回json格式的數(shù)據(jù),即使是錯誤信息也要以json格式來返回,然而目前無論是Laravel框架還是ThinkPHP框架,都只提供了返回json數(shù)據(jù)的方法,對異常的處理并不是以json格式來返回給我們,所以這里就需要我們自己來改寫。
首先我們在app/Exceptions目錄新建一個ExceptionHandler.php繼承自Handler.php
namespace App\Exceptions; class ExceptionHandler extends Handler { }
然后我們在bootstrap/app.php中,使用我們自定義的異常處理類ExceptionHandler替換掉默認(rèn)的Handler類
//改為我們自定義的ExceptionHandler類 $app->singleton( Illuminate\Contracts\Debug\ExceptionHandler::class, App\Exceptions\ExceptionHandler::class );
接下來我們就開始重寫渲染方法
在render方法里,我們根據(jù).env文件中的APP_DEBUG來判斷,如果是調(diào)試模式,我們還是按照默認(rèn)方式來渲染錯誤,如果是非調(diào)試模式,我們就返回JSON格式的信息
namespace App\Exceptions; use Exception; class ExceptionHandler extends Handler { public function render($request, Exception $exception) { if (env('APP_DEBUG')) { return parent::render($request, $exception); } return response()->json([ 'code' => $exception->getCode(), 'msg' => $exception->getMessage() ]); } }
這樣我們就可以根據(jù)APP_DEBUG的值設(shè)置是否返回JSON格式的數(shù)據(jù)了,現(xiàn)在我們把.env的APP_DEBUG的值設(shè)為false來測試一下,然后我們故意把代碼寫錯,通過postman或?yàn)g覽器來訪問接口
Route::get('/', function () { //這是一段缺少了分號的代碼,會報(bào)異常 echo 'Hello World!' });
在APP_DEBUG=true的情況下還仍然是默認(rèn)渲染,方便我們查找錯誤排錯
異常類默認(rèn)會把異常以日志的形式記錄在storage/logs目錄下,并且以laravel-日期(YYYY-MM-DD)命名的形式,.log為后綴保存錯誤日志
我們打開這個日志文件查看記錄的錯誤信息,我們可以發(fā)現(xiàn)錯誤信息記錄的非常詳細(xì),除了錯誤說明之外,還記錄了調(diào)用棧,如下圖所示
基本上紅框里的信息就夠我們排錯了,不需要像現(xiàn)在這樣記錄的這么詳細(xì),所以要想不記錄調(diào)用棧,我們可以重寫report方法
首先我們看一下框架的report方法,代碼在(src/Illuminate/Foundation/Exceptions/Handler.php),我用紅框框起來的代碼就是調(diào)用棧信息,我們在重寫這個方法時只需要完全拷貝這個方法里的所有代碼到我們自定義的report方法里,然后把紅框里的代碼去掉即可
我們在我們自定義的異常處理類ExceptionHandler.php中重寫report方法
public function report(Exception $exception) { if ($this->shouldntReport($exception)) { return; } if (Reflector::isCallable($reportCallable = [$exception, 'report'])) { return $this->container->call($reportCallable); } try { $logger = $this->container->make(LoggerInterface::class); } catch (Exception $ex) { throw $exception; } $logger->error( $exception->getMessage() ); }
然后我們再重新請求一下接口再去查看錯誤日志的記錄,可以發(fā)現(xiàn)確實(shí)沒有記錄調(diào)用棧信息了,但是下面的信息還是不夠,我們沒法根據(jù)下面的信息判斷錯誤發(fā)生在哪一個文件和哪一行,如果能在記錄錯誤信息的時候同時記錄發(fā)生錯誤的文件和行就更好了,所以借著修改report方法
public function report(Exception $exception) { if ($this->shouldntReport($exception)) { return; } if (Reflector::isCallable($reportCallable = [$exception, 'report'])) { return $this->container->call($reportCallable); } try { $logger = $this->container->make(LoggerInterface::class); } catch (Exception $ex) { throw $exception; } $logger->error( $exception->getMessage()." at ".$exception->getFile().":".$exception->getLine() ); }
在代碼里我通過exception的getFile()、getLine()方法加上了文件和行數(shù),保存代碼再次訪問接口,查看錯誤日志文件我們可以看到發(fā)生錯誤的文件和行數(shù)已經(jīng)記錄下來了,有了這些信息基本我們就可以找到錯誤
截止到這里實(shí)現(xiàn)最初的需求我們的ExceptionHandler.php只需要有這些代碼
namespace App\Exceptions; use Exception; use Illuminate\Support\Reflector; use Psr\Log\LoggerInterface; class ExceptionHandler extends Handler { public function render($request, Exception $exception) { if (env('APP_DEBUG')) { return parent::render($request, $exception); } return response()->json([ 'code' => $exception->getCode(), 'msg' => $exception->getMessage() ]); } public function report(Exception $exception) { if ($this->shouldntReport($exception)) { return; } if (Reflector::isCallable($reportCallable = [$exception, 'report'])) { return $this->container->call($reportCallable); } try { $logger = $this->container->make(LoggerInterface::class); } catch (Exception $ex) { throw $exception; } $logger->error( $exception->getMessage()." at ".$exception->getFile().":".$exception->getLine() ); } }
然后還不夠,我們發(fā)現(xiàn)剛剛我們把服務(wù)器端的錯誤信息以JSON格式返回給客戶端了,這是不允許的,我們應(yīng)該只把一些客戶端錯誤返回給客戶端,比如密碼不足六位、身份證不合法諸如此類,而服務(wù)端出現(xiàn)錯誤時我們只返回給客戶端一個模糊的信息即可,比如“服務(wù)器錯誤”,把真實(shí)的服務(wù)器錯誤信息記錄在日志里面方便開發(fā)人員排查錯誤
所以我們需要定義一個客戶端異常專門用戶返回客戶端錯誤,使用如下命令在app/Exceptions目錄下生成一個ClientException.php文件
php artisan make:exception ClientException
修改為構(gòu)造方法為如下代碼
namespace App\Exceptions; use Exception; class ClientException extends Exception { public function __construct($code, $msg) { parent::__construct($msg, $code); } }
接著我們繼續(xù)修改ExceptionHandler.php
namespace App\Exceptions; use Exception; use Illuminate\Support\Reflector; use Psr\Log\LoggerInterface; class ExceptionHandler extends Handler { /** * @var int 錯誤碼 */ protected $code; /** * @var string 錯誤信息 */ protected $message; protected $dontReport = [ ClientException::class ]; public function render($request, Exception $exception) { if ($exception instanceof ClientException) { $this->code = $exception->getCode(); $this->message = $exception->getMessage(); } else { if (env('APP_DEBUG')) { return parent::render($request, $exception); } $this->code = 500; $this->message = '服務(wù)器錯誤'; } return response()->json([ 'code' => $this->code, 'msg' => $this->message ]); } public function report(Exception $exception) { if ($this->shouldntReport($exception)) { return; } if (Reflector::isCallable($reportCallable = [$exception, 'report'])) { return $this->container->call($reportCallable); } try { $logger = $this->container->make(LoggerInterface::class); } catch (Exception $ex) { throw $exception; } $logger->error( $exception->getMessage()." at ".$exception->getFile().":".$exception->getLine() ); } }
對于上面的修改做一下說明,laravel的$dontReport屬性的異常類都不會被上報(bào),因?yàn)榭蛻舳隋e誤信息我們不需要記錄,所以將其添加到$dontReport屬性里,并且在render方法里把異常大概分為了兩大類,一大類就是客戶端異常,另一大類就是服務(wù)器異常,我們把服務(wù)器異常統(tǒng)一code為500,錯誤信息為服務(wù)器錯誤,將真實(shí)的錯誤信息記錄在了錯誤日志里,避免把服務(wù)器信息暴露給了客戶端。
現(xiàn)在我們來測試我們重寫異常的結(jié)果
假如我們想返回客戶端異常,比如沒有權(quán)限,這類客戶端異常在錯誤日志里都不會產(chǎn)生記錄,我們本身也不需要記錄
Route::get('/', function () { throw new \App\Exceptions\ClientException(403, '你沒有權(quán)限'); });
對于服務(wù)器端的錯誤,如少些了分號,客戶端就只會知道服務(wù)器的某個接口出了問題,但是不清楚具體問題是什么
Route::get('/', function () { echo 'Hello World!' });
但是真實(shí)的錯誤信息會記錄在錯誤日志里,我們?nèi)耘f可以通過錯誤日志來修改我們服務(wù)端的錯誤
我們還可以在render方法中加入告警代碼,如果是服務(wù)端錯誤就給管理員發(fā)送郵件。
至此,我們的重寫Laravel異常處理類就算完成啦,希望對正在準(zhǔn)備使用Laravel做前后端分離項(xiàng)目的你有所幫助。
到此這篇關(guān)于如何重寫Laravel異常處理類的文章就介紹到這了,更多相關(guān)重寫Laravel異常處理類內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
php操縱mysqli數(shù)據(jù)庫的實(shí)現(xiàn)方法
下面小編就為大家?guī)硪黄猵hp操縱mysqli數(shù)據(jù)庫的實(shí)現(xiàn)方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-09-09php加水印的代碼(支持半透明透明打水印,支持png透明背景)
一個簡單的打水印代碼(圖片水?。С炙⊥该鞫仍O(shè)置,也支持png透明背景格式圖片打水印2013-01-01PHP使用DOMDocument類生成HTML實(shí)例(包含常見標(biāo)簽元素)
這篇文章主要介紹了PHP使用DOMDocument類生成HTML實(shí)例,包含常見標(biāo)簽元素,如表單、表格、CSS樣式等,最后寫了一個比較完整的例子,給需要的朋友參考下2014-06-06Laravel使用PHPQRCODE實(shí)現(xiàn)生成帶有LOGO的二維碼圖片功能示例
這篇文章主要介紹了Laravel使用PHPQRCODE實(shí)現(xiàn)生成帶有LOGO的二維碼圖片功能,涉及php引入PHPQRCODE類生成二維碼圖片的相關(guān)調(diào)用與設(shè)置操作技巧,需要的朋友可以參考下2017-07-07PHP內(nèi)置函數(shù)生成隨機(jī)數(shù)實(shí)例
在本篇文章里小編給大家分享了關(guān)于PHP內(nèi)置函數(shù)生成隨機(jī)數(shù)實(shí)例內(nèi)容,對此有興趣的朋友們可以學(xué)習(xí)下。2019-01-01PHP實(shí)現(xiàn)的英文名字全拼隨機(jī)排號腳本
這篇文章主要介紹了PHP實(shí)現(xiàn)的英文名字全拼隨機(jī)排號腳本,根據(jù)一個需求寫出的一個解決方案,需要的朋友可以參考下2014-07-07