読者です 読者をやめる 読者になる 読者になる

CodeIgniterの余計なお世話

PHP

CodeIgniter(CI)は"?"以降のクエリ文字列を綺麗に無視し、$_GETを空っぽにしてくれる。CI曰く「パスベースのルーティングがあれば、クエリ文字列なんて要らない」もしくは「GETリクエストにオプションパラメータなど許してはいけない」だそうな。

おいおい、それ本気か?

たしかに、同じひとつのメソッドに到達する複数のURLが存在するのはSEO的に親切じゃないとも言える。?c=myclass&m=mymethodと書いても?m=mymethod&c=myclassと書いてもいいし、さらに、無視されるパラメータをいくら付けてもいい。

ところで、複雑な検索クェリはどうか?少し複雑なフォームで指定したいろいろな検索オプションをPOSTしてしまうと、結果表示画面の1ページ目を表示した瞬間、パラメータが失われてしまう。その後、戻るボタンでは戻ってこれないのは不便だ。また、URLだけで検索リクエストを再現できないということは、「ページ閲覧リクエストなのにブックマークできない」という問題を生む。
そればかりでなく、ページネーションが検索オプションを再現する場合も、セッション的なもの(クッキーか隠しタグ)に直近の検索設定を保存しておく必要があり、また、ページネーションをPOSTで継続しなければいけない。
かといって、スラッシュ区切りのパスルーティングでは、デフォルトでよい項目も含めて、すべてのパラメータを順番どおりに指定する必要がある。たとえば、search?word=php+fuck で、日付を問わず全データから検索されるとする。パスベースで表現したら、search/php%20fuck/1970/1/1/2008/10/9/no_filters/lang_unspecified/...(以降想定しているすべてのオプションが続く) とかいう感じになる。それが嫌なら、すべての検索オプションの組み合わせ数ぶん、個々にメソッドを書くことになる。 search_no_filters_lang_unspecified/php%20fuck/... ありえねー。
悲劇的なのは機能追加メンテが入ったときで、検索オプションが増えたら、パス要素の長さが変わってくる。SEO的にURLを固定するために、旧メソッドは残したまま、新機能を新たな別のメソッドに実装するということになる。その調子でやっていくと、結局機能拡張回数だけ、メソッドが増えていくことになり、しかも、検索エンジンに登録されているパスでは最新の機能が使われない。というわけで、SEO的にすごく悪化。

まぁ、↑後半は妄想だけどね。でも、どこか途中までは、けっこう当たってるんじゃないかな?ようするに、「コマンドラインオプションのように、あったりなかったりする複雑なパラメータを持つ閲覧系ページ」へのニーズは存在するということ。せっかく、外向きのPHPフレームワークの現実解最有力候補なのに、そのへんの多様なニーズに対して、もっと配慮が欲しかったところ。

普通は検索エンジン側でクェリパラメータとURIパスを区別してくれていると期待して、

  • 絶対的なもの(例:ブログエントリのIDなど)はパスに含む
  • 選択的なもの(例:同時に表示されるブログエントリ数など)はクェリパラメータにする

というポリシーにするのがいいと思う。

この選択肢を残すために、仮にパスベースのルーティングを選択したとしても、無条件に$_GETを空にする必要はなかった。$_GET以外にも攻撃の窓口はいっぱいあるし、だいたい、$_REQUESTで値を取れるんだから、$_GETだけ空になってても、何の意味もないし。

# あまりにもCIが$_GETを否定するのは、もしかしたら自分が知らない何かがあるのかもしれません。どなたか、CIがこんなにGETを完全否定する動機について、気づいたことがあったらコメント↓で教えてもらえると嬉しいです。

      • -

さて、最大の余計なお世話はここまでで、関連する余計なお世話をひとつ。じつはCIは、「"?"以降のクエリ文字列を綺麗に無視」できていない。

http://hostname/index.php/myclass/mymethod?foo=1

は、myclass.phpにmymethodメソッドがあっても、なぜか 404 Not Found になる。

http://hostname/index.php/myclass/mymethod?foo=1&bar=2

ならOK。期待通りに($_GET空で)メソッドが呼ばれる。

これ、system/application/config/config.php に、uri_protocolというのがあって。

| This item determines which server global should be used to retrieve the
| URI string.  The default setting of "AUTO" works for most servers.
| If your links do not seem to work, try one of the other delicious flavors:
|
| 'AUTO'                Default - auto detects
| 'PATH_INFO'           Uses the PATH_INFO
| 'QUERY_STRING'        Uses the QUERY_STRING
| 'REQUEST_URI'         Uses the REQUEST_URI
| 'ORIG_PATH_INFO'      Uses the ORIG_PATH_INFO

ていうことらしい。

system/libralies/URI.php を見ると、

if (strtoupper($this->config->item('uri_protocol')) == 'AUTO')
{
    // If the URL has a question mark then it's simplest to just
    // build the URI string from the zero index of the $_GET array.
    // This avoids having to deal with $_SERVER variables, which
    // can be unreliable in some environments
    
    if (is_array($_GET) AND count($_GET) == 1 AND trim(key($_GET), '/') != '')
    {
        $this->uri_string = key($_GET);			
        return;
    }
    //以下略

となっている。えぇと、AUTOにしていると、$_GETの要素数が1のとき、そのパラメータ名をURIとして採用する!? しかも、その動作が最優先!!

http://hostname/index.php?myclass/mymethod

と書けるそうな。激しく要らん。
むしろこの動作のほうをマイナーオプションにしておいてくれ。$_SERVER変数を使わなくていいとかそんなのどうでもいいから。$_SERVER変数使えないような弱い環境で仕事するときは、無理にパスでルーティングせず、クエリパラメータでクラスとメソッド指定するからさぁ。どうせsite_url()じゃ、この?付きURLを再現できないんでしょ。

というわけで、ここは削除。ついでに、Input.php

$_GET = array();

も、無条件に

$_GET = $this->_clean_input_data($_GET);

にしておこう。「$_GETを使う理由はない」とマニュアルにあるけど、むしろ本当は、$_GETを空にする理由はないし。もしそれがセキュリティと開発効率にとって有効なんだったら、$_POSTも$_COOKIEも空にしないといけないよね。

http://www.ryuzee.com/contents/blog/734
で、もっと丁寧な方法を示してくれてる方がおられたのですが、どうせコピーしてファイル足すんだから、必要なら書き換えてしまってもいいかな、と思ったり。