參考文章
以下所用到的實例,包含在https://github.com/archer-wong/laravel-orm-relationships工程中,方便使用。
你可在 Eloquent 模型類內將 Eloquent 關聯定義為函數。因為關聯像 Eloquent 模型一樣也可以作為強大的
查詢語句構造器,定義關聯為函數提供了強而有力的鏈式調用及查找功能。
1 一對一關系
1.1 表A和表B的記錄一一對應,比如一個用戶對應一個社交賬號


1.2 定義模型User(位於app/Models文件夾下),並在其中定義與UserAccount的一對一對應關系:
public function getAccount() { return $this->hasOne('App\Models\UserAccounts'); }
注意:hasOne()方法的3種不同的使用情況: Eloquent 默認關聯關系的外鍵
基於模型名稱。默認是:UserAccount模型的主鍵是id,關聯的外鍵是user_id
1> hasOne('App\Models\UserAccounts') - 默認情況
2> hasOne('App\Models\UserAccounts', 'foreign_key') -UserAccount模型的主鍵是id,但是外鍵不是 user_id
3> hasOne('App\Models\UserAccounts', 'foreign_key', 'local_key') - UserAccount模型的主鍵不是id,外鍵也不是user_id
第一個參數是關聯模型的類名稱,
第二個參數,Eloquent 會假設對應關聯的外鍵名稱是基於模型名稱的,會取用自身模型的「蛇形命名」后的名稱,並在后方加上 _id,所以定義模型時候要注意,模型的名稱加上'_id'的組合作為外鍵,我們這里外鍵=>'user'+'_id',也就是$foreign_key = 'user_id'
第三個參數,Eloquent 的默認外鍵在上層模型的 id 字段會有個對應值。
1.3 我們也可以在UserAccount模型中定義與User的一對一關系:
public function user() { return $this->belongsTo('App\Models\User'); }
注意:belongsTo()方法的3種不同的使用情況: Eloquent 默認關聯關系的外鍵
基於關聯方法名稱。默認是:UserAccount模型的主鍵是id,關聯的外鍵是user_id
1> belongsTo('App\Models\User') - 默認情況
2> belongsTo('App\Models\User', 'foreign_key') -UserAccount模型的主鍵是id,但是外鍵不是 user_id
3> belongsTo('App\Models\User', 'foreign_key', 'local_key') - UserAccount模型的主鍵不是id,外鍵也不是user_id
默認情況下,Eloquent將調用belongsTo的關聯方法名user作為關聯關系$relation的值,並將$relation.'_id'作為默認外鍵名對應users表的id,如果表中沒有相應列,又沒有在定義關聯關系的時候指定具體的外鍵,就會報錯。
1.4 控制器中的調用
public function oneToOne(){
$user_account = User::find(1)->getAccount;
$user = UserAccount::find(1)->user;
dd($user_account, $user);
}
調用:
關聯關系被定義后,可以使用 Eloquent 的 '動態屬性' 來獲取關聯關系!
注意:
動態屬性:允許我們訪問關聯函數,就像它們是定義在模型上的屬性一樣!
理解 '動態屬性' 的概念:按理說,我們定義了 getAccount() 方法,應該調用的是一個方法,而這里將其作為了一個 '屬性' 來調用!
1.5 總結:不管是User模型類,還是UserAccount模型類,2者都是以 'User' 模型為主。UserAccount模型還是附屬於User模型。UserAccount模型具有外鍵 'user_id' ,但是要注意hasOne()方法的外鍵基於模型名稱,belongsTo()方法的外鍵基於關聯方法名稱的。
調用結果:

2 一對多關系
2,1 表A的某條記錄對應表B的多條記錄,反之表B的某條記錄歸屬於表A的某條記錄,比如一個用戶發表多篇文章

