Displaying posts written in

7月 2010

Searchプラグインによる検索条件をページングで引き継ぐ(Prgコンポーネントを置き換える)

環境: CakePHP 1.3

CakeDCのSearchプラグインは、全体としては非常に優れたプラグインなのですが、Prgコンポーネントの機能がどうしても私の使い方には合いませんでした。具体的に言うと

  • 検索パラメータの引き継ぎにnamedを使うのは問題が多い(参考)。代わりにQueryStringを使いたい。
  • Searchableビヘイビアと同じような内容の $filterArgs を書くのが手間。
  • そもそも値の検証や制限はモデルでやるべき、と考える。コンポーネント内でバリデーションを実行したりDBアクセスまでするのは仕事のし過ぎ。

これらの点を解消するため、昔作ったSearchPaginationプラグインを全面改訂して、Searchプラグインと連携できるようにしました。Prgコンポーネントの代わりに使うことができます。

http://github.com/tkyk/cakephp-search-pagination (旧版は 1.2 ブランチを参照)

使い方は非常に簡単で、コントローラの中で SearchPagination コンポーネントの setup メソッドを実行するだけです。

class UsersController extends AppController {
  var $components = array('SearchPagination.SearchPagination');
 
  function search() {
    $this->SearchPagination->setup('User');
    //...
  }
}

これによってQueryStringから検索パラメータが取得され、data[ModelName] に格納されるので、あとはそれを Searchable ビヘイビアの parseCriteria に渡すなどして、検索条件を組み立てます。

function search() {
  $this->SearchPagination->setup('User');
 
  // 常に $this->data['User'] に検索パラメータが入っている
 
  $this->paginate['conditions'] = $this->User->parseCriteria($this->data['User']);
  $this->set('users', $this->paginate('User'));
 
  $this->set('groups', $this->User->Group->find('list'));
}

ビューでは特に何もしなくても自動的に検索パラメータが引き継がれます。普通にFormヘルパーやPaginatorヘルパーを使ってください。

echo "<h2>Search Form<h2>";
echo $this->Form->create('User', array('type' => 'get'));
echo $this->Form->input('username');
echo $this->Form->input('created');
echo $this->Form->input('group_id');
echo $this->Form->end('Search');
 
echo $this->Paginator->numbers(array('modulus' => 10));

Formヘルパーのcreateメソッドには、typeとして ‘get’ を指定することをおすすめします。もし指定しなかった場合は(Prgコンポーネントに倣って)自動的にGET用URLを生成してリダイレクトされます。

[解決]iPhone 3GSをiOS4にしてからバッテリーの消耗が早い

症状:
iPhone 3GSをiOS4にアップデートして以来、バッテリーの減りが異常に早くなった。
原因:
(多分)スリープ中もWi-Fiが有効になる状態だったから。
解決策:
(私の場合)スリープボタン+ホームボタン長押しで強制リセットしたら直った。

去年12月に買ったiPhone 3GS。以前は2日充電しなくても余裕があったのに、iOS4にアップデートして以来、1日放置しておくだけで残り15%近くまでバッテリーを消耗する状態になった。「iOS4 バッテリー」というキーワードで検索してみると、同様の症状を訴える人はたくさんいる。中でも『iOS4ではスリープ中でもWi-Fiがオフにならず、そのせいでバッテリーを早く消耗する』という情報が気になった。言われてみれば、iOS4にして以来、スリープから復帰させる時にWi-Fiのシグナルがいつも立っている。以前なら0.5秒〜1秒くらいの間があって「3G」→「Wi-Fiシグナル」と表示が切り替わっていたのに。

しかし特に問題がない人も多いところを見ると、これがiOS4の正規の挙動なのかどうかは怪しい。とりあえずものは試しと、買ってから初めて強制リセットを試してみた。方法は右上のスリープ/スリープ解除ボタンとホームボタンを押し続け、「電源オフ」のスライダが出ても押し続け、画面が暗転して銀色のリンゴが出てくるまで待つ。…すると何かの設定がリセットされたのか、Wi-Fiの挙動が以前の状態に戻り、バッテリーの異常な消耗も解消された。

必ずこの方法で復旧するとは言い切れないが、同様の問題で悩んでいる方は一度試してみると良いかと。

CakeのURLパラメータに特殊文字を使ってはいけない

環境: CakePHP 1.3

CakePHPはURLパラメータのエンコードを一切行わないため、特殊な文字がパラメータに入り込むと容易にルーティングが破綻する。ここで言う「URLパラメータ」とはRoute中に埋め込まれたパラメータ、namedパラメータ、passパラメータのことを指す。

例えば以下のようなRouteがあったとする。

/controller/action/:keyword

ここでパラメータkeywordに特殊な文字を与えると…

keyword => '%'
URL: /controller/action/%
結果: URLとして不正な形式なので"400 Bad Request"になる
keyword => '?foo'
URL: /controller/action/?foo
結果: ?以降はQueryStringと見なされてルーティングから除外され、Routeにマッチしなくなる
keyword => 'a/b/c'
URL: /controller/action/a/b/c
結果: /がそのままパラメータ区切りになるのでRouteにマッチしなくなる

