BEARで始めるWebアプリケーション開発 その6「DBを扱うリソースを作ってみよう」
前回、BEARでのDBを扱うリソースの作り方を学びました。今回は、実際にリソースを設計して実装するところまでやってみたいと思います。
まずはリソース設計から
例によって、まずはリソースの設計から入ります。
- URI
- /sentences
- CRUD各メソッドの処理内容
- "GET /sentences"で変換リクエストのあった文章のリストを出力する。"POST /sentences?s={文章}"で変換リクエストのあった文章をリストに登録する。
- 出力表現の形式
- "GET /sentences"で変換リクエストのあった文章のリストを配列で返す。"POST /sentences?s={文章}"の場合はレスポンスのBODYは空とする。
- 他のリソースとのリンク関係
- 無し。
URIは、以前作成した"/sentence"(単数形)と紛らわしいかもしれませんが、GETした時に文章のリストが欲しいので、複数形で"/sentences"としています。*1
GETで取得できるのは、過去の変換リクエストの履歴(アクセスログ的なもの)なので、POSTで保存する時もパラメータとして文章を渡すだけで、変換結果は保存しない事とします。
ちなみに、今回のリソース設計に関しては結構悩んで、特に、変換リクエストの保存の処理をどこで行うのがよいのか、だいぶ悩んでしまいました。
考え方としては、例えば"GET /sentence?s={文章}"の処理の中で裏でこっそりDBに書き込んでしまうとか、"GET /sentence?s={文章}"の処理の中から"POST /sentences?s={文章}"を呼び出すとか、色々とやり方があると思います。
最初は、「文章の変換を行った時に履歴を保存する訳だから、"GET /sentence?s={文章}"の処理の中で保存処理しないと整合性が取れなくなる」と考えていましたが、よく考えると、変換処理と変換リクエスト保存処理が必ず同期しなければならない訳ではないので、"/sentence"とは無縁に保存処理を行うようにしました。
それにしても、リソース設計は毎回悩んでしまうんですが…リソース指向において、リソース設計こそがWebアプリケーション開発のキモになると思っているので、じっくり時間を掛けて行うべきかなと。
この辺は、色んなリソース設計を繰り返す中で、作り方のコツみたいのを掴んでいくしかないのかなと思っています。
リソースを実装する
そんな訳でリソース設計ができたので、実装に移ります。実際の作り方は前回既にやっているので、それに倣ってリソースのクラスファイルを作成するだけですね。
"App/Ro/sentences.php"ファイルを以下の内容で作成します。
<?php class App_Ro_Sentences extends App_Ro { protected $_table = 'sentences'; public function onInject ( ) { parent::onInject(); $this->_queryConfig['pager'] = 1; // DBページャ利用 $this->_queryConfig['perPage'] = 10; // 1ページ毎のアイテム数 } /** * リソース読み込み * */ public function onRead($values) { $sql = "SELECT sentence,"; $sql.= " (SELECT MAX(created_at) FROM {$this->_table}"; $sql.= " WHERE sentence=a.sentence) AS created_at"; $sql.= " FROM {$this->_table} AS a"; $sql.= " GROUP BY sentence"; $sql.= " ORDER BY created_at DESC"; $result = $this->_query->select($sql, array(), $values); return $result; } /** * リソース作成 * * @required s * @aspect App_Aspect_Transaction */ public function onCreate($values) { $inputs = array( 'id' => md5(uniqid()), 'created_at' => 'NOW()', 'sentence' => $values['s'], ); $result = $this->_query->insert($inputs); if ($this->_query->isError($result)) { throw $this->_exception('登録できませんでした'); } } } ?>
この辺りは特に難しい事もなく、さっくりと。リソースができたところで、POSTリクエストのテストもしておきます。
$ bear create sentences?s=テスト code 200 header Array ( [_request] => create sentences?s=%E3%83%86%E3%82%B9%E3%83%88 ) body
OKですね。ちゃんと登録できたか、GETリクエストを投げて確認してみます。
$ bear read sentences code 200 header Array ( [totalItems] => 1 [page_numbers] => Array ( [current] => 1 [total] => 1 ) [to] => 1 [from] => 1 [limit] => 1 [_request] => read sentences ) body array ( 0 => array ( 'sentence' => 'テスト', 'created_at' => '2010-02-14 12:23:18.465101', ), )
ちゃんと登録できました。機能を"リソース"として定義しているので、こういった単体モジュールでの機能確認がやりやすくて良いですね。
次回は?
次回は、今回作成したリソースをページから呼び出して、"最近変換した文章"として画面表示させるところまでやりたいと思います。
BEARで始めるWebアプリケーション開発 その5「DBを扱うリソースの作り方を学んでみる」
前回までで、極めてシンプルな形のWebアプリケーションではありますが、BEARを使って動作するものを作成するところまで、一通りの流れを見てきました。
ただ、最初の紹介記事でも書いたように、Webアプリケーションが1画面で1機能だけを提供するような事はまず無い訳で、今回から数回に渡って、1画面(BEARで言うと"ページ")で複数のリソースを扱う場合について学習してみたいと思います。
で、どんな機能を追加するかって事なんですが、逆さ語は、文章の入力フォームの下に、最近変換された文章を表示するようにしているので、その機能を実装してみようと思います。
DBを使う場合のリソース作成を試してみる
実際のリソース設計・実装は次回に回すとして、今回はまずDBを使う場合のリソース作成の方法を学びます。
本家Wikiの中だと、デモの中のDBセレクトのページが比較的分かりやすいので、そちらを参考にしました。
DBの接続先の設定
上記ページには特に記載は無いのですが、アプリケーション全体に関わる設定は、"App/app.yml"に書かれていて、DBの接続先もそこで定義するようになっているので、自分の環境に合わせて定義を書き換えます。
App_Db: dsn: default: 'pgsql://user:pass@localhost/sakasago' slave: 'pgsql://user:pass@localhost/sakasago' test: 'pgsql://user:pass@localhost/sakasago'
BEARでは標準だとPEAR/MDB2が使われるので、対応しているRDBSであれば、DSNを適宜書き換えてやれば何でも利用できると思います。
また、最初からマスター/スレーブ構成が想定されていて、DSNを適宜設定してやれば、GETリクエストの時だけスレーブの方を読みに行くようになっています。
リソースの作成
以前のDBを使わないリソース作成の時のように、BEAR_Roクラスを継承してリソース作成してもいいんですが、DBアクセスのように全てのリソースで共通する処理がある場合は、App_Roクラスに抜き出して、App_Roクラスを継承してリソース作成する方が良いようです。
App_Roクラスはinit-appした時点で作成されて、特に設定する事無くそのまま使えるんですが、MySQLを使う事が想定されているようで、PostgreSQLだとそのままでは利用できませんでした。
以下のように、InnoDBを使うように設定しているところがあったので、そこをコメントアウトしたらPostgreSQLでも利用する事ができました。
//$options['default_table_type'] = 'INNODB';
ここまで設定するとリソース内でSQLを発行する事ができるようになります。
ひとまず後々リソース実装する時に使う事になる、"sentences"というテーブルを作成しておいた上で、以下のような内容で、"App/Ro/sentences.php"というファイルを作成しました。
<?php class App_Ro_Sentences extends App_Ro { protected $_table = 'sentences'; public function onInject ( ) { parent::onInject(); $this->_queryConfig['pager'] = 1; // DBページャ利用 $this->_queryConfig['perPage'] = 5; // 1ページ毎のアイテム数 $this->_query = BEAR::dependency('BEAR_Query', $this->_queryConfig, false); } public function onRead($values) { $sql = "SELECT * FROM {$this->_table}"; $result = $this->_query->select($sql, array(), $values); return $result; } } ?>
ポイントは、onInjectメソッドで、BEAR_Queryをインジェクトしているところですね。ここではページャを利用する設定をしています。これだけで、SQLに自動的にLIMIT文などを付与してくれる上に、テンプレートにページャ文字列をアサインするところまでやってくれるようです。
10件ぐらい適当にデータを投入しておいた上で、実際にリソースに読み込みリクエストを投げて、結果を見てみましょう。
$ bear read sentences code 200 header Array ( [totalItems] => 10 [page_numbers] => Array ( [current] => 1 [total] => 2 ) [to] => 5 [from] => 1 [limit] => 5 [_request] => read sentences ) body array ( 0 => array ( 'id' => '0001', 'created_at' => '2010-02-03 18:50:46.209538', 'sentence' => 'テスト1', 'status' => '0', ), 1 => array ( 'id' => '0002', 'created_at' => '2010-02-03 18:50:51.773864', 'sentence' => 'テスト2', 'status' => '0', ), 2 => array ( 'id' => '0003', 'created_at' => '2010-02-03 18:50:56.129688', 'sentence' => 'テスト3', 'status' => '0', ), 3 => array ( 'id' => '0004', 'created_at' => '2010-02-03 18:51:00.626637', 'sentence' => 'テスト4', 'status' => '0', ), 4 => array ( 'id' => '0005', 'created_at' => '2010-02-03 18:51:05.209228', 'sentence' => 'テスト5', 'status' => '0', ), )
きちんとページングが行われている上に、ヘッダ部分にページングに関する情報が自動的に入っています。
このようにBEARのリソースは、"情報自体"と"情報に付随する情報"とが明確に分かれて出力されるようになっています。ヘッダ部は自分で自由に情報を追加する事もできるので、かなり柔軟にリソース設計を行う事ができるようになっていますね。
また、前回作成したリソースのように内部でゴリゴリ処理した結果を自分で返しても、今回のようにDBから取ってきた結果をそのまま返しても、リソースを呼び出す側から見ると全く区別無く利用できるというのも大きなポイントですね。
あと、他のメソッド(onCreate/onUpdate/onDelete)に関しては、デモページのサンプルコードを見れば書き方は分かるので、ここでは省略…。
という訳で、DB周りもかなり使い易く作られているので、DBを使ったリソースも非常に簡単に作る事ができました。
次回は?
次回は、新しく作るリソースを設計した上で、実装するところまでやる予定です。
BEARで始めるWebアプリケーション開発 その4「ページを作ってみる 〜そして(一旦は)完成へ〜」
久々にPHP勉強会(第49回)に参加する事にしまして、BEARの布教活動をしてこようかと企む今日この頃、皆様いかがお過ごしでしょうか。(ここまで挨拶)*1
さて、前回までで、DBを使わないようなシンプルなリソースの作り方について学びました。今回は、リソースを利用する側である"ページ"を作成して、実際に動作するWebアプリケーション完成までやっていきたいと思います。
"ページ"の概念
ページに関しては、本家Wikiのページの説明の辺りを見ると、概念がよく分かります。
最低限必要なのは、onInject・onInit・onOutputの3つのメソッドで、
- onInjectで必要なコンポーネントなどのインジェクトを行う。
- onInitでリソースからのデータの読み込みとテンプレートへの値のセットを行う。
- onDisplayで実際の出力を行う。
というような流れが基本という感じみたいですね。他のメソッドに関しては必要になった時にまた学習しようと思います。
実際にページを作ってみる
ページ作成は割と簡単そうなので、サンプル作成で学習する過程はすっ飛ばして、前回までで作成したsentenceリソースを使って実際に画面表示を行うページを作ってみました。
ページ名はindexでもいいんですが、index.phpにはデフォルトでサンプル用のコードが既に入っているので、今回はtopというページにしました。サンプルのindex.phpの内容を参考に、"htdocs/top.php"を以下のような内容で作成しました。
<?php /** * @APP@ * * @package Page */ require_once 'App.php'; /** * Topページ * * <pre> * </pre> * * @package Page * @author $Author:$ * @version SVN: Release: $Id:$ */ class Page_Top extends App_Page { /** * インジェクト * * @return void */ public function onInject() { parent::onInject(); $this->injectArg('s', $_GET['s']); } /** * 初期化 * * @return void */ public function onInit(array $args) { $uri = 'sentence'; $sentence = ( isset($args['s']) ? $args['s'] : 'これはサンプルです' ); $values = array('s' => $sentence); $params = array( 'uri' => $uri, 'values' => $values, 'options' => array(), ); $this->_resource->read($params)->set('preview'); $this->set('sentence', $sentence); } /** * 出力 * * @return void */ public function onOutput() { $this->display(); } } $options = array(); App_Main::run('Page_Top', $options); ?>
以下、簡単に解説。
onInject
まずは、親のonInjectを呼んでいます。これはほとんどおまじないのようなものだと考えても良さそうですね。あとは、クエリパラメータ"s"の値を、ページ引数としてインジェクトしています。
このように、onInjectは、ページで利用するコンポーネントや値を"注入"する役割を担うという事ですね。
$_GETの値をそのまま使っているのは何だか気持ち悪い感じがしますが、フォームを利用した値の受け渡しに関しては、もっとセキュアに行う仕組みが用意されているようなので、それはまた学習する事として、今回はこんな書き方で…。
onInit
onInitで、実際にリソースからデータを読み込んで、テンプレートに値をセットしています。
リソースへのアクセスは、連想配列でURIとパラメータとオプションを引数にして、$this->_resourceのメソッドを呼ぶ事で行います。
今回の場合は、"GET /sentence?s={文章}"というリクエストを行っているという事ですね。このリクエストで得られた値を"preview"というテンプレート変数にセットしています。
また、入力された文章も出力画面で表示したいので、"sentence"というテンプレート変数にセットするようにしています。
この辺りの書き方が何とも直観的で気持ちがいいですな。( ̄ー ̄)
onDisplay
onDisplayで、セットされた値を出力します。
デフォルトではテンプレートエンジンとしてSmartyが利用されますが、普通にHTMLで出力するだけでなく色々な形式の出力に対応していて、エクセル形式で出力するなんて事もできるみたいですね。
ページ起動部
ページはドキュメントルート配下にある、実際にアクセスされるファイルなので、BEARの起動部を書いておく必要があります。
今回は特にオプション指定はしていませんが、ここでインジェクタの設定やキャッシュの設定などを行う事ができるようです。その辺の学習もまたその内…。
テンプレートを作成する
テンプレートは、ファイル名の命名規則に従っていれば、自動的にファイルを発見して読み込んでくれます。
今回の場合は、ファイル名の命名規則により"App/views/pages/top.tpl"というファイル名になるので、その名前でテンプレートを記述しました。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <title>逆さ語</title> </head> <body> <div id="header"> <h1>逆さ語</h1> </div> <div id="content"> <dl id="controlbox"><form action="" method="get"> <dt class="description">変換したい文を入力して下さい(最大50文字)</dt> <dd class="inputform"> <input type="text" name="s" size="50" maxlength="50"> <input type="submit" value="変換"> </dd> </form></dl> {if isset($preview)} <dl id="preview"> <dt class="sentence"> 「{$sentence}」 </dt> <dd class="convertedkana"> 逆さ語 ⇒ {$preview.kana} </dd> <dd class="convertedroman"> ローマ字 ⇒ {$preview.roman} </dd> </dl> {/if} </div> </body> </html>
完成!
これで完成! 何とも、いとも簡単に仕様を満たすWebアプリケーションが完成してしまいました。エラー処理とか、バリデーションとか一切やっていないので、ホントに基本的な部分だけではありますが、後は枝葉の部分なので、徐々に付け足していけばいいだけですね。
とりあえず、ここまでは"1リソース1画面"のWebアプリケーションの例なので、CakePHPなんかのMVCフレームワークでも比較的楽に作れるのではないかと思います。しかし、1画面で複数のリソースを扱うようになると、なかなかそうもいかないのではないかなと。
次回は?
そんな訳で、ここからがリソース指向たるBEARの本領発揮!という事で、次回から、DBを扱うリソースを一つ追加して、1画面で複数のリソースを扱うようなWebアプリケーションの作成に挑んでみたいと思います。
*1:今回の連載をベースに紹介プレゼンしてもいいかなとも考えたんですが、連載がそこまで進められていないので、それはまたの機会に…。
BEARで始めるWebアプリケーション開発 その3「リソースを作ってみよう」
前回、BEARでのリソースの作り方まで分かったので、今回は実際にリソースを作ってみましょう。
リソースを設計する
リソースを実際に実装する前に、どんなリソースを作るか決めておきたいと思います。リソース指向の場合、先にリソース設計をきちんとやっておくと、後々Webアプリケーションを作るのが楽になるんじゃないかなと思います。
今回の"逆さ語"の場合、ユーザに提供したい機能は非常に明瞭かつシンプルです。
- 与えられた文章を逆さ語に変換する
これだけですね。逆に、これ以外の機能はあってもいいけど、必須ではないという事になります。
という事で、リソースとしては、上記要件を満たすもの1つだけ作れば、今回作成したいWebアプリケーションのキモの部分ができあがる訳ですね。
続いて、もう少し具体的にリソースの定義をしておきます。リソース作成に必要な情報はだいたい以下の4つになります。
リソース設計はかなり自由度が高くて色々な作り方ができるので、難しいところではあるんですが、逆に腕の見せ所とも言えます。
今回の例は比較的単純なものなので、誰がやっても同じになりそうなもんだと思うんですが、それでも例えば、
- 文章をBODYに書いて"POST /sakasago"すると、変換された逆さ語のリソースが作成される
- "PUT /sentences/{文章}"とすると、変換された逆さ語のリソースが作成される
- "GET /sentence?s={文章}"とすると、変換された逆さ語を取得できる
などなど…色々なリソースの作り方のパターンが考えられます。
今回は、この中で3つ目の、パラメータとして文章を入れてGETしたら逆さ語が得られるという設計にしようと思います。
これは、「裏では毎回変換処理してるかもしれないし、キャッシュがあるかもしれない、もしかしたら外部のWebサービスを使ってるかもしれない」なんていう内部実装の事は一切気にする事なく、「とにかくGETしたら逆さ語が得られる」という(リソースの利用者側から見て)分かりやすい設計になる為です。こういうカプセル化ができるのが、リソース指向の大きなメリットですよね。
という事で、今回作るリソースは以下のようなものにしました。
- URI
- /sentence?s={文章}
- CRUD各メソッドの処理内容
- GETのみ。与えられた文章を逆さ語に変換する。
- 出力表現の形式
- 変換された逆さ語を返す。連想配列で、ひらがなとローマ字を返すようにする。
- 他のリソースとのリンク関係
- 無し
URIに関しては、個人的には"GET /sentences/{文章}"という形式がよいかと思ったんですが、BEARの場合、リソースのURIはそのまま"App/Ro"配下のディレクトリ構造にマッピングされる為、パス名の中に動的に変化する部分を含む事ができないとの事なので、文章はパラメータで渡す形にしました。*1
メソッドとしては今回はGETのみ用意すればオッケーで、表現形式として、変換された逆さ語をひらがなとローマ字の2つの形式で返したいので、それらを連想配列で返すようにします。
あと、BEARでは、他のリソースとのリンク関係も定義する事ができて、関連するリソースを芋づる式に読み込む事ができるという素晴らしい機能があるんですが、今回は他にリソースが無いのでこの機能は使用しません。
リソースを実装する
このぐらいまで設計しておけばリソースが実装できるかと思うので、早速リソースの実装に取り掛かります。
以降は、"sakasago"という名前のアプリケーションを、"/home/bear/sakasago"に作成済みという前提で話を進めます。
"/home/bear/sakasago/App/Ro/sentence.php"というファイルを作成して、以下のように記述します。
<?php require_once dirname(__FILE__).'/Sakasago.php'; class App_Ro_Sentence extends BEAR_Ro { /** * onRead * * @required s */ public function onRead($values) { $lib_sakasago = new Sakasago(); $sentence = $values['s']; if ( $lib_sakasago->convert($sentence) === false ) { throw $this->_exception('文章を変換できませんでした。', array( 'code' => BEAR::CODE_ERROR, 'info' => array(), ) ); } $result = array( 'kana' => $lib_sakasago->getConvertedSentence('kana'), 'roman' => $lib_sakasago->getConvertedSentence('roman'), ); return $result; } } ?>
"Sakasago.php"というファイルは、実際に逆さ語のサービスで使用している変換処理を行うクラスファイルです。
実際には、onReadメソッド内にゴリゴリと処理内容を書く事になると思うんですが、今回は既にできあがったものがあったので、それを再利用する事にしました。
これで、リソースの実装は完了です。*2 ね、簡単でしょう?(笑)
では、実際にちゃんとリソースとして動くのか試してみましょう。
$ bear read sentence?s=逆さ語 code 200 header Array ( [request] => Array ( [uri] => sentence ) ) body array ( 'kana' => 'おがさかす', 'roman' => 'ogasakas', )
きちんと動作していますね!
ここまでだと、以前作ったクラスファイルを再利用して、その処理がきちんと動作する事を確かめただけにしか見えないかもしれませんが、この「処理を"リソース"として定義した」という事は、実は非常に大きな意味を持ちます。
…といったところで、今回は時間切れです。続きは、また次回以降に…。
次回は?
次回は、リソースを利用する側である、"ページ"の作成に入っていきたいと思います。
*1:この点に関しては、私自身は"リソースのURIは設計者が自由に設定できるべき"と思っているので、mod_rewriteで静的URLを動的URLに変換するような、リソースのURIと実ファイルとのマッピング機能があってもいいんじゃないかなと思いました。
*2:エラーの返し方は、Wikiのリソースサンプルを参考にしましたが、これで良かったのかどうかは若干不安…。(^^;ゞ (追記:2010/01/27)マニュアルを整備して頂いたのでそちらを参考にしてエラー処理部分を修正しました。
BEARで始めるWebアプリケーション開発 その2「リソースの作り方を学んでみる」
今まで月1ペースぐらいだったブログ更新が急にペースアップしているのは、自分のwktk感の現れです。(笑) 最初の紹介記事のはてブの付き方を見ると、結構注目度も高い感じですね。今まで注目されてなかったのが何とも不思議だ…。
さて、今回から実践編という事で、BEARを使って実際にWebアプリケーションを作っていこうと思います。
お題としては、拙作のWebサービス達の中から、逆さ語をチョイスする事にしました。
逆さ語は、"与えられた文章を逆さ語に変換する"という機能がメインで、画像を扱う訳でもないし、画面遷移もなく1画面で完結するUIなので、最初のお題としてはもってこいかなと。
BEARでのリソース作成を試してみる
という訳でリソースを作っていくんですが、リソース設計をする前に、まずはBEARでどうやってリソースを作るのか勉強しておこうと思います。
BEARでのリソース作成に関しては、本家Wikiのリソースの項目を見てもよく分からなくて(^^;、BEARの作者さんから「DEMOページのサンプルが参考になる」との情報を頂いたので(ありがとうございました!)、そちらを参考にさせて頂きました。
まずは難しい事は抜きにして、GETしたら値を返すだけのリソースを作ってみる事に。とりあえず、"/home/bear/helloworld/App/Ro/Sample.php"というファイルを作って以下のように書いてみました。
<?php class App_Ro_Sample extends BEAR_Ro { public function onRead($values) { $result = array( array('id' => 1, 'name' => 'tom'), array('id' => 2, 'name' => 'stellaqua'), ); return $result; } } ?>
BEARにはCLI用のコマンドがあって、それを使ってCLI上でリソースにアクセスできるので、作成したリソースが使えるかどうか試してみます。
$ bear read Sample code 200 header Array ( [request] => Array ( [uri] => Sample ) ) body array ( 0 => array ( 'id' => 1, 'name' => 'tom', ), 1 => array ( 'id' => 2, 'name' => 'stellaqua', ), )
おぉ、これはスゴイ! これの何がスゴイのかは、次のようなコマンドを叩いてみると分かります。
$ bear read http://rss.asahi.com/f/asahi_newsheadlines code 200 header Array ( [title] => asahi.com [link] => http://www.asahi.com/ [description] => アサヒ・コム [dc:language] => ja [dc:rights] => Copyright 2009 Asahi Shimbun. All rights reserved. No reproduction or republication without written permission. [dc:date] => 2010-01-21T19:31:30+09:00 [dc:publisher] => 朝日新聞社 [dc:creator] => 朝日新聞社 [items] => [type] => rss [request] => Array ( [uri] => http://rss.asahi.com/f/asahi_newsheadlines ) ) body array ( 0 => array ( 'title' => '「こだま」でも頑張って 500系バウムクーヘン', 'link' => 'http://rss.asahi.com/click.phdo?i=f152dafd46bc1b9c0faf4105e0f88e65', 'description' => ' <br clear="both" style="clear: both;"/> <img alt="" style="border: 0;" border="0" src="http://rss.asahi.com/img.phdo?i=f152dafd46bc1b9c0faf4105e0f88e65"/> ', 'dc:subject' => '社会', 'dc:date' => '2010-01-21T19:09:57+09:00', ), 1 => : (略)
このように、リソースが外部にあるか内部にあるかを気にする事なく、かつ、全く同じインターフェイスでアクセスする事ができてしまいます。
この"全く同じインターフェイスで"というのがキモで、裏で動いているのがDBなのか、単なるテキストファイルなのか、はたまた外部にHTTPでアクセスしてるのか、あるいは複雑なロジックの結果なのか…なんていう事情を一切気にしなくても、利用者の立場から見れば、同じ"リソース"というモノとしてアクセスする事ができます。
この事がWebアプリケーション開発にどんな風に効いてくるのかというのは、実際に作っていく過程で見えてくるのではないかなと思っています。
次回は?
という事で、次回は実際にリソースの設計をして、それをBEAR上で実装するところまでやってみる予定です。
BEARで始めるWebアプリケーション開発 その1「インストール〜HelloWorld」
今回から、ちょっとずつBEARを触っていってみようと思います。今回は、とりあえず基本に従って、インストールからHelloWorldまで。
早速インストール
インストールの手順は本家Wikiのインストールのページを参照しました。
→Google Code Archive - Long-term storage for Google Code Project Hosting.
PEARパッケージなので、"pear install"で一発インストールできて楽ちんですね。
$ sudo pear channel-discover pear.bear-project.net $ sudo pear install -a bear/BEAR-beta
結構依存パッケージが多いみたいで、色々と追加でインストールされました。
ちゃんとインストールできたかどうか確認する為、バージョンを表示させてみます。
$ bear --version bear version 0.8.11.
オッケーですね。
こんにちは、Worldさん
さくっとインストールできたので、早速最初の一歩、HelloWorldしてみます。
お試し環境は以下の通りとします。
- ドキュメントルート
- /home/www/
- アプリ配置ディレクトリ
- /home/bear/
Saffold的なものがあるので、さくっとコマンドを叩いてみます。
$ bear init-app /home/bear/helloworld BEAR App files are successfully made at '/home/bear/helloworld'. Thank you for using BEAR.Ok.
簡単ですね!*1
ドキュメントルートから辿れるように、シンボリックリンクを張ります。
$ cd /home/www $ ln -s /home/bear/helloworld/htdocs helloworld
これで準備オッケー。で、HelloWorldを作るんですが、何もしなくてもサンプルとしてindex.phpに、ExciteニュースのRSSを取得するページが設定されるようなので、早速アクセスしてみます。
が…何か、エラーが…。
Fatal error: Class 'BEAR_Util' not found in /home/bear/helloworld/App.php on line 65
App.phpの該当の部分近辺を見ると、
<?php // 開発モード(キャッシュクリア) if ($mode == 1) { BEAR_Util::clearAllCache(false); } BEAR::init($app); ?>
こうなっています。ソースを辿って調べてみると、BEAR_Utilクラスは、BEAR::initした後じゃないと呼び出せないような気がします…。
仕方が無いので、先にBEAR::initが来るように、以下のようにしてみました。
<?php BEAR::init($app); // 開発モード(キャッシュクリア) if ($mode == 1) { BEAR_Util::clearAllCache(false); } ?>
これでとりあえずは、サンプルページの表示まではできました。
でも、CSSが読み込まれてないっぽい…。
生成されたHTMLを見てみると、CSSやJSはドキュメントルート直下に読みに行くようになっているようです。ただ、今回作った環境は、ドキュメントルートから一つディレクトリを掘ったところにCSSが置かれているので、このままではCSSを読み込む事ができません。
どうしようか悩んだんですが、結局、ドキュメントルートに.htaccessを置いて、mod_rewriteのルールを設定する事で対応する事にしました。*2
RewriteCond %{HTTP_REFERER} helloworld/ RewriteRule ^css/(.*) /helloworld/css/$1 [P,L] RewriteCond %{HTTP_REFERER} helloworld/ RewriteRule ^js/(.*) /helloworld/js/$1 [P,L] RewriteCond %{HTTP_REFERER} helloworld/ RewriteRule ^__bear/(.*) /helloworld/__bear/$1 [R,L] RewriteCond %{HTTP_REFERER} helloworld/ RewriteRule ^__panda/(.*) /helloworld/__panda/$1 [R,L]
また別途記事で触れようと思いますが、BEARにはかなり強力なデバッグツールが用意されていて、htdocsにデバッグツール用のシンボリックリンクがあったので、そちらも使えるようにルールを定義しておきました。
ちょっとイレギュラーな方法ではありますが、元々、init-appで作成されたhtdocsディレクトリをドキュメントルートに設定する事が想定されていると思うので、まぁこのくらい設定が必要なのは仕方ないところですかね。
今度こそ、こんにちはWorldさん!
前置きが長くなりましたが、やっとHelloWorldです。手順は、本家Wikiのチュートリアルページを参照しました。
→Google Code Archive - Long-term storage for Google Code Project Hosting.
まず、htdocs配下に、helloworld.phpというページファイルを作成します。
<?php require_once('App.php'); /** * HelloWorldページ * */ class Helloworld extends App_Page { function onInit() { $this->set('message', 'Hello World'); } function onOutput() { $this->display(); } } BEAR_Main::run('Helloworld'); ?>
チュートリアルでは、クラス名が"Page_Hello_World"になっていましたが、"Page_"の接頭語はなくても大丈夫なようです。
あと、テンプレートのパスが"templates/hello/world.tpl"になっていましたが、"App/views/pages/hello/world.tpl"の間違いと思われます。
上記で作ったサンプルでは、アンダースコアを無しにしたので、テンプレートのファイルは、"App/views/pages/helloworld.tpl"に作成しました。
<html> <head> <title>{$message}</title> </head> <body> {$message} </body> </html>
これで、無事に画面に"Hello World"と表示されました。めでたし、めでたし…。
余談
BEARでは、bearmodeというサーバ環境変数を変更する事で、簡単に動作モードを切り替える事ができます。デフォルトでは、.htaccessに以下のような記載があり、デバッグモードで動作するようになっています。
SetEnv bearmode 1
本番環境にあたるライブモードだとどうなるのか見てみようと、bearmodeを0に設定してみたところ、なぜだかレスポンスコード503が返るようになってしまいました。
エラーを見てみたところ、どうやらerror_reportingの設定のところで問題があったようでした。(PEARディレクトリ配下のPanda.phpの338行目)
<?php ini_set('display_errors', 0); error_reporting(self::$_config[self::CONFIG_ERROR_LEVEL_LIVE]); assert_options(ASSERT_ACTIVE, 0); ?>
$_configの内容を読むようになっていますが、CONFIG_ERROR_LEVEL_LIVEに既にエラーレベルが定義されているので、そのままerror_reportingに渡すようにすればよいようです。
<?php ini_set('display_errors', 0); error_reporting(self::CONFIG_ERROR_LEVEL_LIVE); assert_options(ASSERT_ACTIVE, 0); ?>
HelloWorldでこの調子だと、まだ結構バグがあったりするのかなぁ…。まぁ、また記事を書きながら気付いたら、随時記事に追記していくようにしようかなと思います。
次回は?
これでやっとスタート地点に立てたところな訳ですが、次回は実際にWebアプリケーションを作っていく過程に入っていく予定です。
という訳で、次回からはBEARの魅力たっぷりにお送りしたいと思います!(多分…。)
リソース指向フレームワークのBEARが何やらスゴイ予感
これ、密かにスゴイぞ…。
→Google Code Archive - Long-term storage for Google Code Project Hosting.
「こんなWebアプリケーションフレームワークがあったらなぁ…。」と最近ぼんやりと考えていた、そのものズバリな感じで、かなり衝撃を受けています。今まで捕捉できてなかった自分に反省…。orz
今までRails以降の、いわゆるMVCフレームワークに何ともかんともなじめなかったんですが、このBEARに関しては、かなりすんなり考え方が入ってきました。
Rails型のフレームワークになぜしっくりこないのか?
せっかくの機会なので、そもそも、なぜRails型のフレームワークが自分的にしっくりこなかったのか、ちょっと考えてみました。
まず、自分のイメージとしてRails型のフレームワークの場合、"URL1つに対してMVCの塊1つが対応する"という感じと思っています。
- URLが与えられる
- どのコントローラかが決まる
- 必要な情報がモデルから引き出される
- ビューによって表現が決められる
大雑把にいえば、動き的にはこんな感じですよね。
Railsの場合、このMVCの塊に対してCRUDのインターフェイスが与えられる訳で、REST的な考えで言うと、"MVCの塊1つ=リソース"として見るとちょうどいいのかなと。
結果的に、1画面1リソースというのが基本になると思うので、1画面に色々な情報が詰まっているようなWebアプリケーションを作るのは難しいように思います。
ただ普通は、Webアプリケーションを作る場合は、1画面に色々な情報を表示する事も多いし、画面遷移が伴う場合も多い訳で、1画面1リソースという設計になる事は少ないですよね。
という辺りで、頭の中で思い描いているぼんやりとした設計が、直接MVCのフレームワークとうまく結び付かなくて、結局、ごにょごにょしないとうまく作れない事が多くて、「何だかなぁ…」と思ってしまっていました。
じゃあどういう形がいいの?
基本的な考え方として、"まずリソースありき"、つまり"ユーザに提供したいモノが何か?"という質問が最初に考えるべき問題だと考えます。
リソースが定義できた上で、それらをどのように表現するか・どのような画面遷移にするか、という事を考える…という流れだと、役割分担もすっきりして、きれいにWebアプリケーションを作れるのではないかと思います。
具体例として拙作の名付けて.ね〜むの場合、先に提供したい情報を洗い出して、"意味語"や"訳語"といったリソースを定義しました。
定義したリソースはRailsで実装して、そのリソースを読み込んだり登録したりするインターフェイスは、別途PHPで実装しました。
もちろん、Railsだけ・PHPだけでも作成できたとは思いますが、リソース層とWebアプリケーション層にきっちり分離できた事が重要だったと考えます。
が…わざわざRailsとPHPで別々に作っている訳で、やっぱり面倒だった事は否めません。(^^; それに、アクセスがある度にPHP側からわざわざHTTP通信で情報取得しにいくので、無駄が多いのも事実です。
そこでBEARですよ
今のところ、マニュアルをざっと読んでみただけではありますが、自分の理解だと、BEARはリソース層とWebアプリケーション層*1というような考え方の分離がなされているリソース指向のフレームワークで、かな〜り素敵な感じです。
現時点で理解したまでの知識で、ひじょ〜に大雑把に特徴を挙げると、以下のような感じです。
- RESTfulなリソースを定義できる。(URIにより識別され、HTTPでのアクセスと同じようなCRUDアクセスできる。)
- バックエンドでどのように情報を取得しているか(DB・外部Web・テキストファイル・etc...)に関わらず、リソースに対して透過的にアクセスできる。
- 定義されたリソースをどのように表現するかは、"ページ"という単位で定義する。
- ページでテンプレートに渡す値を"push"するだけでなく、テンプレート側からリソースの情報を"pull"する事もできる。
- あるリソースから別のリソースの情報をpullしたりもできる。
他にもおいしい機能いっぱいなんですが、自分的に一番コアなのは上記の辺りじゃないかと思います。
まずは使ってみよう
昨年1年は、"作ってみる年"と決めて、アイデアを出す練習と、アイデアから動くモノを作る練習として、"毎月1つWebサービスを作る"という目標を立てて実践してきました。
おかげさまで(?)、10個ほどのサービスが手元にあるので、まずはこれらをBEARに載せるという形でBEARを使ってみて、使い心地を試してみようかなと思います。使ってみた感想は、また追々…。
*1:この層の名前は自分的な勝手な名付けで、BEARのドキュメント内では出てこないのであしからず…。