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