概要
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));
}
};