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

Posted under CakePHP by uechoco on 金曜日 19 11月 2010 at 11 : 27 : 32

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

ただ、プログラムはめんどくさいんです。現在、CakePHP 1.3.3とMySQLを使用していますが、一番簡単なのは

php:
  1. $this->paginate = array('order'=>'RAND()');

として、すべてをMySQLに任せてしまうことです。これは非常に簡単です。コンテンツやユーザ数が少ないうちはパフォーマンスもそれほど気にはなりません(※ORDER BY RAND()は重い処理ということだけは覚えておいてください)。ただ、すぐにクライアントにこう言われてしまいます。「1ページ目と2ページ目に同じものが含まれている」とか「1ページ目に戻ったらさっきと全く違う結果になった」とかです。

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

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

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

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

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

php:
  1. // environment: CakePHP 1.3.3
  2.     function search() {
  3.         $this->set('title_for_layout', 'ランダム ページネーション');
  4.        
  5.         $page = isset($this->params['named']['page']) ? $this->params['named']['page'] : null;
  6.         if ($page !== null) {
  7.             $seed = $this->Session->read('User.search.seed');
  8.         } else {
  9.             $seed = mt_rand();
  10.             $this->Session->write('User.search.seed', $seed);
  11.         }
  12.        
  13.         $this->paginate = array('order'=>'RAND(('.$seed.'))', 'limit'=>10);
  14.         $this->set('user_list', $this->paginate('User'));
  15.     }

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

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

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


[CakePHP]LIKE文のワイルドカードをエスケープする

Posted under CakePHP,php by uechoco on 木曜日 16 9月 2010 at 16 : 43 : 12

CakePHPでは等号(=)以外のSQL演算子を特殊な方法で指定します。たとえば、比較演算子であれば以下のような感じ。

php:
  1. $params['conditions']['User.created <'] = date('Y-m-d');

同様にLIKE文もこのように指定できます。

php:
  1. $params['conditions']['User.name LIKE'] = $this->data['User']['name'];

ここで困ったのがワイルドカードを使用する場合。検索文字列を%%でくくるなんていうことはよくあることですが、フォームに「%」を入れられてしまった場合、エスケープ処理をしないと全レコードが一致してしまい、検索結果が大変なことになります。

php:
  1. // エスケープ処理をしていないワイルドカード検索
  2. $params['conditions']['User.name LIKE'] = '%'.$this->data['User']['name'].'%';

LIKE文に指定する文字列は独自のエスケープ関数をAppModelに作成して呼び出すことにしましょう。なお、下記ソースコードはUTF-8での使用を想定しているので、そのほかの文字コードで記述する場合はstr_replace()の代わりにmb_ereg_replace()を使用することを検討してください。

php:
  1. class AppModel extends Model
  2. {
  3.     /**
  4.      * SQLのLIKE分のワイルドカードをエスケープする
  5.      *
  6.      * @params string $str LIKE文に指定する検索文字列
  7.      * @params boolean $before 検索文字列の前に % を付与するか
  8.      * @params boolean $after 検索文字列の後に % を付与するか
  9.      * @return string ワイルドカードがエスケープされたLIKE文
  10.      */
  11.     function escapeLikeSentence($str, $before = false, $after = false)
  12.     {
  13.         $result = str_replace('\\', '\\\\', $str); // \ -> \\
  14.         $result = str_replace('%', '\\%', $result); // % -> \%
  15.         $result = str_replace('_', '\\_', $result); // _ -> \_
  16.         return (($before) ? '%' : '').$result.(($after) ? '%' : '');
  17.     }
  18. }

モデル内での使用方法

php:
  1. // エスケープ処理をしたワイルドカード検索
  2. $like = $this->escapeLikeSentence($this->data['User']['name'], true, true);
  3. $params['conditions']['User.name LIKE'] = $like;

こういうエスケープ処理ってフレームワーク側でやってくれるものかと思ってたんですが、ワイルドカードで検索される可能性もあるから、勝手にエスケープはしないんですかね。


