概要
laravelではリクエストをコントローラーにディスパッチするまでや、コントローラーのメソッドを呼び出す前にミドルウェアを適用しています。
その処理をしているのがPipeline::then
になります。
今回はその処理を追っていきます。
start
protected function sendRequestThroughRouter($request)
{
//...
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
public function send($passable)
{
$this->passable = $passable;
return $this;
}
public function through($pipes)
{
$this->pipes = is_array($pipes) ? $pipes : func_get_args();
return $this;
}
send()はrequestをプロパティにセットして、through()はmiddlewareをプロパティにセットしています。
then()
に渡しているのはdispatchToRouter()
の返り値です。
引数に$request
を受け取ってdispatch($request)
するClosureです。
protected function dispatchToRouter()
{
return function ($request) {
$this->app->instance('request', $request);
return $this->router->dispatch($request);
};
}
then()
を見ていきます。
public function then(Closure $destination)
{
$pipeline = array_reduce(
array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
);
return $pipeline($this->passable);
}
array_reduce()
は第一引数に渡された配列に、第二引数に渡されたClosureを適用して、1つの値を返す関数です。ここでは1つのClosureを返しています。
array_reduce
は第三引数が渡されているときはそれを最初に解決していきます。
protected function prepareDestination(Closure $destination)
{
return function ($passable) use ($destination) {
try {
return $destination($passable);
} catch (Exception $e) {
return $this->handleException($passable, $e);
} catch (Throwable $e) {
return $this->handleException($passable, new FatalThrowableError($e));
}
};
}
このメソッドもClosureを返します。
$destination
はthen()
に渡されたClosureなので、書き換えるとこうなります。
return function ($passable) use ($destination) {
try {
return function ($passable) {
$this->app->instance('request', $passable);
return $this->router->dispatch($passable);
};
} catch (Exception $e) {
return $this->handleException($passable, $e);
} catch (Throwable $e) {
return $this->handleException($passable, new FatalThrowableError($e));
}
};
try-catchで囲んだだけですね。
prepareDestination()
の返り値を利用して、$this->carry()
のClosureをarray_reverse($this->pipes)
に順に適用していきます。
$this->pipes
にはKernel
で定義したMiddlewareの配列が入っています。
carry()
を重要なところだけ抜き出してみました。
protected function carry()
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
$parameters = [$passable, $stack];
$carry = method_exists($pipe, $this->method)
? $pipe->{$this->method}(...$parameters)
: $pipe(...$parameters);
return $this->handleCarry($carry);
};
};
}
最初にcarry()
が呼ばれたときは、$stack
にprepareDestination()
の返り値のClosureが入って、$pipe
にarray_reverse($this->pipes)
の1つ目の値(middleware)が入ってきます。
$this->method
には"handle"
が代入されているので、middlewareにhandleメソッドが定義されていれば、それが呼ばれることになります。middlewareを作成するときにhandle()
を定義するのはこのためだったんですね。
順番に$this->carry()
を適用していくと以下のようなClosureにまとめられます。
function ($passable) {
return $pipe1->handle($passable, function ($passable) {
return $pipe2->handle($passable, function ($passable){
return $pipe3->handle($passable, $this->prepareDestination());
})
})
}
middlewareのhandle()
にリクエストと次のClosureを渡す層になっています。
middlewarehandle()
内に書いてある$next()
とは次のmiddlewareのhandle()
を呼び出すClosureを指定していたわけなんですね!
public function handle($request, Closure $next)
{
return $next($request);
}
最終的にmiddlewareを全て通ったリクエストは、最初に$this->prepareDestination()
で作成されたClosureに渡され、routerにdispatchされていきます。
return function ($passable) use ($destination) {
try {
return function ($passable) {
$this->app->instance('request', $passable);
return $this->router->dispatch($passable);
};
} catch (Exception $e) {
return $this->handleException($passable, $e);
} catch (Throwable $e) {
return $this->handleException($passable, new FatalThrowableError($e));
}
};