必要な機能から、APIに何が必要か考えてゆきます。
新しく策定されたPSR-7のHttp-Messageを採用します。
まずはウェブアプリケーションとは何かを考えるところから、設計を始めました。もちろん答えはたくさんありますが、この一行が簡単に表していると思います。
$response = $app($request);
この$app
がウェブアプリケーションです。$request
と$response
はPSR-7のインターフェースを実装したオブジェクトになります。
これから、アプリケーションAPIが決まります。
interface ApplicationInterface {
public function __invoke($request);
}
返り値はレスポンスオブジェクトになります。場合によってはテキストや配列のこともあると思います。何か返り値があれば、ミドルウェアの実行は終了です。
ここをスタートに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と同じ形で、リクエストを受け取って、レスポンスを返します。 またフィルターはクロージャーとして使いたい場合が多いと思うので、__invokeメソッドで実装します。
ただし、ミドルウェアとしてではなく、単独で走らせる場合、リクエストに対する変更処理が問題となります。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について考えてみます。単純にリクエストとレスポンスがあれば足りるはずです。
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>');
}
}