WHAT'S NEW?
Loading...

CakePHP2.0にSearch Pluginをいれてラクラク検索しよう【2/2】

■準備

前回の記事を見て同じようにやっておけば、そのままの続きで話を勧められるけど、一応まとめとして前回のおさらいをしておこう。

まず、CakePHP2.0がブラウザで閲覧できる状態であり、エラーも無いとしよう。
その状態でCakeDC謹製の検索プラグインであるSearchプラグインをインストールした。
  1. ページャ機能を使う
  2. 検索結果もページャに反映させる
のが目的なので、まずは1の『ページャ機能を使う』を実装。
今回は2の『検索結果もページャに反映させる』を行うよ。それで終わり。



■その前に

実はCakePHP1.3と2.0で変更があった部分が多く、その中で意外と気づかなかった部分を紹介するよ。

$this->Paginator->sort()の引数指定が逆になってる

英語で良ければここに書いてあるよ。

つまり、1.3では
sort('ユーザID', 'User.id')
の順番だけど、2.0からは
sort('User.id', 'ユーザID')
という順序になった。

なんで気づきにくいかというと、2.0上で1.3方式そのままやってもエラーがでないから。
ただ項目名にモデル名が表示されるだけ。

当然『ユーザID』なんていうモデルデータは無いから、実際には動かないんだけどね。
そこんとこ、ヨロシク!だ。

ほかにも1.3系列で動かしていて『あれ?』と思ったら、英語でもいいのでCookbookを見ておくことをおすすめする。

Paginator関連

海外のマニュアルは英語なだけでなく、サンプルが極端に少ない場合が多いので、ある程度使い込んだ人じゃないと意味がわからない場合も多いけど、そのうち日本でCakePHP2.0の本が出るはずなので、それまで待つのも手だね。

というわけで、早速indexビューを変更しておこう。
以下のような感じにして、項目名ソート機能を実装だ。

app/View/Users/index.ctp
<?php echo $this->element('searchForm')?>

<?php echo $this->element('pager')?>
<table>
<tr>
  <th><?php echo $this->Paginator->sort('User.id', 'ID')?></th>
  <th><?php echo $this->Paginator->sort('User.username', 'ユーザ名')?></th>
  <th><?php echo $this->Paginator->sort('Profile.nickname', 'ニックネーム')?></th>
  <th><?php echo $this->Paginator->sort('User.created', '作成日')?></th>
  <th><?php echo $this->Paginator->sort('User.modified', '更新日')?></th>
</tr>
<?php foreach($users as $user):?>
<tr>
  <td><?php echo $user['User']['id']?></td>
  <td><?php echo $user['User']['username']?></td>
  <td><?php echo $user['Profile']['nickname']?></td>
  <td><?php echo $user['User']['created']?></td>
  <td><?php echo $user['User']['modified']?></td>
</tr>
<?php endforeach?>
</table>
<?php echo $this->element('pager')?>

変更したのは6〜10行だ。

1.3では
$paginator->sort()
だったけど、2.0からはヘルパーは
$this->Paginator->sort()
的な記述をすることになっているんで、ちゃんと規約通りに書くようにしようじゃないか。

記述が長くなってしまうけど、これは気にしないのが精神衛生上よろしいと思われるんで、とりあえずやってから文句言えばいいと思う。
だからやると良いよ。

この状態でブラウザで見てみると、こんな感じになってるはずだ。
なってなかったら、もう一度布団に入り、レム睡眠で6時間ほど眠り、起き直すと良いかも。


項目名の『ID』『ユーザID』『ニックネーム』『作成日』『更新日』がテキストリンクになったはずだ。

そしてクリックすると、クリックした項目を基準に昇順/降順に並び替えることができるんだ。

URLを見れば一目瞭然。GET形式で向きを指定しているだけ。それを受け取って向きを変えてくれるのがPaginatorに備わっている、というわけよ。どうよ?

なかなか憂い奴だべ。

■Searchプラグインの準備

さて、本題だ。

Searchプラグインを使うためにはいろいろ準備がいる。そもそもプラグインのプラグというものは、いわゆる日本で言うところのコンセントだ。挿せば使える。挿さないと使えない。

当然通常の電化製品ってのは、コンセント差した瞬間に使えるようなものは少ないわけで、何かしらのスイッチが必要になる。

掃除機も、コンセントに差した瞬間に吸い込み始めたら厄介だ。

というわけで、インストールしただけでは動かないようになっている。
『プラグイン』って名前がついてるのに、インストールしただけじゃ動かないの?と言われれば、『そうだ』と答えるよ。

でも俺の答えなんかハナクソと同価なので、いちいち一喜一憂しないほうが世のため人のためだよ。

