2016年1月16日土曜日

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

Qiitaに投稿しました。


PSR-7でよく見るミドルウェアの引数(シグネチャ)に関する話です。どうして、あの形が使われるのか考えてみました。

なんというか、LTネタ的な話です。

2016年1月7日木曜日

2015年を振り返って2(Tuum/Respond開発日誌メモ)

2015年を振り返って、の続きです。

昨年の2015年6月から始めたのがTuum/Respondというプロジェクト。開発方針を転換して、PSR-7ベースのマイクロフレームワークに後付でViewの機能を追加するパッケージを目指しました。

面白かったのは、開発してゆくにつれ、どんどんコードが簡単になってゆきました。機能を絞ったことで、何をしているか理解できたからと思います。

いったい何をするのか。
自分で理解した形で説明します。
使い方とかは、Githubのページを見てください。

要するに次の2つを管理するパッケージです。

  • ViewData: ビューを作るのに必要な情報を運ぶデータ転送オブジェクト。これに必要な情報を設定してゆく。
  • ViewInterface: ViewDataからテンプレートなどでビューを構築して、レスポンスを返すインターフェース。

これだけなのですね。

ViewerInterface


Tuum/Respondの肝は、様々な方法でレスポンスオブジェクトを構築すること。

レスポンスを構築する、ということは、ビューを構築する、とほぼ同じです。ということで、次のインターフェースが出来ました。

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Tuum\Respond\Responder\ViewData;
interface PresenterInterface {
    /**
     * renders $view and returns a new $response.
     *
     * @param ServerRequestInterface $request
     * @param ResponseInterface      $response
     * @param ViewData               $view
     * @return ResponseInterface
     */
    public function render(
ServerRequestInterface $request,
ResponseInterface $response,
$view);
}

リクエストとレスポンスのオブジェクト、それとビュー構築に必要なデータの入ったViewDataというオブジェクトを受け取ります。適宜ボディを構築して、レスポンスオブジェクトを返します。

ついでに同じインターフェースで、ちょっと動きの違うAPIを定義しました。

interface ErrorViewInterface extends PresenterInterface {}
interface ViewerInterface extends PresenterInterface {}

この3つのインターフェースの使い分けは、


  • PresenterInterface:
    $viewを受け取って、レスポンスを返す汎用インターフェース。
  • ViewerInterface:
    テンプレートからビューを描画することで、レスポンスを返す。
  • ErrorViewInterface:
    ステータスコードに対応するテンプレートを描画することで、レスポンスを返す。エラー用。


それぞれのインターフェースを実装したクラスを作っておいて、呼び出します。次のコードを見ると、どれがどのAPIに対応するか、すぐわかると思います。

Response::view($req)->view('template/filename');
Response::error($req)->forbidden();
Response::present($req)->call(MyPresenter::class);


ViewData


もう一つの肝が、ViewDataというデータ転送オブジェクト(DTO)。Tuum/Respondとは、要するにViewDataを設定しつつ持ちまわるためのライブラリです。そして、最後にPresenterInterfaceのオブジェクトを呼び出します。

このコードだと、$viewを直接いじってからテンプレートを描画します。

Respond::view($req)
  ->withViewData(function($view) use($value) {
    return $view->setData('key', $value);
  })->view('my/template');

あるいはRedirectでは、ViewDataをSessionのフラッシュに保存します。

Respond::redirect($req)
  ->withMessage('hello')
  ->to('/my/path');

次のリクエストで、フラッシュからViewDataを読み込むことで、データを簡単に利用することが出来ます。


インターフェース遷移


想定しているマイクロフレームワークとTuum/Respondについて、インターフェースという切り口で考えて見たら面白かったので、書いてみます。

最初に、リクエストとレスポンスから**ミドルウェア**が始まります。`$next`が次のミドルウェアですね。

middleware($req, $res, closure $next);

ミドルウェアの最後にルーターが走り、実行する**コントローラー**を呼び出します。`$args`がルートパターンでの変数です。此処から先が、ユーザーコードに入ってゆく感じです。

controller($req, $res, array $args);

コントローラーでは、ドメインを操作したり、必要なビューを構築してレスポンスを返します。

ビューの構築を手助けするのが、`Tuum/Respond`で、次のようなインターフェースになります。

viewer($req, $res, ViewData $view);

$viewに情報を設定する、テンプレートを描画する、レスポンスを構築する、など様々な処理を行えます。

なんかMiddleware-View-Controller (MVC)みたい。

開発は楽しい


開発していると、動くところまで作るのは楽しいとよく言われます。

その後でも、リファクタリングしたり作りこむのも、理解が深まったり新しい見方が出てきたりして別の楽しさがあります。自分の趣味のパッケージでないと中々出来ないので、これからも続けてゆきたいです。

2015年を振り返って(Tuum開発日誌メモ)

2016年になったので、自分用のメモとして去年のTuumPHP開発を振り返ってみます。

Tuum/Webの開発


思えば、Tuum/Webを作り始めたのは2014年の11月頃。一度ぐらい満足できるフレームワークを自作してみたい、と思い立ってしまい、

  • 良いとされる設計を積極的に使う(PSR-7、ミドルウェア、DIコンテナなど)、
  • そのうえで、自分の欲しい機能を実装する、

という方向で作り始めました。

2015年5月頃には、ほぼ完成したのですが、どうも気にかかる点が出来てしまいました。おそらく、次の二点。

1. 思った以上に複雑になった。
2. 欲しいのはフレームワークではなかった。

なので、基本ボツに。

えぇ〜!
せっかく作ったのに。

複雑すぎる


一つ一つの設計は自分なりに納得して作ってるので、妙な動きはしてはいないのですが。思った以上に動きが複雑になってしまいました。原因を考えたのですが、次の2点になると思います。

  • 良いと思える設計から少しずれた、
  • 自分が欲しい機能について、理解してなかった、

一番最初にミドルウェアの引数をどうするか悩んだのですが、できるだけ簡単な形を選んでしまいました。使う分には簡単そうに見えるのですが、フレームワーク側が複雑になってしまいました。

自分が採用した形が、

$response = $middleware($request);

の形。
これだと、帰ってくる$responseに対して処理を行うことが出来ません。そのため、戻りループ用の別インターフェースを作って、としているうちにコードが「ちょっと」複雑に。ちなみに、Symfonyも似たようなことをしてるので、それほど悪い実装ではないはず。

一方、ほぼ標準になりつつある形が

$response = $middleware($request, $response, $next);

$nextとか面倒だなと思ってました。が、フレームワーク作ってみて、このAPIの良さがわかってきました。

もう一点は、次のTuum/Respondの開発をして分かりました。自分の欲しかった機能を十分に理解できてなかったからです。単体のパッケージを作ることで、機能についてじっくりと考えられたわけです。


欲しいのはフレームワークではない


で、この理由。

勉強のために作ったというのもあるので、そこそこ良いフレームワークができたら、もういいかなという気になった。と言うのはあります。

さらに、PSR-7ベースで、素晴らしいマイクロフレームワークがいくつも出てきてます。Slim3とかExpressive。それにRelayもミドルウェアディスパッチャーとしてよく出来てます。

結局、自分が欲しいのは、先に書いた「欲しい機能」の部分であり、フレームワークではありません。すでにあるマイクロフレームワークを利用して、自分の欲しい機能が追加できれば、十分なわけです。

コーディングの勉強には最適


とはいえ、フレームワークを自作するのは、コーディングの練習には最適ですね。やってよかったなと思ってます。

ということで、次の開発話に続きます