BEAR.Sundayを触ってみたメモ (3)はじめてのページリソース
※この記事は2012年7月19日時点のsandboxアプリケーションを元に書いたものなので、あまり参考にはなりません。
BEAR.Sundayを触ってみたメモ (1)インストール
BEAR.Sundayを触ってみたメモ (2)はじめてのアプリケーションリソース の続きです。
今回は はじめてのwebページ と、はじめてのリソースリクエスト をなぞってみます。
どちらもページリソースですが、前者はアプリケーションリソースを利用しない単純なページリソース、後者は前回触ってみたGreetingアプリケーションリソースを利用するページリソースになります。
ページリソース
まず最初にアプリケーションリソースを利用しない最小限のページクラスを作成します。 (モデルを使わないコントローラーだけのHelloWorldページのようなページです。)
http://code.google.com/p/bearsunday/wiki/my_first_web_page
BEARでは、いわゆるHelloWorldもリソースとして実装するわけですね。
(BEARの名前の由来が「Because Everything is A Resource.」ということでした)
前回はチュートリアルで使われるアプリケーションリソース sandbox\Resource\App\First\Greerting がすでにリポジトリに用意されていたのですが、今回扱うページリソースはまだ用意されていません。
チュートリアルに習って作成しますが、せっかくなので前回のアプリケーションリソースと同様、クエリーストリングからパラメータを受け付けるようにしてみます。
Greetingページリソース
URI page://self/first/greeting?name=名前
リソースクラス名 sandbox\Resource\Page\First\Greerting
リソースクラスファイル apps/sandbox/Resource/Page/First/Greeting.php
アプリケーションリソースの基底クラスは BEAR\Resource\AbstractObject でしたが、ページリソースは BEAR\Framework\Resource\AbstractPage を継承して作成します。
(前者が BEAR.Resource として別パッケージで提供されており、composer経由でvendorディレクトリに配置されるのに対して、後者はBEAR.Sundayのpackageディレクトリに配置されている)
チュートリアルで「スロット」と呼ばれているものが、ページリソースのbodyプロパティの配列要素になります。
GETリクエストで name パラメータを受け付けるようにしてみました。デフォルト値は"World"です。
前回と同様、普段開発で使っているXAMPPとは別に、CLI&ビルトインサーバ専用としてインストールしたPHP5.4を使います。
チュートリアル通り、CLIからapi.phpを実行して、ページリソースにリクエストしてみます。
$ cd apps/sandbox/htdocs $ php api.php get page://self/first/greeting 200 OK cache-control: no-cache date: Fri, 20 Jul 2012 09:50:00 GMT content-type: text/html; charset=UTF-8 [BODY] greeting:Hello, World
bodyの部分が、スロット名:値 という形式で表示されました。
nameパラメータ付きで呼んでみます。
$ php api.php get page://self/first/greeting?name=BEAR 200 OK cache-control: no-cache date: Fri, 20 Jul 2012 09:53:11 GMT content-type: text/html; charset=UTF-8 [BODY] greeting:Hello, BEAR
OKです。
次はリソーステンプレートを用意してみます。
URI app://self/first/greeting?name=名前
リソースクラス名 sandbox\Resource\Page\First\Greerting
リソースクラスファイル apps/sandbox/Resource/Page/First/Greeting.php
リソーステンプレート apps/sandbox/Resource/Page/First/Greeting.tpl
テンプレートの中身はチュートリアルとほぼ同じものです。
(escape修飾子のみ追記しています)
チュートリアル通り、CLIからweb.phpを実行して、ページリソースにリクエストしてみます。
$ php web.php get page://self/first/greeting?name=X\'SS run:sandbox mode=Dev cahce=enable 200 OK x-interceptors: {"onGet":["BEAR\\Framework\\Interceptor\\Logger"]} x-query: {"name":"X'SS"} x-params: ["X'SS"] x-args: ["X'SS"] x-execution-time: 0.016656875610352 x-memory-usage: 19408 cache-control: no-cache date: Fri, 20 Jul 2012 09:59:47 GMT content-type: text/html; charset=UTF-8 [BODY] <!DOCTYPE html> <html lang="en"> <body> <h1>Hello, X'SS</h1> </body> </html>
BODYとして、想定した通りのHTMLが返ってきています。
次に、同じくWebブラウザで確認します。
以下の内容で、sandboxアプリケーションのWebAPIをビルトインサーバで起動するためのショートカットを作成します。
リンク先 C:\php\php.exe -S localhost:8088 web.php
作業フォルダ C:\Users\k_horii\Dropbox\Documents\Projects\BEAR\BEAR.Sunday\apps\sandbox\htdocs
ビルトインサーバを起動して、ブラウザからアクセスしてみます。
http://localhost:8088/first/greeting?name=X'SS
<!DOCTYPE html> <html lang="en"> <body> <h1>Hello, X'SS</h1> </body> </html>
レスポンスコード 200 とともに、CLIで確認したBODYと同じHTMLが返され、"Hello, X'SS"と表示されました。
CLIとビルトインサーバでどちらも同じスクリプトを呼んでいるのですが、それぞれに合ったレスポンスが返されるのは、どういう仕組みなんでしょうか。
web.phpのコードを追ってみました。
// run mode $runMode = App::RUN_MODE_DEV; $useCache = true; error_log('run:' . __NAMESPACE__ . " mode={$runMode} cahce=" . ($useCache ? 'enable' : 'disable')); // Application $app = App::factory($runMode, $useCache);
sandboxアプリケーションが定数 __NAMESPACE__ で、 アプリケーションの実行モードとして定数 App::RUN_MODE_DEV が指定されています。
useCacheは、APCのキャッシュを利用するかどうかのフラグでしょうか。
これらの設定を利用して App:factory() が実行され、sandboxのアプリケーションオブジェクトが返されるようです。
web.php続き
// Route $globals = (PHP_SAPI === 'cli') ? new Globals($argv) : $GLOBALS; // $router = require dirname(__DIR__) . '/scripts/router/standard_router.php'; $router = new Router; // Dispatch list($method, $pagePath, $query) = $router->match($globals); // Request try { $page = $app->resource->$method->uri('page://self/' . $pagePath)->withQuery($query)->eager->request(); } catch (Exception $e) { $page = $app->exceptionHandler->handle($e); }
アプリケーションオブジェクト取得後は、いわゆるリクエストルーティング処理のようです。
$GLOBALS(CLIの場合は $argvを元に生成される、ArrayObjectを継承したGlobalsオブジェクト)を利用して、Routerがリクエストメソッド、ページパス、クエリーストリングを検出し、アプリケーションオブジェクトがそれらを元にリクエストを行った結果、ページリソースが返されています。
($ php web.php get page://self/first/greeting?name=BEAR で呼ばれる部分)
$app->resource の実体は BEAR\Resource\Resource で、BEAR.Resource « BEAR Blog の図「レイヤード・リソース」における Resource client のようです。
いわばサーバーサイドのRESTクライアントでしょうか?公式Wikiにも リソースクライント として、独立した解説があります。
BEARにおいては、全てのリソースオブジェクトを繋ぐ、とても重要なオブジェクトのようです。
web.php続き
// Transfer $app->response->setResource($page)->render()->prepare()->send();
$app->response の実体は BEAR\Framework\Web\SymfonyResponse で、なんでSymfony?と思いつつ、呼ばれているメソッドのコードを読んだどころ、以下のようなことが分かりました。
setResource()…リソースオブジェクト(ここではページリソース)をセット
render()…セットされたリソースオブジェクトの __toString() が実行され(ここでは BEAR\Resource\AbstractObject::__toString())、リソースオブジェクトが持つレンダラー(BEAR\Resource\Renderableを実装したクラス)のrender()メソッドを自身(リソースオブジェクト)を引数として実行し、戻り値がリソースオブジェクトのviewプロパティにセットされる
prepare()…リソースオブジェクトviewプロパティ、codeプロパティ、headersプロパティの配列表現を引数として Symfony\Component\HttpFoundation\Response を生成し、そのprepare()メソッドが実行される
send()…CLIからの実行ではリソースオブジェクトの内容が文字列として出力され、ブラウザから呼ばれる場合は Symfony\Component\HttpFoundation\Response のsend()メソッドが実行される
CLIの場合は文字列の出力処理が独自実装されていて、それ以外の場合は Symfony HttpFoundationコンポーネントのResponseクラスに一任されてるんですね。
また、レンダラーは今回のCLIからの実行では BEAR\Framework\Resource\View\DevRenderer が使われていて、そこから BEAR\Framework\Resource\View\Render を通じて templateEngineAdapter(BEAR\Framework\Module\TemplateEngine\SmartyModule\SmartyAdapter)による出力結果が返されています。
(他のテンプレートエンジン用のモジュールは BEAR\Framework\Module\TemplateEngine\SmartyModule 以下の内容を参考にすれば良さそうな感じ?)
先ほどの公式Wikiにも、このあたりの解説がありました。
リソースレンダラー
リソース状態(リソースの値)を表現にするのはリソースが保持するリソースレンダラーの役割です。リソースの利用者ではありません。例えばMVCでコントローラーがモデルからデータを取得して(あるいはモデルをデータとして扱って)ビューに渡すのと違い、リソースはそれぞれ内部のビュー(リソースレンダラー)を保持します。
レンダラーはDependency Injectorによってインジェクトされます。DIの設定を行うアプリケーションモジュールでこのレンダラーを別のレンダラーにする事ができます。例えば開発中は開発情報が付加表示されるDevRendererがインジェクトされています。Web API用には例えばJSONをレンダリングするJsonRendererをインジェクトします。
http://code.google.com/p/bearsunday/wiki/resource_client
はじめてのリソースリクエスト
http://code.google.com/p/bearsunday/wiki/my_first_web_page_2
あまり深追いしても先に進めなくなりそうなので、次はいよいよリソースクライアントを利用して、ページリソースからアプリケーションリソースを呼んでみます。
といっても、コードの修正はそれ程多くないようです。
@Injectアノテーションでリソースクライアントをインジェクトしてもらうのですが、そのための処理は BEAR\Framework\Inject\ResourceInject というTraitで提供されており、これを use するだけでいいんですね。
後は先ほどweb.phpで見たリソースクライアントを使ったリクエストと同様に、メソッド、URI、クエリーパラメータを指定してリクエストした結果を、greetingスロットにセットします。
手本通り、今度は web.php ではなく api.php を呼んでみます。
$ php api.php get page://self/first/greeting?name=X\'SS 200 OK cache-control: no-cache date: Fri, 20 Jul 2012 10:31:50 GMT content-type: text/html; charset=UTF-8 [BODY] greeting:"Hello, X'SS"
OKです。
次に、web.php で呼んでみます。
$ php web.php get page://self/first/greeting?name=X\'SS run:sandbox mode=Dev cahce=enable exception 'BEAR\Framework\Exception\TemplateNotFound' with message 'C:\Users\k_horii\Dropbox\Documents\Projects\BEAR\BEAR.Sunday\apps\sandbox\Resource\App\First\Greeting.tpl' in C:\Users\k_horii\Dropbox\Documents\Projects\BEAR\BEAR.Sunday\package\BEAR\Framework\src\BEAR\Framework\Module\TemplateEngine\SmartyModule\SmartyAdapter.php:93 ...
ページリソースではなく、アプリケーションリソースのテンプレートファイルを探しに行って、エラーになっているようです。
色々試してみたところ、どうもGreetingアプリケーションリソースのonGet()メソッドが、文字列ではなく自身を返しているのが良くないようでした。
このあたりの動作はよく分からないのですが、とりあえずページリソースに最初に定義していた文字列生成の内容を、そのままアプリケーションリソースに移しました。
web.php で呼んでみます。
run:sandbox mode=Dev cahce=enable 200 OK x-interceptors: {"onGet":["BEAR\\Framework\\Interceptor\\Logger"]} x-query: {"name":"X'SS"} x-params: ["X'SS"] x-args: ["X'SS"] x-execution-time: 0.043697118759155 x-memory-usage: 20936 cache-control: no-cache date: Fri, 20 Jul 2012 10:53:43 GMT content-type: text/html; charset=UTF-8 [BODY] <!DOCTYPE html> <html lang="en"> <body> <h1>Hello, X'SS</h1> </body> </html>
OKでした。
と、記事の公開にぐずついてる間に公式のソースが更新されたようで、Greetingページリソースが追加されてました。(汗)
まだ途中でしたが、今回はここまでにします。
次回もsandboxのコードを利用するつもりですが、リソースクラスは独自に処理を行うものを作成しようと思います。