### 2018 年 11 月 24 日 發(fā)布
## 前言
ThinkPHP5的模型關(guān)聯(lián)是按照面向?qū)ο蟮拈_發(fā)思想重構(gòu)的,和`3.2`時代的關(guān)聯(lián)模型實現(xiàn)和用法完全不同。因此,要掌握`5.*`的模型關(guān)聯(lián),必須要有面向?qū)ο蟮乃枷胍庾R,重新認識新版的模型關(guān)聯(lián),這是理解和掌握模型關(guān)聯(lián)的關(guān)鍵。
掌握模型關(guān)聯(lián)的關(guān)鍵是掌握關(guān)聯(lián)的定義和查詢,關(guān)聯(lián)的寫入完全可以用模型自身的寫入來完成,而不要依賴關(guān)聯(lián)寫入(只有某些特殊的關(guān)聯(lián)才需要單獨的關(guān)聯(lián)寫入)。
本文主要為大家盡量淺顯的講解下模型關(guān)聯(lián)的查詢操作和注意事項,主要指導開發(fā)者打通模型關(guān)聯(lián)的使用障礙和恐懼,從而可以漸入佳境的學習模型關(guān)聯(lián)。由于模型關(guān)聯(lián)涉及的面比較廣,沒法做到面面俱到,因此本文內(nèi)容不做太多深入,以免形成長篇累牘,更加詳細的模型關(guān)聯(lián)用法建議參考官方的《[掌握ThinkPHP5.0數(shù)據(jù)庫和模型](http://www.imay365.com/thinkphp/master-database-and-model)》教程中的「[第八章:模型關(guān)聯(lián)](http://www.imay365.com/thinkphp/master-database-and-model/265559)」。
## 基本用法
關(guān)聯(lián)定義是第一步,如果沒法正確的完成關(guān)聯(lián)定義,你將不得其門而入。但其實關(guān)聯(lián)的定義方法本身很簡單,難點在于判斷你的業(yè)務操作應該使用什么關(guān)聯(lián)關(guān)系,而具體表之間的關(guān)系其實在做數(shù)據(jù)庫架構(gòu)設計的時候已經(jīng)明確了的。
關(guān)聯(lián)關(guān)系通常有一個參照模型,這個參照模型我們一般稱為主模型(或者當前模型),關(guān)聯(lián)關(guān)系對應的模型就是關(guān)聯(lián)模型,關(guān)聯(lián)關(guān)系是指定義在主模型中的關(guān)聯(lián)關(guān)系,有些關(guān)聯(lián)關(guān)系還會設計到一個中間表的概念,但中間表不一定需要存在具體的模型。關(guān)聯(lián)模型本身也是一個普通的獨立模型,具備模型的所有功能。兩個模型之間的關(guān)聯(lián)關(guān)系就是通過關(guān)聯(lián)方法來定義的。
目前已經(jīng)支持的關(guān)聯(lián)關(guān)系如下:
|模型方法|關(guān)聯(lián)類型|
|---|---|
|`hasOne`|一對一HAS ONE|
|`belongsTo`|一對一BELONGS TO|
|`hasMany`|一對多 HAS MANY|
|`hasManyThrough`|遠程一對多 HAS MANY THROUTH|
|`belongsToMany`|多對多 BELONGS TO MANY|
|`morphMany`|多態(tài)一對多 MORPH MANY|
|`morphTo`|多態(tài) MORPH TO|
>[info] 具體的關(guān)聯(lián)定義方法可以參考官方手冊,不是文本的重點,因此不做詳細說明。
以一個簡單的博客和分類表為例,每個分類下有多個博客,這就屬于一對多關(guān)聯(lián)(`hasMany`),每篇博客都屬于某個分類,這就是一對一關(guān)聯(lián)(`blongsTo`),博客模型的`cate_id`和分類模型的`id`屬性則是兩個模型關(guān)聯(lián)的紐帶和約束。
具體的關(guān)聯(lián)定義如下:
博客模型
```
<?php
namespace app\index\model;
use think\Model;
class Blog extends Model
{
/**
* 獲取博客所屬的分類
*/
public function cate()
{
return $this->belongsTo('Cate');
}
}
```
如果你的模型都在同一個命名空間下,直接使用模型名稱就行,如果不在同一個命名空間下,則需要給出完整的類名,例如:
```
<?php
namespace app\index\model;
use think\Model;
class Blog extends Model
{
/**
* 獲取博客所屬的分類
*/
public function cate()
{
return $this->belongsTo(\app\common\Cate::class);
}
}
```
分類模型
```
<?php
namespace app\index\model;
use think\Model;
class Cate extends Model
{
/**
* 獲取分類下的所有博客信息
*/
public function blogs()
{
return $this->hasMany('Blog');
}
}
```
當我們查詢到某個博客數(shù)據(jù)的時候,可以很簡單的通過模型關(guān)聯(lián)獲取到分類數(shù)據(jù)。
```
// 查詢博客數(shù)據(jù)
$blog = Blog::find(3);
// 獲取博客所屬分類模型
$cate = $blog->cate;
```
如果沒有使用模型關(guān)聯(lián)的設計,你必須使用下面的方法獲取分類數(shù)據(jù)。
```
// 查詢博客數(shù)據(jù)
$blog = Blog::find(3);
// 獲取博客所屬分類模型
$cate = Cate::where('id', $blog->cate_id)->find();
```
很明顯,模型關(guān)聯(lián)能夠簡化多個模型之間的查詢,關(guān)聯(lián)關(guān)系越是復雜,帶來的效果更明顯。
需要的時候關(guān)聯(lián)關(guān)系本身還可以多級嵌套,例如:
```
// 查詢博客數(shù)據(jù)
$blog = Blog::find(3);
// 獲取作者的檔案
$info = $blog->user->profile;
```
通過模型的屬性方式是獲取關(guān)聯(lián)模型數(shù)據(jù)的最簡單的一種方式,該方式會查詢滿足關(guān)聯(lián)條件的所有關(guān)聯(lián)數(shù)據(jù)。
對于一對多關(guān)聯(lián)而言,如果只需要獲取部分關(guān)聯(lián)數(shù)據(jù),就需要改成關(guān)聯(lián)方法的調(diào)用方式,下面一個例子給出了兩種方式代碼的區(qū)別。
```
$cate = Cate::find(1);
// 獲取當前分類下所有的博客
$blogs = $cate->blogs;
// 獲取當前分類的最新三條博客
$blogs = $cate->blogs()
->order('create_time', 'desc')
->limit(3)
->select();
```
至于具體的關(guān)聯(lián)方法怎么定義,可以參考官方手冊,已經(jīng)給出了明確的說明,就不再多說了,這里總結(jié)一些注意事項。
## 駝峰命名的關(guān)聯(lián)方法獲取
關(guān)于關(guān)聯(lián)定義有一個很多開發(fā)者容易混淆的地方,所有的關(guān)聯(lián)方法定義必須使用首字母小寫的駝峰命名規(guī)范,但在獲取關(guān)聯(lián)屬性的時候,則推薦使用小寫+下劃線方式的對應屬性名稱,但方法調(diào)用依然是保持駝峰命名。
舉例說明如下:
```
<?php
namespace app\index\model;
use think\Model;
class Cate extends Model
{
/**
* 獲取分類下的所有博客信息
*/
public function currentBlogs()
{
return $this->hasMany('Blog');
}
}
```
關(guān)聯(lián)查詢代碼
```
$cate = Cate::find(1);
// 獲取當前分類下所有的博客
$blogs = $cate->current_blogs;
// 獲取當前分類的最新三條博客
$blogs = $cate->currentBlogs()
->order('create_time', 'desc')
->limit(3)
->select();
```
## 一個關(guān)聯(lián)關(guān)系可以定義多個關(guān)聯(lián)方法
一個關(guān)聯(lián)關(guān)系并不代表只能定義一個關(guān)聯(lián)方法,我們可以為不同的查詢需求定義多個不同的關(guān)聯(lián)方法,以分類和博客的一對多關(guān)聯(lián)關(guān)系為例,我們可以這樣定義。
```
<?php
namespace app\index\model;
use think\Model;
class Cate extends Model
{
/**
* 獲取分類下的所有博客信息
*/
public function blogs()
{
return $this->hasMany('Blog');
}
/**
* 獲取分類下的最新三條博客信息
*/
public function lastThreeBlog()
{
return $this->hasMany('Blog')
->order('create_time', 'desc')
->limit(3);
}
}
```
>[info] 你可以在模型關(guān)聯(lián)方法中使用查詢構(gòu)造器完成對關(guān)聯(lián)數(shù)據(jù)的條件約束,`5.1`版本的話還可以支持在關(guān)聯(lián)方法中調(diào)用模型的自定義方法。
可以通過下面的代碼來調(diào)用不同的關(guān)聯(lián)數(shù)據(jù)。
```
$cate = Cate::find(1);
// 獲取當前分類下所有的博客
$blogs = $cate->blogs;
// 獲取當前分類的最新三條博客
$blogs = $cate->last_three_blog
```
## 關(guān)聯(lián)方法定義支持參數(shù)
還是上面的需求,我們希望獲取最近的N條博客數(shù)據(jù),但希望具體多少條在查詢的時候傳入,這需要我們首先給關(guān)聯(lián)方法增加一個參數(shù)。
```
/**
* 獲取分類下的最新N條博客信息
*/
public function lastBlog($number = 3)
{
return $this->hasMany('Blog')
->order('create_time', 'desc')
->limit($number);
}
```
查詢的時候使用
```
$cate = Cate::find(1);
// 獲取當前分類下最新的10條博客
$blogs = $cate->lastBlog(10)->select();
```
## 典型的`N+1`查詢問題
如果要查詢3個分類,以及每個分類的博客數(shù)據(jù),按照普通的關(guān)聯(lián)查詢就會產(chǎn)生3+1次查詢,隨著數(shù)據(jù)量的越來越大查詢次數(shù)會越來越多,性能也會急劇下降。
```
$cates = Cate::select([1,2,3]);
foreach($cates as $cate) {
$blogs = $cate->blogs;
}
```
如果查看頁面Trace信息可以看到當前產(chǎn)生了4次查詢操作。
預載入查詢就是為了解決這個`N+1`問題而應運而生的,我們只要把代碼改成如下:
```
$cates = Cate::with(['blogs'])->select([1,2,3]);
foreach($cates as $cate) {
$blogs = $cate->blogs;
}
```
你會發(fā)現(xiàn)最終產(chǎn)生的查詢次數(shù)為2次,事實上無論有多少分類數(shù)據(jù),最終的查詢次數(shù)都是2次,很好的解決了查詢性能問題。
>[info] `with`方法支持同時指定多個關(guān)聯(lián)方法,因此使用數(shù)組參數(shù)是更好的規(guī)范。
## 預載入查詢的數(shù)據(jù)篩選
預載入查詢的數(shù)據(jù)篩選有兩種方式,第一種方式前面我們已經(jīng)介紹過了,就是為該關(guān)聯(lián)關(guān)系增加額外的方法定義,然后在方法里面進行查詢條件的篩選,這種對于有明確的篩選需求比較有效,查詢代碼也比較簡單。
```
$cates = Cate::with(['last_three_blog'])
->select([1,2,3]);
```
>[info] `with`方法中的關(guān)聯(lián)方法名可以是實際的關(guān)聯(lián)定義方法名,也可以是關(guān)聯(lián)方法名的小寫+下劃線轉(zhuǎn)換名。
第二種方式就是在預載入查詢的時候通過閉包動態(tài)指定查詢條件。
```
// 給預載入查詢指定篩選條件
$cates = Cate::with(['blogs' => function($query){
$query->order('create_time', 'desc')->limit(3);
}])->select([1,2,3]);
```
## 延遲預載入
關(guān)聯(lián)預載入查詢并非是惰性的,無論最后數(shù)據(jù)是否需要使用,查詢已經(jīng)產(chǎn)生了,有些情況下,需要根據(jù)查詢出來的數(shù)據(jù)來決定是否需要使用關(guān)聯(lián)預載入,使用延遲預載入可以實現(xiàn)關(guān)聯(lián)數(shù)據(jù)的惰性查詢,有效提高性能,避免浪費不必要的查詢。
```
$cates = Cate::select([1,2,3]);
// 使用延遲預載入查詢關(guān)聯(lián)數(shù)據(jù)
$cates->load('blogs');
foreach($cates as $cate) {
$blogs = $cate->blogs;
}
```
## 關(guān)聯(lián)統(tǒng)計
經(jīng)常會有一些對關(guān)聯(lián)數(shù)據(jù)進行聚合統(tǒng)計的需求,框架提供了便捷的關(guān)聯(lián)統(tǒng)計方法。
```
$cates = Cate::withCount(['blogs'])
->select([1,2,3]);
foreach($cates as $cate) {
// 獲取分類下的博客總數(shù)
echo $cate->blogs_count;
}
```
如果要改變默認的統(tǒng)計字段名稱,可以改成
```
$cates = Cate::withCount(['blogs' => 'blog_count'])
->select([1,2,3]);
foreach($cates as $cate) {
// 獲取分類下的博客總數(shù)
echo $cate->blog_count;
}
```
也支持使用閉包方式進行統(tǒng)計查詢的條件限制
```
$cates = Cate::withCount(['blogs' => function($query) {
$query->where('status', 1);
}])->select([1,2,3]);
foreach($cates as $cate) {
// 獲取分類下的博客總數(shù)
echo $cate->blogs_count;
}
```
除了`count`統(tǒng)計外,還支持`sum/max/min/avg`等聚合統(tǒng)計。
```
$cates = Cate::withSum(['blogs' => 'total_read'],'read_count')
->select([1,2,3]);
foreach($cates as $cate) {
// 獲取分類下的博客閱讀總數(shù)
echo $cate->total_read;
}
```
>[info] 關(guān)聯(lián)統(tǒng)計查詢用的是子查詢方式,所以并不會增加額外的查詢次數(shù)。
## 關(guān)聯(lián)數(shù)據(jù)輸出
使用了關(guān)聯(lián)查詢后,依然可以使用`hidden`/`visible`/`append`方法進行模型數(shù)據(jù)的輸出調(diào)整。
```
$blog = Blog::with('cate')->find(1);
// 隱藏分類的部分屬性
$blog->hidden(['cate' => ['remark', 'create_time', 'update_time']])
->toArray();
```
## 根據(jù)關(guān)聯(lián)條件查詢
```
// 查詢博客超過10個的分類
$cates = Cate::has('blogs','>',10)->select();
// 查詢最近3天發(fā)過博客的分類
$cates = Cate::hasWhere('blogs', function($query) {
$query->whereTime('create_time', '-3 days');
})->select();
```
閉包里面的查詢條件是關(guān)聯(lián)模型的約束,如果你需要添加主模型的額外約束條件,可以單獨追加使用`where`方法或者其它的查詢構(gòu)造器,不過要注意由于`hasWhere`方法使用的是`JOIN`查詢,在查詢條件中要指定別名。
```
// 查詢最近3天發(fā)過博客并且狀態(tài)正常的分類 按name排序
$cates = Cate::hasWhere('blogs', function($query) {
$query->whereTime('create_time', '-3 days');
})->where('Cate.status', 1)
->order('Cate.name')
->select();
```
## 自關(guān)聯(lián)
如果你的模型關(guān)聯(lián)到自身(例如子分類和分類的關(guān)聯(lián)關(guān)系),就需要在定義關(guān)聯(lián)的時候設置自關(guān)聯(lián)。
```
<?php
namespace app\index\model;
use think\Model;
class Cate extends Model
{
/**
* 獲取分類下的所有博客信息
*/
public function blogs()
{
return $this->hasMany('Blog');
}
/**
* 獲取當前分類的子分類
*/
public function sub()
{
return $this->hasMany('Cate', 'parent_id')
->selfRelation();
}
}
```
關(guān)聯(lián)查詢代碼
```
$cate = Cate::with('sub')->find(1);
```
- 值得升級到5.1的18個理由
- 5.1.7版本新特性
- JSON字段類型在ORM中的使用
- 文件下載響應對象
- 教你使用5.1的數(shù)組對象查詢
- 模型三大利器之一:搜索器
- 在ThinkPHP中使用Yaconf
- 掌握命令行的表格輸出
- 5.1.25查詢參數(shù)綁定的改進
- ThinkPHP安全規(guī)范指引
- 巧用數(shù)據(jù)集的排序功能實現(xiàn)統(tǒng)計排序
- think-orm ——基于5.1的獨立ORM庫
- think-template——基于ThinkPHP的獨立模板引擎
- ThinkPHP5.1.26版本發(fā)布——修正版本,包含安全更新
- ThinkPHP5.0和3.2再發(fā)安全更新
- 官宣:ThinkPHP發(fā)布首個LTS版本
- 你真的了解Db類和模型的正確使用姿勢么?
- 如何更有效的記錄和管理日志
- 模型三大利器之二:修改器
- ThinkPHP5.1.28版本發(fā)布——修正上一版本問題,改進關(guān)聯(lián)查詢
- 模型三大利器之三:獲取器
- API版本控制的幾種思路
- ThinkPHP5.2第一個Beta版本發(fā)布測試
- 讓你少犯錯的數(shù)據(jù)查詢基本原則
- ThinkPHP發(fā)布5.1.29版本——常規(guī)更新
- 這15個好習慣讓你更容易升級到5.2
- 如何有效提高ThinkPHP的應用性能
- 讓你提高開發(fā)效率的查詢技巧
- 模型關(guān)聯(lián)查詢不完全指南
- 5.2發(fā)布Beta2版本——統(tǒng)一和精簡大量用法
- ThinkPHP發(fā)布5.1.30版本——支持微秒時間字段寫入
- ThinkPHP的數(shù)據(jù)緩存使用
- ThinkPHP5.2安裝及入口文件
- ThinkPHP榮獲2018 年度最受歡迎中國開源開發(fā)框架第1名
- 5.1路由使用心得技巧
- ThinkPHP5.*版本發(fā)布安全更新
- ThinkPHP項目及代碼規(guī)范指北
- 5.2版本的設計規(guī)范指導
- ThinkPHP5.1.32版本發(fā)布——圣誕快樂
- 利用Trait特性給模型增加樂觀鎖功能
- 5.2數(shù)據(jù)庫和模型的變化(摘要)
- ThinkPHP模板引擎實現(xiàn)和常見問題
- ThinkPHP5.0.24版本發(fā)布——安全更新
- 不忘初心,方得始終——ThinkPHP十三周年報告
- ThinkPHP5+相關(guān)資源匯總
- 異步社區(qū)ThinkPHP周年慶專享優(yōu)惠活動
- 5.2路由的調(diào)整和改進
- ThinkPHP發(fā)布5.1.33版本——包含安全更新
- ThinkPHP擴展開發(fā)指南
- ThinkPHP發(fā)布5.2Beta3版本
- ThinkPHP發(fā)布5.1.34版本——喜迎新年
- ThinkPHP發(fā)布5.2RC1版本
- ThinkPHP發(fā)布5.1.35版本——常規(guī)更新
- 5.2配置類的調(diào)整
- 5.2時間查詢的改進和優(yōu)化
- 5.2RC版本升級不完全指導(僅供學習參考)
- ThinkPHP5.2版本正式變更為6.0版本
- ThinkPHP百度云云虛擬主機專享免費活動
- 事件系統(tǒng)以及查詢事件、模型事件的使用
- ThinkPHP6.0RC2版本發(fā)布——架構(gòu)升級、精簡核心
- ThinkPHP5.1.36LTS版本發(fā)布——常規(guī)更新
- 新版Session和Cookie設計變化
- ThinkPHP5.1.37版本發(fā)布——常規(guī)更新
- ThinkPHP6.0RC3版本發(fā)布——細節(jié)完善,體驗優(yōu)化
- 6.0中間件使用詳解
- Composer各大廠商鏡像地址
- ThinkPHP6.0發(fā)布計劃公告
- 「ThinkPHP開發(fā)者周刊」招募志愿者
- ThinkPHP6.0日志變化
- ThinkPHP5.1.38版本發(fā)布——常規(guī)更新
- ThinkPHP6.0RC4版本發(fā)布——ORM獨立,日志多通道支持
- ThinkORM2.0開發(fā)指南上線
- ThinkPHP6.0RC5版本發(fā)布——多應用模式獨立,中間件機制調(diào)整
- ThinkPHP6.0版本發(fā)布——程序員節(jié)福利
- ThinkPHP5.1.39LTS版本發(fā)布——常規(guī)更新
- ThinkPHP6.0.1版本發(fā)布——圣誕快樂!
- 回顧2019,展望2020!
- ThinkPHPV6.0.2版本發(fā)布——2020新春快樂!
- 周年福利系列:Swoole合作優(yōu)惠
- 億速云成為ThinkPHPV6.0獨家贊助發(fā)布商??
- 新冠疫情工具和限免資源專題(保持更新中)
- 周年福利系列:創(chuàng)宇信用認證合作優(yōu)惠
- 周年福利系列:碼云企業(yè)版限時10%優(yōu)惠
- 周年福利系列:想天短說抵現(xiàn)優(yōu)惠
- think-swoole直播:從零開始掌握swoole開發(fā)
- 周年福利系列:B2C開源電商ShopXO授權(quán)8折優(yōu)惠
- 周年福利系列:LayuiAdmin 永久授權(quán)限時優(yōu)惠
- ThinkPHP資源導航站上線——構(gòu)建生態(tài) 服務未來
- ThinkPHP官方技術(shù)支持服務和應用服務市場上線公測
- ThinkPHP市場精選——推廣基本要素
- ThinkPHP市場精選——客服聊天專題
- ThinkPHPV6.0.3版本發(fā)布——端午安康
- ThinkPHP開發(fā)者扶持計劃
- 6.0.3版本關(guān)鍵更新及升級事項
- 「ThinkPHP開發(fā)者周刊」改版重啟
- ThinkPHP市場精選——企業(yè)建站專題
- ThinkPHP 提供統(tǒng)一API接口服務
- ThinkPHP市場精選——直播電商專題
- ThinkAPI服務SDK發(fā)布
- 官方服務市場啟用獨立子域名
- ThinkPHP市場精選——刷臉支付專題
- ThinkAPI推出會員服務計劃
- ThinkPHPV6.0.4版本發(fā)布——中秋國慶雙節(jié)快樂
- ThinkPHPV5.1.40版本發(fā)布——常規(guī)更新
- 1024程序員節(jié)福利走一波
- ThinkPHP V6.0.5版本發(fā)布——兼容Composer2.0
- 知識圖譜應用場景——源論技術(shù)沙龍
- ThinkPHP5.*版本改進Composer2.0的兼容
- 官方市場雙十一精選推薦
- 技術(shù)人做產(chǎn)品有機會么(文末送課程)
- 本周秒殺——古德云售后獲客營銷系統(tǒng)
- ThinkAPI服務更新——支持接口分組和PHP版本依賴調(diào)整
- PHP8新特性盤點
- PHP8新特性系列:構(gòu)造器屬性提升使用及注意事項
- ThinkPHP2021新年寄語
- ThinkPHP V6.0.6&V5.1.41版本發(fā)布——兼容PHP8.0
- PHP如何更優(yōu)雅地調(diào)用API接口
- ThinkPHP V6.0.7發(fā)布——修正版本
- ThinkAPI服務更新——IP白名單
- 最新版ThinkORM對于時間字段的調(diào)整
- ThinkAPI短信接口正式上線
- ThinkPHP V6.0.8版本發(fā)布——多環(huán)境變量配置支持
- 頂想云寫作服務開啟第一次公測
- ThinkSSL上線——官方SSL/TLS證書服務
- MDBootstrap國內(nèi)用戶福利——ThinkPHP官方市場首發(fā)
- ThinkPHP V6.0.9版本發(fā)布——常規(guī)更新
- ThinkORM功能盤點——虛擬模型
- 全面支持主流GIT版本庫——云寫作服務第二次公測
- 云寫作服務私有化部署方案之:版本庫私有化
- 看云雙十一活動
- ThinkPHP V6.0.10LTS發(fā)布——兼容PHP8.1
- ThinkPHP V6.0.12發(fā)布——命令行兼容8.1
- 頂想云知識管理上線公測——構(gòu)建企業(yè)文檔中心和知識庫
- 頂想云上線——助力生態(tài)數(shù)字化建設
- 618活動進行中——官方市場迎來一波更新
- 頂想云知識管理正式上線——看云文檔啟動遷移服務
- ThinkPHP V6.0.13發(fā)布——常規(guī)更新
- 頂想云網(wǎng)站助理服務上線——構(gòu)建產(chǎn)品支持服務
- ThinkPHP發(fā)布6.1.0&6.0.14版本——安全更新
- ThinkPHP新版社區(qū)上線試運營
- ThinkAPI上架人臉核身接口——助力網(wǎng)站實名認證
- 辭舊迎新——舊版社區(qū)停止注冊及發(fā)帖
- ThinkPHP6.1.2版本發(fā)布——兼容PHP8.2
