CodeIgniterのモデルを使いやすくしよう

CodeIgniterのモデルは、自由度が高いですね。他のフレームワークでは、テーブルとモデルが密な関係になっていいて、ちょっと扱いづらいところがあるんですが、CodeIgniterは自由すぎるぐらい自由です。CodeIgniterのソースコードを読むと、その途方も無い自由さは分かります。ただ、自由すぎるがゆえに不便なところもあります。これを使いやすくしましょうというのが、ここでテーマです。

不便その1:いちいちテーブル名を指定すること

たとえば、users_modelというモデルがあるとしましょう。そこから usersテーブルのレコードを引っ張ってくる時は、

$query = $this->db->get_where('users', array('id' => $id));
$result = $query->result_array();

こんな感じで取得します。自由だけど少し面倒ですね。わざわざテーブル名を指定しています。自由度が高いゆえなんですけどね。ぼくとしては、usersモデルの中からusersテーブルのレコードを取得する時は、こう書きたいです。

$result = $this->get(array('id' -> $id));

不便その2:よくjoinするので、結局SQLを直に書いてしまう

さて、他のテーブルとjoin(結合)する時のことを考えてみましょう。たとえば、payments(支払情報)テーブルのレコードを取得する時に、users(会員情報)テーブルから常に会員名を結合して取得したい・・・ということはよくありますね。そんな時、ふつうはこんな感じで書くと思います。


$this->db->select('payments.*,users.name');
$this->db->from('payments');
$this->db->join('users', 'payments.user_id = users.id');
$this->db->where('payments.user_id', $id);
$query = $this->db->get();
$result = $quert->result_array();

面倒ですよね。こんなことならSQLを直に書いちゃいますよ。
でも、フレームワークを使うからにはSQLはあんまり書きたくないんですよね。(といいつつガリゴリ書いていますが)
こんなときでも、よくばりな私は

$result = $this->get(array('payments.user_id' -> $id));
(paymentsモデルから呼び出すことを想定しています)

1行で済ませたいのです。これで$resultに会員名も含まれていたら素敵。
(どっかでアソシエーション設定はしておくのですが、毎回結合条件を書かなくて済みます)

不便その3:getメソッドぐらい用意して欲しい

たとえば、コントローラから特定ユーザーの情報を取得したいって状況があるとします。usersモデルにgetメソッドなるものを書いて中継させると思います。だけど、テーブルから単純にレコードを取得するなんてよくある処理じゃないですか。すべてのモデルにgetメソッドをいそいそと実装するのもバカバカしい気がします。せめてget/insert/update/deleteメソッドがモデルにあったらなぁ。

解決策

私はこれらの問題を解決するために、コアのモデルを拡張することで、これらを実現しています。かれこれ3年以上愛用しています。コアモデルを拡張するには、core/ の中に MY_Model.php という名前でファイルを作成します。
私の拡張モデルは以下の様になっています。

<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');


class MY_Model extends CI_Model
{

    // モデルと関連付けるテーブル名(各モデルで必要な場合は定義)
    var $table;
    
    // SELECT句で取得する列
    var $cols;
    
    // JOINするテーブル
    var $join;
    

    function __construct()
    {
        parent::__construct();
    }

    
    /**
     * 条件に合致するレコード数を取得
     * @param mixed $where WHERE句(連想配列 or 文字列)
     * @return int レコード数
     */
    function get_rows($where, $include_deleted_rec = FALSE)
    {
        $this->set_params($this->table, 'COUNT(*) as count', $this->join, $where, NULL, NULL, NULL, $include_deleted_rec);
        $query = $this->db->get();
        $result = $query->result_array();
        
        return $result[0]['count'];
    }
    
    /**
     * 結果を1件取得する
     * @param mixed $wehre where句(連想配列 or 文字列)
     * @return array 結果を連想配列で返す
     */
    function get_one($where, $include_deleted_rec = FALSE)
    {
        $result = $this->get($where, NULL, NULL, NULL, $include_deleted_rec);
        if (is_array($result))
        {
            return @$result[0];			
        }
        
        return $result;		
    }


    /**
     * テーブルからデータを取得
     * @param mixed $where  WHERE句(連想配列 or 文字列)
     * @param string $order  ORDER BY 句(文字列)
     * @param int $limit  limit数
     * @param int $offset offset数
     * @return array 結果配列
     */
    function get($where = NULL, $order = NULL, $limit = NULL, $offset = NULL, $include_deleted_rec = FALSE)
    {
        $this->set_params($this->table, $this->cols, $this->join, $where, $order, $limit, $offset, $include_deleted_rec);
        $query = $this->db->get();
        return $query->result_array();
    }	
    

    /**
     * レコードの挿入
     * @access public
     * @param mixed $data ... 挿入するデータの連想配列
     * @return int 挿入したデータID
     */
    function insert($data)
    {
        // 登録データ配列をセット
        $this->db->set($data);
        
        // register_datetime等の共通フィールドを初期値で埋める
        $this->db->set(set_common_fields($this->table), '', FALSE);
        
        // INSERT処理
        $this->db->insert($this->table);

        return $this->db->insert_id();			
    }

    
    /**
     * レコードを更新する
     * @param mixed $where WHERE句を配列もしくは文字列で
     * @param array $data  更新するデータの連想配列
     * @return void
     */
    function update($where = NULL, $data)
    {
        if ( is_null($where) || ! is_array($data))
        {
            return FALSE;
        }
        $data = set_update_datetime($this->table, $data);
        $this->set_where($where, "update");
        $this->db->update($this->table, $data);
                
    }
    
