ThinkPHP ViewModel(視圖模型)允許開發者預先定義多表關聯關係(JOIN 條件、所需字段、別名、連接類型),然後像操作單表模型一樣進行查詢、分頁、排序。它將 SQL 寫死在模型定義中,業務代碼只需面向「虛擬單表」,極大簡化控制器邏輯,避免重複編寫 JOIN SQL,並自動處理字段衝突、別名映射。
<?php
// Lib/Model/UserViewModel.class.php
class UserViewModel extends ViewModel {
// 視圖字段定義:核心配置
protected $viewFields = [
// 主表:User (別名 u,左連接)
'User' => [
'id', 'name', 'password', 'dept_id', 'status',
'_as' => 'u', // 表別名
'_type' => 'LEFT' // 連接類型:LEFT/INNER/RIGHT
],
// 關聯表:UserInfo (別名 ui,ON 條件關聯 u.id)
'UserInfo' => [
'email', 'mobile', 'address', 'gender',
'_as' => 'ui',
'_on' => 'ui.user_id = u.id' // 關聯條件,引用別名
],
// 關聯表:Dept (無別名,字段重名映射)
'Dept' => [
'name' => 'dept_name', // 字段別名:Dept.name → dept_name(避免與 User.name 衝突)
'_on' => 'u.dept_id = Dept.id'
],
];
}
?>
| 鍵 | 類型 | 說明 | 範例 |
|---|---|---|---|
| 陣列索引 | 字串 | 資料表名(或模型名),對應 DB 表前綴自動補全 | 'User', 'UserInfo' |
| 值陣列元素 | 混合 | 字段名(字串)或 字段別名映射(關聯陣列) | 'id', 'name'=>'user_name' |
_as | 字串 | 表別名(必填,用於 ON 條件、字段引用) | 'u', 'ui' |
_on | 字串 | JOIN ON 條件,引用別名(非主表必填) | 'ui.user_id = u.id' |
_type | 字串 | 連接類型:LEFT/INNER/RIGHT(預設 INNER) | 'LEFT' |
-- 用戶基礎表
CREATE TABLE `tp_user` (
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(20) NOT NULL,
`password` VARCHAR(32) NOT NULL,
`dept_id` SMALLINT UNSIGNED NOT NULL,
`status` TINYINT UNSIGNED DEFAULT 1
);
-- 用戶詳細信息表
CREATE TABLE `tp_user_info` (
`user_id` INT UNSIGNED NOT NULL,
`nick_name` VARCHAR(30),
`email` VARCHAR(100),
`mobile` VARCHAR(20),
`address` VARCHAR(100),
`gender` TINYINT DEFAULT 0,
KEY `user_id` (`user_id`)
);
-- 部門表
CREATE TABLE `tp_dept` (
`id` SMALLINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(50) NOT NULL
);
<?php
// Lib/Model/UserModel.class.php
class UserModel extends Model {}
// Lib/Model/UserInfoModel.class.php
class UserInfoModel extends Model {}
// Lib/Model/DeptModel.class.php
class DeptModel extends Model {}
<?php
class IndexAction extends Action {
public function index() {
// 實例化視圖模型(D 方法自動識別 ViewModel 後綴)
$dao = D('UserView');
// 條件查詢(使用別名 u 避免衝突)
$where['u.id'] = 1;
$where['u.status'] = 1;
// 查詢單條
$user = $dao->where($where)->find();
dump($user);
// 查看生成的 SQL
dump($dao->getLastSql());
// 分頁列表
$list = $dao->where(['u.status'=>1])->order('u.id DESC')->select();
$this->assign('list', $list);
$this->display();
}
}
?>
array(5) {
["id"] => "1"
["name"] => "zzguo28"
["email"] => "zzguo28@163.com"
["mobile"] => "12345678901"
["dept_name"] => "銷售部"
}
SELECT u.id AS id, u.name AS name, ui.email AS email, ui.mobile AS mobile, Dept.name AS dept_name
FROM tp_user u
LEFT JOIN tp_user_info ui ON ui.user_id = u.id
JOIN tp_dept Dept ON u.dept_id = Dept.id
WHERE ( u.id = 1 AND u.status = 1 )
LIMIT 1
視圖模型繼承 Model,完整支援:
$dao->where(...)->order(...)->limit(...)->page(...)->group(...)->having(...)->select();
$dao->count(); $dao->max('field'); $dao->sum('field');
'field' => 'alias' 映射(如 'name' => 'dept_name')。不再需要繼承 ViewModel 定義類,可在基礎模型中動態定義視圖:
<?php
// Lib/Model/UserModel.class.php
class UserModel extends Model {
// 定義視圖關聯(public 屬性)
public $viewFields = [
'User' => ['id','name','dept_id','_as'=>'u'],
'UserInfo'=> ['email','mobile','_as'=>'ui','_on'=>'ui.user_id=u.id'],
'Dept' => ['name'=>'dept_name','_on'=>'u.dept_id=Dept.id'],
];
}
// 控制器
$dao = D('User');
$dao->setView(['User','UserInfo','Dept']); // 指定啟用的視圖
$user = $dao->find(1);
優點:單一模型類同時支援單表操作與多視圖查詢,更靈活。
class UserModel extends Model {
public $viewFields = [
// 視圖 1:用戶+部門
'withDept' => [
'User' => ['id','name','dept_id','_as'=>'u'],
'Dept' => ['name'=>'dept_name','_on'=>'u.dept_id=Dept.id'],
],
// 視圖 2:用戶+詳細信息+部門
'withInfoDept' => [
'User' => ['id','name','dept_id','_as'=>'u'],
'UserInfo'=> ['email','mobile','_as'=>'ui','_on'=>'ui.user_id=u.id'],
'Dept' => ['name'=>'dept_name','_on'=>'u.dept_id=Dept.id'],
],
];
}
// 使用
D('User')->setView('withInfoDept')->find(1);
嚴禁 使用 '*' 或省略字段列表。明確列出每個表需要的字段,減少網絡傳輸、內存佔用、查詢時間。
_on 條件中的關聯字段(外鍵)有索引:user_info.user_id, user.dept_id。u.status, u.id)建立索引。單個視圖模型關聯表建議 ≤ 5 張。超過則拆分為多個視圖或改用原生 SQL/查詢構建器。
$user = S('user_view_'.$uid);
if (!$user) {
$user = D('UserView')->find($uid);
S('user_view_'.$uid, $user, 3600);
}
視圖模型主要用於讀取。寫入(add()/save()/delete())行為未定義/不支援,請使用對應的基礎模型(D('User')、D('UserInfo'))進行寫入。
| 錯誤 | 原因 | 修正 |
|---|---|---|
| 字段衝突導致數據錯誤 | 多表有同名字段未映射別名 | 全部字段顯式列出並用 'field'=>'alias' 映射 |
| _on 條件寫錯表名 | 使用了實際表名而非別名 | _on 中必須用別名:'ui.user_id = u.id' |
| LEFT JOIN 變 INNER JOIN | WHERE 條件放到了右表字段導致隱式轉換 | 右表條件放在 ON 中,或用 WHERE 時注意邏輯 |
| 動態視圖 setView 失效 | 屬性定義為 protected 而非 public | ThinkPHP 3.2.3+ 需 public $viewFields |
| 嘗試用視圖模型寫入 | 視圖模型不支援寫入 | 寫入用基礎模型 D('User')、D('UserInfo') |
ThinkPHP 視圖模型(ViewModel/動態 $viewFields)將多表 JOIN 封裝為可復用的「虛擬單表」,讓控制器代碼保持單表操作的簡潔性,同時享受關聯查詢的強大威力。核心優勢在於:字段別名映射解決衝突、_on/_type 靈活定義關聯、完整支援連貫操作、動態多視圖切換。生產環境務必遵循「只選所需字段、關聯字段建索引、讀寫分離(視圖讀/基礎模型寫)、熱點緩存」原則,發揮最大效能。