錯誤處理

簡介

在開始新的 Laravel 專案時,Laravel 已經先幫你設定好錯誤與 Exception Handler(例外處理常式)。在你的專案中擲回(Throw)的所有 Exception 都會由 App\Exceptions\Handler 負責紀錄 Log(日誌) 並轉譯給使用者。我們會在這篇說明文件中深入瞭解這個類別。

設定

config/app.php 設定檔中的 debug 選項用來判斷錯誤在實際顯示給使用者時要包含多少資訊。預設情況下,這個選項被設為依照 APP_DEBUG 環境變數值,該環境變數儲存於 .env 檔內。

在本機上開發時,應將 APP_DEBUG 環境變數設為 true在正式環境上,這個值一定要是 false。若在正式環境上將該值設為 true,則會有將機敏設定值暴露給應用程式終端使用者的風險。

Exception Handler

回報 Exception

所有的 Exception 都由 App\Exceptions\Handler 類別負責處理。該類別中包含了一個 register 方法,可用來註冊所有自訂的 Exception 回報與轉譯回呼。我們來詳細看看其中各個概念。「回報 Exception」就是指將例外紀錄到 Log(日誌),或是傳送到如 FlareBugsnagSentry⋯⋯等外部服務。預設情況下,Laravel 會使用專案的Log 設定來紀錄 Exception。不過,我們也可以隨意調整 Exception 要如何紀錄。

舉例來說,如果想以不同的方式回報不同類型的 Exception,可以使用 reportable 方法來註冊一個閉包。這個閉包會在給定類型的 Exception 需要回報時被呼叫。Laravel 會自動使用該閉包的型別提示(Type-Hint)來推導該閉包接受什麼類型的 Exception:

1use App\Exceptions\InvalidOrderException;
2 
3/**
4 * Register the exception handling callbacks for the application.
5 *
6 * @return void
7 */
8public function register()
9{
10 $this->reportable(function (InvalidOrderException $e) {
11 //
12 });
13}
1use App\Exceptions\InvalidOrderException;
2 
3/**
4 * Register the exception handling callbacks for the application.
5 *
6 * @return void
7 */
8public function register()
9{
10 $this->reportable(function (InvalidOrderException $e) {
11 //
12 });
13}

使用 reportable 方法定義自訂的 Exception 回報回呼時,Laravel 還是會使用專案的預設 Log 設定來紀錄例外。若想停止將 Exception 傳播(Propagation)給預設的日誌 Stack,請在定義回報回呼時使用 stop 方法,或是在該回呼內回傳 false

1$this->reportable(function (InvalidOrderException $e) {
2 //
3})->stop();
4 
5$this->reportable(function (InvalidOrderException $e) {
6 return false;
7});
1$this->reportable(function (InvalidOrderException $e) {
2 //
3})->stop();
4 
5$this->reportable(function (InvalidOrderException $e) {
6 return false;
7});
lightbulb

若要為給定的例外自訂 Exception 回報,可使用 Reportable 的例外

全域 Log 上下文

當有目前使用者 ID 的時候,Laravel 會自動將使用者 ID 加到所有的例外 Log 訊息,以作為上下文(Context)資料。可以複寫專案中 App\Exceptions\Handler 類別的 context 來定義你自己的全域上下文資料。這個資料會被包含在專案輸出的所有例外 Log 訊息中:

1/**
2 * Get the default context variables for logging.
3 *
4 * @return array
5 */
6protected function context()
7{
8 return array_merge(parent::context(), [
9 'foo' => 'bar',
10 ]);
11}
1/**
2 * Get the default context variables for logging.
3 *
4 * @return array
5 */
6protected function context()
7{
8 return array_merge(parent::context(), [
9 'foo' => 'bar',
10 ]);
11}

Exception Log 的上下文

為所有 Log 訊息都新增額外的上下文可能會很實用,但有些特別的 Exception 可能會有一些獨特的上下文,而我們也想將這類上下文加到 Log 上。只要在我們的其中一個自訂 Exception 中定義一個 context 方法,就可以指定與該 Exception 相關的資料,將這些資料包含到例外的 Log 中:

1<?php
2 
3namespace App\Exceptions;
4 
5use Exception;
6 
7class InvalidOrderException extends Exception
8{
9 // ...
10 
11 /**
12 * Get the exception's context information.
13 *
14 * @return array
15 */
16 public function context()
17 {
18 return ['order_id' => $this->orderId];
19 }
20}
1<?php
2 
3namespace App\Exceptions;
4 
5use Exception;
6 
7class InvalidOrderException extends Exception
8{
9 // ...
10 
11 /**
12 * Get the exception's context information.
13 *
14 * @return array
15 */
16 public function context()
17 {
18 return ['order_id' => $this->orderId];
19 }
20}

report 輔助函式

