Create a gist now

Instantly share code, notes, and snippets.

自作フレームワークでのミドルウェア設計

TuumPHP:ミドルウェア設計

必要な機能から、APIに何が必要か考えてゆきます。

PSR-7の採用

新しく策定されたPSR-7のHttp-Messageを採用します。

ミドルウェアの設計

アプリケーションAPI

まずはウェブアプリケーションとは何かを考えるところから、設計を始めました。もちろん答えはたくさんありますが、この一行が簡単に表していると思います。

$response = $app($request);

この$appがウェブアプリケーションです。$request$responseはPSR-7のインターフェースを実装したオブジェクトになります。

これから、アプリケーションAPIが決まります。

interface ApplicationInterface {
    public function __invoke($request);
}

返り値はレスポンスオブジェクトになります。場合によってはテキストや配列のこともあると思います。何か返り値があれば、ミドルウェアの実行は終了です。

ここをスタートにAPIを設計してゆきます。

ミドルウェアAPI

次は、アプリケーションを数珠つなぎで実行できるようにいします。こんなコードで使えるようにしましょう。

$response = App::forge()
    ->push(new Middleware)
    ->push(new Middleware2)
    ->__invoke($request);

そこでインターフェースはこうなりました。

interface MiddlewareInterface extends ApplicationInterface
{
    /**
     * @param ApplicationInterface $handler
     * @return $this
     */
    public function push($handler);

    /**
     * @param ApplicationInterface $handler
     * @return MiddlewareInterface
     */
    public function prepend($handler);
}

ミドルウェア単体でもウェブアプリケーションとして使えるよう、ApplicationInterfaceを継承してます。APIのpushはミドルウェアスタックの一番最後に、appendはスタックの一番最初に、ミドルウェアを追加します。

想定する使い方です。

class AllGetFooFooMiddleware implements MiddlewareInterface {
    public function __invoke($request) {
        $request  = $request->withMethod('get');
        $response = $this->next($request); // call next middlware!
        return $response->getBody()->write('<div>FooFoo</div>');
    }
}

コードが実行している内容は冗談ですけど、何をするかわかると思います。まず自分の必要な処理を行ってから次のミドルウェアを実行します。次のミドルウェアは$this->nextにあります。戻ってきたレスポンスに対して、必要ならば更に処理を追加します。

これでミドルウェアの基本APIは完成です。

フィルターとリリース

ミドルウェアにするほどではないけれど、特定の条件で走らせたい処理は多くあります。例えば認証やCsRfトークンのチェックなどです。

同様に、レスポンスに対して後処理をかけたい場合があります。例えばビュー(テンプレート)に対してログなどを追加する場合です。

こういう単純な処理に関しては、ミドルウェアにするのではなく、単独で実行できるようにします。

フィルターAPI

まずはフィルターAPIについて検討します。

考え方としては、アプリケーションAPIと同じ形で、リクエストを受け取って、レスポンスを返します。 またフィルターはクロージャーとして使いたい場合が多いと思うので、__invokeメソッドで実装します。

$requestの受け渡し

ただし、ミドルウェアとしてではなく、単独で走らせる場合、リクエストに対する変更処理が問題となります。PSR-7でのリクエストは不変オブジェクトだからです。フィルター内でリクエストを修正した場合、元のミドルウェアに戻す必要があります。このため、簡単なクロージャー$nextを導入することにします。

interface FilterInterface {
    /**
     * @param Request       $request
     * @param callable|null $next
     * @return null|Response
     */
    public function __invoke($request, $next = null);
}

フィルターAPIが想定する使い方です。 例として、全てのリクエストをGETメソッドに変更してしまうフィルターを書いてみました。

class MakeEverythingGetFilter implements FilterInterface {
    public function  __invoke($request, $response = null, $next = null) {
        return $next ? $next($request->withMethod('get')) : null;
    }
}

リリースAPI

一方、レスポンスに対して処理を行うリリースAPIについて考えてみます。単純にリクエストとレスポンスがあれば足りるはずです。

interface ReleaseInterface  {
    /**
     * @param Request  $request
     * @param null|Response $response
     * @return null|Response
     */
    public function release($request, $response);
}

想定する使い方です。レスポンスは返す必要があります。 例として、レスポンスのボディの最後に「FooFoo」を追加するリリースです。

class FooFooRelease implements ReleaseInterface {
    public function release(request, $response) {
        return $response->getBody->write('<div>FooFoo</div>');
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment