在 CodeIgniter (CI) 開發中,模型常被誤解為單純的資料庫操作類。但隨著專案規模增長,將業務邏輯混雜在控制器或模型中會導致維護困難。本文深入探討如何構建輕量、彈性的模型層,實現關注點分離,並透過 ActiveRecord 模式封裝通用 CRUD 操作,提升開發效率。
MVC 架構中,控制器負責流程控制,視圖負責呈現,模型應專注於資料持久化。業務規則、驗證邏輯、跨表操作等應移至 Service 層或 Repository 層。這樣模型類將變得極其輕量,僅封裝單表基本操作,如:
CI 不強制使用實體物件傳參,開發者可自由控制參數輸入輸出(常用陣列),這賦予了極大靈活性。
所有資料庫存取必須經過模型。通常一張表對應一個模型類,確保資料存取邏輯集中管理,便於追蹤與優化。
透過 database.php 的 group_name 配置,模型可輕鬆切換不同資料庫連線。主從架構下,更可實現讀寫分離邏輯(詳見後續文章)。
控制器常用公用基類分發,但模型不宜照搬。建議:
MY_Model 繼承 CI_Model,處理資料庫初始化與通用方法Admin_Model、Api_Model)MY_Model 即可CI 的 Query Builder (AR) 方法繁多,記憶負擔重。針對高頻操作進行二次封裝,例如「依屬性查詢單筆」:
// 封裝前:需記住 from/where/get/row_array 等鏈式呼叫
$query = $this->db->from('user')->where(['username' => 'BobbyPeng'])->get();
return $query->row_array();
// 封裝後:僅需記住一個方法
public function findByAttributes($where = []) {
$query = $this->db->from($this->tableName())->where($where)->get();
return $query->row_array();
}
各模型只需實作 tableName() 回傳表名,即可複用此方法。
將 findByAttributes、save、updateByPk 等通用方法集中在 ActiveRecord 類別,再讓 MY_Model 繼承它。這樣:
MY_Model 直接修改通用方法造成風險以下為完整的 ActiveRecord.php,提供常用單表操作封裝:
<?php
if (!defined('BASEPATH')) exit('No direct script access allowed');
class ActiveRecord extends CI_Model {
/** 新增資料,成功回傳 insert_id,失敗回傳 false */
public function save($data) {
if ($this->db->set($data)->insert($this->tableName())) {
return $this->db->insert_id();
}
return FALSE;
}
/** Replace 操作 */
public function replace($data) {
return $this->db->replace($this->tableName(), $data);
}
/** 依主鍵更新 */
public function updateByPk($pk, $attributes, $where = []) {
$where[$this->primaryKey()] = $pk;
return $this->updateAll($attributes, $where);
}
/** 批次更新 */
public function updateAll($attributes, $where = []) {
return $this->db->where($where)->update($this->tableName(), $attributes);
}
/** 依主鍵刪除 */
public function deleteByPk($pk, $where = []) {
$where[$this->primaryKey()] = $pk;
return $this->deleteAll($where);
}
/** 批次刪除 */
public function deleteAll($where = [], $limit = NULL) {
return $this->db->delete($this->tableName(), $where, $limit);
}
/** 依主鍵查詢單筆 */
public function findByPk($pk, $where = []) {
$where[$this->primaryKey()] = $pk;
$query = $this->db->from($this->tableName())->where($where)->get();
return $query->row_array();
}
/** 依屬性查詢單筆 */
public function findByAttributes($where = []) {
$query = $this->db->from($this->tableName())->where($where)->limit(1)->get();
return $query->row_array();
}
/** 多條件查詢列表,支援排序、分頁、模糊查詢 */
public function findAll($where = [], $limit = 0, $offset = 0, $sort = NULL) {
$this->db->from($this->tableName())->where($where);
if ($sort !== NULL) {
if (is_array($sort)) {
foreach ($sort as $value) {
$this->db->order_by($value, '', FALSE);
}
} else {
$this->db->order_by($sort);
}
}
if ($limit > 0) {
$this->db->limit($limit, $offset);
}
$query = $this->db->get();
return $query->result_array();
}
/** 統計符合條件筆數 */
public function count($where = []) {
return $this->db->from($this->tableName())->where($where)->count_all_results();
}
/** 執行原生 SQL,支援參數綁定 */
public function query($sql, $param = []) {
$query = $this->db->query($sql, $param);
return $query->result_array();
}
}
/* End of file ActiveRecord.php */
/* Location: ./application/core/database/ActiveRecord.php */
MY_Model 繼承 ActiveRecord,並實作智慧型資料庫連線復用機制:
<?php
if (!defined('BASEPATH')) exit('No direct script access allowed');
require_once APPPATH . 'core/database/ActiveRecord.php';
class MY_Model extends ActiveRecord {
public function __construct($group_name = '') {
$this->initDb($group_name);
parent::__construct();
}
protected function initDb($group_name = '') {
$db_conn_name = $this->getDbName($group_name);
$CI =& get_instance();
if (isset($CI->{$db_conn_name}) && is_object($CI->{$db_conn_name})) {
$this->db = $CI->{$db_conn_name};
} else {
$CI->{$db_conn_name} = $this->db = $this->load->database($group_name, TRUE);
}
}
private function getDbName($group_name = '') {
return ($group_name === '') ? 'db' : 'db_' . $group_name;
}
}
/* End of file MY_Model.php */
/* Location: ./application/core/MY_Model.php */
建立具體模型時,只需實作 tableName() 與 primaryKey():
class User_model extends MY_Model {
protected function tableName() { return 'users'; }
protected function primaryKey() { return 'id'; }
// 即可直接使用:
// $user = $this->User_model->findByPk(1);
// $users = $this->User_model->findByAttributes(['status' => 1]);
// $this->User_model->save(['username' => 'new', 'email' => 'a@b.c']);
}
這套架構將模型還原為純粹的資料存取層,透過 ActiveRecord 封裝消除重複樣板碼,配合 MY_Model 的連線管理,實現:
複雜關聯查詢、跨表事務等進階操作,建議在 Service 層組合多個模型完成,保持模型純粹性。未來可進一步擴展:軟刪除、事件監聽、查詢範疇等功能。