有時候,我們可能會想回報某個 Exception,但又想繼續執行目前的 Request。使用 report 輔助函式,就能輕鬆地在不轉譯出錯誤頁面的情況下使用 Exception Handler 來回報這個 Exception:

1public function isValid($value)
2{
3 try {
4 // Validate the value...
5 } catch (Throwable $e) {
6 report($e);
7 
8 return false;
9 }
10}
1public function isValid($value)
2{
3 try {
4 // Validate the value...
5 } catch (Throwable $e) {
6 report($e);
7 
8 return false;
9 }
10}

以類型忽略例外

在製作專案時,我們可能會想忽略一些類型的 Exception,讓這些 Exception 永遠不要被回報。在專案中的 Exception Handler 中包含了一個 $dontReport 屬性,該屬性被初始化為空陣列。只要將任何類別加到該屬性中,這些類別就不會被回報。不過,還是可以為這些類別定義自訂的轉譯邏輯:

1use App\Exceptions\InvalidOrderException;
2 
3/**
4 * A list of the exception types that should not be reported.
5 *
6 * @var array
7 */
8protected $dontReport = [
9 InvalidOrderException::class,
10];
1use App\Exceptions\InvalidOrderException;
2 
3/**
4 * A list of the exception types that should not be reported.
5 *
6 * @var array
7 */
8protected $dontReport = [
9 InvalidOrderException::class,
10];
lightbulb

Laravel 已經預先幫你在內部忽略了一些類型的錯誤。如:產生 404 HTTP「找不到」錯誤的 Exception、還有因為無效 CSRF Token 產生的 419 HTTP Response。

轉譯 Exception

預設情況下,Laravel 的 Exception Handler 會幫你把 Exception 轉成 HTTP Response。不過,我們也可以自由地為某個類型的 Exception 註冊自訂轉譯閉包(Rendering Closure)。只要使用 Exception Handler 的 renderable 方法,就註冊轉譯閉包。

傳給 renderable 方法的閉包應回傳一個 Illuminate\Http\Response 的實體。可以使用 response 輔助函式來產生該實體。Laravel 會依照該閉包的型別提示來判斷這個閉包能轉移哪種類型的 Exception:

1use App\Exceptions\InvalidOrderException;
2 
3/**
4 * Register the exception handling callbacks for the application.
5 *
6 * @return void
7 */
8public function register()
9{
10 $this->renderable(function (InvalidOrderException $e, $request) {
11 return response()->view('errors.invalid-order', [], 500);
12 });
13}
1use App\Exceptions\InvalidOrderException;
2 
3/**
4 * Register the exception handling callbacks for the application.
5 *
6 * @return void
7 */
8public function register()
9{
10 $this->renderable(function (InvalidOrderException $e, $request) {
11 return response()->view('errors.invalid-order', [], 500);
12 });
13}

也可以使用 renderable 方法來複寫 Laravel 或 Symfony 內建 Exception 的轉移行外。如:NotFoundHttpException。若傳給 renderable 方法的閉包未回傳任何值,則會使用 Laravel 的預設 Exception 轉譯:

1use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
2 
3/**
4 * Register the exception handling callbacks for the application.
5 *
6 * @return void
7 */
8public function register()
9{
10 $this->renderable(function (NotFoundHttpException $e, $request) {
11 if ($request->is('api/*')) {
12 return response()->json([
13 'message' => 'Record not found.'
14 ], 404);
15 }
16 });
17}
1use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
2 
3/**
4 * Register the exception handling callbacks for the application.
5 *
6 * @return void
7 */
8public function register()
9{
10 $this->renderable(function (NotFoundHttpException $e, $request) {
11 if ($request->is('api/*')) {
12 return response()->json([
13 'message' => 'Record not found.'
14 ], 404);
15 }
16 });
17}

可回報(Reportable)可轉譯(Renderable)的 Exception

除了在 Exception Handler 的 register 方法上設定 Exception 的類型外,我們還可以直接在我們的自訂 Exception 上定義 reportrender 方法。當這些方法存在時,Laravel 會自動呼叫這些方法:

1<?php
2 
3namespace App\Exceptions;
4 
5use Exception;
6 
7class InvalidOrderException extends Exception
8{
9 /**
10 * Report the exception.
11 *
12 * @return bool|null
13 */
14 public function report()
15 {
16 //
17 }
18 
19 /**
20 * Render the exception into an HTTP response.
21 *
22 * @param \Illuminate\Http\Request $request
23 * @return \Illuminate\Http\Response
24 */
25 public function render($request)
26 {
27 return response(...);
28 }
29}
1<?php
2 
3namespace App\Exceptions;
4 
5use Exception;
6 
7class InvalidOrderException extends Exception
8{
9 /**
10 * Report the exception.
11 *
12 * @return bool|null
13 */
14 public function report()
15 {
16 //
17 }
18 
19 /**
20 * Render the exception into an HTTP response.
21 *
22 * @param \Illuminate\Http\Request $request
23 * @return \Illuminate\Http\Response
24 */
25 public function render($request)
26 {
27 return response(...);
28 }
29}

