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) && ! 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" && $include_deleted_rec === FALSE && $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を書かずして構築できます。それは次回以降にご紹介しましょう。
この続きは現在作成中です。
