CakePHP 1.2標準の paginate 機能で、複雑な検索条件をページング用リンクに引き継げるようにするためのプラグイン SearchPagination を公開しました。
http://github.com/tkyk/cakephp-search-pagination
cd /path/to/your/app/plugins
git clone git://github.com/tkyk/cakephp-search-pagination.git search_pagination
PHP 5.2.xでしかテストしていませんが、それ以前のバージョンでも動くはずのコードなので、もし動かなかったら教えてください。
使い方
検索専用のモデルを作る方法と、作らない方法(他モデルを流用する方法)とがあります。ある程度以上複雑な検索を行う場合は専用モデルを作る方法をおすすめします。ここでは最初に専用モデルを作る方法から説明して、作らない方法については最後の節で説明します。
検索専用のモデルを作る
複雑な検索を行うための肝は専用のモデルを作ることです。検索条件を組み立てるロジックをモデルの中にカプセル化してしまえば、どれだけ複雑な検索フォーム・検索条件にも対応できます。ここでは次のような検索フォームを例に説明していきます。(いろいろ省略されていますが)ブログの記事を検索すると思ってください。
この検索フォームに対応するモデルは概ね次のようになります。buildQuery が検索条件を組み立てるためのメソッドです(記事に対応するモデルは Entry とします)。
<?php
class EntrySearch extends AppModel
{
var $useTable = false;
var $_schema = array('title' => array('type' => 'string'),
'created_from' => array('type' => 'date'),
'category_ids' => array('type' => 'array'),
);
/**
* buildQuery メソッドの中で検索条件を組み立てます。
* もし検索フォーム内でソート順や表示件数を指定できるようにする場合は、
* limit や order もここで設定します。
*/
function buildQuery()
{
$p = $this->data[$this->alias];
$conditions = array();
if(!empty($p['created_from'])){
$conditions['Entry.created >= '] = $p['created_from'];
}
//....(省略) ...
return array('conditions' => $conditions,
'joins' => $joins,
/* ... */);
}
}
検索フォーム表示部分のビューは次のようになるでしょう。
echo $form->create('EntrySearch', array('action'=>'search'));
echo $form->input('title', array('label' => 'タイトル:');
echo $form->input('created_from', array('label' => '投稿日:',
'after' => '以降'));
echo $form->input("category_ids", array('label' => 'カテゴリ:',
'multiple' => 'checkbox',
'options' => $categories));
echo $form->end('検索');
これでフォームから検索パラメータを受け取る準備ができました。
SearchPaginationコンポーネント
コントローラの中では SearchPagination コンポーネントの setup メソッドを呼び出します。第1引数は検索用モデルの名前です。ここでは EntrySearch を指定します。またオプションの第2引数として、検索フォームの初期値を与えることもできます。下のコードでは検索開始日を1ヶ月前の日付としてみました(この部分のコードは本来はモデルに置くべきかもしれません)。
<?php
class EntriesController extends AppController
{
var $components = array('SearchPagination.SearchPagination');
function search()
{
$oneMonthAgo = date('Y-m-d', strtotime('1 month ago'));
$this->SearchPagination->setup('EntrySearch', array('created_from' => $oneMonthAgo));
$this->paginate = $this->EntrySearch->buildQuery();
/*
... 必要ならさらに $this->paginate を加工する ...
*/
$this->set('entries', $this->paginate('Entry'));
$this->set('categories', $this->Entry->Category->find('list'));
}
}
setup メソッドは自動的に検索用モデルを読み込んで、モデルの set メソッドで値を設定します。だからあとは buildQuery メソッドを呼んで検索条件を組み立て、paginate を実行するだけです。
ビュー
ビューでは何も特別なことはありません。PaginatorHelper を使って普通にページング用リンクを表示してください。sort メソッドも使用可能です。
echo $paginator->numbers(array('modulus' => 12));
foreach($entries as $entry) {
//検索結果の表示
}
生成されたリンクの中には、次のような形で検索条件が含まれているでしょう。
/entries/search/page:2?title=XXXX&created_from=2009-11-01&category_ids[0]=2&category_ids[1]=4
リンクをクリックすれば、これらのパラメータが検索条件として引き継がれます。同時にコントローラの $data プロパティにも引き継がれるため、FormHelper で作った検索フォームにも入力値が自動的に設定されます。
GETメソッドで検索したい
以上でページング用リンクに対する検索パラメータの引き継ぎが行われるようになりました。しかし検索フォームからの最初の検索がPOSTメソッドで行われることが少し気持ち悪いかも知れません。これは date などの複雑な要素を使う場合の、FormHelper の制約です。
この制約を回避するために、SearchPagination プラグインではオマケ機能として、POST用のフォームをGETに変換するJavaScriptを用意しています。動作にはjQueryが必要ですが、他のライブラリに移植することは容易なはずです。次のように使用します。
// jQuery および search_pagination.js を読み込む
$javascript->link('jquery-1.3.2.min', false);
$javascript->link('/search_pagination/js/search_pagination', false);
// form に search-pagination クラスを指定する
echo $form->create('EntrySearch', array('action'=>'search', 'class'=>'search-pagination'));
専用モデルを作るほど複雑ではない場合
検索フォームも検索条件も単純で、検索専用に新たなモデルを作るほどではない場合、既存のモデルを検索用に流用することができます。例えば次のようなごくごく単純な検索フォームの場合、
値を受け取るためのモデルとして、Entry モデルをそのまま使ってしまえば良いでしょう。この場合、setup メソッドの第1引数には Entry を渡し、Entryモデルもしくはコントローラの $data プロパティから検索条件を組み立てます。
<?php
class EntriesController extends AppController
{
var $components = array('SearchPagination.SearchPagination');
function search()
{
$this->SearchPagination->setup('Entry');
if(!empty($this->data['Entry']['title'])) {
$this->paginate['conditions']
= array('Entry.title like' => '%'.$this->data['Entry']['title'].'%');
}
$this->set('entries', $this->paginate('Entry'));
}
}
ビューは専用モデルを使った場合と全く同様ですが、これくらい単純なフォームの場合は FormHelper の create メソッドで type => get を指定しても問題なく動きます。
<?php
echo $form->create('Entry', array('action'=>'search', 'type'=>'get'));
echo $form->input('title');
echo $form->end('検索');
echo $paginator->numbers(array('modulus' => 12));
foreach($entries as $etnry) {
//...
}