2.2 我們在用戶模型User中定義與文章模型Post的一對多關系如下:
public function getPosts() { return $this->hasMany('App\Models\Post'); }
當然,因為所有的關聯也都提供了查詢語句構造器的功能,因此你可以對獲取到的評論進一步增加條件,通過調用 posts()方法然后在該方法后面鏈式調用查詢條件:
$posts = User::find(1)->getPosts()->where('title','vitae')->get(); dd($posts);
需要注意的是這里我們調用的是getPosts()方法,而不是動態屬性getPosts,當然使用getPosts動態屬性也可以支持鏈式調用。
2.3 同樣,我們可以在文章模型Post中定義文章所屬用戶模型User的對應關系:
//1> 最基本寫法
public function user() { return $this->belongsTo('App\Models\User'); }
//2> 當定義的方法名不是user的時候,傳入了額外參數,意為posts表中的user_id對應users表中的id
public function author() { return $this->belongsTo('App\Models\User', 'user_id', 'id'); }
2.4 控制器中調用
public function oneToMany(){
$post = User::find(1)->getPosts;
//如果我們想增加更多的附加條件,可以使用posts()方法,這樣仍然支持鏈式。
$post2 = User::find(1)->getPosts()->where('title','vitae')->get();
//區分user和user2的區別
$user = Post::find(1)->user;
$user2 = Post::find(1)->user();
//注意author方法的話,需要增加鍵的限制
$author = Post::find(1)->author;
dd($post, $post2, $user, $user2, $author);
}
2.4 總結
寫法和一對一關系中非常相像。

3 多對多關系
3.1 表A的某條記錄通過中間表C與表B的多條記錄關聯,反之亦然。比如一個用戶有多種角色,反之一個角色對應多個用戶。


提示:定義中間表的時候沒有在結尾加s並且命名規則是按照字母表順序,將role放在前面,user放在后面,並且用_分隔,在定義多對多關聯的時候如果沒有指定中間表,Eloquent默認的中間表使用這種規則拼接出來,比如模型1 user,模型2 role 那么表名就是role_user,Eloquent默認的中間表使用這種規則拼接出來。
3.2 我們在模型User中定義多對多關聯如下:
public function getRoles() { return $this->belongsToMany('App\Models\Role'); }
注意:
完整寫法:
return $this->belongsToMany('App\Models\Role', 'user_roles', 'user_id', 'role_id');
Eloquent 會合並兩個關聯模型的名稱並依照字母順序命名。當然你也可以隨意重寫這個約定。可通過傳遞第二個參數至 belongsToMany 方法來實現:
return $this->belongsToMany('App\Models\Role', 'user_roles');
除了自定義合並數據表的名稱,你也可以通過傳遞額外參數至 belongsToMany 方法來自定義數據表里的鍵的字段名稱。第三個參數是你定義在關聯中的模型外鍵名稱,而第四個參數則是你要合並的模型外鍵名稱:
3.3 相對的我們也可以在模型Role中定義獲取對應User模型的方法:
public function getUsers() { return $this->belongsToMany('App\Models\Users'); }
3.4 此外我們還可以通過動態屬性pivot獲取中間表字段:
$roles = Users::find(1)->roles; foreach ($roles as $role) { echo $role->pivot->role_id.'<br>'; }
注意:我們取出的每個 Role 模型對象,都會被自動賦予 pivot 屬性。此屬性代表中間表的模型,它可以像其它的 Eloquent 模型一樣被使用。
默認情況下,pivot 對象只提供模型的鍵。
如果你的 pivot 數據表包含了其它的屬性,則可以在定義關聯方法時指定那些字段:
return $this->belongsToMany('App\Role')->withPivot('column1', 'column2');
如果你想要中間表自動維護 created_at 和 updated_at 時間戳,可在定義關聯方法時加上 withTimestamps 方法:
return $this->belongsToMany('App\Role')->withTimestamps();
3.5 控制器中調用:
public function manyToMany(){
$roles = User::find(1)->getRoles;
$users = Role::find(1)->getUsers;
//通過動態屬性pivot獲取中間表字段
foreach ($roles as $role) {
$pivot = $role->pivot->role_id;
echo $pivot . '<br>';
}
dd($roles, $users);
}

4 遠層一對多
4.1 所謂的“遠層一對多”指的是通過一個中間關聯對象訪問遠層的關聯關系,比如國家與用戶之間存在一對多關系,用戶與文章之間也存在一對多關系,那么通過用戶可以建立國家與文章的之間的一對多關聯關系,我們稱之為“遠層一對多”。

