前提
Laravelでは、ミドルウェア(主にリクエスト時とかレスポンス時にフィルタリングとしての機能を書く)からコントローラー(実際のリクエストに対する処理を書く)の実行を、柔軟に変更するためにIlluminate\Pipeline\Pipelineを実装し対応している
Pipelineは、実行前に個々の実装(ここではMiddleware)した処理を構成し実行することができる
多段のクロージャでタマネギみたいな構造を構成する
これでMiddlewareのオン、オフや優先度の変更が簡単にできるようになっている
Laravelはリクエストのハンドリングの際Pipelineで、適応するミドルウェアのスタックが走るように設計されている
App\Http\Kernelでミドルウェアスタックを管理している
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
// 全てのリクエスト時に走るミドルウェア(後の解説で使う)
protected $middleware = [
\App\Http\Middleware\AMiddleware::class,
\App\Http\Middleware\BMiddleware::class,
];
// ミドルウェアをグルーピングできる
protected $middlewareGroups = [
'session' => [
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
],
];
// 各ルートごとに設定するミドルウェア
// ルーティング構成時にmiddlewareオプションで設定できる
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
];
// ミドルウェアの優先度を設定
protected $middlewarePriority = [
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
];
}
class AMiddleware
{
public function handle($request, Closure $next)
{
Print "Aの前処理";
$ret = $next($request);
Print "Aの後処理";
return $ret;
}
}
class BMiddleware
{
public function handle($request, Closure $next)
{
Print "Bの前処理";
$ret = $next($request);
Print "Bの後処理";
return $ret;
}
}
コアコードを追う
リクエストを受けレスポンスを構成するまでに、
Illuminate\Foundation\Http\KernelのsendRequestThroughRouterが実行される
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
Pipelineを通して使いたいリクエストをsendメソッドで設定し、終点までに実行したい処理(ミドルウェア)をthroughメソッドで設定、それからthenによりメインの処理を引数で渡して設定する
この段階で設定されるミドルウェアは、App\Http\Kernelで設定した$middlewareの値
function then(Closure $destination)
{
// array_reduceでミドルウェアをイテレートする
$pipeline = array_reduce(
array_reverse($this->middlewares),
// $nextClosureは一つ前のイテレーションで返されたクロージャ
function ($nextClosure, $middlewareClass) {
// ミドルウェアの定義時の$nextが$nextClosureに当たる
return function ($request) use ($nextClosure, $middlewareClass) {
// class名からミドルウェアインスタンスの取得
$middleware = app($middlewareClass);
// $this->methodはミドルウェアの定義時のhandleメソッド
// ここで次の処理に$nextClosureと$requestを次に渡す
return $middleware->{$this->method}($request, $nextClosure);
};
},
// イテレーションで空を返すと$destinationが最後に設定される
function ($request) use ($destination) {
return $destination($request);
}
);
return $pipeline($this->request);
}
上記は、thenメソッドを要約したコード、これの返り値の$pipelineは下記のようになる
function ($request) {
return app(\App\Http\Middleware\AMiddleWare::class)
->handle($request, function ($request) {
return app(\App\Http\Middleware\BMiddleWare::class)
->handle($request, function ($request) {
return $destination($request);
});
});
};
App\Http\Kernelで設定した$middlewareの処理がクロージャで階層化されて順次実行され、最下層に$destinationが設定されるようにPipelineが構成され、以下のように実行されるようになる
"Aの前処理"
"Bの前処理"
$destination($request)
"Bの後処理"
"Aの後処理"
Middlewareごとに次のクロージャを覆う形になるのでタマネギ構造とか言われているらしい
どのクロージャも最後はレスポンスオブジェクトを返すので、最終的にレスポンスオブジェクトが返る
ここでの$destinationは$this->dispatchToRouter()でこれを実行すると再び別のpipelineが設定される
$middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);
return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
return $this->prepareResponse(
$request, $route->run()
);
});
ここでの$middlewareは、App\Http\Kernelで設定した$routeMiddlewareもしくは、$middlewareGroupsをルーティングで設定したもので、$route->run()でコントローラーが設定される
タマネギの中にタマネギが入っている感じ
ちなみに、LaravelのドキュメントではMiddlewareの前処理をBeforeMiddleware、後処理をAfterMiddlewareとかで書かれている
参考サイト
How handle() method of Laravel middleware is called using 'Clousre $next' in another Middleware?