2015年7月6日月曜日

Responder Module:フレームワークを分割して考える

作ってきたTuum/Respondが何なのか、良い説明を思いついたので書いてみます。

フレームワークの機能


最初に、フレームワークの機能を次のように分割してみます。

  1. ブートストラップとアプリ構築。
  2. ルーティングとディスパッチ。
  3. モデル/ドメイン実行。
  4. ビューなどのリスポンス構成。

この中で、最も重要と思うのが(3)のドメイン部分。これこそがプロジェクトの本質で、一番時間を使って開発したい部分。

フルスタック・フレームワークとは、(3)以外のほぼすべての機能を使いやすい形にまとめあげておくことで、ドメインの開発に集中するためのものだと考えてます。

一方、俗にいう「マイクロフレームワーク」というのがあります。これは、(1)と(2)の部分を担っていると考えるとスッキリします。返信するのが簡単なJSONであれば、複雑なビューなどを省くことで、コードが簡潔になります。

Responder Module


こんなふうに分割してみると、この(4)だけをターゲットにしたミニフレームワークみたいなものがあってもいいのでは?

と考えて、この(4)の機能の部分を作ってみたのが「Tuum/Respond」というモジュールです。

ついでに、この部分に「Responder Module」と名前をつけてみました。もう名前があるのかもしれないけれど、見つからなかったので、適当に呼んだだけです。

Tuum/Respond


Tuum/Respondの使い方としては、例えばSlim frameworkのようなマイクロフレームワークと一緒に使うとかを想定してます。

レスポンスの種類


想定しているレスポンスの種類です。

  • View:HTMLなどのコンテンツのあるレスポンス。
  • Redirect:別URIへのリダイレクト。
  • Error:エラー。コンテンツがある場合もある。

それぞれHTTPステータスの200番台、300番台、そして400と500番台に対応します。

どのレスポンスを返す場合でも同じAPIが使えます。更には、リダイレクトした次のビューにデータを渡す場合でも、同じAPIになります。つまり、こんなコード。

$app = new App(); // some micro-framework app. 

// redirects to /jumped.
$app->get('/jumper', function($request, $response) {
    return Respond::redirect($request, $response)
        ->withMessage('bad input!') // <- set up info.
        ->withInputData(['some' => 'value'])
        ->withInputErrors(['some' => 'bad value'])
        ->toPath('/jumped');
    });

// ...and this is jumped.
$app->get('/jumped', function($request, $response) {
    return Respond::view($request, $response)
        ->asView('template'); // with the 'welcome!' message.
});

どこかで見たコードですね。

必要なサービスとインターフェース


Tuum/Respondの全ての機能を使うには、外部サービスが必要です。全てのサービスにはインターフェースが定義されています。

  • ViewStreamInterface:
    ビュー(テンプレート展開)用API。PSR7のStreamInterfaceを継承していて、内部でテンプレートを展開。
  • SessionStorageInterface
    セッションおよびフラッシュ用API。ほぼAura.Sessionのsegmentと一致してます。
  • ErrorViewInterface
    エラー表示用API。

これらのサービスを実装して適宜設定することになります。

もう少し細かい説明は前にブログに書いてあるので、そちらも参考にしてみてください。

2015年7月1日水曜日

汎用ページネーション(PSR7も使えます)

汎用ページネーションのパッケージを作ってみた。
一応、PSR7のServerRequestInterfaceも使えます。

セッションを使って、ページ番号やフォーム入力を覚えるのが特徴。簡単に最後と同じページを作成できます。

何故作ったのか?


仕事でDoctrine2を採用してみたのだが、いい感じのページネーションがなかったので作ってみた。ただし仕事には間に合わなかったので、実サイトでの実績はないです。

こういうページネーションに、どのぐらい需要があるのかわからないけど、この際なのでパッケージとして作ってみた。

ライセンス:MIT
PSR準拠 :Psr-1、Psr-2、Psr-4

使い方


インストール


composerで。

$ composer require "wscore/pagination"


Pagerオブジェクト


Pagerオブジェクトを最初に作ります。

use WScore\Pagination\Inputs;
use WScore\Pagination\Pager;

// construction
$pager = new Pager(['_limit' => 15]);

// set up pager using Psr-7 ServerRequestInterface.
$pager = $pager->withRequest($request);
// or from global data. 
$pager = $pager->withQuery($_GET, '/find');

次に、データベースへのクエリなどを実行します。callメソッドにクロージャーを渡してください。引数はInputsというクラスのオブジェクトです。

$inputs = $pager->call(
    function(Inputs $inputs) use($pdo) {
        // query the PDO!
        $found = $pdo->prepare("SELECT * FROM tbl WHERE type=? and num>? OFFSET ? LIMIT ?")
            ->execute([
                $inputs->get('type'),
                $inputs->get('num'),
                $inputs->getOffset(),
                $inputs->getLimit(),
            ])
            ->fetchAll();
        $inputs->setList($found);
    });
$found = $inputs->getList();
$type  = $inputs->get('type');

後は、クロージャー内で必要な処理を書き込みます。
Inputsオブジェクトから、オフセットとリミットや、フォームの入力を読み取れます。

HTML出力


最後に、HTMLへの出力は、Paginateというオブジェクトを使います。

use WScore\Pagination\Html\Paginate;

$inputs->paginate(new Paginate());
echo $inputs->__toString();


すると、こんなページネーションが表示されます。


リクエストの仕方

ここが、このパッケージの肝になります。
「_page」を使いこなします。

1.検索条件フォーム


ページネーションを行う場合は、検索条件をフォームで選ぶ場合が多いと思います。その場合は、普通にフォームを作ってください。

<form>
<input type="text" name="type" />
<input type="integer" name="num" />
<input type="submit" />
</form>

注意:_pageは使わないで下さい。
フォームの内容は自動でセッションに登録されます。セッションは事前に走らせておいて下さい。

2.ページの指定


指定するページを表示するには_pageにページ番号を指定します。

GET /find?_page=2

検索に使う条件はセッションに保持されているので、同じ条件でオフセットを替えてクエリを実行できます。

3.最後の検索画面


検索画面を表示する際、_pageにページ番号をなしでGetしてみてください。

GET /find?_page

すると、セッションから最後のページ番号と検索条件を読みだします。

To Do


動くはずですが、実際に使ったことがないのでAPIなど変わるかもしれません。なのでアルファリリースです。