PSR-7のミドルウェアは、何故ああなのか?

  • 16
    いいね
  • 2
    コメント

PSR-7のフレームワークとしては、zend-expressiveSlimPHP/Slim 3、そしてフレームワークではありませんがRelay/Relayなどが出てきました。

嬉しいのは、ミドルウェアのAPIが全て同じことです。

use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;

function __invoke(
    ServerRequestInterface $request, 
    ResponseInterface $response, 
    callable $next);

ひとつミドルウェアを作れば、上記3つのフレームワークのどれでも使いまわせることになります。

なんて素晴らしい!

でも、なぜ、ミドルウェアはこの形なのでしょう?

Express.JS

この形は、node.js用のウェブアプリケーションフレームワークであるExpress.jsのミドルウェアと同じ、という説明があります。でも、答えになってない!

疑問が頭から離れず夜も寝られなくて。やっといくつか理由を考えついたので書いてみます。

ServerRequestInterface $request

まずは最初の引数、$requestから。

これがないと、なんの処理をしていいのか皆目わからない。ので、必須なのは自明、と言った感じでしょうか。

ちなみに。

Symfonyや、同じコンポーネントを使っているStackPHPでのミドルウェアは、$requestだけにフラグが2つという構造になっています。(HttpKernelInterface参照)

例えばAPIを$requestだけにしてしまえば、簡単で分かりやすいのに、なぜ3つも引数が増えてしまうのだろう?というのが疑問の始まり。

ResponseInterface $response

次は$response

ミドルウェアに、なぜレスポンスオブジェクトが必要なのか?

PSR-7や最初に説明したミドルウェアの大事な点として、フレームワークに依存しないコードを書きたいというのがあります。

例えば認証用ミドルウェアなどは、認証に失敗した場合にレスポンスを返す場合があります。フレームワークに依存しないということは、オブジェクトを生成できない、と同じことだと気が付きました。

実際にPSR-7にはオブジェクト生成に関するAPIはありません

なので、レスポンスオブジェクトを渡してもらう必要があるわけです。

callable $next

最後の$nextというコーラブル変数。これは、こんな感じで使います。

function __invoke($req, $res, $next) {
    // ここで前処理をする!
    if($next) {
        $res = $next($req, $res); // next, please!
    }
    // ここで後処理もできる!
    return $res;
}

この形の良い所は、次のミドルウェアを呼ぶ前と後の両方で処理を書けることです。

ところで$nextがない場合は、どうやって後処理をするのでしょう?

Symfonyの場合は、TerminableInterfaceというのを使います。ミドルウェアがTerminableInterfaceを実装している場合は、アプリの実行が終了したら、terminateメソッドが呼ばれることになってます。

多分。実は確認できないので、間違ってるかも。

突然、面倒な感じになってきました

また前処理、後処理以外でも、場合によっては次の処理を呼ばないという実装も簡単に実装できます(コメント参照)。外側のフレームワークに依存することなく、とても柔軟性の高い処理を実装できるといえると思います。

最後に

今のAPIで最初に疑問に思ったのは、$nextが入ることでした。本来の処理に必要な$request$response以外に、次のミドルウェアを呼び出すという、別の責務がミドルウェアに発生するからです。

このAPIでミドルウェア作ると、他の形に応用しずらい気がするんですよね。理由は$nextが入り込んでくるから。

でも、この形が一番シンプルになる気がしてきました。やはり使われるコードというのは理由があるのですね。後から考えると当たり前な結論な気がしますが、考えている間は面白かったです。

なお、この形が本当に標準のようになるかは、まだ分かりません。