1.2・検索結果をpaginateする方法

cakePHPのpaginationは確かに便利。
あのインデックスを作ったりは本当にめんどうなので助かるのですが、
それをちょっと複雑に使おうとすると一苦労。


ていうか検索した結果をpaginateしたいと思うのは普通だと
思うんですけど、その割には使いにくい…。


しかもcakePHPのバージョンでまるで動作が異なるようで、
Webの情報がばらんばらんなんですね。
ということで、自分なりにまとめて書いてみようと思います。
(cakePHP1.2.4.8284)

インデックス

  • paginateは二つからなる(コントローラ側・ビュー側)
  • paginateはSQLを作ってくれるコンポーネントに過ぎない
  • 検索条件は一度渡しただけでは消えてしまうので、保存する必要あり
  • ヘルパーのsortの動作は微妙なので要注意
  • sortでascとdescを切り替える際の注意
  • sortした際にページを1ページに戻すようにする際の注意

何がどうなってるのか?

cakePHPのpagination(paginateやらpaginatorやらと呼ばれる)
は二つのパートからなっています。

  • findの代わりに用いる「paginateコントローラ」
  • paginateした結果を表示するための「paginatorヘルパー」

基本的な使い方

コントローラ内

(1) findを使うように、paginateにパラメータを渡す
(2) paginateが返したデータをViewに渡して表示する

ビュー内

(3) paginateのデータを展開する
(4) 各ページやソートなどを行うリンクをpaginatorヘルパーで表示する

コントローラ内(paginate)

まずはパラメータを設定する

paginateメソッドはページング専用のfindだと思ってください。
なので単純にfindで渡すようなパラメータをpaginateに渡せばいいです。
例えばPOSTされたkeywordという文字列のLIKE検索をしたければこんな感じ。

# model_controller.php
function search() {
  if(!empty($this->data)) {
    // パラメータの設定
    $params['condition'] = 
      array('Model.string LIKE' =>
        '%'.$this->data['Model']['keyword'].'%');
    $params['limit'] = 100;  // 100件ずつページング

    // パラメータをpaginateに渡す
    $this->paginate = $params;

    // DBの検索結果をViewに渡す
    $this->set('models', $this->paginate());
  }
}

# search.ctp
pr($models);  // とりあえず展開してみる
$paginator->prev('前へ ');
$paginator->number();
$paginator->next('次 ');

条件指定してpaginateに渡して、$this->paginateすることで
結果を得ます。これは通常のfindと同じような感じです。
で、あとはこれをViewに渡すということになります。


ここで条件を渡すのに使っているparamsですが、paramsには
以下のような項目が設定できるようです。

  • 'conditions' => array(検索条件),
  • 'fields' => array(取得するフィールド),
  • 'page' => int(表示するページ番号),
  • 'limit' => int(一回あたりに表示する件数),
  • 'sort' => string(ソートのキーとなるフィールド名),
  • 'direction' => string(asc / desc)
  • 'recursive' => よくわかりません。。

View側では、とりあえずの検索結果がダンプされ、
下の方に「前へ」「1 2 3 4 …」「次へ」というリンクが
貼られていると思います。


このリンクはpaginatorヘルパーがpaginateの結果と同調して
生成したリンクで、paginateはこのリンクから渡される引数を
元に、どういう処理を行うべきなのかを判断して処理を続行します。

ここまででは検索条件が消える

これでブラウザ等で該当のページを見ても、1回目はうまくページングされると思いますが、
数字(ページ番号)のリンクや「次へ」「前へ」などのリンクを
クリックしても、うまくページが遷移しないと思います。


それもそのはずで、cakePHPでのpaginateは検索条件を保存しておいてくれない!のです。なんということでしょう。
ということで、先ほどのソースにSessionを用いた改造を施します。

# model_controller.php
function search() {
  if(!empty($this->data)) {
    // パラメータの設定
    $params['condition'] = 
      array('Model.string LIKE' =>
        '%'.$this->data['Model']['keyword'].'%');
    $params['limit'] = 100;  // 100件ずつページング

    // パラメータをセッション変数に保存
    $this->Session->write('params', $params);
  } else {
    // セッション変数の展開
    if($this->Session->check('params')) {
      $params = $this->Session->read('params');
    }
  }

  // パラメータをpaginateに渡す
  $this->paginate = $params;

  // DBの検索結果をViewに渡す
  $this->set('models', $this->paginate());
}

エラートラップは目をつぶってください。

フォームデータのPOSTによってこのアクションが参照されて
いないということは、$this->dataがないということです。
逆にいうと$this->dataが存在するということは、ユーザーが
条件を指定して検索をスタートさせたか、検索条件を変更したか
のどちらかしかありません。

ということで、$this->dataがある場合はその条件をセッションに
保存し、逆に$this->dataがない場合はセッションからその条件を
読み取ることにしました。
これでページング自体はうまくいくはずです。

sortをさせたい

paginatorヘルパーは非常に強力なヘルパーで、paginateに指示を
出すための様々なリンクを作ってくれます。
今回自分が必要だったのはソートを行ってくれるリンクでした。

$paginator->sortの動作
  • sort と directionを指定する
  • 現在のdirectionがascなら、生成されるリンクはdescになる
  • すなわち本当ならasc/descがフリップフロップするようにリンクを自動生成してくれるはず
