跳到內容

錯誤處理

簡介

當您開始一個新的 Laravel 專案時,錯誤和例外處理已經為您設定完成;然而,在任何時候,您都可以使用應用程式 bootstrap/app.php 中的 withExceptions 方法來管理您的應用程式如何報告和渲染例外。

提供給 withExceptions 閉包的 $exceptions 物件是 Illuminate\Foundation\Configuration\Exceptions 的實例,負責管理應用程式中的例外處理。我們將在本文件中深入探討這個物件。

設定

您的 config/app.php 設定檔中的 debug 選項決定了實際向使用者顯示多少錯誤資訊。預設情況下,此選項設定為尊重 .env 檔案中儲存的 APP_DEBUG 環境變數的值。

在本地開發期間,您應該將 APP_DEBUG 環境變數設定為 true在您的生產環境中,此值應始終為 false。如果該值在生產環境中設定為 true,您將有暴露敏感設定值給應用程式最終使用者的風險。

處理例外

報告例外

在 Laravel 中,例外報告用於記錄例外或將其發送到外部服務,例如 SentryFlare。預設情況下,例外將根據您的 記錄設定進行記錄。但是,您可以自由地按照您希望的方式記錄例外。

如果您需要以不同的方式報告不同類型的例外,您可以使用應用程式 bootstrap/app.php 中的 report 例外方法來註冊一個閉包,當需要報告給定類型的例外時,應執行該閉包。Laravel 將透過檢查閉包的類型提示來確定閉包報告的例外類型。

->withExceptions(function (Exceptions $exceptions) {
$exceptions->report(function (InvalidOrderException $e) {
// ...
});
})

當您使用 report 方法註冊自訂例外報告回呼時,Laravel 仍會使用應用程式的預設記錄設定來記錄例外。如果您希望停止例外傳播到預設記錄堆疊,您可以在定義報告回呼時使用 stop 方法,或從回呼中返回 false

->withExceptions(function (Exceptions $exceptions) {
$exceptions->report(function (InvalidOrderException $e) {
// ...
})->stop();
 
$exceptions->report(function (InvalidOrderException $e) {
return false;
});
})
lightbulb - Laravel 框架

要自訂給定例外的例外報告,您也可以使用可報告的例外

全域記錄上下文

如果可用,Laravel 會自動將目前使用者的 ID 作為上下文資料新增到每個例外的記錄訊息中。您可以使用應用程式的 bootstrap/app.php 檔案中的 context 例外方法定義您自己的全域上下文資料。此資訊將包含在您的應用程式所寫的每個例外的記錄訊息中。

->withExceptions(function (Exceptions $exceptions) {
$exceptions->context(fn () => [
'foo' => 'bar',
]);
})

例外記錄上下文

雖然將上下文新增到每個記錄訊息中可能很有用,但有時特定的例外可能具有您希望包含在記錄中的唯一上下文。透過在應用程式的其中一個例外上定義 context 方法,您可以指定應新增到例外記錄項中的任何與該例外相關的資料。

<?php
 
namespace App\Exceptions;
 
use Exception;
 
class InvalidOrderException extends Exception
{
// ...
 
/**
* Get the exception's context information.
*
* @return array<string, mixed>
*/
public function context(): array
{
return ['order_id' => $this->orderId];
}
}

report 輔助函數

有時您可能需要報告例外,但繼續處理目前請求。report 輔助函數可讓您快速報告例外,而不會向使用者渲染錯誤頁面。

public function isValid(string $value): bool
{
try {
// Validate the value...
} catch (Throwable $e) {
report($e);
 
return false;
}
}

重複數據刪除報告的例外

如果您在整個應用程式中使用 report 函數,您偶爾可能會多次報告相同的例外,在記錄中建立重複的條目。

如果您想確保只報告一次例外的單個實例,您可以在應用程式的 bootstrap/app.php 檔案中調用 dontReportDuplicates 例外方法。

->withExceptions(function (Exceptions $exceptions) {
$exceptions->dontReportDuplicates();
})

現在,當使用相同的例外實例調用 report 輔助函數時,只會報告第一次調用。

$original = new RuntimeException('Whoops!');
 
report($original); // reported
 
try {
throw $original;
} catch (Throwable $caught) {
report($caught); // ignored
}
 
report($original); // ignored
report($caught); // ignored

例外記錄級別

當訊息寫入應用程式的記錄時,訊息會以指定的記錄級別寫入,這表示正在記錄的訊息的嚴重程度或重要性。

如上所述,即使您使用 report 方法註冊了自訂例外報告回呼,Laravel 仍將使用應用程式的預設記錄設定來記錄例外;但是,由於記錄級別有時會影響訊息記錄的通道,您可能希望設定某些例外記錄的記錄級別。

為了實現這一點,您可以使用應用程式的 bootstrap/app.php 檔案中的 level 例外方法。此方法將例外類型作為其第一個參數,將記錄級別作為其第二個參數。

use PDOException;
use Psr\Log\LogLevel;
 
