StackPHPとミドルウェアについて調べてみた

ちょっと[StackPHP](http://stackphp.com/)についてを調べたので、まとめを書いてみます。

Middlewareとは何だろうと思い、調べていると[StackPHPのウェブサイト](http://stackphp.com/)やスライド（[Do You Stack Up? (slideshare)](https://speakerdeck.com/h4cc/stackphp-do-you-stack-up)）が出てきます。考え方もコードも実に簡単なのですが、今ひとつ重要性がピンと来ませんでした。

が、このブログ「[HttpKernel middlewares](https://igor.io/2013/02/02/http-kernel-middlewares.html)」を読んだら腑に落ちました。ということで、このブログを元に自分なりに理解した内容をまとめます。


HTTPの動作を理解する
-----------

### HTTP

HTTPの基本は何でしょう？
簡単にPHPで表すと…

```php
function call($request) {
  /* do something */
  return new Response($contents);
}
```

これだけ！

HTTPのリクエスト（```$request```）を受け取り、結果を```Response```として返す。

レスポンスを返さなくても、リクエストを操作する場合もあります。例えば、IPアドレスから送信元の国を探してリクエストに設定する、などの機能も考えられます。

この基本動作を元にしてミドルウェアを構築します。

### MiddleWare

先のコードをミドルウェアに変えます。

```php
class MiddleWare {
  protected $app;
  function __construct($app) {
    $this->app = $app;
  }
  function call(Request $request) {
    /* do your stuff */
    $response = $this->app->call($request); // call next middleware
    /* do more stuff if necessary */
    return $response;
  }
}
```

あまり変わってませんが…
ポイントはcallメソッドの中で次のミドルウェアを呼び出していること（```$this->app->call```）です。

このコードだと、こんな風に走らせることになります。

```php
$app = new MiddleWare(new YourMiddleware(...));
$response = $app->call(Request::createFromGlobals());
```

こうやってミドルウェアを次々と呼び出すことで、処理を自由に積み重ねることが出来ます。必要なミドルウェアを適当に途中に挟んでも、大きな問題なく動くのも理解できます。

ちなみにミドルウェアでレスポンスを返すことも出来ます。認証ミドルウェアなら、認証してない場合は別URLに戻すレスポンスを返す、などできます。逆に、レスポンスを元にログをとったりキャッシュに保存したりすることも自由自在です。


StackPHPについて
--------

このHttpの基本をもとにStackPHPは構築されています。

### Middlewareサンプル

StackPHPでのミドルウェアの書き方です。

```php
class MyMiddleWare implements HttpKernelInterface {
  protected $app;
  function __construct($app) {
    $this->app = $app;
  }
  function handle(Request $request, $type, $catch) {
    /* do your stuff */
    $response = $this->app->handle($request, $type, $catch); // call next middleware
    /* do more stuff if necessary */
    return $response;
  }
}
```

あまり変わってませんね。

大事な点は、StackPHPではSymfonyの```Request```と```Response```クラスを使います。同じくミドルウェアには```HttpKernelInterface```を利用するので、```handle```というメソッドで処理を行います。

コンストラクターの最初の引数に、次のミドルウェアを受け取るのも大事なポイントです。これでスタック可能になります。


### アプリケーションの構築

実際のStackPHPの使い方です。

[Stack/Builder](https://github.com/stackphp/builder)を利用します。githubに書いてあるコードを少し簡単にしてあります。ちなみにSilexをウェブアプリとして使う場合です。

```php
$app = new Silex\Application();
/* set up $app */

$stack = (new Stack\Builder())
  ->push('Stack\Session')
  ->push('More\Middleware')
  ->resolve($app); // 最後はresolveを呼ぶこと

// ミドルウェアとアプリ実行
$request  = Request::createFromGlobals();
$response = $stack->handle($request);
$response->send();
$stack->terminate($request, $response);
```

最後にresolveを呼ぶことで、スタックを構築します。


### たった３つのルール

まとめると、次の簡単な３つのルールを守るだけでミドルウェアを作れます。

1. SymfonyのHttpKernelInterfaceを実装する。
2. コンストラクターの最初の引数として次のミドルウェアを受け取る。
3. handleメソッド内で次のミドルウェアを呼び出す。

これだけの簡単なルールで、

*   安全で再利用しやすい、
*   フレームワークに依存しない、

ミドルウェアができる、というのがStackPHPの特徴です。


StackPHPの未来
------

StackPHPは、LaravelやDrupalにも採用されているので、これからも様々なミドルウェアが登場すると思います。しかもフレームワークに依存しないので、さまざまな場合で使えるようになると思います。


### Symfonyへの依存問題

フレームワークのクラスに依存しないのは確かですが、Symfonyのインターフェースだけには依存します。

これが原因で問題が起きたことはあるようです。CakePHP3の開発が進んだ段階でStackPHPの採用について検討をしたようですが、すでに独自のリクエストやレスポンスクラスを採用した後だったため、Symfonyのクラスに依存させると問題が多くて断念したという話です[CakePHP3: Improve middleware (github issue #2544)](https://github.com/cakephp/cakephp/issues/2544)。

### PHP-FIGによる標準化

今は、PHP-Figにより、Httpのインターフェース標準化作業が進められています（[PHP-FigによるHTTPプロトコル](https://github.com/php-fig/fig-standards/blob/master/proposed/http-message-meta.md)）。

標準が決まれば、StackPHPも採用すると考えられます。すると、さらに互換性が高くなり、StackPHPの採用も進むと考えられます。

### 豊富なミドルウェア

StackPHPのウェブサイトにある[ミドルウェアの一覧](http://stackphp.com/middlewares/)です。豊富、かどうかはおいておいて、今後も増えてゆくといいですね。