[追記:2018/03/04] ミドルウェアの仕様が「PSR-15: HTTP Server Request Handlers」として標準化されました。ここで説明した内容と大きく異るインターフェースが採用されてます。採用の経緯はこちらのPSR-15ブログ(英語)が参考になります。
PSR-7のフレームワークとしては、zend-expressive、SlimPHP/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
が入り込んでくるから。
でも、この形が一番シンプルになる気がしてきました。やはり使われるコードというのは理由があるのですね。後から考えると当たり前な結論な気がしますが、考えている間は面白かったです。
なお、この形が本当に標準のようになるかは、まだ分かりません。