# 關聯模型
### 關聯關系
通常我們所說的關聯關系包括下面三種:
* 一對一關聯 :ONE_TO_ONE,包括HAS_ONE 和 BELONGS_TO
* 一對多關聯 :ONE_TO_MANY,包括HAS_MANY 和 BELONGS_TO
* 多對多關聯 :MANY_TO_MANY
關聯關系必然有一個參照表,例如:
有一個員工檔案管理系統項目,這個項目要包括下面的一些數據表:基本信息表、員工檔案表、部門表、項目組表、銀行卡表(用來記錄員工的銀行卡資料)。
這些數據表之間存在一定的關聯關系,我們以員工基本信息表為參照來分析和其他表之間的關聯:
* 每個員工必然有對應的員工檔案資料,所以屬于HAS_ONE關聯;
* 每個員工必須屬于某個部門,所以屬于BELONGS_TO關聯;
* 每個員工可以有多個銀行卡,但是每張銀行卡只可能屬于一個員工,因此屬于HAS_MANY關聯;
* 每個員工可以同時在多個項目組,每個項目組同時有多個員工,因此屬于MANY_TO_MANY關聯;
分析清楚數據表之前的關聯關系后,我們才可以進行關聯定義和關聯操作。
### 關聯定義
ThinkPHP可以很輕松的完成數據表的關聯CURD操作,目前支持的關聯關系包括下面四種:
#### HAS_ONE、BELONGS_TO、HAS_MANY和MANY_TO_MANY。
一個模型根據業務模型的復雜程度可以同時定義多個關聯,不受限制,所有的關聯定義都統一在模型類的 $_link 成員變量里面定義,并且可以支持動態定義。要支持關聯操作,模型類必須繼承Think\Model\RelationModel類,關聯定義的格式是:
```php
namespace Home\Model;
use Think\Model\RelationModel;
class UserModel extends RelationModel{
protected $_link = array(
'關聯1' => array(
'關聯屬性1' => '定義',
'關聯屬性N' => '定義',
),
'關聯2' => array(
'關聯屬性1' => '定義',
'關聯屬性N' => '定義',
),
'關聯3' => HAS_ONE, // 快捷定義
...
);
}
```
下面我們首先來分析下各個關聯方式的定義:
### HAS_ONE
HAS_ONE關聯表示當前模型擁有一個子對象,例如,每個員工都有一個人事檔案。我們可以建立一個用戶模型UserModel,并且添加如下關聯定義:
```php
namespace Home\Model;
use Think\Model\RelationModel;
class UserModel extends RelationModel{
protected $_link = array(
'Profile'=> self::HAS_ONE,
);
}
```
上面是最簡單的方式,表示其遵循了系統內置的數據庫規范,完整的定義方式是:
```php
namespace Home\Model;
use Think\Model\RelationModel;
class UserModel extends RelationModel{
protected $_link = array(
'Profile'=>array(
'mapping_type' => self::HAS_ONE,
'class_name' => 'Profile',
// 定義更多的關聯屬性
……
),
);
}
```
關聯HAS_ONE支持的關聯屬性有:
>mapping_type :關聯類型
這個在HAS_ONE 關聯里面必須使用HAS_ONE 常量定義。
>class_name :要關聯的模型類名
例如,class_name 定義為Profile的話則表示和另外的Profile模型類關聯,這個Profile模型類是無需定義的,系統會自動定位到相關的數據表進行關聯。
>mapping_name :關聯的映射名稱,用于獲取數據用
該名稱不要和當前模型的字段有重復,否則會導致關聯數據獲取的沖突。如果mapping_name沒有定義的話,會取class_name的定義作為mapping_name。如果class_name也沒有定義,則以數組的索引作為mapping_name。
>foreign_key : 關聯的外鍵名稱
外鍵的默認規則是當前數據對象名稱_id,例如: UserModel對應的可能是表think_user (注意:think只是一個表前綴,可以隨意配置) 那么think_user表的外鍵默認為 user_id,如果不是,就必須在定義關聯的時候顯式定義 foreign_key 。
>condition : 關聯條件
關聯查詢的時候會自動帶上外鍵的值,如果有額外的查詢條件,可以通過定義關聯的condition屬性。
>mapping_fields : 關聯要查詢的字段
默認情況下,關聯查詢的關聯數據是關聯表的全部字段,如果只是需要查詢個別字段,可以定義關聯的mapping_fields屬性。
>as_fields :直接把關聯的字段值映射成數據對象中的某個字段
這個特性是ONE_TO_ONE 關聯特有的,可以直接把關聯數據映射到數據對象中,而不是作為一個關聯數據。當關聯數據的字段名和當前數據對象的字段名稱有沖突時,還可以使用映射定義。
### BELONGS_TO
Belongs_to 關聯表示當前模型從屬于另外一個父對象,例如每個用戶都屬于一個部門。我們可以做如下關聯定義。
```php
'Dept' => self::BELONGS_TO
```
完整方式定義為:
```php
'Dept' => array(
'mapping_type' => self::BELONGS_TO,
'class_name' => 'Dept',
'foreign_key' => 'userId',
'mapping_name' => 'dept',
// 定義更多的關聯屬性
……
),
```
關聯BELONGS_TO定義支持的關聯屬性有:
|屬性| 描述|
|----|-----|
|class_name|要關聯的模型類名|
|mapping_name|關聯的映射名稱,用于獲取數據用 該名稱不要和當前模型的字段有重復,否則會導致關聯數據獲取的沖突。|
|foreign_key|關聯的外鍵名稱|
|mapping_fields|關聯要查詢的字段|
|condition|關聯條件|
|parent_key |自引用關聯的關聯字段 默認為parent_id 自引用關聯是一種比較特殊的關聯,也就是關聯表就是當前表。|
|as_fields|直接把關聯的字段值映射成數據對象中的某個字段|
### HAS_MANY
HAS_MANY 關聯表示當前模型擁有多個子對象,例如每個用戶有多篇文章,我們可以這樣來定義:
```php
'Article' => self::HAS_MANY
```
完整定義方式為:
```php
'Article' => array(
'mapping_type' => self::HAS_MANY,
'class_name' => 'Article',
'foreign_key' => 'userId',
'mapping_name' => 'articles',
'mapping_order' => 'create_time desc',
// 定義更多的關聯屬性
……
),
```
關聯HAS_MANY定義支持的關聯屬性有:
|屬性 |描述|
|----|-----|
|class_name |要關聯的模型類名|
|mapping_name|關聯的映射名稱,用于獲取數據用 該名稱不要和當前模型的字段有重復,否則會導致關聯數據獲取的沖突。|
|foreign_key|關聯的外鍵名稱|
|parent_key|自引用關聯的關聯字段 默認為parent_id|
|condition|關聯條件 關聯查詢的時候會自動帶上外鍵的值,如果有額外的查詢條件,可以通過定義關聯的condition屬性。|
|mapping_fields|關聯要查詢的字段 默認情況下,關聯查詢的關聯數據是關聯表的全部字段,如果只是需要查詢個別字段,可以定義關聯的mapping_fields屬性。|
|mapping_limit|關聯要返回的記錄數目|
|mapping_order|關聯查詢的排序|
外鍵的默認規則是當前數據對象名稱_id,例如:UserModel對應的可能是表think_user (注意:think只是一個表前綴,可以隨意配置) 那么think_user表的外鍵默認為 user_id,如果不是,就必須在定義關聯的時候定義 foreign_key 。
### MANY_TO_MANY
MANY_TO_MANY 關聯表示當前模型可以屬于多個對象,而父對象則可能包含有多個子對象,通常兩者之間需要一個中間表類約束和關聯。例如每個用戶可以屬于多個組,每個組可以有多個用戶:
```php
'Group' => self::MANY_TO_MANY
```
完整定義方式為:
```php
'Group' => array(
'mapping_type' => self::MANY_TO_MANY,
'class_name' => 'Group',
'mapping_name' => 'groups',
'foreign_key' => 'userId',
'relation_foreign_key' => 'groupId',
'relation_table' => 'think_group_user' //此處應顯式定義中間表名稱,且不能使用C函數讀取表前綴
)
```
MANY_TO_MANY支持的關聯屬性定義有:
|屬性 |描述|
|----|-----|
|class_name |要關聯的模型類名|
|mapping_name|關聯的映射名稱,用于獲取數據用 該名稱不要和當前模型的字段有重復,否則會導致關聯數據獲取的沖突。|
|foreign_key|關聯的外鍵名稱 外鍵的默認規則是當前數據對象名稱_id|
|relation_foreign_key|關聯表的外鍵名稱 默認的關聯表的外鍵名稱是表名_id|
|mapping_limit|關聯要返回的記錄數目|
|mapping_order|關聯查詢的排序|
|relation_table|多對多的中間關聯表名稱|
多對多的中間表默認表規則是:數據表前綴_關聯操作的主表名_關聯表名
如果think_user 和 think_group 存在一個對應的中間表,默認的表名應該是 如果是由group來操作關聯表,中間表應該是 think_group_user,如果是從user表來操作,那么應該是think_user_group,也就是說,多對多關聯的設置,必須有一個Model類里面需要顯式定義中間表,否則雙向操作會出錯。 中間表無需另外的id主鍵(但是這并不影響中間表的操作),通常只是由 user_id 和 group_id 構成。 默認會通過當前模型的getRelationTableName方法來自動獲取,如果當前模型是User,關聯模型是Group,那么關聯表的名稱也就是使用 user_group這樣的格式,如果不是默認規則,需要指定relation_table屬性。
3.2.2版本開始,relation_table定義支持簡化寫法,例如:
```php
'relation_table'=>'__USER_GROUP__'
```
### 關聯查詢
由于性能問題,新版取消了自動關聯查詢機制,而統一使用relation方法進行關聯操作,relation方法不但可以啟用關聯還可以控制局部關聯操作,實現了關聯操作一切盡在掌握之中。
```php
$User = D("User");
$user = $User->relation(true)->find(1);
```
輸出$user結果可能是類似于下面的數據:
```php
array(
'id' => 1,
'account' => 'ThinkPHP',
'password' => '123456',
'Profile' => array(
'email' => 'liu21st@gmail.com',
'nickname' => '流年',
),
)
```
我們可以看到,用戶的關聯數據已經被映射到數據對象的屬性里面了。其中Profile就是關聯定義的mapping_name屬性。
如果我們按照下面的方式定義了as_fields屬性的話,
```php
protected $_link = array(
'Profile'=>array(
'mapping_type' => self::HAS_ONE,
'class_name' => 'Profile',
'foreign_key' => 'userId',
'as_fields' => 'email,nickname',
),
);
```
查詢的結果就變成了下面的結果
```php
array(
'id' => 1,
'account' => 'ThinkPHP',
'password' => 'name',
'email' => 'liu21st@gmail.com',
'nickname' => '流年',
)
```
email和nickname兩個字段已經作為user數據對象的字段來顯示了。
如果關聯數據的字段名和當前數據對象的字段有沖突的話,怎么解決呢?
我們可以用下面的方式來變化下定義:
```php
'as_fields' => 'email,nickname:username',
```
表示關聯表的nickname字段映射成當前數據對象的username字段。
默認會把所有定義的關聯數據都查詢出來,有時候我們并不希望這樣,就可以給relation方法傳入參數來控制要關聯查詢的。
```php
$User = D("User");
$user = $User->relation('Profile')->find(1);
```
關聯查詢一樣可以支持select方法,如果要查詢多個數據,并同時獲取相應的關聯數據,可以改成:
```php
$User = D("User");
$list = $User->relation(true)->Select();
```
如果希望在完成的查詢基礎之上 再進行關聯數據的查詢,可以使用
```php
$User = D("User");
$user = $User->find(1);
// 表示對當前查詢的數據對象進行關聯數據獲取
$profile = $User->relationGet("Profile");
```
事實上,除了當前的參考模型User外,其他的關聯模型是不需要創建的。
### 關聯操作
除了關聯查詢外,系統也支持關聯數據的自動寫入、更新和刪除
### 關聯寫入
```php
$User = D("User");
$data = array();
$data["account"] = "ThinkPHP";
$data["password"] = "123456";
$data["Profile"] = array(
'email' =>'liu21st@gmail.com',
'nickname' =>'流年',
);
$result = $User->relation(true)->add($data);
```
這樣就會自動寫入關聯的Profile數據。
同樣,可以使用參數來控制要關聯寫入的數據:
```php
$result = $User->relation("Profile")->add($data);
```
當MANY_TO_MANY時,不建議使用關聯插入。
### 關聯更新
數據的關聯更新和關聯寫入類似
```php
$User = D("User");
$data["account"] = "ThinkPHP";
$data["password"] = "123456";
$data["Profile"] = array(
'email' =>'liu21st@gmail.com',
'nickname' =>'流年',
);
$result = $User-> relation(true)->where(array('id'=>3))->save($data);
```
Relation(true)會關聯保存User模型定義的所有關聯數據,如果只需要關聯保存部分數據,可以使用:
```php
$result = $User->relation("Profile")->save($data);
```
這樣就只會同時更新關聯的Profile數據。
關聯保存的規則:
* HAS_ONE: 關聯數據的更新直接賦值
* HAS_MANY: 的關聯數據如果傳入主鍵的值 則表示更新 否則就表示新增
* MANY_TO_MANY: 的數據更新是刪除之前的數據后重新寫入
### 關聯刪除
```php
//刪除用戶ID為3的記錄的同時刪除關聯數據
$result = $User->relation(true)->delete("3");
// 如果只需要關聯刪除部分數據,可以使用
$result = $User->relation("Profile")->delete("3");
```
- 前言
- 基礎
- 關于MuuCmf
- 獲取MuuCmf
- 環境要求
- 目錄結構
- 安裝
- 開發規范
- 控制器
- 控制器定義
- 前置和后置操作
- AJAX返回
- Action參數綁定
- 偽靜態
- URL大小寫
- Url生成
- 跳轉和重定向
- 輸入變量
- 請求類型
- 空操作
- 空控制器
- 插件控制器
- 操作綁定到類
- 模型
- 模型的定義
- 模型實例化
- 字段定義
- 連接數據庫
- 切換數據庫
- 分布式數據庫支持
- 連貫操作
- WHERE
- TABLE
- ALIAS
- DATA
- FIELD
- ORDER
- LIMIT
- PAGE
- GROUP
- HAVING
- JOIN
- UNION
- DISTINCT
- LOCK
- CACHE
- COMMENT
- RELATION
- USING
- fetchSql
- TOKEN
- STRICT
- INDEX
- 命名范圍
- CURD操作
- 數據創建
- 數據寫入
- 數據讀取
- 數據更新
- 數據刪除
- ActiveRecord
- 字段映射
- 查詢語言
- 查詢方式
- 表達式查詢
- 快捷查詢
- 區間查詢
- 組合查詢
- 統計查詢
- SQL查詢
- 動態查詢
- 子查詢
- 自動驗證
- 自動完成
- 參數綁定
- 虛擬模型
- 模型分層
- 視圖模型
- 關聯模型
- 高級模型
- Mongo模型
- 視圖
- 模板定義
- 模板主題
- 模板賦值
- 模板渲染
- 獲取模板地址
- 獲取內容
- 模板引擎
- 模板
- 變量輸出
- 系統變量
- 使用函數
- 默認值輸出
- 使用運算符
- 標簽庫
- 模板繼承
- 修改定界符
- 三元運算
- 包含文件
- 內置標簽
- Volist標簽
- Foreach標簽
- For標簽
- Switch標簽
- 比較標簽
- 范圍判斷標簽
- IF標簽
- Present標簽
- Empty標簽
- Defined標簽
- Assign標簽
- Define標簽
- 標簽嵌套
- import標簽
- 使用PHP代碼
- 原樣輸出
- 模板注釋
- 模板布局
- 模板替換
- 模塊開發
- 模塊的定義
- 開發規范
- 后臺構建器Builder
- 安裝與卸載
- 插件開發
- REST API
- RESTAPI定義
- 后臺使用指南
- 二次開發指南
- 官方模塊手冊