というわけで、ダウンロードしたSearchプラグインを解凍し、フォルダ名を変更して、しかるべき場所に設置したことにより、コンセントは刺された状態となったのは言うまでもない。
  1. GithubでCake2.0用のSearchプラグインをダウンロード
  2. ダウンロードしたファイルを解凍
  3. 解凍したフォルダをSearchにリネーム
  4. app/Plugin内にSearchフォルダを設置
さて、次は電源を入れようじゃないか。
どうやって入れるのか?

CakePHPのプラグイン達は、家電で言えば冷蔵庫だ。一度電源を入れたら、かなりの間電源を消すことはない。まずこれを覚えておいて欲しい。

次に、app/Configの中にある、bootstrap.phpを見てもらいたいんだ。
テキストエディタやお好きなIDE、ないしは隣の奥さんに朗読してもらうのも良い。
末尾の方に、こんな記述があるはずだ。

app/Config/bootstrap.php
/**
 * Plugins need to be loaded manually, you can either load them one by one or all of them in a single call
 * Uncomment one of the lines below, as you need. make sure you read the documentation on CakePlugin to use more
 * advanced ways of loading plugins
 *
 * CakePlugin::loadAll(); // Loads all plugins at once
 * CakePlugin::load('DebugKit'); //Loads a single plugin named DebugKit
 *
 */

さて、これはつまりどういうこと?

クソ翻訳してみると、
CakePHP2.0でプラグイン使うときは、このファイルに名指しでプラグインを指定するか、面倒くさいヤツはプラグイン全部よむ指定をかけや、な、ボウズ。
と書いてある。

本当にそう書いてあるのかどうかはまるで保証しないけど、とにかく以下のように記述しないと、プラグインの電源は入らないことは必然だ!!

CakePlugin::load(<プラグイン名>);
というわけで、今回使うプラグインは『Search』という名前なので、
CakePlugin::load('Search');
と記述するわけだ。

プラグインはCamelCaseなので、大文字から始めよう。

■Searchプラグインを使ってみよう

さて、次は具体的にソースコードに記述しながら説明しようと思う。

使うファイルは
  • app/Controller/UsersController.php
  • app/Model/User.php
だ。

コントローラ側ではプラグインのコンポーネント、モデル側ではプラグインのビヘイビアの指定をそれぞれする。

その他に、検索フォームのどの項目を、モデルのフィールドに合わせるのか、検索するタイプなども同時に指定することになる。

言うなれば上記の2種類の指定をするだけで、誰でも簡単に検索結果を維持したままページネーションさせることができるんだ。

というわけで、まずはモデルを先にやっつけることにする。
モデルでは、ビュー(検索フォーム)から送られてくるデータをフィルタする必要があるらしく、そのフィルタの設定は$filterArgsというメンバ変数ですることになっている。

指定はそれほど難しくなく、検索フォームで使っているinputタグなどの、name属性を使い、それぞれ型と、ペアとなるモデルデータのフィールドを指定するだけだ。

今回検索フォームには『ID』『ユーザID』『ニックネーム』の3つがあり、それぞれname属性は『id』『username』『nickname』となる。
このname属性は実は何でもいい。『id』を『unko』、『username』を『chinco』、『nickname』を『namco』としても問題ない。それらname属性に対して、実際のモデルデータでどこのフィールをペアにするのかが指定できるからね。

というわけで、早速$filterArgsを設定してみよう。
  • name『id』はfield『User.id』
  • name『username』はfield『User.username』
  • name『nickname』はfield『Profile.nickname』
という指定をするよ。

app/Model/User.php
// 検索対象のフィルタ設定
  public $filterArgs = array(
    array('name' => 'id', 'type' => 'value', 'field' => 'User.id'),
    array('name' => 'username', 'type' => 'like', 'field' => 'User.username'),
    array('name' => 'nickname', 'type' => 'like', 'field' => 'Profile.nickname'),
  );

これを一番下に追記しておく。

typeに入れる『value』とか『like』ってなに?と思うかもしれないが、思った方が健全だ。
これは、例えば検索フォームに入力されたキーワードを部分一致させたい場合は『like』を、IDなどの数字を完全一致で検索したい場合は『value』を指定、というようなニュアンスを掴んでもらいたい。

当然これら2つだけじゃなく、他にも『query』『subquery』なんかの型も用意されている。
詳しくは、Searchプラグインに同梱されている、readme.mdを見ていただきたい。
mdフィルってのはマークダウン方式で記述された、ただのテキストファイルなので、お好きなテキストエディタで開くと良いよ。

開けなかったら、もう一度Githubに言って、readme.mdファイルを選択すれば、整形済みのmdファイルの中身を閲覧できるよ。

readme.md

というわけで、これで検索フォームから投稿された検索データがモデルで扱えるようになった。

次はこれらのデータをコントローラでも扱わなければいけない。
その際Searchプラグインの作法として、コントローラ側で使うプリセットというものを設定しておかないといけない。

これは少々面倒だ。

