在Laravel 5.4 內活用 Handler & Exception處理錯誤頁面(淺談)
Laravel 在預設上已經幫我們把Exception處理的很好了,相關的內容可以參考官方文件,這邊會先假設文章已經稍微看過,不針對裡面內容做細數。從相關文件我們可以知道,例外的處理一律都是在App\Exceptions\Handler
這隻檔案。對Exception
來說,處理錯誤頁面的部份在render
這個方法實作。
實作
我們可以先看到5.4版本的render 實作方式
public function render($request, Exception $exception)
{
return parent::render($request, $exception);
}
接著我們看看parent
(ExceptionHandler)內的方法
public function render($request, Exception $e)
{
$e = $this->prepareException($e);
if ($e instanceof HttpResponseException) {
return $e->getResponse();
} elseif ($e instanceof AuthenticationException) {
return $this->unauthenticated($request, $e);
} elseif ($e instanceof ValidationException) {
return $this->convertValidationExceptionToResponse($e, $request);
}
return $this->prepareResponse($request, $e);
}
首先我們可以看到這邊針對一些Laravel 獨有的 Exception
去做處理。若是其他的Exception
則會跑到$this->prepareResponse($request, $e);
這一段來執行。
再來我們看一下在prepareResponse
這一段程式碼。
protected function prepareResponse($request, Exception $e)
{
if ($this->isHttpException($e)) {
return $this->toIlluminateResponse($this->renderHttpException($e), $e);
} else {
return $this->toIlluminateResponse($this->convertExceptionToResponse($e), $e);
}
}
//...
protected function isHttpException(Exception $e)
{
return $e instanceof HttpException;
}
所以我們知道當Exception
是HttpException
時,我們可以執行到renderHttpException
這個方法。再來我們看看這個方法:
protected function renderHttpException(HttpException $e)
{
$status = $e->getStatusCode();
view()->replaceNamespace('errors', [
resource_path('views/errors'),
__DIR__.'/views',
]);
if (view()->exists("errors::{$status}")) {
return response()->view("errors::{$status}", ['exception' => $e], $status, $e->getHeaders());
} else {
return $this->convertExceptionToResponse($e);
}
}
所以我們可以知道執行到這個方法後便會去找出相對應的StatusCode
.blade去做顯示。
而若這些Exception
不是HttpException
時則會跑到convertExceptionToResponse
方法。
protected function convertExceptionToResponse(Exception $e)
{
$e = FlattenException::create($e);
$handler = new SymfonyExceptionHandler(config('app.debug', false));
return SymfonyResponse::create($handler->getHtml($e), $e->getStatusCode(), $e->getHeaders());
}
當執行到這段程式碼時,錯誤訊息會在APP_DEBUG = true
的時候做顯示。一般我們在正式環境時,會將APP_DEBUG = false
,此時只要是拋出Exception
,就會直接顯示Whoops, looks like something went wrong.
避免在正式環境暴露出相關的錯誤程式代碼。
但是在有些狀況下,反而會希望使用者看到某些特定的錯誤訊息方便我們去做排錯,而不是在狀況發生後再透過相關的時間/操作上去推敲log位置,例如是內部人員使用的系統。
但是我們又不希望直接噴出相關程式碼的錯誤,指希望拋出我們指定的訊息。
所以我們只要把程式碼導向renderHttpException
這個方法,就可以取得blade
的頁面做客製化的需求。
假設我們現在有一個CustomerException
//XXXService.php...
throw new CustomerException('SOMETHING YOU SHOULD KNOW');
//
在拋出時透過Handler
的render
到prepareResponse
。所以我們要在App\Exceptions\Handler
內新增prepareResponse
去覆寫:
//App\Exceptions\Handler.php
protected function prepareResponse($request, Exception $e)
{
if (!config('app.debug') and !$this->isHttpException($e)) {
$e = new HttpException(500, $e->getMessage(), $e);
}
return parent::prepareResponse($request, $e);
}
如此一來,只要當.env
裡面的APP_DEBUG = false
,錯誤訊息都會被捕捉,且會以errors.500.blade.php
這個畫面顯示(當然你要記得新增這個檔案出來)。相關的應用如400
系列的錯誤訊息也可用類似方法撰寫。
後記
因為筆者手上還有Laravel 5.4以下的專案(受限於php 5.6),所以這部份需要自己手刻,但其實目前最新的版本Laravel 5.7已經針對這個項目做過優化,若您是使用Laravel5.7可當做走馬看花看看就好。