若你的 Exception 繼承的 Exception 已經是可轉譯的(Renderable)了 (如 Laravel 或 Symfony 內建的 Exception),可在該 Exception 的 render 方法內回傳 false 來轉譯某個 Exception 的預設 HTTP Response:

1/**
2 * Render the exception into an HTTP response.
3 *
4 * @param \Illuminate\Http\Request $request
5 * @return \Illuminate\Http\Response
6 */
7public function render($request)
8{
9 // Determine if the exception needs custom rendering...
10 
11 return false;
12}
1/**
2 * Render the exception into an HTTP response.
3 *
4 * @param \Illuminate\Http\Request $request
5 * @return \Illuminate\Http\Response
6 */
7public function render($request)
8{
9 // Determine if the exception needs custom rendering...
10 
11 return false;
12}

若你的 Exception 中包含了只有在特定情況下才會使用的自訂回報邏輯,則可讓 Laravel 在某些時候使用預設的 Exception 處理設定來回報這個 Exception。若要這麼做,請在該 Exception 的 report 方法內回傳 false

1/**
2 * Report the exception.
3 *
4 * @return bool|null
5 */
6public function report()
7{
8 // Determine if the exception needs custom reporting...
9 
10 return false;
11}
1/**
2 * Report the exception.
3 *
4 * @return bool|null
5 */
6public function report()
7{
8 // Determine if the exception needs custom reporting...
9 
10 return false;
11}
lightbulb

可以在 report 方法中型別提示任何的相依性(Dependency)。Laravel 的 Service Container 會自動插入這些相依性。

依型別映射 Exception

專案中使用的第三方函式庫可能會擲回 Exception,而有時候我們會想讓這些 Exception 變成是可被轉譯的,但因為我們無法控制第三方的 Exception,因此無法做到。

幸好,在 Laravel 中,我們可以將這些 Exception 映射為其他由專案所管理的 Exception 型別。若要映射這些 Exception,可以在 Exception Handler 的 register 方法內呼叫 map 方法:

1use League\Flysystem\Exception;
2use App\Exceptions\FilesystemException;
3 
4/**
5 * Register the exception handling callbacks for the application.
6 *
7 * @return void
8 */
9public function register()
10{
11 $this->map(Exception::class, FilesystemException::class);
12}
1use League\Flysystem\Exception;
2use App\Exceptions\FilesystemException;
3 
4/**
5 * Register the exception handling callbacks for the application.
6 *
7 * @return void
8 */
9public function register()
10{
11 $this->map(Exception::class, FilesystemException::class);
12}

若想進一步控制目標 Exception,可以傳入一個閉包給 map 方法:

1use League\Flysystem\Exception;
2use App\Exceptions\FilesystemException;
3 
4$this->map(fn (Exception $e) => new FilesystemException($e));
1use League\Flysystem\Exception;
2use App\Exceptions\FilesystemException;
3 
4$this->map(fn (Exception $e) => new FilesystemException($e));

HTTP Exception

有的 Exception 是用來描述伺服器的 HTTP 錯誤代碼。例如,這些 Exception 可能是:「找不到頁面」錯誤 (404)、「未經授權」錯誤 (401)⋯⋯等,甚至是開發人員造成的 500 錯誤。在你的程式中的任何地點內,若要產生這種 Response,可使用 abort 輔助函式(Helper)

1abort(404);
1abort(404);

自訂 HTTP 錯誤頁面

在 Laravel 中,要給各種 HTTP 狀態碼顯示自訂錯誤頁非常容易。舉例來說,若要自訂 404 HTTP 狀態碼的錯誤頁面,請建立 resources/views/errors/404.blade.php View 樣板。程式中只要產生 404 錯誤,就會轉譯這個 View。在該目錄中的 View 應以對應的 HTTP 狀態碼來命名。由 abort 函式產生的 Symfony\Component\HttpKernel\Exception\HttpException 實體會以 $exception 變數傳給該 View:

1<h2>{{ $exception->getMessage() }}</h2>
1<h2>{{ $exception->getMessage() }}</h2>

可以使用 vendor:publish Artisan 指令來將 Laravel 的預設錯誤頁樣板安裝(Publish)到專案內。安裝好樣板後,就可以隨意自訂這些樣板:

1php artisan vendor:publish --tag=laravel-errors
1php artisan vendor:publish --tag=laravel-errors
翻譯進度
100% 已翻譯
更新時間:
2023年2月11日 上午10:27:00 [世界標準時間]
翻譯人員:
  • cornch
幫我們翻譯此頁

留言

尚無留言

“Laravel” is a Trademark of Taylor Otwell.
The source documentation is released under MIT license. See laravel/docs on GitHub for details.
The translated documentations are released under MIT license. See cornch/laravel-docs-l10n on GitHub for details.