WHAT'S NEW?
Loading...

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

さて、今回でSearch Pluginの説明は終わらせる予定だ。
CakePHPには他にもびっくりするくらい便利なプラグインが山盛り存在する。

もちろんヘルパ、コンポーネント、ビヘイビアなども沢山あるので、一度Bakeryを見てみるのも良いと思う。

というわけで本題に入るが、前回適当すぎて投げやりだった、ページャのスタイルを直してしまおうと思う。



以下のCSSスニペットを、/app/webroot/css/cake.generic.cssの最終行にでも追記しておこう。最低限のスタイルを設定してみた。
.pagers {
  text-align:center;
  font-size:large;
}
.pagers a,
.pagers span {
  padding:0 0.2em;
}
.pagers .disabled {
  color:#ccc;
}
これでどうにか人様にお見せできるようになった。



■モデルを作ろう

さて、SearchPluginを使うためには、モデルファイルが必要だ。
以下の場所にモデルファイルを作ろう。

モデル /app/models/user.php
class User extends AppModel {
  public $name = 'User';

  // 検索プラグイン
  public $actsAs = array('Search.Searchable');

}

$actsAsで、SearchPluginを読み込む。つまりこの場合はビヘイビアだ。

これでモデルはできたけど、実はSearchPlugin、このモデルファイルに設定を記述することになっている。
モデルだけではなく、コントローラ側にも必要だ。

正直面倒臭いが、とりあえず教科書通りやってみよう。

まずは、どのモデルのどのフィールドを検索対象にするのか、を連想配列で指定する。これをフィルタと呼ぶ。
というわけで、publicな$filterArgsに代入する。

モデル /app/models/user.php
class User extends AppModel {
  public $name = 'User';

  // 検索プラグイン
  public $actsAs = array('Search.Searchable');

  // 検索対象のフィルタ設定
  public $filterArgs = array(
    array('name' => 'id', 'type' => 'value', 'field' => 'User.id'),
  );
}
これで、Userモデルのidフィールドを対象に検索させる設定になる。
  • name
  • type
  • field
それぞれに当てはまる内容を記述すれば良い。

注意点として、nameはフィールド名ではなく、inputタグで使うname属性が元になっていると言う事だ。

そしてtypeはDOMで言うところの[type="text"]に近い扱いになる。textならvalueと指定すればOK。他にもlike、query、subquelyなどあるので、部分一致させたい場合などはlikeにすれば良い。
fieldは、モデルとフィールドを指定する。
その他にもmethodという項目もあり、独自に実装したメソッド名を指定することもできるが、詳しくは付属のmdファイルをテキストエディタで開いて一読しておくことを強くおススメする。

■コントローラを編集

というわけで、とりあえずUserモデルのidフィールドの設定をしてみたが、今度はこれをコントローラ側で受けなければいけない。
そのためにコントローラを編集する必要がある。

受けるための配列はpublicな$presetVarsだ。
ついでにコンポーネント、モデルの指定などもpublicで指定しておくことにする。

コントローラ /app/controller/users_controller.php
class UsersController extends AppController {
  public $name = 'Users';
  public $uses = array('User', 'Profile');
  public $components = array('Search.Prg');

  // 検索対象のフィールド設定
  public $presetVars = array(
    array('field' => 'id', 'type' => 'value'),
  );
 ・
 ・
 ・
$presetvarsの内容だけど、
  • field
  • type
とある。

fieldはfilterVarsで指定したnameを指定し、filterVarsと対になるようにしておくこと。
typeは通常valueで問題ないが、複数選択などの場合はcheckboxなどを指定できるようになっている。
詳しくは付属のmdファイルを軽く読んでいただきたい。

で、モデルファイルとコントローラファイルの2ヶ所にズラズラと同じ数だけ連想配列を書くのがちょっとどうかな、と思っている。
いきなりだけど、コントローラではモデルの配列を代入するだけにしておいて、この内容もモデルに書いてしまおうと思う。
これは別に個人の自由なので、やらなくてもいいし、もちろんやっても良い。

コントローラは以下のようにしておくと良い(全ソース)。

コントローラ /app/controllers/users_controller.php
class UsersController extends AppController {

  public $name = 'Users';
  public $uses = array('User', 'Profile');
  public $components = array('Search.Prg');
  public $presetVars = array();

  public function beforeFilter()
  {

    // 検索対象のフィールド設定代入
    $this->presetVars = $this->User->presetVars;

    // ページャ設定
    $pager_numbers = array(
      'before' => ' - ',
      'after'=>' - ',
      'modulus'=> 10,
      'separator'=> ' ',
      'class'=>'pagenumbers'
    );
    $this->set('pager_numbers', $pager_numbers);
  }

  public function index()
  {
    $this->paginate = array(
      'limit' => 3
    );

    $this->set('users', $this->paginate('User'));
  }

}
で、代入元となるpresetVarsはモデルファイルに書いてしまおう。

モデル /app/models/user.php
class User extends AppModel {
  public $name = 'User';

  // 検索プラグイン
  public $actsAs = array('Search.Searchable');

  // 検索対象のフィルタ設定
  public $filterArgs = array(
    array('name' => 'id', 'type' => 'value', 'field' => 'User.id'),
  );
  
