您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關怎么在Laravel中實現異常處理,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。
class HandleExceptions { 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'); } } public function handleError($level, $message, $file = '', $line = 0, $context = []) { if (error_reporting() & $level) { throw new ErrorException($message, 0, $level, $file, $line); } } }
set_exception_handler([$this, 'handleException'])
將HandleExceptions的handleException方法注冊為程序的全局處理器方法:
public function handleException($e) { if (! $e instanceof Exception) { $e = new FatalThrowableError($e); } $this->getExceptionHandler()->report($e); if ($this->app->runningInConsole()) { $this->renderForConsole($e); } else { $this->renderHttpResponse($e); } } protected function getExceptionHandler() { return $this->app->make(ExceptionHandler::class); } // 渲染CLI請求的異常響應 protected function renderForConsole(Exception $e) { $this->getExceptionHandler()->renderForConsole(new ConsoleOutput, $e); } // 渲染HTTP請求的異常響應 protected function renderHttpResponse(Exception $e) { $this->getExceptionHandler()->render($this->app['request'], $e)->send(); }
在處理器里主要通過ExceptionHandler的report方法上報異常、這里是記錄異常到storage/laravel.log文件中,然后根據請求類型渲染異常的響應生成輸出給到客戶端。這里的ExceptionHandler就是\App\Exceptions\Handler類的實例,它是在項目最開始注冊到服務容器中的:
// bootstrap/app.php /* |-------------------------------------------------------------------------- | Create The Application |-------------------------------------------------------------------------- */ $app = new Illuminate\Foundation\Application( realpath(__DIR__.'/../') ); /* |-------------------------------------------------------------------------- | Bind Important Interfaces |-------------------------------------------------------------------------- */ ...... $app->singleton( Illuminate\Contracts\Debug\ExceptionHandler::class, App\Exceptions\Handler::class );
這里再順便說一下set_error_handler函數,它的作用是注冊錯誤處理器函數,因為在一些年代久遠的代碼或者類庫中大多是采用PHP那件函數trigger_error函數來拋出錯誤的,異常處理器只能處理Exception不能處理Error,所以為了能夠兼容老類庫通常都會使用set_error_handler注冊全局的錯誤處理器方法,在方法中捕獲到錯誤后將錯誤轉化成異常再重新拋出,這樣項目中所有的代碼沒有被正確執行時都能拋出異常實例了。
/** * Convert PHP errors to ErrorException instances. * * @param int $level * @param string $message * @param string $file * @param int $line * @param array $context * @return void * * @throws \ErrorException */ public function handleError($level, $message, $file = '', $line = 0, $context = []) { if (error_reporting() & $level) { throw new ErrorException($message, 0, $level, $file, $line); } }
常用的Laravel異常實例
Laravel中針對常見的程序異常情況拋出了相應的異常實例,這讓開發者能夠捕獲這些運行時異常并根據自己的需要來做后續處理(比如:在catch中調用另外一個補救方法、記錄異常到日志文件、發送報警郵件、短信)
在這里我列一些開發中常遇到異常,并說明他們是在什么情況下被拋出的,平時編碼中一定要注意在程序里捕獲這些異常做好異常處理才能讓程序更健壯。
Illuminate\Database\QueryException Laravel中執行SQL語句發生錯誤時會拋出此異常,它也是使用率最高的異常,用來捕獲SQL執行錯誤,比方執行Update語句時很多人喜歡判斷SQL執行后判斷被修改的行數來判斷UPDATE是否成功,但有的情景里執行的UPDATE語句并沒有修改記錄值,這種情況就沒法通過被修改函數來判斷UPDATE是否成功了,另外在事務執行中如果捕獲到QueryException 可以在catch代碼塊中回滾事務。
Illuminate\Database\Eloquent\ModelNotFoundException 通過模型的findOrFail和firstOrFail方法獲取單條記錄時如果沒有找到會拋出這個異常(find和first找不到數據時會返回NULL)。
Illuminate\Validation\ValidationException 請求未通過Laravel的FormValidator驗證時會拋出此異常。
Illuminate\Auth\Access\AuthorizationException 用戶請求未通過Laravel的策略(Policy)驗證時拋出此異常
Symfony\Component\Routing\Exception\MethodNotAllowedException 請求路由時HTTP Method不正確
Illuminate\Http\Exceptions\HttpResponseException Laravel的處理HTTP請求不成功時拋出此異常
擴展Laravel的異常處理器
上面說了Laravel把\App\Exceptions\Handler 注冊成功了全局的異常處理器,代碼中沒有被catch到的異常,最后都會被\App\Exceptions\Handler捕獲到,處理器先上報異常記錄到日志文件里然后渲染異常響應再發送響應給客戶端。但是自帶的異常處理器的方法并不好用,很多時候我們想把異常上報到郵件或者是錯誤日志系統中,下面的例子是將異常上報到Sentry系統中,Sentry是一個錯誤收集服務非常好用:
public function report(Exception $exception) { if (app()->bound('sentry') && $this->shouldReport($exception)) { app('sentry')->captureException($exception); } parent::report($exception); }
還有默認的渲染方法在表單驗證時生成響應的JSON格式往往跟我們項目里統一的JOSN格式不一樣這就需要我們自定義渲染方法的行為。
public function render($request, Exception $exception) { //如果客戶端預期的是JSON響應, 在API請求未通過Validator驗證拋出ValidationException后 //這里來定制返回給客戶端的響應. if ($exception instanceof ValidationException && $request->expectsJson()) { return $this->error(422, $exception->errors()); } if ($exception instanceof ModelNotFoundException && $request->expectsJson()) { //捕獲路由模型綁定在數據庫中找不到模型后拋出的NotFoundHttpException return $this->error(424, 'resource not found.'); } if ($exception instanceof AuthorizationException) { //捕獲不符合權限時拋出的 AuthorizationException return $this->error(403, "Permission does not exist."); } return parent::render($request, $exception); }
自定義后,在請求未通過FormValidator驗證時會拋出ValidationException, 之后異常處理器捕獲到異常后會把錯誤提示格式化為項目統一的JSON響應格式并輸出給客戶端。這樣在我們的控制器中就完全省略了判斷表單驗證是否通過如果不通過再輸出錯誤響應給客戶端的邏輯了,將這部分邏輯交給了統一的異常處理器來執行能讓控制器方法瘦身不少。
使用自定義異常
這部分內容其實不是針對Laravel框架自定義異常,在任何項目中都可以應用我這里說的自定義異常。
我見過很多人在Repository或者Service類的方法中會根據不同錯誤返回不同的數組,里面包含著響應的錯誤碼和錯誤信息,這么做當然是可以滿足開發需求的,但是并不能記錄發生異常時的應用的運行時上下文,發生錯誤時沒辦法記錄到上下文信息就非常不利于開發者進行問題定位。
下面的是一個自定義的異常類
namespace App\Exceptions\; use RuntimeException; use Throwable; class UserManageException extends RuntimeException { /** * The primitive arguments that triggered this exception * * @var array */ public $primitives; /** * QueueManageException constructor. * @param array $primitives * @param string $message * @param int $code * @param Throwable|null $previous */ public function __construct(array $primitives, $message = "", $code = 0, Throwable $previous = null) { parent::__construct($message, $code, $previous); $this->primitives = $primitives; } /** * get the primitive arguments that triggered this exception */ public function getPrimitives() { return $this->primitives; } }
定義完異常類我們就能在代碼邏輯中拋出異常實例了
class UserRepository { public function updateUserFavorites(User $user, $favoriteData) { ...... if (!$executionOne) { throw new UserManageException(func_get_args(), 'Update user favorites error', '501'); } ...... if (!$executionTwo) { throw new UserManageException(func_get_args(), 'Another Error', '502'); } return true; } } class UserController extends ... { public function updateFavorites(User $user, Request $request) { ....... $favoriteData = $request->input('favorites'); try { $this->userRepo->updateUserFavorites($user, $favoritesData); } catch (UserManageException $ex) { ....... } } }
除了上面Repository列出的情況更多的時候我們是在捕獲到上面列舉的通用異常后在catch代碼塊中拋出與業務相關的更細化的異常實例方便開發者定位問題,我們將上面的updateUserFavorites 按照這種策略修改一下
public function updateUserFavorites(User $user, $favoriteData) { try { // database execution // database execution } catch (QueryException $queryException) { throw new UserManageException(func_get_args(), 'Error Message', '501' , $queryException); } return true; }
在上面定義UserMangeException類的時候第四個參數$previous是一個實現了Throwable接口類實例,在這種情景下我們因為捕獲到了QueryException的異常實例而拋出了UserManagerException的實例,然后通過這個參數將QueryException實例傳遞給PHP異常的堆棧,這提供給我們回溯整個異常的能力來獲取更多上下文信息,而不是僅僅只是當前拋出的異常實例的上下文信息, 在錯誤收集系統可以使用類似下面的代碼來獲取所有異常的信息。
while($e instanceof \Exception) { echo $e->getMessage(); $e = $e->getPrevious(); }
看完上述內容,你們對怎么在Laravel中實現異常處理有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。