首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

CodeIgniter 模型設計最佳實踐:從資料存取層到 ActiveRecord 封裝

     編輯:PHP 綜合知識

前言:為什麼需要重新思考 CI 模型架構?

在 CodeIgniter (CI) 開發中,模型常被誤解為單純的資料庫操作類。但隨著專案規模增長,將業務邏輯混雜在控制器或模型中會導致維護困難。本文深入探討如何構建輕量、彈性的模型層,實現關注點分離,並透過 ActiveRecord 模式封裝通用 CRUD 操作,提升開發效率。

核心原則:模型即資料存取層 (DAL)

MVC 架構中,控制器負責流程控制,視圖負責呈現,模型應專注於資料持久化。業務規則、驗證邏輯、跨表操作等應移至 Service 層或 Repository 層。這樣模型類將變得極其輕量,僅封裝單表基本操作,如:

  • 依主鍵查詢/更新/刪除
  • 條件查詢與分頁
  • 統計計數
  • 原生 SQL 執行

CI 不強制使用實體物件傳參,開發者可自由控制參數輸入輸出(常用陣列),這賦予了極大靈活性。

五大關鍵設計決策

1. 禁用控制器直接操作資料庫

所有資料庫存取必須經過模型。通常一張表對應一個模型類,確保資料存取邏輯集中管理,便於追蹤與優化。

2. 多資料庫連線支援

透過 database.php 的 group_name 配置,模型可輕鬆切換不同資料庫連線。主從架構下,更可實現讀寫分離邏輯(詳見後續文章)。

3. 模組化繼承策略

控制器常用公用基類分發,但模型不宜照搬。建議:

  • 建立 MY_Model 繼承 CI_Model,處理資料庫初始化與通用方法
  • 特定業務模組可繼承專用基類(如 Admin_Model、Api_Model)
  • 大多數模型直接繼承 MY_Model 即可

4. 封裝 ActiveRecord 基礎操作

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() 回傳表名,即可複用此方法。

5. 將通用方法抽離至 ActiveRecord 類別

將 findByAttributes、save、updateByPk 等通用方法集中在 ActiveRecord 類別,再讓 MY_Model 繼承它。這樣:

  • 核心邏輯集中維護,減少重複程式碼
  • 避免在 MY_Model 直接修改通用方法造成風險
  • 符合「開放封閉原則」:擴展開放,修改關閉

程式碼實作:ActiveRecord 核心類別

以下為完整的 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 整合多資料庫連線管理

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 的連線管理,實現:

  • 單一職責:模型只管資料存取
  • 零配置複用:繼承即享有完整 CRUD API
  • 多資料庫無感切換:建構子傳入 group_name 即可
  • 易於測試與擴展:核心邏輯集中在 ActiveRecord

複雜關聯查詢、跨表事務等進階操作,建議在 Service 層組合多個模型完成,保持模型純粹性。未來可進一步擴展:軟刪除、事件監聽、查詢範疇等功能。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved