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


## 概要

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

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

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

## start

```php:Illuminate/Foundation/Http/Kernel
protected function sendRequestThroughRouter($request)
{
    //...

    return (new Pipeline($this->app))
                ->send($request)
                ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                ->then($this->dispatchToRouter());
}
```

```php
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です。

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

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

`then()`を見ていきます。

```php: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を適用して、１つの値を返す関数です。ここでは１つのClosureを返しています。

[公式リファレンス:array_reduce](https://www.php.net/manual/ja/function.array-reduce.php)

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

```php: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を返します。

`$destination`は`then()`に渡されたClosureなので、書き換えるとこうなります。

```php
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()`を重要なところだけ抜き出してみました。

```php
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)`の１つ目の値（middleware）が入ってきます。

`$this->method`には`"handle"`が代入されているので、middlewareにhandleメソッドが定義されていれば、それが呼ばれることになります。middlewareを作成するときに`handle()`を定義するのはこのためだったんですね。

順番に`$this->carry()`を適用していくと以下のようなClosureにまとめられます。

```php
function ($passable) {
    return $pipe1->handle($passable, function ($passable) {
        return $pipe2->handle($passable, function ($passable){
            return $pipe3->handle($passable, $this->prepareDestination());
        })
    })
}
```

middlewareの`handle()`にリクエストと次のClosureを渡す層になっています。

middleware`handle()`内に書いてある`$next()`とは次のmiddlewareの`handle()`を呼び出すClosureを指定していたわけなんですね！

```php
public function handle($request, Closure $next)
{
    return $next($request);
}
```

最終的にmiddlewareを全て通ったリクエストは、最初に`$this->prepareDestination()`で作成されたClosureに渡され、routerにdispatchされていきます。

```php: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));
    }
};
```