## 關聯關系
通常我們所說的關聯關系包括下面三種:
~~~
一對一關聯 :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`類,關聯定義的格式是:
~~~
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,并且添加如下關聯定義:
~~~
namespace Home\Model;
use Think\Model\RelationModel;
class UserModel extends RelationModel{
protected $_link = array(
'Profile'=> self::HAS_ONE,
);
}
~~~
上面是最簡單的方式,表示其遵循了系統內置的數據庫規范,完整的定義方式是:
~~~
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 關聯表示當前模型從屬于另外一個父對象,例如每個用戶都屬于一個部門。我們可以做如下關聯定義。
~~~
'Dept' => self::BELONGS_TO
~~~
完整方式定義為:
~~~
'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 關聯表示當前模型擁有多個子對象,例如每個用戶有多篇文章,我們可以這樣來定義:
~~~
'Article' => self::HAS_MANY
~~~
完整定義方式為:
~~~
'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 關聯表示當前模型可以屬于多個對象,而父對象則可能包含有多個子對象,通常兩者之間需要一個中間表類約束和關聯。例如每個用戶可以屬于多個組,每個組可以有多個用戶:
~~~
'Group' => self::MANY_TO_MANY
~~~
完整定義方式為:
~~~
'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定義支持簡化寫法,例如:
~~~
'relation_table'=>'__USER_GROUP__'
~~~
## 關聯查詢
由于性能問題,新版取消了自動關聯查詢機制,而統一使用relation方法進行關聯操作,relation方法不但可以啟用關聯還可以控制局部關聯操作,實現了關聯操作一切盡在掌握之中。
~~~
$User = D("User");
$user = $User->relation(true)->find(1);
~~~
輸出$user結果可能是類似于下面的數據:
~~~
array(
'id' => 1,
'account' => 'ThinkPHP',
'password' => '123456',
'Profile' => array(
'email' => 'liu21st@gmail.com',
'nickname' => '流年',
),
)
~~~
我們可以看到,用戶的關聯數據已經被映射到數據對象的屬性里面了。其中Profile就是關聯定義的mapping_name屬性。
如果我們按照下面的方式定義了as_fields屬性的話,
~~~
protected $_link = array(
'Profile'=>array(
'mapping_type' => self::HAS_ONE,
'class_name' => 'Profile',
'foreign_key' => 'userId',
'as_fields' => 'email,nickname',
),
);
~~~
查詢的結果就變成了下面的結果
~~~
array(
'id' => 1,
'account' => 'ThinkPHP',
'password' => 'name',
'email' => 'liu21st@gmail.com',
'nickname' => '流年',
)
~~~
email和nickname兩個字段已經作為user數據對象的字段來顯示了。
如果關聯數據的字段名和當前數據對象的字段有沖突的話,怎么解決呢?
我們可以用下面的方式來變化下定義:
~~~
'as_fields' => 'email,nickname:username',
~~~
表示關聯表的nickname字段映射成當前數據對象的username字段。
默認會把所有定義的關聯數據都查詢出來,有時候我們并不希望這樣,就可以給relation方法傳入參數來控制要關聯查詢的。
~~~
$User = D("User");
$user = $User->relation('Profile')->find(1);
~~~
關聯查詢一樣可以支持select方法,如果要查詢多個數據,并同時獲取相應的關聯數據,可以改成:
~~~
$User = D("User");
$list = $User->relation(true)->Select();
~~~
如果希望在完成的查詢基礎之上 再進行關聯數據的查詢,可以使用
~~~
$User = D("User");
$user = $User->find(1);
// 表示對當前查詢的數據對象進行關聯數據獲取
$profile = $User->relationGet("Profile");
~~~
事實上,除了當前的參考模型User外,其他的關聯模型是不需要創建的。
## 關聯操作
除了關聯查詢外,系統也支持關聯數據的自動寫入、更新和刪除
### 關聯寫入
~~~
$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數據。
同樣,可以使用參數來控制要關聯寫入的數據:
~~~
$result = $User->relation("Profile")->add($data);
~~~
> 當MANY_TO_MANY時,不建議使用關聯插入。
### 關聯更新
數據的關聯更新和關聯寫入類似
~~~
$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模型定義的所有關聯數據,如果只需要關聯保存部分數據,可以使用:
~~~
$result = $User->relation("Profile")->save($data);
~~~
這樣就只會同時更新關聯的Profile數據。
關聯保存的規則:
**HAS_ONE**: 關聯數據的更新直接賦值
**HAS_MANY**: 的關聯數據如果傳入主鍵的值 則表示更新 否則就表示新增
**MANY_TO_MANY**: 的數據更新是刪除之前的數據后重新寫入
### 關聯刪除
~~~
//刪除用戶ID為3的記錄的同時刪除關聯數據
$result = $User->relation(true)->delete("3");
// 如果只需要關聯刪除部分數據,可以使用
$result = $User->relation("Profile")->delete("3");
~~~
- 序言
- 基礎
- 獲取ThinkPHP
- 環境要求
- 目錄結構
- 入口文件
- 自動生成
- 模塊
- 控制器
- 開發規范
- 配置
- 配置格式
- 配置加載
- 讀取配置
- 動態配置
- 擴展配置
- 批量配置
- 架構
- 模塊化設計
- URL模式
- 多層MVC
- CBD模式
- 命名空間
- 自動加載
- 應用模式
- 項目編譯
- 系統流程
- 路由
- 路由定義
- 規則路由
- 正則路由
- 靜態路由
- 閉包支持
- 實例說明
- 控制器
- 控制器定義
- 前置和后置操作
- Action參數綁定
- 偽靜態
- URL大小寫
- URL生成
- AJAX返回
- 跳轉和重定向
- 輸入變量
- 請求類型
- 空操作
- 空控制器
- 插件控制器
- 操作綁定到類
- 模型
- 模型定義
- 模型實例化
- 字段定義
- 連接數據庫
- 切換數據庫
- 分布式數據庫支持
- 連貫操作
- 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代碼
- 原樣輸出
- 模板注釋
- 模板布局
- 模板替換
- 調試
- 調試模式
- 異常處理
- 日志記錄
- 頁面Trace
- Trace方法
- 變量調試
- 性能調試
- 錯誤調試
- 模型調試
- 緩存
- 數據緩存
- 快速緩存
- 查詢緩存
- 靜態緩存
- 安全
- 輸入過濾
- 表單合法性檢測
- 表單令牌
- 防止SQL注入
- 目錄安全文件
- 保護模板文件
- 上傳安全
- 防止XSS攻擊
- 其他安全建議
- 擴展
- 類庫擴展
- 驅動擴展
- 緩存驅動
- 數據庫驅動
- 日志驅動
- Session驅動
- 存儲驅動
- 模板引擎驅動
- 標簽庫驅動
- 行為擴展
- 標簽擴展
- Widget擴展
- 應用模式
- 部署
- PATH_INFO支持
- URL重寫
- 模塊部署
- 域名部署
- 入口綁定
- 替換入口
- 專題
- SESSION支持
- Cookie支持
- 多語言支持
- 數據分頁
- 文件上傳
- 驗證碼
- 圖像處理
- RESTFul
- RPC
- SAE
- IP獲取和定位
- 附錄
- 常量參考
- 配置參考
- 升級指導
- 鳴謝