この問題は単純に rawurlencode/rawurldecode を使うだけでは回避できない。またCakeの設計上の問題なので、簡単な修正方法もない。よって任意の文字が含まれる可能性のあるパラメータをURLに埋め込んではいけない。最も簡単な回避策は、URLパラメータを諦めてQueryStringを使うことである。

Router::url(array('?' => array('keyword' => $any_characters)));

問題の原因と回避策の考察

なぜ rawurlencode で回避できないかというと、CakePHPが mod_rewrite 経由で $_GET['url'] から元のURLを取得しているからだ。

RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]

RewriteRuleの後方参照($1の部分)では%エンコーディングのデコードが行われる。またQueryStringから $_GET に格納される際にもPHPによってデコードが行われる。つまりCakeのルーティングが実行される前に、既に2回のデコードが行われている。よってもしこの問題を rawurlencode で回避するなら、3重に rawurlencode した上で rawurldecode しなければならない、ということになる。

ちなみに RewriteRule の[B]オプションを指定することで後方参照のデコードを回避することができる。この場合は rawurlencode は2回で済む。しかしそれでもURLが汚くなることは変わりないし、検索エンジンのロボット等もURL中の文字列を認識してくれないだろう。

RewriteRule ^(.*)$ index.php?url=$1 [QSA,L,B]

別の考え方として、%エンコーディング以外の安全なエンコーディングを用いる方法もある。例えば16進数のバイナリ表現に変換すれば、[a-f0-9]しか現れないので確実に安全である。

$encoded = bin2hex($data);
 
//[0-9a-f]しか含まれないことを事前にチェックしておくこと
$decoded = pack('H*', $encoded);

ただしこの方法だと長さは元の文字列の2倍になるし、可読性も全く損なわれる。ここまでするくらいならやはりQueryStringを使った方が良いと思う。

第2回CakePHP勉強会@福岡 発表資料

7月3日に福岡で開催されたCakePHP勉強会で、『MarkupHelperの紹介および PHPによるDSL実装の可能性について』という題で発表を行いました。以下に資料をアップロードしておきます(右キーで進む、左キーで戻る、上キーでサムネイル表示切り替え)。

資料へのリンク

Studio ODIN – 高橋メソッド風 プレゼンスクリプトを使用しています。発表本番では改造版を使ったのですが、ライセンスが不明なので本家のスクリプトに直接リンクを貼っています。

githubのMarkupHelperリポジトリはこちら:
http://github.com/tkyk/cakephp-markup-helper

補足

質疑応答より。多少内容を変更しています。

Q. デザイナーに触ってもらうのは難しいのではないか?
A. このヘルパーはもともと「プログラマーがHTMLを書くのを楽にしよう」というコンセプトで作られているため、デザイナーとの連携が必要になる場面では使用できない・使用すべきでないと言えます。(デザインが比較的どうでもいい)内部向けの管理画面、ごく小さなelement、HTMLを生成する別のヘルパーの中、などで使用するのに向いています。
別の言い方をするとコードの分量が HTML > PHP の場合には使う意味は薄いです。コードの分量が PHP >= HTML の場合に威力を発揮します。
Q. XHTML以外のマークアップ言語を作ることはできるか?
A. MarkupHelperは「正しいXHTMLのタグか否か」をチェックしていないので、基本的にはどんなマークアップでも作ることができるはずです。ただし「そのタグが閉じタグを持つか否か」という情報を $emptyElements プロパティ(public)で管理しているので、必要に応じて再定義する必要があります。
Q. ループや条件分岐をメソッドチェーンに組み込めるか?
A.PHP言語の機能的な制約により、不可能です(PHP5.3のクロージャを使えば一応はできるかもしれませんが、書式が煩雑なのでDSLとしては実装できないでしょう)。
ループや条件分岐に相当する処理を入れつつメソッドチェーンを切りたくない場合は、その部分の処理を別のヘルパーのメソッドにまとめて連結することをおすすめします。
$markup->ul->MyHelper_loop_li($arr)->end;

……

勉強会で発表するのが初めてだったので緊張してしまい、発表中は聞いている人の反応を見ている余裕がありませんでした。早口に一気にまくしたててしまったような気がして、理解してもらえたかどうか不安だったのですが、あとで感想を聞くとそれなりに好評だったようでほっとしました。それから高橋メソッドによる大きな文字は、Ustream経由でも見やすかったようです。

勉強会の進行は(いくつか想定外の事態があったにも関わらず)大変スムーズで、気持ちよく発表できました。@k1LoWさんはじめ株式会社Fusicの皆さん、素晴らしい時間をありがとうございました。

福岡は魚もお酒も美味しくて、2次会以降ついつい飲み過ぎてしまいました。酔った勢いで、特に3次会ではずいぶん大口を叩いてしまったような記憶があります…。酔っぱらいの戯言が戯言でなくなるように、もっと精進していこうと思います。