❄️PHPでMiddlewareパターンを実装する方法

[前稿]でInterpreterパターン風のMiddlewareパターンの設計を紹介した。前稿の設計では、Middlewareパターンの特性である次の3つのうち、2と3が満たせていない課題があった。

1. クライアントコードとハンドラの間に割って入り、入力を加工する
* ハンドラとクライアントコードの間に割って入り、出力を加工する
* クライアントコードとハンドラの間に割って入り、入力がハンドラに届く前に、早期に出力を返す

本稿ではその課題を解決したパターン、つまり**ちゃんとしたMiddlewareパターン**の設計と実装を紹介する。

[前稿]: https://qiita.com/suin/items/41042739bfca271da729


## Middlewareパターンとは

Middlewareパターンの目的は、「ミドルウェア」と呼ばれるガワの処理で核となるハンドラを包むことで、プログラム本来の振る舞いを拡張可能にすること。MVCウェブフレームワークでは、コントローラの入出力となるHTTPリクエストやレスポンスをアプリケーション全体で統一的に加工する用途などに採用されている。

![貼り付けた画像_2018_04_18_12_20.png](https://qiita-image-store.s3.amazonaws.com/0/889/7f091851-061c-49fe-ecfc-ab0aaffa7172.png)

ミドルウェアができることは次の3つ:

1. クライアントコードとハンドラの間に割って入り、入力を加工する
* ハンドラとクライアントコードの間に割って入り、出力を加工する
* クライアントコードとハンドラの間に割って入り、入力がハンドラに届く前に、早期に出力を返す

## Middlewareパターン

### 設計

![クラス図0.png](https://qiita-image-store.s3.amazonaws.com/0/889/169aabf3-39c7-8fd6-119e-e562262d2934.png)

### 実装

#### 概念を実装する

Middlewareパターンの概念である`Input`、`Output`、`Middleware`、`Handler`をコードに起こす。入出力を表現したクラスである`Input`と`Output`は抽象的な名付けになっているが、例えばHTTPを扱うドメインなら、その界隈の言葉を借りて`HttpRequest`と`HttpResponse`のような具体的な名前でも構わない。

```php
class Input
{
}

class Output
{
}

interface Middleware
{
    public function process(Input $input, Handler $next): Output;
}

interface Handler
{
    public function handle(Input $input): Output;
}
```

`Middleware`は、パイプラインを継続する場合、`$next->process()`を呼ぶ。中断する場合は、呼ばずに`Output`を`return`する。ここがパイプラインの中断ができなかった[前稿]の設計から改善されたところである。

#### クライアントコード利便性のためのクラスを実装する

次に、クライアントコードがMiddlewareパターンを扱いやすくするための補助的なクラス`PipelineBuilder`を作る。これは、ミドルウェアとハンドラを結合しパイプラインを形成するコードを宣言的に書けるようにしてくれるクラスだ。

```php
final class PipelineBuilder
{
    /**
     * @var Middleware[]
     */
    private $middlewares = [];

    // ミドルウェアを登録するメソッド
    public function use(Middleware $middleware): self
    {
        array_unshift($this->middlewares, $middleware);
        return $this;
    }

    // 登録されたミドルウェアとハンドラを合成したハンドラを作るメソッド
    public function build(Handler $handler): Handler
    {
        foreach ($this->middlewares as $middleware) {
            $handler = new MiddlewareHandler($middleware, $handler);
        }
        return $handler;
    }
}
```

このクラスがあることでクライアントコードは次のように宣言的に書くことができる。

```php
$pipeline = (new PipelineBuilder)
    ->use(new OuterMiddleware())
    ->use(new InnerMiddleware())
    ->build(new ConcreteHandler());
$output = $pipeline->handle(new Input());
```

パイプラインを形成するためには、`Middleware`と`Handler`は同一視できなければならない。そこで、MiddlewareのインターフェイスをHandlerのインターフェイスに変換するアダプターを実装する。このクラスは、クライアントコードへ提供するものではなく`PipelineBuilder`で使うことを想定している。

```php
final class MiddlewareHandler implements Handler
{
    /**
     * @var Middleware
     */
    private $middleware;

    /**
     * @var Handler
     */
    private $handler;

    public function __construct(Middleware $middleware, Handler $handler)
    {
        $this->middleware = $middleware;
        $this->handler = $handler;
    }

    public function handle(Input $input): Output
    {
        return $this->middleware->process($input, $this->handler);
    }
}
```

#### ミドルウェアとハンドラの実装

あとは、入出力の加工や中核的な処理など、具体的なコードを書くだけだ。

```php
// 外側のミドルウェア
final class OuterMiddleware implements Middleware
{
    public function process(Input $input, Handler $next): Output
    {
        return $next->handle($input); // ここで$inputやOutputを加工する
    }
}

// 内側のミドルウェア
final class InnerMiddleware implements Middleware
{
    public function process(Input $input, Handler $next): Output
    {
        return $next->handle($input); // ここで$inputやOutputを加工する
    }
}

// ハンドラ
final class ConcreteHandler implements Handler
{
    public function handle(Input $input): Output
    {
        return new Output(); // ここで中核的な処理を行う
    }
}
```

#### クライアントコード

クライアントコードでは、作ったミドルウェアクラスとハンドラクラスを組み合わせてパイプラインを作り、パイプラインを実行するコードを書く。

```php
$pipeline = (new PipelineBuilder)
    ->use(new OuterMiddleware())
    ->use(new InnerMiddleware())
    ->build(new ConcreteHandler());
$output = $pipeline->handle(new Input());
```