3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

LaravelのPipeline::thenでmiddlewareを処理している流れをまとめてみた

Posted at

概要

laravelではリクエストをコントローラーにディスパッチするまでや、コントローラーのメソッドを呼び出す前にミドルウェアを適用しています。

その処理をしているのがPipeline::thenになります。

今回はその処理を追っていきます。

start

Illuminate/Foundation/Http/Kernel
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です。

Illuminate/Foundation/Http/Kernel
protected function dispatchToRouter()
{
    return function ($request) {
        $this->app->instance('request', $request);

        return $this->router->dispatch($request);
    };
}

then()を見ていきます。

Illuminate/Pipeline/Pipeline
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

array_reduceは第三引数が渡されているときはそれを最初に解決していきます。

Illuminate/Pipeline/Pipeline
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を返します。

$destinationthen()に渡された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()が呼ばれたときは、$stackprepareDestination()の返り値のClosureが入って、$pipearray_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されていきます。

Illuminate/Foundation/Http/Kernel
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));
    }
};
3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?