  // 検索対象のフィールド設定
  public $presetVars = array(
    array('field' => 'id', 'type' => 'value'),
  );
  
}
これで、検索項目を増やすときにいちいちモデルファイルとコントローラファイルを行き来して編集する必要がなくなった。

コントローラもスキニーな状態を保てる。その分モデルがファットになるが、本来MVCフレームワークとはそういうものなので甘受しておくと寝覚めが良いだろう。

というわけでモデルはこれで一旦終了。

今度は検索結果を反映させるために、ページャにSearch Pluginを組み込む必要がある。
と言ってもぜんぜん大したこと無い。

ビヘイビアのparseCriteriaというメソッドに対して、GETパラメータを当て込むと、それがfindの条件になってくれるというスグレモノ。

以下のように、コントローラのindexメソッドを変更してみるが良い。

コントローラ /app/controllers/users_controller.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'));
  }
一度$conditions変数に入れているのは、ただわかりやすいからだ。
直接paginateに入れてしまっても構わない。

ここらへんは個人の好きにするのが良い。
いちいちどっちがいいのかなんか、くだらない質問はしてはいけない。みんな忙しいのだ。

■検索してみよう

さて、早速ブラウザで検索してみよう。
ユーザIDに「9」など記入して検索を実行すると、正しく検索結果が反映されたのがわかると思う。


ただこのままだと検索結果が少なすぎて、ページャのテストができない。
今度は検索項目を増やし、ついでにプロフィールも検索できるようにしようじゃないか。

■検索項目を増やす

まずはProfileモデルも検索対象とするため、予めUserモデルとProfileモデルの関連付けをお行う。
モデルファイルを開いて以下のように編集する。

class User extends AppModel {
  public $name = 'User';

  // 検索プラグイン
  public $actsAs = array('Search.Searchable');

  // アソシエーション
  public $hasOne = array(
    'Profile' => array(
      'className'  => 'Profile',
      'foreignKey' => 'user_id',
      'conditions' => null,
      'fields'     => null,
      'dependent'  => true,
    ),
  );
 ・
 ・
 ・
続いて同じくモデルファイルに、項目名を増やす。
今回は3つにしよう。
  • ユーザID
  • ユーザ名
  • ニックネーム
だ。

モデル /app/models/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'),
  );

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

前回、検索フォームのname属性には存在しないフィールド名でもOKと書いたが、このように、どんな名前だろうが、filterVars内のfieldでモデル.フィールドをヒモ付てしまうので、何でもありという意味だ。

というわけで、次は検索フォームに項目を増やそうじゃないか。

エレメント /app/views/elements/searchForm.ctp
<?php e($this->Form->create('User', array('url' => '/users/index')))?>

<fieldset>
  <legend>Search or Die!</legend>
  <dl>
    <dt><label>ユーザID</label></dt>
    <dd><?php e($this->Form->input('id', array(
      'type' => 'text', 'div' => false, 'label' => false)))?></dd>
    <dt><label>ユーザ名</label></dt>
    <dd><?php e($this->Form->input('username', array(
      'type' => 'text', 'div' => false, 'label' => false )))?></dd>
    <dt><label>ニックネーム</label></dt>
    <dd><?php e($this->Form->input('nickname', array(
      'type' => 'text', 'div' => false, 'label' => false )))?></dd>
  </dl>

  <?php e($this->Form->submit('検索', array('div' => false, 'escape' => false)))?>

</fieldset>

<?php e($this->Form->end())?>
9行目に「ユーザ名」を入れてみた。そして本体となるindex.ctp側も編集しよう。

ビュー /app/views/users/index.ctp
<?php $paginator->options(array('url' => $this->passedArgs)); ?>

<?php e($this->element('searchForm'))?>

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


ブラウザで見てみると、こんなふうになったはずだ。
早速、ユーザ名に「com」と入れて検索してみよう。2ページ分表示されたはずだ。


そしてページャで2ページ目をクリックしても、検索条件は消えないで残っているはず。

どうして残るのかというと、Search PluginはURLパラメータに検索条件をすべて組み込んでしまうから、URLを意識してくれるページャと併用すると威力を発揮するわけだ。

これはつまり、ぜんぜん関係ないページから検索条件付きのURLでリンクしても、検索結果を一発で表示できると言う意味になる。

例えば
8 管理者
4 代理店
2 クライアント
1 一般ユーザ
というパラメータを持ったユーザをUserモデルだけで管理する場合、ユーザロールとしてrollフィールドを付けると考えてみよう。

クライアントだけ見せたい場合、わざわざユーザ管理画面からクライアントをプルダウンやラジオボタンで選択し、検索ボタンをクリック、なんてことはせず、ただ単純に

/users/index/roll:2

と、条件をURLに付与してリンクさせれば、それでOKだ。
試しに、以下のURLを別のタブで開いたブラウザで開いてみて欲しい。

/users/index/nickname:太郎

マッキー太郎がヒットした状態でページが表示されたはずだ。
これは便利。

というわけで、いろいろ活用できる便利なプラグイン、SearchPluginの導入のヒントを書いてみた。