sortの書き方
#search.ctp
$paginator->sort('IDで並べ替え', 'id');

この状態で実行してみるとわかるのですが、並び替えのデフォルト
設定はascのため、小さい順でデータが表示されます。
ではここで「IDで並び替え」をクリックしたら…大きい順になって
欲しい!ですよね。
でも、実際には小さい順のままでまた表示されます。


しかし不思議なことにこの状態でもう一度「IDで並び替え」を
クリックするとちゃんと大きい順にソートされて表示されるのです。
困った困った。

原因と対策

この原因は並び替えのパラメータをpaginateに渡していないから起こる現象です。
ちょっと複雑なのですがsortの仕様がそのようになっているようで、
asc→descに切り替わるには、現在指定されている並び替え順がascでなくてはならない
ようなのです。
で、考えてみると並び替えの指定をしていない。
従ってまたコントローラの方をいじることで解決してあげます。

# model_controller.php
function search() {
  if(!empty($this->data)) {
    // パラメータの設定
    $params['condition'] = 
      array('Model.string LIKE' =>
        '%'.$this->data['Model']['keyword'].'%');
    $params['limit'] = 100;  // 100件ずつページング

    // パラメータをセッション変数に保存
    $this->Session->write('params', $params);

    // 並び替えの条件を指定する
    $params['sort'] = 'id';
    $params['direction'] = 'asc';
  } else {
    // セッション変数の展開
    if($this->Session->check('params')) {
      $params = $this->Session->read('params');
    }
  }

  // パラメータをpaginateに渡す
  $this->paginate = $params;

  // DBの検索結果をViewに渡す
  $this->set('models', $this->paginate());
}

セッションに保存した後にこの指定をしているのは、1番最初の
検索時にだけこのオプションを持たせたいからです。
もしこれらをセッションに保存してしまうと、常にascで検索が
かかるようになってしまい、sort側でasc/descのフリップフロップ
起こらなくなってしまいます。


ここまでで、おそらくsortのasc/descもうまく切り替わっているのではないでしょうか。

sortの表示を昇順/降順で切り替えたい

「IDで並び替え」だけ表示されてもわかんないですよね。
「IDで並び替え▲」「IDで並び替え▼」くらいに表示を
切り替えて、せめてどういう風に並び替えるかは知りたいところ。
それには$this->passedArgsを参照するといいです。

$this->passedArgsってなんだ?

いろいろなブログで紹介されているこのpassedArgsですが、
これはただ「使われたパラメータ」というだけのもんです。
書き込みももちろんできますが、書き込んだところでどうにも
ならないです。どういう条件で前回の検索がされたかを示すだけのものです。

どうするのか

こんな感じです。

# search.ctp
  if(@$this->passedArgs['sort'] === 'id' &&
     @$this->passedArgs['direction'] === 'desc') {
        echo $paginator->sort('IDで並び替え▲', 'id');
  } else {
        echo $paginator->sort('IDで並び替え▼', 'id');
  }

「どんな風に検索されたか」を参照してやって、その内容で
sortに渡す引数を切り替えているだけです。簡単です。
こんな時にpassedArgsは使えます。

sort順を変更した際に1ページ目から表示させたい

意外と困ったのがこれ。
asc/descを切り替えられるようになったけど、普通これを切り替えたら
1ページ目に戻るんじゃね?という要望。


優秀なんだかなんなんだか、sortでこれをやると、asc/descを
切り替えて検索をして、現在いるページ番号のページに遷移
してくれるんですね。
これはsortに含まれる「page:x」という引数を利用して飛ぶページを
決定しているためです。
であれば、これを変えてしまえばいい。
ビューのコードを改造します。

# search.ctp
  if(@$this->passedArgs['sort'] === 'id' &&
     @$this->passedArgs['direction'] === 'desc') {
        // urlのオプションを変える
        $paginator->options(array(
            'url' => array('page' => '1'))
        );
        echo $paginator->sort('IDで並び替え▲', 'id');
  } else {
        // urlのオプションを変える
        $paginator->options(array(
            'url' => array('page' => '1'))
        );
        echo $paginator->sort('IDで並び替え▼', 'id');
  }


これは相当悩んだんですが、paginatorヘルパーのオプションを変えればいいんだと。
しかもURLに含まれるpageの引数を変えることでうまいリンクを
生成してくれます。


とりあえずはこんな感じでしょうか。
cakePHPのpaginateはすばらしいですが、こんな感じで微妙に
使い勝手の悪さが残っています。
これからさらによくはなっていくんでしょうが、結構苦労しました…。


Paginatorの使い方。CakePHP1.2 - CPA-LABテクニカル
http://www.cpa-lab.com/tech/Paginator%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9%E3%80%82CakePHP1.2


"条件をつけたpaginateでページ繰りができない" フォーラム - CakePHP Users in Japan
http://cakephp.jp/modules/newbb/viewtopic.php?viewmode=flat&topic_id=1258&forum=3


机上の楼閣: CakePHP1.2のPaginateについて
http://castleonthedesk.seesaa.net/article/117356585.html


paginationのソート表示で、画像を使う - cakephperの日記(cakePHP1.2ベース)
http://d.hatena.ne.jp/cakephper/20081028/1225156993


CakePHPでのPaginator - 院生エンジニアのにっき
http://d.hatena.ne.jp/charly24/20070417