検索内容を変更するとき、いちいちUser.phpとUsersController.phpを編集するのは厄介。
コントローラ側でモデルの配列を引っ張ってきて、その場で代入すればいいじゃん、と俺は思ってる。

だからマニュアルにはコントローラにかけと書いてある$presetVarsを、モデルファイルに書いてしまうと思う。

というわけで、引き続きモデルファイルの末尾に以下を追記しよう。

app/Model/User.php
// 検索対象のフィールド設定
  public $presetVars = array(
    array('field' => 'id', 'type' => 'value'),
    array('field' => 'username', 'type' => 'value'),
    array('field' => 'nickname', 'type' => 'value'),
  );

さて、このPresetVarsの指定、fieldとtypeってあるけど、さっきのfilterArgsと何が違うの?と思うかもしれない。

俺もそう思った。

とりあえず説明すると、fieldはモデルデータのフィールド名。つまりUserモデルのidフィールドなら、idと書けばいいし、typeは例えば複数選択ならcheckboxと書いたり、lookupなんていう凝った指定もできる。

まだ解析してないのでなんとも言えないけどこの$presetVarsは、検索結果用のURLを生成するためのロジックへ渡すための処理なんだと思ってる。それをSearch.Prgというコンポーネントで行なっている(と思う)。

で、本来コントローラに書くデータをモデルにまとめたので、コントローラ側では一旦この$presetVarsを読み出す処理がひつようになるよね。

というわけで、コントローラをいじろう。
まずはSearch.Prgコンポーネントの指定、それからモデルで記述した$presetvarsの代入だ。

app/Controller/UsersController.php
public $name = 'Users';
  public $uses = array('User', 'Profile');
  public $components = array('Search.Prg');
  public $presetVars = array();

$conponentsにSearch.Prgの指定、それと$presetVarsの初期化だ。
次に、モデルに記述した$presetVarsを、この初期化したコントローラ側の$presetVarsに代入するため、beforeFilter内に代入式を書いておくようにする。

app/Controller/UsersController.php
public function beforeFilter()
  {
    // 検索対象のフィールド設定代入
    $this->presetVars = $this->User->presetVars;
   
    // ページャ設定
    $pager_numbers = array(
      'before' => ' - ',
      'after'=>' - ',
      'modulus'=> 10,
      'separator'=> ' ',
      'class'=>'pagenumbers'
    );
    $this->set('pager_numbers', $pager_numbers);
  }

3〜4行目を追記だ。

これでSearchプラグインの準備は万端。
次にページネーションされたURLと、検索されたURLをくっつける処理が必要になる。

ページネーションはindexアクションで行ってるので、ここを少し修正してみよう。

app/Controller/UsersController.php
// 検索条件設定
  $this->Prg->commonProcess();
  // 検索条件取得
  $conditions = $this->User->parseCriteria($this->passedArgs);

検索条件設定でURLを生成。検索条件取得で検索条件を設定。
それをページネータに絡ませるには、上記の記述も含めて、以下のようにすればOK。

app/Controller/UserController.php
public function index()
  {
    // 検索条件設定
    $this->Prg->commonProcess();
    // 検索条件取得
    $conditions = $this->User->parseCriteria($this->passedArgs);

    $this->paginate = array(
      'conditions' => $conditions,
      'limit' => 3,
    );
    $this->set('users', $this->paginate('User'));
  }

これで完成だ。

■検索してみよう

さて、検索フォームの『ユーザID』に『com』と入れて検索してみよう。
メールアドレスにcomが付くユーザは結構いるので、数名ヒットするだろうね。

そして1ページ3件にしているので、ページネーションもちゃんとジャンプメニューが表示されている状態だ。なにげないね。


この状態でページネーターのジャンプリンクをクリックした場合、もしSearchプラグインが入ってなかったら、検索した『com』は無視され、ただ無検索状態で指定したページが表示されるはずだ。

しかし今回Searchプラグインで『検索結果もページャーに反映させる』を行ったので、期待する通り、検索結果を考慮したURLが生成されるはず。

というわけで、まず『com』を検索してみただけの時のURLは以下のようになった。

/users/index/id:/username:com/nickname:

ユーザIDにcom、つまり『username:com』ってことなのは一目瞭然だと思う。

そして次は、2ページ目のジャンプリンクをクリックしてみよう。


正しく検索キーワードもそのまま残り、ページ遷移も問題無いことがわかった。
この時のURLは、

/users/index/id:/username:com/nickname:/page:2

だ。2ページ目のジャンプリンクをクリックした時の、page:2が追加されているよ。
これならもう、どんなキーワードだって検索できちゃうこと請け合いなしだね。

■終わり

プラグインってすごく便利。これからじゃんじゃん使っていこうと思うんで、まずはどんなプラグインが存在するのかをまとめておくとかすると良いかもね。