    /**
     * レコードを物理削除する
     * @param mixed $where WHERE句を配列もしくは文字列で
     * @return void
     */
    function real_delete($where = NULL)
    {
        if ( is_null($where))
        {
            return FALSE;
        }

        $this->set_where($where, "delete");
        $this->db->delete($this->table);
        
    }
    
    /**
     * レコードを削除する。delete_datetimeのあるテーブルはそこに現在時刻を埋め込む。(論理削除)
     * @param mixed $where WHERE句を配列もしくは文字列で
     * @return void
     */
    function delete($where = NULL)
    {
        if ( is_null($where))
        {
            return FALSE;
        }

        if ( ! $this->db->field_exists('delete_datetime', $this->table))
        {
            $this->real_delete($where);
        }
        else
        {
            $this->update($where, array('delete_datetime' => @date('Y-m-d H:i:s')));	
        }

        return TRUE;

    }	
    
    

    /**
     * パラメータ設定(外部から呼び出すことはあまりない)
     * 
     * @access public
     * @param mixed $table  テーブル名
     * @param mixed $cols (default: NULL) SELECT句で指定するカラム
     * @param mixed $join (default: NULL) JOINするテーブル情報を配列で [0] テーブル名  [1] 結合条件  [2] 結合方法  left/inner
     * @param mixed $where (default: NULL) WHERE句を連想配列もしくは文字列
     * @param mixed $order (default: NULL) order句を文字列で
     * @param mixed $limit (default: NULL) 取得する件数
     * @param mixed $offset (default: NULL) 取得開始位置
     * @param mixed $include_deleted_rec (default: FALSE) 論理削除したレコードも含める
     * @return void
     */
    function set_params($table, $cols = NULL, $join = NULL, $where = NULL, $order = NULL, $limit = NULL, $offset = NULL, $include_deleted_rec = FALSE)
    {
            
        if ( ! $table)
        {
            return FALSE;
        }
        $this->db->from($table);

        if (is_array($cols))
        {
            $this->db->select(implode(", ", $cols), FALSE);
        }
        else if ($cols)
        {
            $this->db->select($cols, FALSE);
        }

        if (is_array($join))
        {
            foreach ($join as $one)
            {
                $this->db->join($one[0], $one[1], $one[2]);
                
            }
        }
        
        $this->set_where($where, $include_deleted_rec);

        if ( ! is_null($order))
        {
            $this->db->order_by($order);
        }
        
        if ( ! is_null($limit) &amp;&amp; ! is_null($offset))
        {
            $this->db->limit($limit, $offset);
        }
        else if ( ! is_null($limit) )
        {
            $this->db->limit($limit);	
        }	
        
        return TRUE;	
        
    }
    

    
    /**
     * where句のセット(外部からは使わない)
     * 
     * @access public
     * @param mixed $where
     * @param string $mode (default: "select")
     * @param mixed $include_deleted_rec (default: FALSE)
     * @return void
     */
    function set_where($where, $mode = "select", $include_deleted_rec = FALSE)
    {
        
        // WHERE句の処理
        if ( ! is_null($where))
        {
            if (is_array($where))
            {
                foreach ($where as $key => $value)
                {
                    if ($key == "SQL")
                    {
                        if (is_array($value))
                        {
                            $value = implode(" AND ", $value);
                        }
                        $this->db->where($value);
                        continue;
                    }

                    // 配列の場合はLIKE
                    if (is_array($value))
                    {
                        if ($value[1] == 'OR')
                        {
                            $this->db->or_where($key, $value[0]);	
                        }
                        elseif ($value[1] == 'OR_LIKE')
                        {
                            $this->db->or_like($key, $value[0], $value[2]);	
                        }
                        else
                        {
                            $this->db->like($key, $value[0], $value[1]);
                        }
                    }
                    else
                    {
                        $this->db->where($key, $value);
                    }
                }
            }
            else
            {
                $this->db->where($where);
            }			
        }
        
        // delete_datetimeは削除済みレコードなので除外する
        if ($mode != "delete" &amp;&amp; $include_deleted_rec === FALSE &amp;&amp; $this->db->field_exists('delete_datetime', $this->table))
        {
            $this->db->where($this->table . '.delete_datetime IS null');
        }		
        
    }
    

}

そして、原則テーブルごとにモデルを作成しています。(モデルを作らないテーブルもあります)
usersテーブルにはusersモデルを作成します。ここではusersモデルを例にしましょう。

models/ディレクトリに 以下の内容で、Users_model.phpというファイルを作成します。

<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class Users_model extends MY_Model
{

	// 関連付けるテーブル
	var $table = "users";
		
	// SELECT句で取得する列
	var $cols = array(
	);
	
	// JOINするテーブル(JOIN対象テーブル、ONの内容、結合方法)
	var $join = array(
	);

	function __construct()
	{	
		parent::__construct();
	}	

}

?>

このようなファイルをテーブルごとに作るんです。usersという箇所を該当テーブル名に差し替えればいいだけ。
これで準備完了です。

非常にDBへのアクセスが楽になります。
たとえばusersモデルからusersテーブルにアクセスする時は

$result = $this->get(array('users.id' -> $id));

これだけ。
コントローラからusersテーブルにアクセスするときは、


$this->load->model('users_model');
$users = $this->users_model->get();

簡単ですよね。

このモデル拡張は他にも機能をもたせています。簡単にjoinさせることもできますし、すこし複雑なクエリもSQLを書かずして構築できます。それは次回以降にご紹介しましょう。

この続きは現在作成中です。