CakePHPで普通のタグを出力する

Posted under CakePHP by uechoco on 木曜日 19 8月 2010 at 11 : 58 : 29

久しぶりにCakePHP使ってます。CakePHPで一般的な<meta>タグを出力するには

<?php echo $html->meta(array('name' => 'y_key', 'content' => 'xxxxxxxxx'))."\n"; ?>

と配列にする。上記はYahooサイトエクスプローラ用の<meta>タグです。φ(・_・”)メモメモ


[CakePHP]find(“list”)のセレクトボックスに空要素(選択してください)を追加

Posted under CakePHP,php by uechoco on 火曜日 22 9月 2009 at 10 : 30 : 00

CakePHP 1.2で、モデルに対してfind("list")すると、セレクトボックスに最適な配列を返してくれます。CakePHP 1.1だとgenerateList()って呼ばれていたかもしれません。この配列をそのままViewにsetしてinputタグを作ると、セレクトボックスを簡単に作ってくれますが、いわゆる「選択してください」っていう空要素を吐いてはくれません。

「選択してください」を吐くためにわざわざ配列をarray_merge()しなきゃいけないのかなって思っていたのですが、そこは親切フレームワーク。ちゃんと便利な機能があります。

どうやら、FormHelperのinput()メソッドの第2引数の$optionsに、emptyという要素を含めることで簡単に実現できるらしいです。

Controllerでこんな感じにセットして、

php:
  1. $this->set('hoge_list', $this->Hoge->find('list'));

Viewでこんな感じに使います。

php:
  1. <?php echo $form->input('hoge_id', array('options' => $hoge_list, 'empty' => '選択してください')); ?>

すると、選択ボックスの最初に以下のオプションが増えます。

HTML:
  1. <option value="">選択してください</option>

もちろん、マニュアルにも書いてありますよ。
$options[‘empty’] :: フォーム要素の自動生成 :: フォーム :: 主要なヘルパー :: マニュアル :: 1.2 Collection :: The Cookbook

フレームワークって、使い慣れると最高だけど、使い慣れないと大変ですね。

