[CakePHP]ランダム順ページネート

最近「このリストはランダム順でページングしてほしい」という要望を何度かもらいました。ランダムなんだから順番なんてないだろう!って思うのですが、サイトのリリース時にはコンテンツが少ないのでランダム順というのは結構見栄えがいいようです。

ただ、プログラムはめんどくさいんです。現在、CakePHP 1.3.3とMySQLを使用していますが、一番簡単なのは
[phpcode]
$this->paginate = array(‘order’=>’RAND()’);
[/phpcode]
として、すべてをMySQLに任せてしまうことです。これは非常に簡単です。コンテンツやユーザ数が少ないうちはパフォーマンスもそれほど気にはなりません(※ORDER BY RAND()は重い処理ということだけは覚えておいてください)。ただ、すぐにクライアントにこう言われてしまいます。「1ページ目と2ページ目に同じものが含まれている」とか「1ページ目に戻ったらさっきと全く違う結果になった」とかです。

一般的な人が思う「ランダム順にページネート」というのは、

  • 別のページからそのページに来たときにランダムが切り替わる
  • ページングしている間は同じページには同じ内容を表示する(1ページ目->2ページ目->1ページ目ともどると、1回目と2回目の1ページ目の内容は同じ)

さて、どうしたものでしょうか。私の場合は

  • RAND()関数に乱数シードを与えることで、同一シードでは同じ結果が返る
  • 乱数シードをページング中にセッションに保持しておけば、結果順を固定できる

ということを利用して、以下のようなプログラムを書きました。

[phpcode]
// environment: CakePHP 1.3.3
function search() {
$this->set(‘title_for_layout’, ‘ランダム ページネーション’);

$page = isset($this->params[‘named’][‘page’]) ? $this->params[‘named’][‘page’] : null;
if ($page !== null) {
$seed = $this->Session->read(‘User.search.seed’);
} else {
$seed = mt_rand();
$this->Session->write(‘User.search.seed’, $seed);
}

$this->paginate = array(‘order’=>’RAND((‘.$seed.’))’, ‘limit’=>10);
$this->set(‘user_list’, $this->paginate(‘User’));
}
[/phpcode]

ページング中(2ページ目を見たり、また1ページ目に戻ったり)であれば、URLに「page:n」が付くことを利用して、その時はセッションから乱数シードを取得しています。逆に「page:n」がない場合は他のページから来たものとみなして、乱数シードを再生成しています。

ちなみに、RAND()関数の括弧が二重になっているのはちょっとしたハックです。実は一重だと、CakePHPが乱数シードの数字を勝手にバッククォートでくくってしまいます。括弧を二重にすることで、ただしいRAND()関数が呼べるようになります。

今のところこれで適当に動いています。もっといい方法があったら教えてください!

About: uechoco