コントローラやビュースクリプトはHTMLのページごとに1つずつ用意されるのが一般的です。しかし、モデルの場合は規約も少なく、「こう記述すべきだ」という点は少ないです。Alex@Net Blogでは、データベースへ接続し、照会や更新を行う処理はDatabase.phpに記述され、最終的にビュースクリプトへassignされる$model配列を生成する処理が、BlogModel.php内に記述されています。
Database.phpには、Zend_Db_Tableを継承するクラスとしてDbObjectが定義され、データベースにはこれに対応するobject表が定義されています(リスト13)。ブログデータのほとんどはこの表の1レコードとなり、ドメインがusersのデータはusers表、blogsのデータはblogs表といったように区別されます。
class DbObject extends Zend_Db_Table {
protected static function _getDomainID($domain) {
assert('"string" == gettype($domain)');
switch ($domain) {
case 'users': return 1;
case 'blogs': return 2;
case 'posts': return 3;
}
throw new Exception('Undefined domain');
}
各ドメインのクラスは、DbObjectクラスを継承して定義されます。ベースとしてZend_Db_Tableの機能を使用し、共通に使用される拡張メソッドがDbObjectクラス内でオーバーライドされています。各ドメインのデータがinsertされる際、objectと各ドメインのidの値が同じになるように、まずobject表へinsertしてid列の値を自動生成します。その後、Zend_Db_AdapterのlastInsertIdメソッドでid列の値を取得して、それを各ドメイン表のid列値としてinsertしています。
class DbUsers extends DbObject {
protected $_name = 'users';
function _getName($params) {
return $params['login'];
}
}
このように更新処理は主にZend_Db_Tableの機能を利用して行っていますが、照会処理の方はBlogDataクラス内でZend_Db_Adapterを直接利用しています(リスト14)。まず、引数で渡された変数が想定されたデータ型であるかを、assertでチェックしています。その後、Zend_Db_Selectオブジェクトを生成します(引数の有無によって実行するSELECT文を変更するように作成しています)。最後にfetchAllで全行フェッチした後、変数を整えて返しています。
public static function findPosts($id, $tags = null, $limit = null, $offset = null) {
assert('"integer" == gettype($id)');
assert('null == $tags || "array" == gettype($tags)');
assert('null == $limit || "integer" == gettype($limit)');
assert('null == $offset || "integer" == gettype($offset)');
$select = self::$db->select();
$select->from('posts p', 'p.id, p.title, p.content, p.published')
->join('objects o', 'p.id = o.id', 'o.name')
->join('users u', 'o.owner = u.id', 'u.login')
->order('p.published DESC');
if (null == $tags) {
$select->where('p.draft = 0 AND p.blog = ?');
} else {
$select->distinct()
->join('object_tags ot', 'o.id = ot.object')
->join('tags t', 't.id = ot.tag')
->where('p.draft = 0 AND p.blog=? AND '
. self::$db->quoteInto('t.name IN (?)', $tags));
}
if (null != $limit) {
if (null != $offset) {
$select->limit((int)$limit + 1, (int)$offset);
} else {
$select->limit((int)$limit + 1);
}
}
$rowset = self::$db->query($select, $id);
$rows = $rowset->fetchAll();
リスト14のfindPostsメソッドを呼び出しているのは、BlogModel.phpに定義されたGetPostsメソッドです(リスト15)。このメソッドはBlog Controller内で呼ばれ、最終的に返している$blog変数はビュースクリプトへassignされる$model配列です。Database.phpと同様に、ここでもassertによってデータ型のチェックを行っています。
BlogDataクラスのfindBlogWithNameメソッドによって、対象のブログが存在するか確認した後、findPostsメソッドでタグ*とエントリの表示範囲(現在のページ$pageに対して7件表示する)を引き渡します。返ってきた配列を$blog['posts']に代入して、ブログのエントリ(=post)ごとに変数の値を整えます。
public static function GetPosts($blog, $tags, $page) {
assert('"string" == gettype($blog)');
assert('"array" == gettype($tags)');
assert('"integer" == gettype($page)');
$blog = BlogData::findBlogWithName($blog);
if (null == $blog) {
return null;
}
$blog['posts'] = BlogData::findPosts((int)$blog['id'], $tags, 7, ($page - 1) * 7);
foreach ($blog['posts'] as & $post) {
$post['tags'] = BlogData::findTags((int)$post['id']);
$post['url'] = '/Blog/Index/' . date('Y-m-d', $post['published']) . '/' . $post['name'];
}
return $blog;
}
今回は、ブログツールを例に、Zend Framework独特の書き方を紹介しました。ここでは特に、どんなアプリケーションでも使用するような機能を取り上げたつもりです。
必ずしもこれがベストというわけではありませんが、やはりフレームワークの使い方を覚えるには、使用例を見ていくことが近道といえるでしょう。本稿で取り上げたアプリケーションが単純すぎると思われた方は、例えば「データベースアクセスを減らすためにZend_Cacheを利用して$modelをキャッシュする」といったように、Zend Frameworkのほかの機能をつけ足したりして自分なりに書き換えてみてください。特にモデル層は、より効率良く書き換える余地がありそうです。
findPostsメソッド内のSELECT文で、tags表に該当するタグが存在しないか条件節を追加する「self::$db->quoteInto('t.name IN (?)', $tags)」の部分に適用される。
本記事は、オープンソースマガジン2006年11月号「Zend Framework」で加速するPHP開発を再構成したものです。
日本アイ・ビー・エム システムズエンジニアリング株式会社
杉田直哉
日本アイ・ビー・エム システムズ・エンジニアリング株式会社(ISE)は、日本アイ・ビー・エムグループにおけるSE技術者の専門家集団として1992年7月に設立された。
発足以来、IBM製品を中心とした難易度の高い複雑なシステム構築や先進技術の適用場面において、卓越したITスキルによりお客様と開発現場を支援してきた。
近年は、仮想化技術・グリッドコンピューティング・Web 2.0・オープンソースソフトウェア等々の先進技術分野での支援も展開している。
Copyright © ITmedia, Inc. All Rights Reserved.