4.2 我們創建一個模型Country,並在其中定義國家與文章的遠層一對多關系如下:
public function getPostsThroughUser() { return $this->hasManyThrough('App\Models\Post','App\Models\User'); }
其中第一個參數是關聯對象類名,第二個參數是中間對象類名。
當運行關聯查找時,通常會使用 Eloquent 的外鍵約定。如果你想要自定義關聯的鍵,則可以將它們傳遞至 hasManyThrough 方法的第三與第四個參數。第三個參數為中間模型的外鍵名稱,而第四個參數為最終模型的外鍵名稱.
$this->hasManyThrough('App\Models\Post','App\Models\User',$country_id,$user_id);
4.3 接下來我們在控制器中定義測試代碼如下:
public function hasManyThrough(){
$country = Country::find(1);
$posts = $country->getPostsThroughUser;
echo 'Country#'.$country->name.'下的文章:<br>';
foreach($posts as $post){
echo '<<'.$post->title.'>><br>';
}
}
頁面會輸出:該國家下對應的所有文章
Country#China下的文章:
<<vitae>>
<<officiis>>
<<hic>>
<<quam>>
<<veritatis>>
5 多態關聯
5.1 多態關聯允許一個模型在單個關聯下屬於多個不同父模型。比方,某一條評論可能歸屬於某篇文章,也可能歸屬於某個視頻。我們可以在評論表中添加一個item_id字段表示其歸屬節點ID,同時定義一個item_type字段表示其歸屬節點類型。


5.2 分別定義三個模型
在Post和Video模型類中定義關聯評論:
public function comments() { return $this->morphMany('App\Models\Comment','item'); }
同樣也可以在Comment模型中定義相對的關聯關系獲取其所屬節點:
public function item() { return $this->morphTo(); }
//擴展,當不使用item作為方法名,可以傳入自定義參數
public function getItem()
{
return $this->morphTo('item', 'item_type', 'item_id');
}
完整寫法:
1> $this->morphMany('App\Models\Comment',$item,$item_type,$item_id,$id);
其中第一個參數是關聯模型類名,第二個參數是關聯名稱,即$item_id和$item_type中的$item部分,最后一個參數是posts/videos表的主鍵。
2> $this->morphTo($item,$item_type,$item_id); 如果$item部分不等於item可以自定義傳入參數到morphTo:
5.3 控制器中調用:
public function polymorphicRelations(){
$video = Video::find(1);
$videoComments = $video->comments;
$comment = Comment::find(1);
$item = $comment->item;
dd($videoComments, $item);
}
輸出結果如下

6 多態多對多關聯
6.1 最常見的應用場景就是標簽,比如一篇文章對應多個標簽,一個視頻也對應多個標簽,同時一個標簽可能對應多篇文章或多個視頻,


6.2 定義模型方法
在Post/Video中定義關聯關系如下:
public function tags() { return $this->morphToMany('App\Models\Tag','taggable'); }
在Tag中定義相對的關聯關系如下:
public function posts() { return $this->morphedByMany('App\Models\Post','taggable'); } public function videos() { return $this->morphedByMany('App\Models\Video','taggable'); }
完整寫法:
1> $this->morphToMany('App\Models\Tag','taggable','taggable','taggable_id','tag_id',false);
其中第一個參數是關聯模型類名,第二個參數是關聯關系名稱,其中第三個參數是對應關系表名,最后一個值若為true,則查詢的是關聯對象本身,若為false,查詢的是關聯對象與父模型的對應關系。
2> $this->morphedByMany('App\Models\Video','taggable','taggable','tag_id','taggable_id');
其中第一個參數是關聯對象類名,第二個參數是關聯關系名稱,其中第三個參數是對應關系表名。
6.3 控制器中調用
public function manyToManyPolymorphicRelations(){
$post = Post::find(1);
$tags = $post->tags;
$tag = Tag::find(1);
$posts = $tag->posts;
dd($tags, $posts);
}
結果:
