# 關聯模型
[上一頁](77.html "上一頁")[下一頁](79.html "下一頁")
### 關聯關系
通常我們所說的關聯關系包括下面三種:
一對一關聯 :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 成員變量里面定義,并且可以支持動態定義。要支持關聯操作,模型類必須繼承RelationModel類,關聯定義的格式是:`protected?$_link?=?array(<br class="calibre5"></br>????'關聯1'??=>??array(<br class="calibre5"></br>????????'關聯屬性1'?=>?'定義',<br class="calibre5"></br>????????'關聯屬性N'?=>?'定義',<br class="calibre5"></br>????),<br class="calibre5"></br>????'關聯2'??=>??array(<br class="calibre5"></br>????????'關聯屬性1'?=>?'定義',<br class="calibre5"></br>????????'關聯屬性N'?=>?'定義',<br class="calibre5"></br>????),<br class="calibre5"></br>????'關聯3'??=>??HAS_ONE,?//?快捷定義<br class="calibre5"></br>????...<br class="calibre5"></br>);`下面我們首先來分析下各個關聯方式的定義:
**HAS\_ONE**
HAS_ONE關聯表示當前模型擁有一個子對象,例如,每個員工都有一個人事檔案。我們可以建立一個用戶模型UserModel,并且添加如下關聯定義:`class?UserModel?extends?RelationModel{<br class="calibre5"></br>????protected?$_link?=?array(<br class="calibre5"></br>???????'Profile'=>?HAS_ONE,<br class="calibre5"></br>????);<br class="calibre5"></br>}`上面是最簡單的方式,表示其遵循了系統內置的數據庫規范,完整的定義方式是:`class?UserModel?extends?RelationModel{<br class="calibre5"></br>????protected?$_link?=?array(<br class="calibre5"></br>????????????'Profile'=>array(<br class="calibre5"></br>????????????'mapping_type'????=>HAS_ONE,<br class="calibre5"></br>?????????????????'class_name'????=>'Profile',<br class="calibre5"></br>?????????????????//?定義更多的關聯屬性<br class="calibre5"></br>??????????????……<br class="calibre5"></br>?????????????),<br class="calibre5"></br>?????????);<br class="calibre5"></br>}`關聯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'=>?BELONGS_TO`完整方式定義為:
`'Dept'=>?array(??<br class="calibre5"></br>?????'mapping_type'=>BELONGS_TO,<br class="calibre5"></br>??????????'class_name'=>'Dept',<br class="calibre5"></br>??????????'foreign_key'=>'userId',<br class="calibre5"></br>??????????'mapping_name'=>'dept',<br class="calibre5"></br>???????????//?定義更多的關聯屬性<br class="calibre5"></br>????????……<br class="calibre5"></br>),`關聯BELONGS_TO定義支持的關聯屬性有:class\_name 要關聯的模型類名mapping\_name 關聯的映射名稱,用于獲取數據用
該名稱不要和當前模型的字段有重復,否則會導致關聯數據獲取的沖突。foreign\_key 關聯的外鍵名稱mapping\_fields 關聯要查詢的字段condition 關聯條件parent\_key 自引用關聯的關聯字段
默認為parent\_id
自引用關聯是一種比較特殊的關聯,也就是關聯表就是當前表。as\_fields直接把關聯的字段值映射成數據對象中的某個字段**HAS\_MANY**
HAS_MANY 關聯表示當前模型擁有多個子對象,例如每個用戶有多篇文章,我們可以這樣來定義:`'Article'=>?HAS_MANY`完整定義方式為:
`'Article'=>?array(??<br class="calibre5"></br>????'mapping_type'=>HAS_MANY,<br class="calibre5"></br>????????????????????'class_name'=>'Article',<br class="calibre5"></br>????????????????????'foreign_key'=>'userId',<br class="calibre5"></br>????????????????????'mapping_name'=>'articles',<br class="calibre5"></br>????????????????????'mapping_order'=>'create_time?desc',<br class="calibre5"></br>?????????//?定義更多的關聯屬性<br class="calibre5"></br>????????……<br class="calibre5"></br>???????),`關聯HAS_MANY定義支持的關聯屬性有:class\_name要關聯的模型類名mapping\_name 關聯的映射名稱,用于獲取數據用
該名稱不要和當前模型的字段有重復,否則會導致關聯數據獲取的沖突。foreign\_key 關聯的外鍵名稱
外鍵的默認規則是當前數據對象名稱\_id,例如:
UserModel對應的可能是表think\_user (注意:think只是一個表前綴,可以隨意配置)
那么think\_user表的外鍵默認為 user\_id,如果不是,就必須在定義關聯的時候定義 foreign\_key 。parent\_key 自引用關聯的關聯字段
默認為parent\_id condition 關聯條件
關聯查詢的時候會自動帶上外鍵的值,如果有額外的查詢條件,可以通過定義關聯的condition屬性。mapping\_fields 關聯要查詢的字段
默認情況下,關聯查詢的關聯數據是關聯表的全部字段,如果只是需要查詢個別字段,可以定義關聯的mapping\_fields屬性。mapping\_limit 關聯要返回的記錄數目mapping\_order 關聯查詢的排序**MANY\_TO\_MANY**
MANY_TO_MANY 關聯表示當前模型可以屬于多個對象,而父對象則可能包含有多個子對象,通常兩者之間需要一個中間表類約束和關聯。例如每個用戶可以屬于多個組,每個組可以有多個用戶:
`'Group'=>MANY_TO_MANY`完整定義方式為:
`array('mapping_type'=>MANY_TO_MANY,<br class="calibre5"></br>'class_name'=>'Group',<br class="calibre5"></br>'mapping_name'=>'groups',<br class="calibre5"></br>'foreign_key'=>'userId',<br class="calibre5"></br>'relation_foreign_key'=>'goupId',<br class="calibre5"></br>'relation_table'=>'think_gourpUser'<br class="calibre5"></br>)`MANY_TO_MANY支持的關聯屬性定義有:class\_name要關聯的模型類名mapping\_name 關聯的映射名稱,用于獲取數據用
該名稱不要和當前模型的字段有重復,否則會導致關聯數據獲取的沖突。foreign\_key 關聯的外鍵名稱
外鍵的默認規則是當前數據對象名稱\_idrelation\_foreign\_key 關聯表的外鍵名稱
默認的關聯表的外鍵名稱是表名\_idmapping\_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屬性。### 關聯查詢
由于性能問題,新版取消了自動關聯查詢機制,而統一使用relation方法進行關聯操作,relation方法不但可以啟用關聯還可以控制局部關聯操作,實現了關聯操作一切盡在掌握之中。`$User?=?D("User");<br class="calibre5"></br>$user?=????$User->relation(true)->find(1);`輸出$user結果可能是類似于下面的數據:`array(<br class="calibre5"></br>'id'????????=>????1,<br class="calibre5"></br>'account'????=>????'ThinkPHP',<br class="calibre5"></br>'password'????=>????'123456',<br class="calibre5"></br>'Profile'????=>?array(<br class="calibre5"></br>'email'????????=>'liu21st@gmail.com',<br class="calibre5"></br>'nickname'????=>'流年',<br class="calibre5"></br>???),<br class="calibre5"></br>?)`我們可以看到,用戶的關聯數據已經被映射到數據對象的屬性里面了。其中Profile就是關聯定義的mapping_name屬性。
如果我們按照下面的方式定義了as_fields屬性的話,`????protected?$_link?=?array(<br class="calibre5"></br>????????'profile'=>array(<br class="calibre5"></br>????'mapping_type'????=>HAS_ONE,<br class="calibre5"></br>????????????????????'class_name'????=>'Profile',<br class="calibre5"></br>????'foreign_key'=>'userId',<br class="calibre5"></br>????'as_fields'=>'email,nickname',<br class="calibre5"></br>?????????),<br class="calibre5"></br>);`查詢的結果就變成了下面的結果`array(<br class="calibre5"></br>'id'????????=>????1,<br class="calibre5"></br>'account'????=>????'ThinkPHP',<br class="calibre5"></br>'password'????=>????'name',<br class="calibre5"></br>'email'????????=>'liu21st@gmail.com',<br class="calibre5"></br>'nickname'????=>'流年',<br class="calibre5"></br>?)`email和nickname兩個字段已經作為user數據對象的字段來顯示了。
如果關聯數據的字段名和當前數據對象的字段有沖突的話,怎么解決呢?
我們可以用下面的方式來變化下定義:
'as_fields'=>'email,nickname:username',
表示關聯表的nickname字段映射成當前數據對象的username字段。
默認會把所有定義的關聯數據都查詢出來,有時候我們并不希望這樣,就可以給relation方法傳入參數來控制要關聯查詢的。`$User?=?D("User");<br class="calibre5"></br>$user?=????$User->relation('Profile')->find(1);`關聯查詢一樣可以支持select方法,如果要查詢多個數據,并同時獲取相應的關聯數據,可以改成:`$User?=?D("User");<br class="calibre5"></br>$list?=????$User->relation(true)->Select();`如果希望在完成的查詢基礎之上 再進行關聯數據的查詢,可以使用`$User?=?D("User");<br class="calibre5"></br>$user?=?$User->find(1);<br class="calibre5"></br>//?表示對當前查詢的數據對象進行關聯數據獲取<br class="calibre5"></br>$profile?=?$User->relationGet("Profile");`事實上,除了當前的參考模型User外,其他的關聯模型是不需要創建的。### 關聯操作
除了關聯查詢外,系統也支持關聯數據的自動寫入、更新和刪除
**關聯寫入**`$User?=?D("User");<br class="calibre5"></br>$data?=?array();<br class="calibre5"></br>$data["account"]????=?"ThinkPHP";<br class="calibre5"></br>$data["password"]?=?"123456";<br class="calibre5"></br>$data["Profile"]????=?array(<br class="calibre5"></br>????'email'????=>'liu21st@gmail.com',<br class="calibre5"></br>????'nickname'????=>'流年',<br class="calibre5"></br>);<br class="calibre5"></br>$result?=$User->relation(true)->add($data);`這樣就會自動寫入關聯的Profile數據。
同樣,可以使用參數來控制要關聯寫入的數據:`$result?=?$User->relation("Profile")->add($data);`**關聯更新**
數據的關聯更新和關聯寫入類似`$User?=?D("User");<br class="calibre5"></br>$data["account"]????=?"ThinkPHP";<br class="calibre5"></br>$data["password"]?=?"123456";<br class="calibre5"></br>$data["Profile"]????=?array(<br class="calibre5"></br>????'email'????=>'liu21st@gmail.com',<br class="calibre5"></br>????'nickname'????=>'流年',<br class="calibre5"></br>);<br class="calibre5"></br>$result?=?$User->?relation(true)->where('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");`
[上一頁](77.html "上一頁")[下一頁](79.html "下一頁")
- 序言
- 1. 入門
- 1.1 簡介
- 1.2 基礎概念
- 1.3 獲取ThinkPHP
- 1.4 環境要求
- 1.5 許可協議
- 1.6 目錄結構
- 1.7 命名規范
- 1.8 MVC分層
- 1.9 CBD架構
- 1.10 特性概述
- 1.11 系統流程
- 1.12 開發流程
- 2. 入口
- 2.1 入口文件
- 2.2 項目目錄
- 2.3 部署目錄
- 2.4 項目編譯
- 2.5 調試模式
- 3. 配置
- 3.1 配置格式
- 3.2 慣例配置
- 3.3 項目配置
- 3.4 調試配置
- 3.5 分組配置
- 3.6 讀取配置
- 3.7 動態配置
- 3.8 擴展配置
- 4. 函數和類庫
- 4.1 函數庫
- 4.2 類庫
- 5. 控制器
- 5.1 URL模式
- 5.2 模塊和操作
- 5.3 定義控制器
- 5.4 空操作
- 5.5 空模塊
- 5.6 模塊分組
- 5.7 URL偽靜態
- 5.8 URL路由
- 5.9 URL重寫
- 5.10 URL生成
- 5.11 URL大小寫
- 5.12 前置和后置操作
- 5.13 跨模塊調用
- 5.14 頁面跳轉
- 5.15 重定向
- 5.16 獲取系統變量
- 5.17 判斷請求類型
- 5.18 獲取URL參數
- 5.19 AJAX返回
- 5.20 Action參數綁定
- 5.21 多層控制器支持
- 6. 模型
- 6.1 模型定義
- 6.2 模型實例化
- 6.3 字段定義
- 6.4 數據主鍵
- 6.5 屬性訪問
- 6.6 跨庫操作
- 6.7 連接數據庫
- 6.8 切換數據庫
- 6.9 分布式數據庫
- 6.10 創建數據
- 6.11 字段映射
- 6.12 連貫操作
- 6.13 CURD操作
- 6.14 ActiveRecord
- 6.15 自動驗證
- 6.16 命名范圍
- 6.17 自動完成
- 6.18 查詢語言
- 6.19 查詢鎖定
- 6.20 字段排除
- 6.21 事務支持
- 6.22 高級模型
- 6.23 視圖模型
- 6.24 關聯模型
- 6.25 Mongo模型
- 6.26 動態模型
- 6.27 虛擬模型
- 6.28 多層模型支持
- 7. 視圖
- 7.1 模板定義
- 7.2 模板賦值
- 7.3 模板輸出
- 7.4 模板替換
- 7.5 獲取內容
- 7.6 模板引擎
- 7.7 布局模板
- 8. 模板引擎
- 8.1 變量輸出
- 8.2 系統變量
- 8.3 使用函數
- 8.4 默認值輸出
- 8.5 使用運算符
- 8.6 內置標簽
- 8.7 包含文件
- 8.8 導入文件
- 8.9 Volist標簽
- 8.10 Foreach標簽
- 8.11 For標簽
- 8.12 Switch標簽
- 8.13 比較標簽
- 8.14 三元運算
- 8.15 范圍判斷標簽
- 8.16 Present標簽
- 8.17 Empty標簽
- 8.18 Defined標簽
- 8.19 Define標簽
- 8.20 Assign標簽
- 8.21 IF標簽
- 8.22 標簽嵌套
- 8.23 使用PHP代碼
- 8.24 模板布局
- 8.25 模板繼承
- 8.26 原樣輸出
- 8.27 模板注釋
- 8.28 引入標簽庫
- 8.29 修改定界符
- 8.30 避免JS混淆
- 9. 日志
- 9.1 日志級別
- 9.2 記錄方式
- 9.3 手動記錄
- 10. 錯誤
- 10.1 異常處理
- 10.2 異常模板
- 10.3 異常顯示
- 11. 調試
- 11.1 運行狀態
- 11.2 頁面Trace
- 11.3 調試方法
- 12. 緩存
- 12.1 緩存方式
- 12.2 動態緩存
- 12.3 緩存隊列
- 12.4 快捷緩存
- 12.5 快速緩存
- 12.6 查詢緩存
- 12.7 SQL解析緩存
- 12.8 靜態緩存
- 13. 擴展
- 13.1 行為擴展
- 13.2 類庫擴展
- 13.3 控制器擴展
- 13.4 模型擴展
- 13.5 驅動擴展
- 13.6 Widget擴展
- 13.7 模式擴展
- 13.8 引擎擴展
- 14. 安全
- 14.1 表單令牌
- 14.2 字段類型驗證
- 14.3 防止SQL注入
- 14.4 輸入過濾
- 14.5 上傳安全
- 14.6 防止XSS攻擊
- 14.7 其他安全建議
- 14.8 目錄安全文件
- 14.9 保護模板文件
- 15. 性能
- 15.1 關閉調試模式
- 15.2 開啟緩存
- 15.3 合并字段緩存
- 15.4 優化SQL
- 15.5 替換入口
- 15.6 前端優化
- 16. 部署
- 16.1 PATH_INFO支持
- 16.2 隱藏index.php
- 16.3 二級域名部署
- 16.4 定制錯誤頁面
- 16.5 設置時區
- 17. SAE支持
- 17.1 SAE介紹
- 17.2 獲取SAE
- 17.3 SAE開發
- 18. REST支持
- 18.1 REST介紹
- 18.2 REST模式
- 18.3 REST配置
- 18.4 REST路由
- 18.5 REST方法
- 19. 雜項
- 19.1 Session支持
- 19.2 Cookie支持
- 19.3 日期和時間
- 19.4 WML開發
- 19.5 多語言
- 19.6 數據分頁
- 19.7 文件上傳
- 19.8 驗證碼
- 19.9 圖片添加水印
- 19.10 IP獲取和定位
- 20. 附錄
- 20.1 常量參考
- 20.2 配置參考
- 20.3 關于升級
- 20.4 大事記
- 鳴謝
- 關于