concrete5のORMクラス、DatabaseItemListを理解する
Posted by admin at 8:32 日時 2013/11/05
concrete5にもO/Rマッパーやアクティブレコードと呼ばれるデータベースアクセスを抽象化するクラスが用意されていますが、ちょっと使い勝手が特殊なので、かんたんにまとめておきます。O/Rマッパーやアクティブレコードがどうあるべきかとか、DAOとどう違うのかとか、つっこまれても自分は詳しくないので誰か教えてください!
さて、concrete5の(多分)特殊な部分ですが、データベースからの情報を取得するクラスと挿入・削除をするクラスが分かれています。データベースからの検索はアイテムリストが担当し、テーブルから取得した各行をオブジェクトモデルにマッピングします。データの挿入はオブジェクトモデル自身が担当します。この関係を図示したものが下図です。
例えば、concrete5の中核的なオブジェクトモデル「Page」「User」「File」のうちのひとつ「Page」で説明します。アイテムリストに相当するのは「PageList」クラスです。PageListクラスを使って、諸条件でデータベースからページを取得することができます。返ってくるのはPageクラスのインスタンスの配列になります。
Loader::model('page_list'); // PageListクラスのインスタンスを取得 $pl = new PageList(); // 検索 $pl->filterByKeywords('hogehoge'); // ソート $pl->sortByName(); // 条件に合致するページを取得 $pages = $pl->getPage();
PageListクラスではページの追加または削除はできません。それができるのはPageクラスの方です。
// ページを追加する親ページを取得 $parentPage = Page::getByPath("/sample"); // ページのデータを作る $data = array( 'name' => "sample2", 'cHandle' => "sample2handle", 'cDescription' => "Add page test." ); // ページタイプを取得 $pt = CollectionType::getByHandle("page_type_handle"); // ページを追加する $newPage = $parentPage->add($pt,$data);
PageListクラスはDatabaseItemListクラスを継承しています。DatabaseItemListクラスはADODBライブラリーをラッピングしています。DatabaseItemListクラスはSQLデータベースに特化していますが、さらに抽象的なItemListクラスを継承して作られています。
ここで、本題に戻りますが、DatabaseItemListクラスを使えばデータベースからデータを取得することが楽になります。下記は、コアでは検索APIがなぜか用意されていない、アクセスログが保存されるPageStatisticsテーブルからデータを取得するアイテムリストの作例です。
class StatisticsPageList extends DatabaseItemList { protected $itemsPerPage = 10; protected $autoSortColumns = array('count'); protected function setBaseQuery() { $this->setQuery('select cID, count(cID) as count from PageStatistics'); } public function createQuery() { if (!$this->queryCreated) { $this->setBaseQuery(); $this->groupBy('cID'); $this->sortBy('count','desc'); $this->queryCreated = 1; } } public function get($itemsToGet = 10, $offset = 0) { $users = array(); $this->createQuery(); $r = parent::get($itemsToGet, $offset); foreach($r as $row) { $sp = StatisticsPage::get($row); $users[] = $sp; } return $users; } public function getTotal() { $this->createQuery(); return parent::getTotal(); } public function filterByUID($uID) { $this->filter('uID', $uID, '='); } }
PageStatisticsテーブルのカラムは pstID, cID, date, timestamp, uID からなり、pstIDはプライマリキー、cIDはページのID、uIDはユーザーIDです。どのページにだれがいつアクセスしたか、を単純に記録しています。
setBaseQuery()メソッドで基本になるクエリーを記述します。ここではアクセスログからcIDとcIDの出現数をカウントした数値を取得するとしています。setBaseQuery()では多くの場合はプライマリキーだけ取得すると思いますので、この例は特殊です。
次にcreateQuery()メソッドで必要なクエリーを組み立てます。setBaseQuery()に長々と書かずに、createQuery()でDatabaseItemListのAPIをつかって組み立てるのがポイントです。groupByやsortByなどあります。
get()メソッドで実際にデータベースからデータを取得していますが、ここで各行のデータをオブジェクトモデルにマッピングします。ここではStatisticsPageクラスですね。
あとは、DatabaseItemListクラスにfilterメソッドが用意されていますが、さらにショートカットメソッドを用意したりします。この作例ではfilterByUID()メソッドを追加しています。
では次にStatisticsPageListクラス内でマッピングされるStatisticsPageクラスの中身です。こちらも非常にシンプルな内容になっています。
class StatisticsPage extends Object { public static function get($data) { $sp = new StatisticsPage; $sp->setPropertiesFromArray($data); if(is_object($sp)) { return $sp; } } public function getCount() { return $this->count; } public function getCID() { return $this->cID; } public function getPage() { $page = Page::getByID($this->getCID()); if(is_object($page)) { return $page; } } }
静的なget()メソッド内でsetPropertiesFromArray()メソッドを呼んで、データベースから受け渡されたデータをプロパティーにマッピングしています。StaticticsPageListクラスからはcIDとcountが渡されているので、それぞれの値が$cIDと$countに代入されます。
オブジェクトモデルとしてはこれでオッケーなんですが、この作例ではcIDからPageオブジェクトのインスタンスを返すgetPage()メソッドを追加しています。これはちょっとイレギュラーな例です。
さて、実際にStaticticsPageListクラスを使ってデータを取得する例が下記です。
$user_list = new StatisticsUserList; $users = $user_list->getPage();
getPage()メソッドを呼んでいるだけです。うわ楽ですね!あとは$usersをforeachで回したりしたらデータベースから取得したデータを表示できます。
ページネーションも自動で出力してくれます。
$page_list->displayPagingV2();
特定のカラムでソートするリンクもかんたんに書けます。ただし、$autoSortColumnsプロパティーでソート対象のカラムとして指定しておく必要があります。
<a href="<?php echo $page_list->getSortByURL('count','desc')?>">カウント</a>
SQLをほとんど書かないので、SQLで発想すると全体像がつかめないのですが、ItemListとDatabaseItemListをいちどさらっと見てみるとより把握しやすいと思います。また、完全にSQLが不要と言うわけでもなくて、DatabaseItemListは結構SQLを書いたことがないと分からないかもしれません。いずれにしても慣れればconcrete5に独自のオブジェクトモデルを追加することが短時間でできますので、追加カスタマイズの際に役立つと思います!
参考ドキュメント:Search/Sort/Pagination