2011-07-18
■CodeIgnierを使ってみて

世間ではなんですか、最後のRubyKaigiとか、サッカー女子で盛り上がってるんですか?
まあPHPerな私には関係ないですので、空気も読まず、CodeIgniterの話しまーす。
最近CodeIgniterを使ってとあるプロジェクトの仕事をしたので、メモがてら記事にしてみますよっと。
■導入

CIってなに?
- CodeIgniter(コードイグナイター)
- PHPのWebアプリケーションフレームワーク
- オープンソース
- ライセンス
- Apache/BSD-style open source license
- アメリカ製(EllisLab)
- 最新のバージョンは2.0.2(2011/7/1現在)
- 2.0系はJanuary 28, 2011から
特徴は?
- MVC
- とにかく軽いってこと
- 覚えることが少ないってこと
- PHP5.1で使えるってこと
- で、どこに使うよ?
- チュートリアルくれ!
- 後で書く…かもしれん
■いい点/悪い点

以下比較対象はsymfony1.4
いい点
- Route.phpが書きやすい
- 設定はすべてPHP(!Yaml)
- コアのソースコードが
わりとすごい短いのですぐgrepできる - すげーーー軽い
- 何にも依存してない
- インストールコマンド必要ない。ディレクトリ解凍するだけ
- ロードしたいライブラリは、設定でON
何でもかんでもロードしておけばいいってもんじゃねーぞ
- PHP5.1.6で動くよ!
symfonyは1.4で5.1系を切ったから。これは強力なメリット
- クラス名→ファイル名の名前付けが素直でいい
class Super_default extends CI_Controller
ならファイルは
super_default.php だし
symfonyではアンダーバーをつけた時の挙動がよくわからんwww
- 便利なform helperがある
set_radio,set_selectなどの第3引数が優秀すぎて生きるのがつらい
<input type="radio" name="must_flag" value='0' <?php echo set_radio("must_flag", "0", TRUE); ?>/>いいえ <input type="radio" name="must_flag" value='1' <?php echo set_radio("must_flag", "1"); ?>/>はい
- 設定を環境毎に別ファイルに書ける
以下の様に設定が分けられる
config/development/email.php config/production/email.php
たとえば、開発環境ではメール送信にgmailを、本番環境ではsendmailを使うとし、本番環境は共有サーバーなのでアカウント情報はアップしたくない場合
単純にdevelopment以下をアップしなければOK(index.phpに設定変更は1行必要だが)で対応できる
symfonyの場合設定は一つのymlに別環境の設定を分けて書いてあるので、共有サーバーなどに置きたくない設定値(メールサーバーパスあーどとか)があれば、ファイルの中身をいじってデプロイしなければならないなど面倒
- sessionは設定変えるだけでDBにかけるよ
- 当然あってしかるべき機能だがと思うけど一応ほめておくか。すごいすごい
悪い点
DB周り
- ORMが
くそ簡易機能。
Event_modelってモデルクラスがあったとして、このメソッドで1件EventをSelectしたとするじゃん、この戻り値って何オブジェクトだと思うよ?
stdObjectオブジェクトなんだよ。はあ?
ここはどう考えても、Event_modelオブジェクトを返してほしい。ORMなんだから
なぜなら、Event_modelクラスにいろいろユーティリティメソッドを書いておいて、それを使いたいんだよ。なんでできない?
→重くなるからですね。わかります
- JOINが貧弱
JOINしたら、フィールドがすべて結合されたフラットな状態で帰ってくるので、同じ名前のフィールドがあれば上書きされてしまう?
symfonyでは Hoge->getPage()->field のように、JOINしたテーブル(page)を特別なメソッドで呼べるので見分けがついた
- テンプレート内で、外部キーをによるデータ取得ができない?
Bookmark->getEntry()->getAccount()->getName() みたいなやーつ
最初からgetName()のところまで取得しておく必要がある
- テーブル名に hoge as h などのような名前を付けられない?
テンプレート回り
Globalテンプレートがないので、自分でそういう動作を書かなければならない
すべてのアクションで同じテンプレートを呼び、その中で、個々のテンプレートをインクルードする形で
なのでslotとかが使えないので、一番下のテンプレートからグローバルテンプレートの値などを変更する手段がない
XSS周り
とりあえずクソすぎる
- その1
$config['global_xss_filtering'] = TRUE;
という設定でフォームに
<script>alert("aaaa")<script>
と入力すると
[removed]alert("aaaa")[removed]
という感じに変換されてactionに渡ってくる
そんなことしてくれなくても、テンプレート側でhtmlspecialcharを通してくれればいいんです
なので私はglobal_xss_filteringを切っている
$config['global_xss_filtering'] = FALSE;
イカンイカン。ユーザーに切られる警報装置ってどうなのよ。ヒューマンエラーの温床じゃね?
- その2
viewの中でecho してるすべての箇所を自動的にhtmlspecialchars()してくれる機能はねーのかよ
system/helpers/form_helper.php
に自前の関数
_h()
を実装して使うことにした
使いどころは
<?php echo _h( $event->title ); ?>
また、フォームの値を取得するヘルパであるset_valueは、中でいったんエスケープ処理をしているのだが、バグがあり、2度同じform名でフォームヘルパを呼ぶと、2度目以降エスケープされていないすっごいおっきいすごいXSS脆弱性がある。
参考
というわけでこの項の結論
- viewでフォームに値をセットする場合は set_value()を使う (ただしパッチ適用してね)
- 単に値を echoする場合は _h()を使う
コマンドライン
コマンドラインという機能自体無い
(テストどーすんのさ!)
- 日本語メール(ISO-2022-JP)が文字化けを起こす
コアのstripslashes()が悪い。数行書き換えるだけで動きますが、、
日本語パック使えばパッチあたってるよ!
でも、野良パッケージじゃなくて公式で対応してくれんかな〜
ドキュメント回り
割と知りたいことが載ってない。(一通りそろってはいるが)
set_radioってどのライブラリロードすればいいんだっけな?とか、関数/メソッド名から逆引き出来ろよ!くそが
- チュートリアル系がない。
でもフレームワーク自体小さいので使いながら覚えられるけどね(要、他フレームワーク経験)
ログ回り
これはsymfony1.4くらいまででも言えるけど、貧弱すぎ
ciはさらに貧弱すぎ
ログレベルも少ないし
ファイルサイズローテートできないし
(日付またぎでファイルが作られる)
ファイルに書き込むログしかないし
Zend_log使いたい
テスト
テストが貧弱
ブラウザ関連のテストが一切できない
結合テストできなくね?w
unit testは入ってる
テストをコントローラ経由で起動しなきゃならん
→そのアクション放置しておいたらそれがセキュリティホールとか重大なパフォーマンス低下があり得る)につながるんじゃないの?そんなテストで大丈夫か?
テスト書くところおかしくない?コントローラーに書いていいの?
Validater
どうも動きがあやしいというか、少し空気を読みすぎる
たとえばルールにrequiredを追加しなかったら、フォーム入力がない場合そのほかのルールをすっ飛ばしたり
これを回避するにはcallback_なメソッドを指定してやればいいが、
なぜかバグがあり、
callback_hoge[message]
というルールのmessage部分が無視されてしまう
バグを直す
system\libraries\Form_validation.php
function _execute($row, $rules, $postdata = NULL, $cycles = 0)
{
..
//if (preg_match("/(callback_\w+)/", implode(' ', $rules), $match))
if (preg_match("/(callback_[^ ]+)/", implode(' ', $rules), $match))
アクセス制御をコントロールする機能がない
管理者向けのページとかそういうのどーすんのさ?
InputクラスにHTTPメソッドを判定するメソッドが付いていない
以下のようなコードで判定したいんだけど出来ない
if( $this->input->is_post() ) { var_dump("post"); }
なので以下のようなメソッドを追加します
system\core\Input.php
function is_post()
{
return $_SERVER['REQUEST_METHOD'] == "POST";
}
こういうことはユーザー自身でやってねってのが設計ポリシーなんだろうな
CI_modelをインスタンス化すると、staticプロパティにアクセスできない
class Event_model extends CI_Model
{
var $ipaddr = '';
const STATUS_FLAG_APPLIED = 1; // アクセスできない
private static $HOGE = null; // アクセスできない
}
というクラスがあって
$this->load->model( 'Event_model', '', TRUE ); var_dump( $this->Event_model );
実行結果
object(Event_model)[20]
public 'ipaddr' => string '' (length=0)
メソッド経由でとれってこと?
なのでマジックメソッドを付けといてやる
class Event_model extends CI_Model
{
//const STATUS_FLAG_APPLIED = 123; // これはアクセスできない。無理です
private static $HOGE = 123;
//private staticな値を参照するメソッド
//get + static変数名でアクセス
//例: getHOGE()
public function __call($name,$parameters)
{
$method_prefix = "get";
if( strpos( $name, $method_prefix ) )
{
$name = substr( $name, strlen( $method_prefix) );
return self::$$name;
}
thorw new Exception("unknown class property:" $name);
}
}
実行コード
$this->load->model( 'Event_model', '', TRUE ); echo $this->Event_model->getHOGE(); // 123 echo $this->Event_model->getPAGE(); // throws exceptin
どちらともとれる特徴
良い点とも、悪い点とも考えられる特徴
Debugツールバーがない
代わりにvar_dumpで結構代用できるし問題ない
その分軽いしね。
var_dump()デバッグ最高です。
_ ∩ ( ゚∀゚)彡 var_dump()! var_dump()! ⊂彡
でも、symfonyのデバッグツールバーのSQLログは最高だったな
_ ∩ ( ゚∀゚)彡 XYZ_Modelクラスのインスタンスをvar_dump()! var_dump()! ⊂彡
symfonyのデバッグツールバーは入ってるライブラリとかもわかるじゃん
各スタックごとに時間も出るし
ぐぬぬ…
※追記 2011/07/19
コメント欄でid:Kenji_sさんにProfilerを教えていただきました。ありがとうございます!使い方は記事の末尾に記載
symfonyほどではないにしても、いいログ取れます!
_ ∩ ( ゚∀゚)彡 Profiler! Profiler! ⊂彡
全体
印象に過ぎないかもしれないけど、割とユーザーが好きなように書ける(レールが敷かれていない感じ)ので、複数人で開発に向いてないかも?って気がするね
たとえばcontrollerでエラーがあった場合はどのエラーテンプレートを自前で呼ぶかというのはユーザーが決める。っていうかsfもそうかもw
まとめ
CIは軽い
すこしバグってる/機能不足なので、最初にモンキーパッチあてる
5.1なら迷わず使え
中〜大規模以上にはお勧めできない
■特別付録

CI使い始めたらまずやること
Form_validationを継承したクラスを作り、ここに独自のバリデータを書く
application\libraries\MY_Form_validation.php
class MY_Form_validation extends CI_Form_validation
{
function __construct()
{
parent::__construct();
}
...
}
emailにパッチを当てる
system\libraries\Email.php
public function message($body)
{
// 日本語メールの文字化けを治す
// copy from ci-ja-all-in-one-2.0.1-1.zip
//$this->_body = stripslashes(rtrim(str_replace("\r", "", $body)));
if (strtolower($this->charset) == 'iso-2022-jp')
{
$this->_body = rtrim(str_replace("\r", "", $body));
}
else
{
$this->_body = stripslashes(rtrim(str_replace("\r", "", $body)));
}
return $this;
}
form_helperにパッチを当てる
system\helpers\form_helper.php
function form_prep($str = '', $field_name = '')
{
...
if (isset($prepped_fields[$field_name]))
{
//bug:
//そのまま返しちゃだめ。
//return $str;
return $prepped_fields[$field_name];
}
...
if ($field_name != '')
{
//bug:
//field_nameセットしてどうするの?
//$prepped_fields[$field_name] = $field_name;
$prepped_fields[$field_name] = $str;
}
ついでにメソッド追加
//htmlspecialchars のショートカット
if ( ! function_exists('_h'))
{
function _h($data)
{
return htmlspecialchars( $data, ENT_QUOTES );
}
}
//echo(_h($data))とおなじこと。テンプレートの中ではなるべくこれを使ってください
if ( ! function_exists('echo_h'))
{
function echo_h($data)
{
echo( _h( $data ) );
}
}
Input.phpにパッチ当てる
system\core\Input.php
function is_post()
{
return $_SERVER['REQUEST_METHOD'] == "POST";
}
Profilerを開発環境でON
application\core\MY_Controller.php
class MY_Controller extends CI_Controller
{
function __construct()
{
parent::__construct();
if( ENVIRONMENT === 'development' )
{
$this->output->enable_profiler();
}
}
...
}
そして全部のcontrollerをMY_Controllerから継承すればおk
application/controller/Hoge.php <?php defined('BASEPATH') or exit('No direct script access allowed'); class Hoge extends MY_Controller { ... }
これでSQLのクエリログとかも出ます!
enjoy CI!
bravewood the PHPer!








例えば、
http://d.hatena.ne.jp/localdisk/20110521/1305964812
おお〜そんな便利な機能があったのですね!
教えていただいてありがとうございます!
記事に追記させていただきました!