読者です 読者をやめる 読者になる 読者になる

k-holyのPHPとか諸々メモ

Webで働くk-holyがPHP(スクリプト言語)とか諸々のことをメモしていきます。ソースコードはだいたいWindowsで動かしてます。

BEAR.Sundayを触ってみたメモ (3)はじめてのページリソース

PHP BEAR

※この記事は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&#039;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&#039;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&#039;SS</h1>
  </body>
</html>

OKでした。

と、記事の公開にぐずついてる間に公式のソースが更新されたようで、Greetingページリソースが追加されてました。(汗)

まだ途中でしたが、今回はここまでにします。
次回もsandboxのコードを利用するつもりですが、リソースクラスは独自に処理を行うものを作成しようと思います。