LoginSignup
33
25

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-04-18

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

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

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

Middlewareパターンとは

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

貼り付けた画像_2018_04_18_12_20.png

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

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

Middlewareパターン

設計

クラス図0.png

実装

概念を実装する

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

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()を呼ぶ。中断する場合は、呼ばずにOutputreturnする。ここがパイプラインの中断ができなかった前稿の設計から改善されたところである。

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

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

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;
    }
}

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

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

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

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

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

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

// 外側のミドルウェア
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(); // ここで中核的な処理を行う
    }
}

クライアントコード

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

$pipeline = (new PipelineBuilder)
    ->use(new OuterMiddleware())
    ->use(new InnerMiddleware())
    ->build(new ConcreteHandler());
$output = $pipeline->handle(new Input());
33
25
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
33
25