->withExceptions(function (Exceptions $exceptions) {
$exceptions->level(PDOException::class, LogLevel::CRITICAL);
})

依類型忽略例外

在建置應用程式時,有些類型的例外您永遠不想報告。要忽略這些例外,您可以使用應用程式的 bootstrap/app.php 檔案中的 dontReport 例外方法。提供給此方法的任何類別都永遠不會報告;但是,它們可能仍然具有自訂的渲染邏輯。

use App\Exceptions\InvalidOrderException;
 
->withExceptions(function (Exceptions $exceptions) {
$exceptions->dontReport([
InvalidOrderException::class,
]);
})

或者,您也可以簡單地使用 Illuminate\Contracts\Debug\ShouldntReport 介面「標記」例外類別。當使用此介面標記例外時,Laravel 的例外處理程式永遠不會報告該例外。

<?php
 
namespace App\Exceptions;
 
use Exception;
use Illuminate\Contracts\Debug\ShouldntReport;
 
class PodcastProcessingException extends Exception implements ShouldntReport
{
//
}

在內部,Laravel 已經為您忽略了一些類型的錯誤,例如 404 HTTP 錯誤或由無效 CSRF 令牌產生的 419 HTTP 回應導致的例外。如果您想指示 Laravel 停止忽略給定類型的例外,您可以使用應用程式的 bootstrap/app.php 檔案中的 stopIgnoring 例外方法。

use Symfony\Component\HttpKernel\Exception\HttpException;
 
->withExceptions(function (Exceptions $exceptions) {
$exceptions->stopIgnoring(HttpException::class);
})

渲染例外

預設情況下,Laravel 異常處理程式會將異常轉換為 HTTP 回應。然而,您可以自由地為特定類型的異常註冊自訂的渲染閉包。您可以使用應用程式 bootstrap/app.php 檔案中的 render 異常方法來完成此操作。

傳遞給 render 方法的閉包應返回 Illuminate\Http\Response 的實例,這可以使用 response 輔助函數產生。Laravel 會檢查閉包的類型提示,以確定閉包渲染的異常類型。

use App\Exceptions\InvalidOrderException;
use Illuminate\Http\Request;
 
->withExceptions(function (Exceptions $exceptions) {
$exceptions->render(function (InvalidOrderException $e, Request $request) {
return response()->view('errors.invalid-order', status: 500);
});
})

您也可以使用 render 方法來覆寫 Laravel 或 Symfony 內建異常的渲染行為,例如 NotFoundHttpException。如果傳遞給 render 方法的閉包沒有返回值,則會使用 Laravel 的預設異常渲染。

use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 
->withExceptions(function (Exceptions $exceptions) {
$exceptions->render(function (NotFoundHttpException $e, Request $request) {
if ($request->is('api/*')) {
return response()->json([
'message' => 'Record not found.'
], 404);
}
});
})

將異常渲染為 JSON

在渲染異常時,Laravel 會根據請求的 Accept 標頭自動判斷是否應將異常渲染為 HTML 或 JSON 回應。如果您想自訂 Laravel 如何判斷是否渲染 HTML 或 JSON 異常回應,可以使用 shouldRenderJsonWhen 方法。

use Illuminate\Http\Request;
use Throwable;
 
->withExceptions(function (Exceptions $exceptions) {
$exceptions->shouldRenderJsonWhen(function (Request $request, Throwable $e) {
if ($request->is('admin/*')) {
return true;
}
 
return $request->expectsJson();
});
})

自訂異常回應

極少情況下,您可能需要自訂 Laravel 異常處理程式渲染的整個 HTTP 回應。為此,您可以使用 respond 方法註冊回應自訂閉包。

use Symfony\Component\HttpFoundation\Response;
 
->withExceptions(function (Exceptions $exceptions) {
$exceptions->respond(function (Response $response) {
if ($response->getStatusCode() === 419) {
return back()->with([
'message' => 'The page expired, please try again.',
]);
}
 
return $response;
});
})

可報告和可渲染的例外

您可以在應用程式的異常中直接定義 reportrender 方法,而不是在應用程式的 bootstrap/app.php 檔案中定義自訂的報告和渲染行為。當這些方法存在時,框架將會自動呼叫它們。

<?php
 
namespace App\Exceptions;
 
use Exception;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
 
class InvalidOrderException extends Exception
{
/**
* Report the exception.
*/
public function report(): void
{
// ...
}
 
/**
* Render the exception into an HTTP response.
*/
public function render(Request $request): Response
{
return response(/* ... */);
}
}

如果您的異常擴展了一個已經可以渲染的異常,例如 Laravel 或 Symfony 的內建異常,您可以從異常的 render 方法返回 false,以渲染異常的預設 HTTP 回應。

/**
* Render the exception into an HTTP response.
*/
public function render(Request $request): Response|bool
{
if (/** Determine if the exception needs custom rendering */) {
 
return response(/* ... */);
}
 
return false;
}

