bravewood の日記

2011-07-18

CodeIgnierを使ってみて 17:01 CodeIgnierを使ってみてを含むブックマーク

世間ではなんですか、最後のRubyKaigiとか、サッカー女子で盛り上がってるんですか?

まあPHPerな私には関係ないですので、空気も読まず、CodeIgniterの話しまーす。


最近CodeIgniterを使ってとあるプロジェクトの仕事をしたので、メモがてら記事にしてみますよっと。

導入 17:01 導入 を含むブックマーク

CIってなに?


特徴は?

  • MVC
  • とにかく軽いってこと
  • 覚えることが少ないってこと
  • PHP5.1で使えるってこと
    • CentOS5.6って普通にyum install php したら5.1.6入るんですけど。symfony1.4使えないじゃないですか。バカなの?死ぬの?→CI有るヨ!
    • 自分でPHPインストールできない場合とかにいいんじゃね?(今回のプロジェクトがこれでした)
    • PHP4何ですけど、使えますかね?
      • 無理。

  • で、どこに使うよ?
    • 小規模Webアプリケーション
    • 決して中規模以上に使えないわけじゃないのかなぁ…
    • けど、DB周りとか貧弱なんで、それを補強するコードを書く気ならいいんじゃね
    • あと、ドキュメント少ないので…あとは分るな?

いい点/悪い点 17:01 いい点/悪い点 を含むブックマーク

以下比較対象は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とかが使えないので、一番下のテンプレートからグローバルテンプレートの値などを変更する手段がない

  • 例えばhead内の<title></title>
  • 例えばJSコードを、HTML閉じる直前に出力したい

XSS周り

とりあえずクソすぎる

  • その1
 $config['global_xss_filtering'] = TRUE;

という設定でフォームに

 <script>alert("aaaa")<script>

と入力すると

 [removed]alert&#40;"aaaa"&#41;[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()を使う

コマンドライン

コマンドラインという機能自体無い

(テストどーすんのさ!)



Mail

コアの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なら迷わず使え

中〜大規模以上にはお勧めできない

特別付録 17:01 特別付録 を含むブックマーク

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!

Kenji_sKenji_s 2011/07/19 12:09 Debugはprofilerがありますね

例えば、
http://d.hatena.ne.jp/localdisk/20110521/1305964812

bravewoodbravewood 2011/07/20 00:02 id:Kenji_sさん

おお〜そんな便利な機能があったのですね!
教えていただいてありがとうございます!
記事に追記させていただきました!

トラックバック - http://d.hatena.ne.jp/bravewood/20110718