Google検索のように『GET送信+ページング』をCakePHPで実現するには
【CakePHP 1.3系】 最終更新:2011年12月07日
結論
以下のようなURLを生成すればよい。
【通常】
1ページ目
http://example.com/posts/search?keyword=hoge
2ページ目以降
http://example.com/posts/search/page:2/keyword:hoge【検索文字列にスラッシュが含まれる場合】
1ページ目
http://example.com/posts/search?keyword=hoge/fuga/piyo
2ページ目以降(スラッシュを独自にエスケープしている)
http://example.com/posts/search/page:2/keyword:hoge~!fuga~!piyo
解説
はじめは悩む必要はありません。
【ビュー】
echo $form->create('Post', array('type'=>'get'));
echo $form->input('keyword');
echo $form->end('検索');【生成されるURLの例】
http://www.example.com/posts/search?keyword=abc【コントローラ】
//パラメータ"keyword"の値"abc"を取り出すには
$keyword = $this->params['url']['keyword'];
問題はページを移動した場合。
"keyword"パラメータの部分が消えて、何も表示されなくなります。
http://www.example.com/posts/search/page:2
試してみたところ、以下のように変更すれば正常に表示されました。
URLの末尾に手入力でパラメータを追加しています。
が、Paginateヘルパーで出力されたページング用リンクは、クラス名や
http://www.example.com/posts/search/page:2?keyword=abc
IDが設定されていないため、JavaScriptで後からパラメータ部分を
href属性のURLの末尾に強引に追加、ということができません。
いや、できるかもしれませんがかなり面倒くさいと思います。
もしくは、Paginateヘルパーを使わずに、自分でページング用リンクを
作成するか…。
別の方策を探し、ググってたどり着いたのが下記のページ。
Think Twice · 検索条件を引き継ぐpaginate
これで、ヘルパーで楽をしつつ、検索文字列を引き継ぐことができます。
【ビュー】
echo $paginator->options(array('url'=>array('keyword'=>rawurlencode($keyword))));【生成されるURLの例】
http://www.example.com/posts/search/page:2/keyword:abc【コントローラ】
//パラメータ"keyword"の値"abc"を取り出すには
if(!empty($this->params['named']['keyword'])){
//ページ移動の場合
$keyword = $this->params['named']['keyword'];}elseif(!empty($this->params['url']['keyword']){
//初回の場合
$keyword = $this->params['url']['keyword'];}else{
...
}
しかし、もうひと手間必要です。
このままでは、検索文字列に『/』(スラッシュ)が含まれていると、
正常に表示されず、場合によってはエラーになってしまいます。
『urlencode』でも『rawurlencode』でも、スラッシュは無害化できません。
【例: 『aa/bb/cc』で検索した場合】
http://www.example.com/posts/search/page:2/keyword:aa/bb/cc
//keywordは、『aa』として認識されてしまう
考えてみれば当然ですね。
スラッシュはURL中で使われるものなんですから…。
たとえ『%2F』に変換しても、スラッシュとして扱われるようです。
これは、以下の記号でも同じです。
- : (コロン)
- % (パーセント)
- ? (クエスチョン)
- # (ハッシュ)
- & (アンバサンド)
- ' (シングルクォート)
- \ (バックスラッシュ)
そこで、自作のエスケープ処理でこれらの記号をを別の文字に
変換することにしました。
URLエンコードで『%xx』の形に変換されない『~』(チルダ)と
半角英字を使っています。
【エスケープ】
【ビュー】
$keyword = preg_replace_callback(
'/(~)|(\/)|(:)|(%)|(\?)|(#)|(&)|(\')|(\\\\)/u',
'_escape_callback',
$keyword
);
//置換のコールバック関数
function _escape_callback($matches){
if(isset($matches[9])){
return '~i';
}elseif(isset($matches[8])){
return '~u';
}elseif(isset($matches[7])){
return '~y';
}elseif(isset($matches[6])){
return '~t';
}elseif(isset($matches[5])){
return '~r';
}elseif(isset($matches[4])){
return '~e';
}elseif(isset($matches[3])){
return '~w';
}elseif(isset($matches[2])){
return '~q';
}else{
return '~~';
}
}
//『keyword:aa~!bb~!cc』としてURLに加えられる
$keyword = rawurlencode($keyword);
echo $paginator->options(
array('url'=>array('keyword'=>$keyword))
);
【アンエスケープ】
※1コールバック関数が、クラス内のメンバ関数の場合の書き方
【コントローラ】
function search(){
if(!empty($this->params['named']['keyword'])){
//----------------------------
//ページ移動の場合
//----------------------------
$keyword = $this->params['named']['keyword'];
$keyword = rawurldecode($keyword);
//元に戻す。
$keyword = preg_replace_callback(
'/(~~)|(~q)|(~w)|(~e)|(~r)|(~t)|(~y)|(~u)|(~i)/u',
array($this,'_search_callback'), //※1
$keyword
);
}elseif(!empty($this->params['url']['keyword'])){
//----------------------------
//初回の場合
//----------------------------
$keyword = $this->params['url']['keyword'];
}else{
...
}
}
//置換のコールバック関数
function _search_callback($matches){
if(isset($matches[9])){
return '\\';
}elseif(isset($matches[8])){
return '\'';
}elseif(isset($matches[7])){
return '&';
}elseif(isset($matches[6])){
return '#';
}elseif(isset($matches[5])){
return '?';
}elseif(isset($matches[4])){
return '%';
}elseif(isset($matches[3])){
return ':';
}elseif(isset($matches[2])){
return '/';
}else{
return '~';
}
}
参考:http://www.karak.jp/articles/blog/preg_replace_callback.html