如果您的異常包含自訂的報告邏輯,而這些邏輯僅在滿足某些條件時才需要,您可能需要指示 Laravel 有時使用預設的異常處理配置來報告異常。為此,您可以從異常的 report 方法返回 false

/**
* Report the exception.
*/
public function report(): bool
{
if (/** Determine if the exception needs custom reporting */) {
 
// ...
 
return true;
}
 
return false;
}
lightbulb - Laravel 框架

您可以為 report 方法設定任何必需的依賴項的類型提示,它們將由 Laravel 的 服務容器自動注入到方法中。

節流報告的例外

如果您的應用程式報告了大量的異常,您可能需要限制實際記錄或發送到應用程式外部錯誤追蹤服務的異常數量。

為了對異常進行隨機採樣,您可以使用應用程式 bootstrap/app.php 檔案中的 throttle 異常方法。throttle 方法接收一個閉包,該閉包應返回一個 Lottery 實例。

use Illuminate\Support\Lottery;
use Throwable;
 
->withExceptions(function (Exceptions $exceptions) {
$exceptions->throttle(function (Throwable $e) {
return Lottery::odds(1, 1000);
});
})

也可以根據異常類型進行條件採樣。如果您只想對特定異常類別的實例進行採樣,您可以僅針對該類別返回一個 Lottery 實例。

use App\Exceptions\ApiMonitoringException;
use Illuminate\Support\Lottery;
use Throwable;
 
->withExceptions(function (Exceptions $exceptions) {
$exceptions->throttle(function (Throwable $e) {
if ($e instanceof ApiMonitoringException) {
return Lottery::odds(1, 1000);
}
});
})

您也可以透過返回一個 Limit 實例而不是 Lottery 來限制記錄或發送到外部錯誤追蹤服務的異常速率。如果您想防止異常突然爆發而導致日誌過多,例如,當您的應用程式使用的第三方服務關閉時,這會很有用。

use Illuminate\Broadcasting\BroadcastException;
use Illuminate\Cache\RateLimiting\Limit;
use Throwable;
 
->withExceptions(function (Exceptions $exceptions) {
$exceptions->throttle(function (Throwable $e) {
if ($e instanceof BroadcastException) {
return Limit::perMinute(300);
}
});
})

預設情況下,限制將使用異常的類別作為速率限制鍵。您可以使用 Limit 上的 by 方法指定自己的鍵來自訂此行為。

use Illuminate\Broadcasting\BroadcastException;
use Illuminate\Cache\RateLimiting\Limit;
use Throwable;
 
->withExceptions(function (Exceptions $exceptions) {
$exceptions->throttle(function (Throwable $e) {
if ($e instanceof BroadcastException) {
return Limit::perMinute(300)->by($e->getMessage());
}
});
})

當然,您可以為不同的異常返回 LotteryLimit 實例的混合。

use App\Exceptions\ApiMonitoringException;
use Illuminate\Broadcasting\BroadcastException;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Lottery;
use Throwable;
 
->withExceptions(function (Exceptions $exceptions) {
$exceptions->throttle(function (Throwable $e) {
return match (true) {
$e instanceof BroadcastException => Limit::perMinute(300),
$e instanceof ApiMonitoringException => Lottery::odds(1, 1000),
default => Limit::none(),
};
});
})

HTTP 例外

有些異常描述了來自伺服器的 HTTP 錯誤代碼。例如,這可能是一個「找不到頁面」錯誤 (404),一個「未授權錯誤」(401),甚至是開發人員產生的 500 錯誤。為了從應用程式中的任何位置產生這樣的回應,您可以使用 abort 輔助函數。

abort(404);

自訂 HTTP 錯誤頁面

Laravel 可以輕鬆地為各種 HTTP 狀態碼顯示自訂錯誤頁面。例如,要自訂 404 HTTP 狀態碼的錯誤頁面,請建立一個 resources/views/errors/404.blade.php 檢視範本。此檢視將為您的應用程式產生的所有 404 錯誤渲染。此目錄中的檢視應命名為與其對應的 HTTP 狀態碼相符。由 abort 函數引發的 Symfony\Component\HttpKernel\Exception\HttpException 實例將作為 $exception 變數傳遞給檢視。

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

您可以使用 vendor:publish Artisan 命令發佈 Laravel 的預設錯誤頁面範本。發佈範本後,您可以根據自己的喜好自訂它們。

php artisan vendor:publish --tag=laravel-errors

後備 HTTP 錯誤頁面

您還可以為一系列 HTTP 狀態碼定義「後備」錯誤頁面。如果沒有與發生的特定 HTTP 狀態碼對應的頁面,則將渲染此頁面。要完成此操作,請在應用程式的 resources/views/errors 目錄中定義一個 4xx.blade.php 範本和一個 5xx.blade.php 範本。

定義後備錯誤頁面時,後備頁面不會影響 404500503 錯誤回應,因為 Laravel 對這些狀態碼有內部專用頁面。要自訂為這些狀態碼渲染的頁面,您應該分別為每個狀態碼定義一個自訂錯誤頁面。