#はじめに
ミドルウェアは、アプリケーションに入ってくる HTTP リクエストを検査・フィルタリングする便利な仕組みを提供します。例えば、Laravel にはユーザーが認証済みかどうかを検証するミドルウェアが含まれています。ユーザーが認証されていなければ、そのミドルウェアはログイン画面へリダイレクトします。認証されていれば、リクエストはアプリケーション内でさらに処理されます。
認証以外にも様々な処理を行うミドルウェアを作成できます。例えば、ログ記録用のミドルウェアは、アプリケーションに入るすべてのリクエストをログに記録するかもしれません。Laravel フレームワークには認証や CSRF 保護などのミドルウェアがいくつか含まれており、これらはすべて app/Http/Middleware ディレクトリにあります。
#ミドルウェアの定義
新しいミドルウェアを作成するには、make:middleware Artisan コマンドを使います:
php artisan make:middleware EnsureTokenIsValid
このコマンドは app/Http/Middleware ディレクトリに新しい EnsureTokenIsValid クラスを作成します。このミドルウェアでは、渡された token 入力が指定した値と一致する場合のみルートへのアクセスを許可し、それ以外の場合はユーザーを home URI にリダイレクトします:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureTokenIsValid
{
/**
* 受信したリクエストを処理します。
*
* @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 が秘密のトークンと一致しない場合は、ミドルウェアは HTTP リダイレクトを返します。一致する場合はリクエストをアプリケーション内でさらに処理します。リクエストを次の処理に渡す(ミドルウェアを「通過」させる)には、$next コールバックに $request を渡して呼び出します。
ミドルウェアは、HTTP リクエストがアプリケーションに到達するまでに通過する「層」のようにイメージするとわかりやすいです。各層はリクエストを検査し、場合によっては完全に拒否することもできます。
すべてのミドルウェアは サービスコンテナ を通じて解決されるため、ミドルウェアのコンストラクタで必要な依存を型宣言できます。
#ミドルウェアとレスポンス
もちろん、ミドルウェアはリクエストをアプリケーションに渡す前後で処理を行えます。例えば、以下のミドルウェアはリクエストがアプリケーションに処理される前に何らかの処理を行います:
<?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
{
// 処理を実行
return $next($request);
}
}
一方、こちらのミドルウェアはリクエストがアプリケーションに処理された後に処理を行います:
<?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);
// 処理を実行
return $response;
}
}
#ミドルウェアの登録
#グローバルミドルウェア
すべての HTTP リクエストでミドルウェアを実行したい場合は、app/Http/Kernel.php クラスの $middleware プロパティにミドルウェアクラスをリストしてください。
#ルートへのミドルウェア割り当て
特定のルートにミドルウェアを割り当てたい場合は、ルート定義時に middleware メソッドを呼び出します:
use App\Http\Middleware\Authenticate;
Route::get('/profile', function () {
// ...
})->middleware(Authenticate::class);
複数のミドルウェアを割り当てる場合は、middleware メソッドにミドルウェア名の配列を渡します:
Route::get('/', function () {
// ...
})->middleware([First::class, Second::class]);
利便性のため、app/Http/Kernel.php ファイルでミドルウェアにエイリアスを割り当てられます。デフォルトでは、このクラスの $middlewareAliases プロパティに Laravel に含まれるミドルウェアのエイリアスが登録されています。独自のミドルウェアを追加し、任意のエイリアスを割り当てられます:
// App\Http\Kernel クラス内...
protected $middlewareAliases = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
HTTP カーネルでミドルウェアエイリアスを定義したら、ルートにミドルウェアを割り当てる際にエイリアスを使えます:
Route::get('/profile', function () {
// ...
})->middleware('auth');
#ミドルウェアの除外
ルートグループにミドルウェアを割り当てる際、グループ内の特定のルートだけミドルウェアを適用したくない場合があります。その場合は withoutMiddleware メソッドを使います:
use App\Http\Middleware\EnsureTokenIsValid;
Route::middleware([EnsureTokenIsValid::class])->group(function () {
Route::get('/', function () {
// ...
});
Route::get('/profile', function () {
// ...
})->withoutMiddleware([EnsureTokenIsValid::class]);
});
特定のミドルウェアをルート定義のグループ全体から除外することもできます:
use App\Http\Middleware\EnsureTokenIsValid;
Route::withoutMiddleware([EnsureTokenIsValid::class])->group(function () {
Route::get('/profile', function () {
// ...
});
});
withoutMiddleware メソッドはルートミドルウェアのみを除外でき、グローバルミドルウェアには適用されません。
#ミドルウェアグループ
複数のミドルウェアをまとめて一つのキーにグループ化し、ルートに割り当てやすくすることがあります。これは HTTP カーネルの $middlewareGroups プロパティで実現できます。
Laravel には、web および api のルートに適用したい一般的なミドルウェアを含む、事前定義された web と api のミドルウェアグループが用意されています。これらのミドルウェアグループは、アプリケーションの App\Providers\RouteServiceProvider サービスプロバイダによって、対応する web および api のルートファイル内のルートに自動的に適用されることを忘れないでください:
/**
* アプリケーションのルートミドルウェアグループ。
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
ミドルウェアグループは、個別のミドルウェアと同じ構文でルートやコントローラーアクションに割り当てられます。ミドルウェアグループを使うと、一度に多くのミドルウェアをルートに割り当てるのが便利です:
Route::get('/', function () {
// ...
})->middleware('web');
Route::middleware(['web'])->group(function () {
// ...
});
デフォルトで、web と api のミドルウェアグループは App\Providers\RouteServiceProvider によって対応する routes/web.php と routes/api.php ファイルに自動的に適用されます。
#ミドルウェアの実行順序の指定
まれに、ミドルウェアを特定の順序で実行したいが、ルートに割り当てる際に順序を制御できない場合があります。その場合は、app/Http/Kernel.php ファイルの $middlewarePriority プロパティで優先順位を指定できます。このプロパティはデフォルトで存在しない場合があるため、存在しなければ以下の定義をコピーしてください:
/**
* 優先順位でソートされたミドルウェアのリスト。
*
* これにより、グローバルでないミドルウェアは常に指定した順序になります。
*
* @var string[]
*/
protected $middlewarePriority = [
\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\Contracts\Auth\Middleware\AuthenticatesRequests::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class,
\Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
\Illuminate\Contracts\Session\Middleware\AuthenticatesSessions::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
];
#ミドルウェアのパラメータ
ミドルウェアは追加のパラメータを受け取ることもできます。例えば、認証済みユーザーが特定の「ロール」を持っているかを検証する EnsureUserHasRole ミドルウェアを作成し、ロール名を追加の引数として受け取れます。
追加のミドルウェアパラメータは、$next 引数の後に渡されます:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureUserHasRole
{
/**
* 受信したリクエストを処理します。
*
* @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)) {
// リダイレクト...
}
return $next($request);
}
}
ミドルウェアパラメータは、ルート定義時にミドルウェア名とパラメータを : で区切って指定します:
Route::put('/post/{id}', function (string $id) {
// ...
})->middleware('role:editor');
複数のパラメータはカンマで区切れます:
Route::put('/post/{id}', function (string $id) {
// ...
})->middleware('role:editor,publisher');
#終了可能なミドルウェア
ミドルウェアは、HTTP レスポンスがブラウザに送信された後に何らかの処理を行う必要がある場合があります。ミドルウェアに terminate メソッドを定義し、ウェブサーバーが FastCGI を使っている場合、レスポンス送信後に自動的に terminate メソッドが呼ばれます:
<?php
namespace Illuminate\Session\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class TerminatingMiddleware
{
/**
* 受信したリクエストを処理します。
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
return $next($request);
}
/**
* レスポンス送信後の処理を行います。
*/
public function terminate(Request $request, Response $response): void
{
// ...
}
}
terminate メソッドはリクエストとレスポンスの両方を受け取る必要があります。終了可能なミドルウェアを定義したら、app/Http/Kernel.php のルートまたはグローバルミドルウェアのリストに追加してください。
terminate メソッドを呼ぶ際、Laravel は サービスコンテナ から新しいミドルウェアインスタンスを解決します。handle と terminate の両方で同じインスタンスを使いたい場合は、AppServiceProvider の register メソッドでコンテナに singleton として登録してください:
use App\Http\Middleware\TerminatingMiddleware;
/**
* アプリケーションサービスを登録します。
*/
public function register(): void
{
$this->app->singleton(TerminatingMiddleware::class);
}