いちいち調べてたらアプリが期日までに作れねーYo!(’A`)/

がんばろっと。


[SQL]都道府県コードに沿ったSQL

Posted under CakePHP,php,symfony by uechoco on 月曜日 21 9月 2009 at 11 : 27 : 46

適当なアプリを作ると、都道府県テーブルってよく作るよなってことで、SQLを備忘録で貼っときます。HTMLのセレクトボックスで公開していたり、同じようなSQLを公開している人はもちろん居るのですが、symfonyやCakePHPのようなCoCフレームワークのカラム名のものがなかったので、自分のために貼っときます。一応MySQLばっか使うので、MySQLの出力結果になっています。適当にENGINEとか変えれば他でも使えます。もちろん、JIS X 0401に準拠した都道府県コードを使っています。

CREATE文(utf8を想定)

SQL:
  1. DROP TABLE IF EXISTS `prefs`;
  2. CREATE TABLE IF NOT EXISTS `prefs` (
  3.   `id` int(10) UNSIGNED NOT NULL,
  4.   `name` varchar(5) NOT NULL,
  5.   PRIMARY KEY  (`id`)
  6. ) ENGINE=MyISAM  DEFAULT CHARSET=utf8;

INSERT文

SQL:
  1. INSERT INTO `prefs` (`id`, `name`) VALUES
  2. (1, '北海道'),
  3. (2, '青森県'),
  4. (3, '岩手県'),
  5. (4, '宮城県'),
  6. (5, '秋田県'),
  7. (6, '山形県'),
  8. (7, '福島県'),
  9. (8, '茨城県'),
  10. (9, '栃木県'),
  11. (10, '群馬県'),
  12. (11, '埼玉県'),
  13. (12, '千葉県'),
  14. (13, '東京都'),
  15. (14, '神奈川県'),
  16. (15, '新潟県'),
  17. (16, '富山県'),
  18. (17, '石川県'),
  19. (18, '福井県'),
  20. (19, '山梨県'),
  21. (20, '長野県'),
  22. (21, '岐阜県'),
  23. (22, '静岡県'),
  24. (23, '愛知県'),
  25. (24, '三重県'),
  26. (25, '滋賀県'),
  27. (26, '京都府'),
  28. (27, '大阪府'),
  29. (28, '兵庫県'),
  30. (29, '奈良県'),
  31. (30, '和歌山県'),
  32. (31, '鳥取県'),
  33. (32, '島根県'),
  34. (33, '岡山県'),
  35. (34, '広島県'),
  36. (35, '山口県'),
  37. (36, '徳島県'),
  38. (37, '香川県'),
  39. (38, '愛媛県'),
  40. (39, '高知県'),
  41. (40, '福岡県'),
  42. (41, '佐賀県'),
  43. (42, '長崎県'),
  44. (43, '熊本県'),
  45. (44, '大分県'),
  46. (45, '宮崎県'),
  47. (46, '鹿児島県'),
  48. (47, '沖縄県');

一応HTMLの<select>タグ形式

HTML:
  1. <select name="pref_id">
  2.   <option value="1">北海道</option>
  3.   <option value="2">青森県</option>
  4.   <option value="3">岩手県</option>
  5.   <option value="4">宮城県</option>
  6.   <option value="5">秋田県</option>
  7.   <option value="6">山形県</option>
  8.   <option value="7">福島県</option>
  9.   <option value="8">茨城県</option>
  10.   <option value="9">栃木県</option>
  11.   <option value="10">群馬県</option>
  12.   <option value="11">埼玉県</option>
  13.   <option value="12">千葉県</option>
  14.   <option value="13">東京都</option>
  15.   <option value="14">神奈川県</option>
  16.   <option value="15">新潟県</option>
  17.   <option value="16">富山県</option>
  18.   <option value="17">石川県</option>
  19.   <option value="18">福井県</option>
  20.   <option value="19">山梨県</option>
  21.   <option value="20">長野県</option>
  22.   <option value="21">岐阜県</option>
  23.   <option value="22">静岡県</option>
  24.   <option value="23">愛知県</option>
  25.   <option value="24">三重県</option>
  26.   <option value="25">滋賀県</option>
  27.   <option value="26">京都府</option>
  28.   <option value="27">大阪府</option>
  29.   <option value="28">兵庫県</option>
  30.   <option value="29">奈良県</option>
  31.   <option value="30">和歌山県</option>
  32.   <option value="31">鳥取県</option>
  33.   <option value="32">島根県</option>
  34.   <option value="33">岡山県</option>
  35.   <option value="34">広島県</option>
  36.   <option value="35">山口県</option>
  37.   <option value="36">徳島県</option>
  38.   <option value="37">香川県</option>
  39.   <option value="38">愛媛県</option>
  40.   <option value="39">高知県</option>
  41.   <option value="40">福岡県</option>
  42.   <option value="41">佐賀県</option>
  43.   <option value="42">長崎県</option>
  44.   <option value="43">熊本県</option>
  45.   <option value="44">大分県</option>
  46.   <option value="45">宮崎県</option>
  47.   <option value="46">鹿児島県</option>
  48.   <option value="47">沖縄県</option>

symfony系のYAML定義(symfony的にはテーブル名の最後のsはつけない方が一般的かな?)

TEXT:
  1. propel:
  2.   prefs:
  3.     _attributes: { phpName: Prefs }
  4.     id:
  5.     name: { type: VARCHAR, size: '5', required: true, defaultValue: '' }

そのうちフレームワーク毎のモデルクラスとかも貼っておこうかな。


« 前ページへ次ページへ »

Copyright © 2012 うえちょこ@ぼろぐ. WP Theme created by Web Top.