CodeIgniter1.7.3でもセグメントアプローチ+$_GETを使いたい

CodeIgniter(以下CI)1系では、セグメントアプローチでのルーティングの際には、問答無用で$_GETが破棄されます。「まぁそういう仕様なんだね」ということでスルーしていたのですが、使えたらいいよねという感じで拡張してみました。

ある仕様とコンフリクトする可能性がある、というご指摘を受けて追記しました。↓にて追記してます

さらに追記:$_SERVER['QUERY_STRING']まで削除するのはやりすぎだと気づきました。

そもそもの発端

CI2.0 Reactorでは、$config['arrow_get_query']の設定により、$_GETが使えるようになっているそうです。なので、まだ現役な1.x系でも使えたらいいのに・・・と思ったので試しにやってみました。検索クエリの取り回しに使えるかもしれませんね。使用しているのはCI1.7.3です。

どこで破棄されるの?

Inputクラスが初期化された際に破棄されます。_sanitize_globals()メソッド内で「$_GET = array();」とありますね。Oh。
ただし、$config['enable_query_strings'] = TRUE;として、クエリベースアプローチの場合は破棄されません。

処理の順番的に

CIの初期化プロセスはシンプルなので、処理の順番が関わってきます。system/codeigniter/CodeIgniter.phpを見ると分かりますが、

  1. Configクラス
  2. URIクラス
  3. Routerクラス
  4. Outputクラス
  5. Inputクラス

の順にロードされていきます。なので、Inputクラスが初期化される前にゴニョゴニョしておく必要があるのですが、少し困った問題があります。


URIクラスでURI文字列を正規化する際に、$config['uri_protocol'] = 'AUTO'だと、
クエリストリングがあるとそれをURI情報に追加してしまう

ということになっているようです。試しにデフォルトのままクエリ文字列をつけてアクセスすると404に…。じゃあ設定を変えればいいじゃん、となるわけですが、こういう制限をかけるのはちょっと面白く無いので、$config['uri_protocol'] = 'AUTO'でも動作するようにしてみます。というわけで、拡張するのはURIクラスです。

一時的に退避→ルーティング後に復帰というやや強引な手法


URI正規化とルーティングの時点では$_GETは無かったことにして、ルーティング終了後にリカバリさせればいいかな

ということで、URIクラスのコンストラクタで退避させます:


class MY_URI extends CI_URI
{
var $_get_stack;

// override constructor.
function MY_URI()
{
parent::CI_URI();

// hack: We can use $_GET query at segment approche
if (count($_GET) > 0
&& config_item('uri_protocol') === 'AUTO'
&& config_item('enable_query_strings') === FALSE)
{
$this->_get_stack = $_GET;
$_SERVER['QUERY_STRING'] = '';
$_GET = array();
}
}

// recovery $_GET.
function __recovery_get_query()
{
$IPT = load_class('Input');
$_GET = $IPT->_clean_input_data($this->_get_stack);
}
.
.
.

$config['uri_protocol'] = 'AUTO'の場合、URIの正規化に使われるのは$_GETと$_SERVER['QUERY_STRING']の値のようなので(URI::_fetch_uri_string()参照)、
コンストラクタの時点で条件判定し、_get_stackに退避して、一時的に各パラメータはなかったことにします。
こうすることで、Routerクラスのルーティング時には$_SERVER['PATH_INFO']の値を使って、純粋にセグメントを元にコントローラが呼ばれます。


2011/03/04追記:
_fetch_uri_string()での正規化の順番を見たところ、


1. $_GET
2. $_SERVER['PATH_INFO']
3. $_SERVER['QUERY_STRING']


の順番でしたので、上記方法の場合、PATH_INFOの時点で正規化が完了する(はず)です。なので、3は特に考えなくても良い、さらに$config['index_page'] = 'index.php?';のような場合でも3でルーティングできるのではないか、と考えました。

あとは戻すだけ

ルーティングが完了後、_get_stackから戻すだけなのですが、その処理を実行するポイントが見つかりませんでした><
なので、私は(seezooでは)pre_controllerのフックでコントローラの実行が始まる直前にリカバリしました。
Controllerクラスのコンストラクタでやってもいいかな、と思います。Inputクラスを再度ロードして、_clean_input_data()メソッドに退避した$_GETのデータを通しておきます。

コントローラで使えるよ

これで、コントローラ内とかで、


$hoge = $this->input->get('hogehoge');

とやれば、セグメントアプローチでも$_GETパラメータが使えるようになりました。
それぞれのパラメータのセキュリティチェックは忘れずに。

感想とか

  • 本来ならInputクラスを拡張してやることなのかもしれない

今回は既にURIクラスを拡張したものを使っていたので、 そのまま拡張を続けた感じです。

  • スーパーグローバル変数に値を入れたりできるのはやっぱりどうかと思ったり。

「そういうものだよ」と納得せざるを得ないです。


他に問題とかあれば教えてください。

追記

@kenji_sさんよりご指摘をいただきましたー。

PATH_INFOが設定されていないサーバの場合のトラブルシューティングの方法

トラブルシューティング:CodeIgniter ユーザガイド 日本語版

と仕様がコンフリクトする可能性があります。$config['index_page'] = 'index.php?'とクエリ文字列で渡した場合、最初に$_GETを消してしまうとルーティング出来なくなっちゃいますね。

PATH_INFOが設定されているサーバでのみ有効、つまり、上記トラブルシューティングを発動しなくても動作できるサーバーで、ということに訂正させていただきます。