簡介 (Introduction)
Middleware 提供了一個方便的機制來檢查和過濾進入應用程式的 HTTP 請求。例如,Laravel 包含一個 Middleware,用於驗證應用程式的使用者是否已驗證。如果使用者未驗證,Middleware 將把使用者重新導向到應用程式的登入畫面。但是,如果使用者已驗證,Middleware 將允許請求進一步進入應用程式。
除了驗證之外,還可以編寫其他 Middleware 來執行各種任務。例如,日誌記錄 Middleware 可能會記錄所有傳入應用程式的請求。Laravel 包含多種 Middleware,包括用於驗證和 CSRF 保護的 Middleware;但是,所有使用者定義的 Middleware 通常位於應用程式的 app/Http/Middleware 目錄中。
定義 Middleware (Defining Middleware)
要建立新的 Middleware,請使用 make:middleware Artisan 指令:
php artisan make:middleware EnsureTokenIsValid
此指令將在您的 app/Http/Middleware 目錄中放置一個新的 EnsureTokenIsValid 類別。在此 Middleware 中,我們只會在提供的 token 輸入與指定值匹配時允許存取路由。否則,我們會將使用者重新導向回 /home URI:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureTokenIsValid
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if ($request->input('token') !== 'my-secret-token') {
return redirect('/home');
}
return $next($request);
}
}
如您所見,如果給定的 token 與我們的秘密 Token 不匹配,Middleware 將向客戶端返回 HTTP 重新導向;否則,請求將進一步傳遞到應用程式中。要將請求更深入地傳遞到應用程式中(允許 Middleware「通過」),您應該使用 $request 呼叫 $next Callback。
最好將 Middleware 想像為一系列「層」,HTTP 請求必須通過這些層才能到達您的應用程式。每一層都可以檢查請求,甚至完全拒絕它。
[!NOTE] 所有 Middleware 都透過 Service Container 解析,因此您可以在 Middleware 的建構子中對所需的任何依賴項進行型別提示。
Middleware 與回應 (Middleware And Responses)
當然,Middleware 可以在將請求更深入地傳遞到應用程式之前或之後執行任務。例如,以下 Middleware 將在應用程式處理請求之前執行某些任務:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class BeforeMiddleware
{
public function handle(Request $request, Closure $next): Response
{
// Perform action
return $next($request);
}
}
但是,此 Middleware 將在應用程式處理請求之後執行其任務:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class AfterMiddleware
{
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
// Perform action
return $response;
}
}
註冊 Middleware (Registering Middleware)
全域 Middleware (Global Middleware)
如果您希望 Middleware 在應用程式的每個 HTTP 請求期間執行,您可以在應用程式的 bootstrap/app.php 檔案中將其附加到全域 Middleware 堆疊:
use App\Http\Middleware\EnsureTokenIsValid;
->withMiddleware(function (Middleware $middleware): void {
$middleware->append(EnsureTokenIsValid::class);
})
提供給 withMiddleware Closure 的 $middleware 物件是 Illuminate\Foundation\Configuration\Middleware 的實例,負責管理分配給應用程式路由的 Middleware。append 方法將 Middleware 新增到全域 Middleware 清單的末尾。如果您想將 Middleware 新增到清單的開頭,應使用 prepend 方法。
手動管理 Laravel 的預設全域 Middleware (Manually Managing Laravels Default Global Middleware)
如果您想手動管理 Laravel 的全域 Middleware 堆疊,您可以將 Laravel 的預設全域 Middleware 堆疊提供給 use 方法。然後,您可以根據需要調整預設的 Middleware 堆疊:
->withMiddleware(function (Middleware $middleware): void {
$middleware->use([
\Illuminate\Foundation\Http\Middleware\InvokeDeferredCallbacks::class,
// \Illuminate\Http\Middleware\TrustHosts::class,
\Illuminate\Http\Middleware\TrustProxies::class,
\Illuminate\Http\Middleware\HandleCors::class,
\Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Http\Middleware\ValidatePostSize::class,
\Illuminate\Foundation\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
]);
})
將 Middleware 分配給路由 (Assigning Middleware To Routes)
如果您想將 Middleware 分配給特定路由,您可以在定義路由時呼叫 middleware 方法:
use App\Http\Middleware\EnsureTokenIsValid;
Route::get('/profile', function () {
// ...
})->middleware(EnsureTokenIsValid::class);
您可以透過將 Middleware 名稱陣列傳遞給 middleware 方法來將多個 Middleware 分配給路由:
Route::get('/', function () {
// ...
})->middleware([First::class, Second::class]);
排除 Middleware (Excluding Middleware)
將 Middleware 分配給一組路由時,您可能偶爾需要防止 Middleware 應用於群組內的單個路由。您可以使用 withoutMiddleware 方法來實現:
use App\Http\Middleware\EnsureTokenIsValid;
Route::middleware([EnsureTokenIsValid::class])->group(function () {
Route::get('/', function () {
// ...
});
Route::get('/profile', function () {
// ...
})->withoutMiddleware([EnsureTokenIsValid::class]);
});
您還可以從整個路由定義 群組 中排除給定的 Middleware 集:
use App\Http\Middleware\EnsureTokenIsValid;
Route::withoutMiddleware([EnsureTokenIsValid::class])->group(function () {
Route::get('/profile', function () {
// ...
});
});
withoutMiddleware 方法只能移除路由 Middleware,不適用於 全域 Middleware。
Middleware 群組 (Middleware Groups)
有時您可能希望將多個 Middleware 分組在單個鍵下,以便更容易將它們分配給路由。您可以在應用程式的 bootstrap/app.php 檔案中使用 appendToGroup 方法來實現:
use App\Http\Middleware\First;
use App\Http\Middleware\Second;
->withMiddleware(function (Middleware $middleware): void {
$middleware->appendToGroup('group-name', [
First::class,
Second::class,
]);
$middleware->prependToGroup('group-name', [
First::class,
Second::class,
]);
})
Middleware 群組可以使用與單個 Middleware 相同的語法分配給路由和 Controller Action:
Route::get('/', function () {
// ...
})->middleware('group-name');
Route::middleware(['group-name'])->group(function () {
// ...
});
Laravel 的預設 Middleware 群組 (Laravels Default Middleware Groups)
Laravel 包含預定義的 web 和 api Middleware 群組,其中包含您可能想要應用於 Web 和 API 路由的常見 Middleware。請記住,Laravel 會自動將這些 Middleware 群組應用於相應的 routes/web.php 和 routes/api.php 檔案:
web Middleware 群組 |
|---|
Illuminate\Cookie\Middleware\EncryptCookies |
Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse |
Illuminate\Session\Middleware\StartSession |
Illuminate\View\Middleware\ShareErrorsFromSession |
Illuminate\Foundation\Http\Middleware\ValidateCsrfToken |
Illuminate\Routing\Middleware\SubstituteBindings |
api Middleware 群組 |
|---|
Illuminate\Routing\Middleware\SubstituteBindings |
如果您想將 Middleware 附加或前置到這些群組,您可以在應用程式的 bootstrap/app.php 檔案中使用 web 和 api 方法。web 和 api 方法是 appendToGroup 方法的便捷替代方案:
use App\Http\Middleware\EnsureTokenIsValid;
use App\Http\Middleware\EnsureUserIsSubscribed;
->withMiddleware(function (Middleware $middleware): void {
$middleware->web(append: [
EnsureUserIsSubscribed::class,
]);
$middleware->api(prepend: [
EnsureTokenIsValid::class,
]);
})
您甚至可以用您自己的自訂 Middleware 替換 Laravel 的預設 Middleware 群組項目之一:
use App\Http\Middleware\StartCustomSession;
use Illuminate\Session\Middleware\StartSession;
$middleware->web(replace: [
StartSession::class => StartCustomSession::class,
]);
或者,您可以完全移除 Middleware:
$middleware->web(remove: [
StartSession::class,
]);
手動管理 Laravel 的預設 Middleware 群組 (Manually Managing Laravels Default Middleware Groups)
如果您想手動管理 Laravel 預設 web 和 api Middleware 群組中的所有 Middleware,您可以完全重新定義這些群組。以下範例將使用其預設 Middleware 定義 web 和 api Middleware 群組,允許您根據需要自訂它們:
->withMiddleware(function (Middleware $middleware): void {
$middleware->group('web', [
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
]);
$middleware->group('api', [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
// 'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
]);
})
[!NOTE] 預設情況下,
web和apiMiddleware 群組會由bootstrap/app.php檔案自動應用於應用程式對應的routes/web.php和routes/api.php檔案。
Middleware 別名 (Middleware Aliases)
您可以在應用程式的 bootstrap/app.php 檔案中為 Middleware 分配別名。Middleware 別名允許您為給定的 Middleware 類別定義簡短的別名,這對於具有長類別名稱的 Middleware 特別有用:
use App\Http\Middleware\EnsureUserIsSubscribed;
->withMiddleware(function (Middleware $middleware): void {
$middleware->alias([
'subscribed' => EnsureUserIsSubscribed::class
]);
})
一旦在應用程式的 bootstrap/app.php 檔案中定義了 Middleware 別名,您就可以在將 Middleware 分配給路由時使用該別名:
Route::get('/profile', function () {
// ...
})->middleware('subscribed');
為了方便起見,Laravel 的一些內建 Middleware 預設已設定別名。例如,auth Middleware 是 Illuminate\Auth\Middleware\Authenticate Middleware 的別名。以下是預設 Middleware 別名的清單:
| 別名 | Middleware |
|---|---|
auth | Illuminate\Auth\Middleware\Authenticate |
auth.basic | Illuminate\Auth\Middleware\AuthenticateWithBasicAuth |
auth.session | Illuminate\Session\Middleware\AuthenticateSession |
cache.headers | Illuminate\Http\Middleware\SetCacheHeaders |
can | Illuminate\Auth\Middleware\Authorize |
guest | Illuminate\Auth\Middleware\RedirectIfAuthenticated |
password.confirm | Illuminate\Auth\Middleware\RequirePassword |
precognitive |
|
signed | Illuminate\Routing\Middleware\ValidateSignature |
subscribed | \Spark\Http\Middleware\VerifyBillableIsSubscribed |
throttle |
|
verified | Illuminate\Auth\Middleware\EnsureEmailIsVerified |
排序 Middleware (Sorting Middleware)
在極少數情況下,您可能需要 Middleware 以特定順序執行,但在將它們分配給路由時無法控制它們的順序。在這些情況下,您可以在應用程式的 bootstrap/app.php 檔案中使用 priority 方法指定 Middleware 優先順序:
->withMiddleware(function (Middleware $middleware): void {
$middleware->priority([
\Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class,
\Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
\Illuminate\Auth\Middleware\Authorize::class,
]);
})
Middleware 參數 (Middleware Parameters)
Middleware 也可以接收額外的參數。例如,如果您的應用程式需要在執行給定動作之前驗證經過驗證的使用者是否具有給定的「角色」,您可以建立一個 EnsureUserHasRole Middleware,該 Middleware 接收角色名稱作為額外參數。
額外的 Middleware 參數將在 $next 參數之後傳遞給 Middleware:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureUserHasRole
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next, string $role): Response
{
if (! $request->user()->hasRole($role)) {
// Redirect...
}
return $next($request);
}
}
在定義路由時,可以透過使用 : 分隔 Middleware 名稱和參數來指定 Middleware 參數:
use App\Http\Middleware\EnsureUserHasRole;
Route::put('/post/{'{id}'}', function (string $id) {
// ...
})->middleware(EnsureUserHasRole::class.':editor');
多個參數可以用逗號分隔:
Route::put('/post/{'{id}'}', function (string $id) {
// ...
})->middleware(EnsureUserHasRole::class.':editor,publisher');
可終止的 Middleware (Terminable Middleware)
有時 Middleware 可能需要在 HTTP 回應已發送到瀏覽器之後執行某些工作。如果您在 Middleware 上定義了 terminate 方法,並且您的 Web 伺服器正在使用 FastCGI,則在回應發送到瀏覽器後,terminate 方法將自動被呼叫:
<?php
namespace Illuminate\Session\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class TerminatingMiddleware
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
return $next($request);
}
/**
* Handle tasks after the response has been sent to the browser.
*/
public function terminate(Request $request, Response $response): void
{
// ...
}
}
terminate 方法應同時接收請求和回應。一旦您定義了可終止的 Middleware,您應該將其新增到應用程式 bootstrap/app.php 檔案中的路由或全域 Middleware 清單中。
在 Middleware 上呼叫 terminate 方法時,Laravel 將從 Service Container 解析 Middleware 的新實例。如果您希望在呼叫 handle 和 terminate 方法時使用相同的 Middleware 實例,請使用 Container 的 singleton 方法向 Container 註冊 Middleware。通常這應該在您的 AppServiceProvider 的 register 方法中完成:
use App\Http\Middleware\TerminatingMiddleware;
/**
* Register any application services.
*/
public function register(): void
{
$this->app->singleton(TerminatingMiddleware::class);
}