數(shù)據(jù)表經(jīng)常要與其它表做關(guān)聯(lián),比如一篇博客文章可能有很多評(píng)論,或者一個(gè)訂單會(huì)被關(guān)聯(lián)到下單用戶,Eloquent變得簡(jiǎn)單,并且支持多種不同類型的關(guān)聯(lián)關(guān)系:
Eloquent關(guān)聯(lián)關(guān)系以Eloquent模型類方法的形式被定義。和Eloquent模型本身一樣,關(guān)聯(lián)關(guān)系也是強(qiáng)大的查詢構(gòu)建器,定義關(guān)聯(lián)關(guān)系為函數(shù)能夠提供功能強(qiáng)大的方法鏈和查詢能力。例如:
$user->posts()->where('active', 1)->get();
但是,在深入使用關(guān)聯(lián)關(guān)系之前,讓我們先學(xué)習(xí)如何定義每種關(guān)聯(lián)類型:
一對(duì)一關(guān)聯(lián)是一個(gè)非常簡(jiǎn)單的關(guān)聯(lián)關(guān)系,例如,一個(gè)User
模型有一個(gè)與之對(duì)應(yīng)的Phone
模型。要定義這種模型,我們需要將phone
方法置于User
模型中,phone
方法應(yīng)該返回Eloquent模型基類上hasOne
方法的結(jié)果:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model{
/**
* 獲取關(guān)聯(lián)到用戶的手機(jī)
*/
public function phone()
{
return $this->hasOne('App\Phone');
}
}
傳遞給hasOne
方法的第一個(gè)參數(shù)是關(guān)聯(lián)模型的名稱,關(guān)聯(lián)關(guān)系被定義后,我們可以使用Eloquent的動(dòng)態(tài)屬性獲取關(guān)聯(lián)記錄。動(dòng)態(tài)屬性允許我們?cè)L問(wèn)關(guān)聯(lián)函數(shù)就像它們是定義在模型上的屬性一樣:
$phone = User::find(1)->phone;
Eloquent默認(rèn)關(guān)聯(lián)關(guān)系的外鍵基于模型名稱,在本例中,Phone
模型默認(rèn)有一個(gè)user_id
外鍵,如果你希望重寫這種約定,可以傳遞第二個(gè)參數(shù)到hasOne
方法:
return $this->hasOne('App\Phone', 'foreign_key');
此外,Eloquent假設(shè)外鍵應(yīng)該在父級(jí)上有一個(gè)與之匹配的id
,換句話說(shuō),Eloquent將會(huì)通過(guò)user
表的id
值去phone
表中查詢user_id
與之匹配的Phone
記錄。如果你想要關(guān)聯(lián)關(guān)系使用其他值而不是id
,可以傳遞第三個(gè)參數(shù)到hasOne
來(lái)指定自定義的主鍵:
return $this->hasOne('App\Phone', 'foreign_key', 'local_key');
我們可以從User
中訪問(wèn)Phone
模型,相應(yīng)的,我們也可以在Phone
模型中定義關(guān)聯(lián)關(guān)系從而讓我們可以擁有該phone
的User
。我們可以使用belongsTo
方法定義與hasOne
關(guān)聯(lián)關(guān)系相對(duì)的關(guān)聯(lián):
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Phone extends Model{
/**
* 獲取手機(jī)對(duì)應(yīng)的用戶
*/
public function user()
{
return $this->belongsTo('App\User');
}
}
在上面的例子中,Eloquent將會(huì)嘗試通過(guò)Phone
模型的user_id
去User
模型查找與之匹配的記錄。Eloquent通過(guò)關(guān)聯(lián)關(guān)系方法名并在方法名后加_id
后綴來(lái)生成默認(rèn)的外鍵名。然而,如果Phone
模型上的外鍵不是user_id
,也可以將自定義的鍵名作為第二個(gè)參數(shù)傳遞到belongsTo
方法:
/**
* 獲取手機(jī)對(duì)應(yīng)的用戶
*/
public function user(){
return $this->belongsTo('App\User', 'foreign_key');
}
如果父模型不使用id
作為主鍵,或者你希望使用別的列來(lái)連接子模型,可以將父表自定義鍵作為第三個(gè)參數(shù)傳遞給belongsTo
方法:
/**
* 獲取手機(jī)對(duì)應(yīng)的用戶
*/
public function user(){
return $this->belongsTo('App\User', 'foreign_key', 'other_key');
}
“一對(duì)多”是用于定義單個(gè)模型擁有多個(gè)其它模型的關(guān)聯(lián)關(guān)系。例如,一篇博客文章?lián)碛袩o(wú)數(shù)評(píng)論,和其他關(guān)聯(lián)關(guān)系一樣,一對(duì)多關(guān)聯(lián)通過(guò)在Eloquent模型中定義方法來(lái)定義:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model{
/**
* 獲取博客文章的評(píng)論
*/
public function comments()
{
return $this->hasMany('App\Comment');
}
}
記住,Eloquent會(huì)自動(dòng)判斷Comment
模型的外鍵,為方便起見(jiàn),Eloquent將擁有者模型名稱加上id
后綴作為外鍵。因此,在本例中,Eloquent假設(shè)Comment
模型上的外鍵是post_id
。
關(guān)聯(lián)關(guān)系被定義后,我們就可以通過(guò)訪問(wèn)comments
屬性來(lái)訪問(wèn)評(píng)論集合。記住,由于Eloquent提供“動(dòng)態(tài)屬性”,我們可以像訪問(wèn)模型的屬性一樣訪問(wèn)關(guān)聯(lián)方法:
$comments = App\Post::find(1)->comments;
foreach ($comments as $comment) {
//
}
當(dāng)然,由于所有關(guān)聯(lián)同時(shí)也是查詢構(gòu)建器,我們可以添加更多的條件約束到通過(guò)調(diào)用comments
方法獲取到的評(píng)論上:
$comments = App\Post::find(1)->comments()->where('title', 'foo')->first();
和hasOne
方法一樣,你還可以通過(guò)傳遞額外參數(shù)到hasMany
方法來(lái)重新設(shè)置外鍵和本地主鍵:
return $this->hasMany('App\Comment', 'foreign_key');
return $this->hasMany('App\Comment', 'foreign_key', 'local_key');
現(xiàn)在我們可以訪問(wèn)文章的所有評(píng)論了,接下來(lái)讓我們定義一個(gè)關(guān)聯(lián)關(guān)系允許通過(guò)評(píng)論訪問(wèn)所屬文章。要定義與hasMany
相對(duì)的關(guān)聯(lián)關(guān)系,需要在子模型中定義一個(gè)關(guān)聯(lián)方法去調(diào)用belongsTo
方法:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model{
/**
* 獲取評(píng)論對(duì)應(yīng)的博客文章
*/
public function post()
{
return $this->belongsTo('App\Post');
}
}
關(guān)聯(lián)關(guān)系定義好之后,我們可以通過(guò)訪問(wèn)動(dòng)態(tài)屬性post
來(lái)獲取一條Comment
對(duì)應(yīng)的Post
:
$comment = App\Comment::find(1);
echo $comment->post->title;
在上面這個(gè)例子中,Eloquent嘗試匹配Comment
模型的post_id
與Post
模型的id
,Eloquent通過(guò)關(guān)聯(lián)方法名加上_id
后綴生成默認(rèn)外鍵,當(dāng)然,你也可以通過(guò)傳遞自定義外鍵名作為第二個(gè)參數(shù)傳遞到belongsTo
方法,如果你的外鍵不是post_id
,或者你想自定義的話:
/**
* 獲取評(píng)論對(duì)應(yīng)的博客文章
*/
public function post(){
return $this->belongsTo('App\Post', 'foreign_key');
}
如果你的父模型不使用id
作為主鍵,或者你希望通過(guò)其他列來(lái)連接子模型,可以將自定義鍵名作為第三個(gè)參數(shù)傳遞給belongsTo
方法:
/**
* 獲取評(píng)論對(duì)應(yīng)的博客文章
*/
public function post(){
return $this->belongsTo('App\Post', 'foreign_key', 'other_key');
}
多對(duì)多關(guān)系比hasOne
和hasMany
關(guān)聯(lián)關(guān)系要稍微復(fù)雜一些。這種關(guān)聯(lián)關(guān)系的一個(gè)例子就是一個(gè)用戶有多個(gè)角色,同時(shí)一個(gè)角色被多個(gè)用戶共用。例如,很多用戶可能都有一個(gè)“Admin”角色。要定義這樣的關(guān)聯(lián)關(guān)系,需要三個(gè)數(shù)據(jù)表:users
、roles
和role_user
,role_user
表按照關(guān)聯(lián)模型名的字母順序命名,并且包含user_id
和role_id
兩個(gè)列。
多對(duì)多關(guān)聯(lián)通過(guò)編寫一個(gè)調(diào)用Eloquent基類上的belongsToMany
方法的函數(shù)來(lái)定義:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model{
/**
* 用戶角色
*/
public function roles()
{
return $this->belongsToMany('App\Role');
}
}
關(guān)聯(lián)關(guān)系被定義之后,可以使用動(dòng)態(tài)屬性roles
來(lái)訪問(wèn)用戶的角色:
$user = App\User::find(1);
foreach ($user->roles as $role) {
//
}
當(dāng)然,和所有其它關(guān)聯(lián)關(guān)系類型一樣,你可以調(diào)用roles
方法來(lái)添加條件約束到關(guān)聯(lián)查詢上:
$roles = App\User::find(1)->roles()->orderBy('name')->get();
正如前面所提到的,為了決定關(guān)聯(lián)關(guān)系連接表的表名,Eloquent以字母順序連接兩個(gè)關(guān)聯(lián)模型的名字。然而,你可以重寫這種約定——通過(guò)傳遞第二個(gè)參數(shù)到belongsToMany
方法:
return $this->belongsToMany('App\Role', 'user_roles');
除了自定義連接表的表名,你還可以通過(guò)傳遞額外參數(shù)到belongsToMany
方法來(lái)自定義該表中字段的列名。第三個(gè)參數(shù)是你定義的關(guān)系模型的外鍵名稱,第四個(gè)參數(shù)你要連接到的模型的外鍵名稱:
return $this->belongsToMany('App\Role', 'user_roles', 'user_id', 'role_id');
要定義與多對(duì)多關(guān)聯(lián)相對(duì)的關(guān)聯(lián)關(guān)系,只需在關(guān)聯(lián)模型中在調(diào)用一下belongsToMany
方法即可。讓我們?cè)?code>Role模型中定義users
方法:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Role extends Model{
/**
* 角色用戶
*/
public function users()
{
return $this->belongsToMany('App\User');
}
}
正如你所看到的,定義的關(guān)聯(lián)關(guān)系和與其對(duì)應(yīng)的User
中定義的一模一樣,只是前者引用App\Role
,后者引用App\User
,由于我們?cè)俅问褂昧?code>belongsToMany方法,所有的常用表和鍵自定義選項(xiàng)在定義與多對(duì)多相對(duì)的關(guān)聯(lián)關(guān)系時(shí)都是可用的。
正如你已經(jīng)學(xué)習(xí)到的,處理多對(duì)多關(guān)聯(lián)要求一個(gè)中間表。Eloquent提供了一些有用的方法來(lái)與其進(jìn)行交互,例如,我們假設(shè)User
對(duì)象有很多與之關(guān)聯(lián)的Role
對(duì)象,訪問(wèn)這些關(guān)聯(lián)關(guān)系之后,我們可以使用模型上的pivot
屬性訪問(wèn)中間表:
$user = App\User::find(1);
foreach ($user->roles as $role) {
echo $role->pivot->created_at;
}
注意我們獲取到的每一個(gè)Role
模型都被自動(dòng)賦上了pivot
屬性。該屬性包含一個(gè)代表中間表的模型,并且可以像其它Eloquent模型一樣使用。
默認(rèn)情況下,只有模型鍵才能用在privot
對(duì)象上,如果你的privot
表包含額外的屬性,必須在定義關(guān)聯(lián)關(guān)系時(shí)進(jìn)行指定:
return $this->belongsToMany('App\Role')->withPivot('column1', 'column2');
如果你想要你的privot
表自動(dòng)包含created_at
和updated_at
時(shí)間戳,在關(guān)聯(lián)關(guān)系定義時(shí)使用withTimestamps
方法:
return $this->belongsToMany('App\Role')->withTimestamps();
“遠(yuǎn)層一對(duì)多”關(guān)聯(lián)為通過(guò)中間關(guān)聯(lián)訪問(wèn)遠(yuǎn)層的關(guān)聯(lián)關(guān)系提供了一個(gè)便利之道。例如,Country
模型通過(guò)中間的User
模型可能擁有多個(gè)Post
模型。在這個(gè)例子中,你可以輕易的聚合給定國(guó)家的所有文章,讓我們看看定義這個(gè)關(guān)聯(lián)關(guān)系需要哪些表:
countries
id - integer
name - string
users
id - integer
country_id - integer
name - string
posts
id - integer
user_id - integer
title - string
盡管posts
表不包含country_id
列,hasManyThrough
關(guān)聯(lián)提供了通過(guò)$country->posts
來(lái)訪問(wèn)一個(gè)國(guó)家的所有文章。要執(zhí)行該查詢,Eloquent在中間表$users
上檢查country_id
,查找到相匹配的用戶ID后,通過(guò)用戶ID來(lái)查詢posts
表。
既然我們已經(jīng)查看了該關(guān)聯(lián)關(guān)系的數(shù)據(jù)表結(jié)構(gòu),接下來(lái)讓我們?cè)?code>Country模型上進(jìn)行定義:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Country extends Model{
/**
* 獲取指定國(guó)家的所有文章
*/
public function posts()
{
return $this->hasManyThrough('App\Post', 'App\User');
}
}
第一個(gè)傳遞到hasManyThrough
方法的參數(shù)是最終我們希望訪問(wèn)的模型的名稱,第二個(gè)參數(shù)是中間模型名稱。
當(dāng)執(zhí)行這種關(guān)聯(lián)查詢時(shí)通常Eloquent外鍵規(guī)則會(huì)被使用,如果你想要自定義該關(guān)聯(lián)關(guān)系的外鍵,可以將它們作為第三個(gè)、第四個(gè)參數(shù)傳遞給hasManyThrough
方法。第三個(gè)參數(shù)是中間模型的外鍵名,第四個(gè)參數(shù)是最終模型的外鍵名。
class Country extends Model{
public function posts()
{
return $this->hasManyThrough('App\Post', 'App\User', 'country_id', 'user_id');
}
}
多態(tài)關(guān)聯(lián)允許一個(gè)模型在單個(gè)關(guān)聯(lián)下屬于多個(gè)不同模型。例如,假如你想要為產(chǎn)品和職工存儲(chǔ)照片,使用多態(tài)關(guān)聯(lián),你可以在這兩種場(chǎng)景下使用單個(gè)photos
表,首先,讓我們看看構(gòu)建這種關(guān)聯(lián)關(guān)系需要的表結(jié)構(gòu):
staff
id - integer
name - string
products
id - integer
price - integer
photos
id - integer
path - string
imageable_id - integer
imageable_type - string
兩個(gè)重要的列需要注意的是photos
表上的imageable_id
和imageable_type
。imageable_id
列包含staff
或product
的ID值,而imageable_type
列包含所屬模型的類名。當(dāng)訪問(wèn)imageable
關(guān)聯(lián)時(shí),ORM根據(jù)imageable_type
列來(lái)判斷所屬模型的類型并返回相應(yīng)模型實(shí)例。
接下來(lái),讓我們看看構(gòu)建這種關(guān)聯(lián)關(guān)系需要在模型中定義什么:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Photo extends Model{
/**
* 獲取所有擁有的imageable模型
*/
public function imageable()
{
return $this->morphTo();
}
}
class Staff extends Model{
/**
* 獲取所有職員照片
*/
public function photos()
{
return $this->morphMany('App\Photo', 'imageable');
}
}
class Product extends Model{
/**
* 獲取所有產(chǎn)品照片
*/
public function photos()
{
return $this->morphMany('App\Photo', 'imageable');
}
}
數(shù)據(jù)表和模型定義好以后,可以通過(guò)模型訪問(wèn)關(guān)聯(lián)關(guān)系。例如,要訪問(wèn)一個(gè)職員的所有照片,可以通過(guò)使用photos
的動(dòng)態(tài)屬性:
$staff = App\Staff::find(1);
foreach ($staff->photos as $photo) {
//
}
你還可以通過(guò)訪問(wèn)調(diào)用morphTo
方法名來(lái)從多態(tài)模型中獲取多態(tài)關(guān)聯(lián)的所屬對(duì)象。在本例中,就是Photo
模型中的imageable
方法。因此,我們可以用動(dòng)態(tài)屬性的方式訪問(wèn)該方法:
$photo = App\Photo::find(1);
$imageable = $photo->imageable;
Photo
模型上的imageable
關(guān)聯(lián)返回Staff
或Product
實(shí)例,這取決于那個(gè)類型的模型擁有該照片。
除了傳統(tǒng)的多態(tài)關(guān)聯(lián),還可以定義“多對(duì)多”的多態(tài)關(guān)聯(lián),例如,一個(gè)博客的Post
和Video
模型可能共享一個(gè)Tag
模型的多態(tài)關(guān)聯(lián)。使用對(duì)多對(duì)的多態(tài)關(guān)聯(lián)允許你在博客文章和視頻之間有唯一的標(biāo)簽列表。首先,讓我們看看表結(jié)構(gòu):
posts
id - integer
name - string
videos
id - integer
name - string
tags
id - integer
name - string
taggables
tag_id - integer
taggable_id - integer
taggable_type - string
接下來(lái),我們準(zhǔn)備在模型中定義該關(guān)聯(lián)關(guān)系。Post
和Video
模型都有一個(gè)tags
方法調(diào)用Eloquent基類的morphToMany
方法:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model{
/**
* 獲取指定文章所有標(biāo)簽
*/
public function tags()
{
return $this->morphToMany('App\Tag', 'taggable');
}
}
接下來(lái),在Tag
模型中,應(yīng)該為每一個(gè)關(guān)聯(lián)模型定義一個(gè)方法,例如,我們定義一個(gè)posts
方法和videos
方法:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Tag extends Model{
/**
* 獲取所有分配該標(biāo)簽的文章
*/
public function posts()
{
return $this->morphedByMany('App\Post', 'taggable');
}
/**
* 獲取分配該標(biāo)簽的所有視頻
*/
public function videos()
{
return $this->morphedByMany('App\Video', 'taggable');
}
}
定義好數(shù)據(jù)庫(kù)和模型后可以通過(guò)模型訪問(wèn)關(guān)聯(lián)關(guān)系。例如,要訪問(wèn)一篇文章的所有標(biāo)簽,可以使用動(dòng)態(tài)屬性tags
:
$post = App\Post::find(1);
foreach ($post->tags as $tag) {
//
}
還可以通過(guò)訪問(wèn)調(diào)用morphedByMany
的方法名從多態(tài)模型中獲取多態(tài)關(guān)聯(lián)的所屬對(duì)象。在本例中,就是Tag模型中的posts
或者videos
方法:
$tag = App\Tag::find(1);
foreach ($tag->videos as $video) {
//
}
由于Eloquent所有關(guān)聯(lián)關(guān)系都是通過(guò)函數(shù)定義,你可以調(diào)用這些方法來(lái)獲取關(guān)聯(lián)關(guān)系的實(shí)例而不需要再去手動(dòng)執(zhí)行關(guān)聯(lián)查詢。此外,所有Eloquent關(guān)聯(lián)關(guān)系類型同時(shí)也是查詢構(gòu)建器,允許你在最終在數(shù)據(jù)庫(kù)執(zhí)行SQL之前繼續(xù)添加條件約束到關(guān)聯(lián)查詢上。
例如,假定在一個(gè)博客系統(tǒng)中一個(gè)User模型有很多相關(guān)的Post模型:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model{
/**
* 獲取指定用戶的所有文章
*/
public function posts()
{
return $this->hasMany('App\Post');
}
}
你可以像這樣查詢posts
關(guān)聯(lián)并添加額外的條件約束到該關(guān)聯(lián)關(guān)系上:
$user = App\User::find(1);
$user->posts()->where('active', 1)->get();
你可以在關(guān)聯(lián)關(guān)系上使用任何查詢構(gòu)建器!
關(guān)聯(lián)關(guān)系方法 VS 動(dòng)態(tài)屬性
如果你不需要添加額外的條件約束到Eloquent關(guān)聯(lián)查詢,你可以簡(jiǎn)單通過(guò)動(dòng)態(tài)屬性來(lái)訪問(wèn)關(guān)聯(lián)對(duì)象,例如,還是拿User
和Post
模型作為例子,你可以像這樣訪問(wèn)所有用戶的文章:
$user = App\User::find(1);
foreach ($user->posts as $post) {
//
}
動(dòng)態(tài)屬性就是”懶惰式加載來(lái)預(yù)加載他們知道在加載模型時(shí)要被訪問(wèn)的關(guān)聯(lián)關(guān)系。渴求式加載有效減少了必須要被執(zhí)以加載模型關(guān)聯(lián)的SQL查詢。
查詢已存在的關(guān)聯(lián)關(guān)系
訪問(wèn)一個(gè)模型的記錄的時(shí)候,你可能希望基于關(guān)聯(lián)關(guān)系是否存在來(lái)限制查詢結(jié)果的數(shù)目。例如,假設(shè)你想要獲取所有至少有一個(gè)評(píng)論的博客文章,要實(shí)現(xiàn)這個(gè),可以傳遞關(guān)聯(lián)關(guān)系的名稱到has
方法:
// 獲取所有至少有一條評(píng)論的文章...
$posts = App\Post::has('comments')->get();
你還可以指定操作符和大小來(lái)自定義查詢:
// 獲取所有至少有三條評(píng)論的文章...
$posts = Post::has('comments', '>=', 3)->get();
還可以使用”.“來(lái)構(gòu)造嵌套has語(yǔ)句,例如,你要獲取所有至少有一條評(píng)論及投票的所有文章:
// 獲取所有至少有一條評(píng)論獲得投票的文章...
$posts = Post::has('comments.votes')->get();
如果你需要更強(qiáng)大的功能,可以使用whereHas
和orWhereHas
方法將where
條件放到has
查詢上,這些方法允許你添加自定義條件約束到關(guān)聯(lián)關(guān)系條件約束,例如檢查一條評(píng)論的內(nèi)容:
// 獲取所有至少有一條評(píng)論包含foo字樣的文章
$posts = Post::whereHas('comments', function ($query) {
$query->where('content', 'like', 'foo%');
})->get();
當(dāng)以屬性方式訪問(wèn)數(shù)據(jù)庫(kù)關(guān)聯(lián)關(guān)系的時(shí)候,關(guān)聯(lián)關(guān)系數(shù)據(jù)時(shí)”懶惰式加載“的,這意味著關(guān)聯(lián)關(guān)系數(shù)據(jù)直到第一次訪問(wèn)的時(shí)候才被加載。然而,Eloquent可以在查詢父級(jí)模型的同時(shí)”渴求式加載“關(guān)聯(lián)關(guān)系??是笫郊虞d緩解了N+1
查詢問(wèn)題,要闡明N+1
查詢問(wèn)題,考慮下關(guān)聯(lián)到Author
的Book
模型:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Book extends Model{
/**
* 獲取寫這本書的作者
*/
public function author()
{
return $this->belongsTo('App\Author');
}
}
現(xiàn)在,讓我們獲取所有書及其作者:
$books = App\Book::all();
foreach ($books as $book) {
echo $book->author->name;
}
該循環(huán)先執(zhí)行1次查詢獲取表中的所有書,然后另一個(gè)查詢獲取每一本書的作者,因此,如果有25本書,要執(zhí)行26次查詢:1次是獲取書本身,剩下的25次查詢是為每一本書獲取其作者。
謝天謝地,我們可以使用渴求式加載來(lái)減少該操作到2次查詢。當(dāng)查詢的時(shí)候,可以使用with
方法指定應(yīng)該被渴求式加載的關(guān)聯(lián)關(guān)系:
$books = App\Book::with('author')->get();
foreach ($books as $book) {
echo $book->author->name;
}
在該操作中,只執(zhí)行兩次查詢即可:
select * from books
select * from authors where id in (1, 2, 3, 4, 5, ...)
有時(shí)候你需要在單個(gè)操作中渴求式加載幾個(gè)不同的關(guān)聯(lián)關(guān)系。要實(shí)現(xiàn)這個(gè),只需要添加額外的參數(shù)到with
方法即可:
$books = App\Book::with('author', 'publisher')->get();
要渴求式加載嵌套的關(guān)聯(lián)關(guān)系,可以使用”.“語(yǔ)法。例如,讓我們?cè)谝粋€(gè)Eloquent語(yǔ)句中渴求式加載所有書的作者及所有作者的個(gè)人聯(lián)系方式:
$books = App\Book::with('author.contacts')->get();
有時(shí)候我們希望渴求式加載一個(gè)關(guān)聯(lián)關(guān)系,但還想為渴求式加載指定更多的查詢條件:
$users = App\User::with(['posts' => function ($query) {
$query->where('title', 'like', '%first%');
}])->get();
在這個(gè)例子中,Eloquent只渴求式加載title包含first的文章。當(dāng)然,你可以調(diào)用其它查詢構(gòu)建器來(lái)自定義渴求式加載操作:
$users = App\User::with(['posts' => function ($query) {
$query->orderBy('created_at', 'desc');
}])->get();
有時(shí)候你需要在父模型已經(jīng)被獲取后渴求式加載一個(gè)關(guān)聯(lián)關(guān)系。例如,這在你需要?jiǎng)討B(tài)決定是否加載關(guān)聯(lián)模型時(shí)可能很有用:
$books = App\Book::all();
if ($someCondition) {
$books->load('author', 'publisher');
}
如果你需要設(shè)置更多的查詢條件到渴求式加載查詢上,可以傳遞一個(gè)閉包到load
方法:
$books->load(['author' => function ($query) {
$query->orderBy('published_date', 'asc');
}]);
Eloquent提供了便利的方法來(lái)添加新模型到關(guān)聯(lián)關(guān)系。例如,也許你需要插入新的Comment
到Post
模型,你可以從關(guān)聯(lián)關(guān)系的save
方法直接插入Comment
而不是手動(dòng)設(shè)置Comment
的post_id
屬性:
$comment = new App\Comment(['message' => 'A new comment.']);
$post = App\Post::find(1);
$comment = $post->comments()->save($comment);
注意我們沒(méi)有用動(dòng)態(tài)屬性方式訪問(wèn)comments
,而是調(diào)用comments
方法獲取關(guān)聯(lián)關(guān)系實(shí)例。save
方法會(huì)自動(dòng)添加post_id
值到新的Comment
模型。
如果你需要保存多個(gè)關(guān)聯(lián)模型,可以使用saveMany
方法:
$post = App\Post::find(1);
$post->comments()->saveMany([
new App\Comment(['message' => 'A new comment.']),
new App\Comment(['message' => 'Another comment.']),
]);
當(dāng)處理多對(duì)多關(guān)聯(lián)的時(shí)候,save
方法以數(shù)組形式接收額外的中間表屬性作為第二個(gè)參數(shù):
App\User::find(1)->roles()->save($role, ['expires' => $expires]);
除了save
和saveMany
方法外,還可以使用create
方法,該方法接收屬性數(shù)組、創(chuàng)建模型、然后插入數(shù)據(jù)庫(kù)。save
和create
的不同之處在于save
接收整個(gè)Eloquent模型實(shí)例而create
接收原生PHP數(shù)組:
$post = App\Post::find(1);
$comment = $post->comments()->create([
'message' => 'A new comment.',
]);
使用create
方法之前確保先瀏覽屬性批量賦值文檔。
更新belongsTo
關(guān)聯(lián)的時(shí)候,可以使用associate
方法,該方法會(huì)在子模型設(shè)置外鍵:
$account = App\Account::find(10);
$user->account()->associate($account);
$user->save();
移除belongsTo
關(guān)聯(lián)的時(shí)候,可以使用dissociate
方法。該方法在子模型上取消外鍵和關(guān)聯(lián):
$user->account()->dissociate();
$user->save();
處理多對(duì)多關(guān)聯(lián)的時(shí)候,Eloquent提供了一些額外的幫助函數(shù)來(lái)使得處理關(guān)聯(lián)模型變得更加方便。例如,讓我們假定一個(gè)用戶可能有多個(gè)角色同時(shí)一個(gè)角色屬于多個(gè)用戶,要通過(guò)在連接模型的中間表中插入記錄附加角色到用戶上,可以使用attach
方法:
$user = App\User::find(1);
$user->roles()->attach($roleId);
附加關(guān)聯(lián)關(guān)系到模型,還可以以數(shù)組形式傳遞額外被插入數(shù)據(jù)到中間表:
$user->roles()->attach($roleId, ['expires' => $expires]);
當(dāng)然,有時(shí)候有必要從用戶中移除角色,要移除一個(gè)多對(duì)多關(guān)聯(lián)記錄,使用detach
方法。detach
方法將會(huì)從中間表中移除相應(yīng)的記錄;然而,兩個(gè)模型在數(shù)據(jù)庫(kù)中都保持不變:
// 從指定用戶中移除角色...
$user->roles()->detach($roleId);
// 從指定用戶移除所有角色...
$user->roles()->detach();
為了方便,attach
和detach
還接收數(shù)組形式的ID作為輸入:
$user = App\User::find(1);
$user->roles()->detach([1, 2, 3]);
$user->roles()->attach([1 => ['expires' => $expires], 2, 3]);
你還可以使用sync
方法構(gòu)建多對(duì)多關(guān)聯(lián)。sync
方法接收數(shù)組形式的ID并將其放置到中間表。任何不在該數(shù)組中的ID對(duì)應(yīng)記錄將會(huì)從中間表中移除。因此,該操作完成后,只有在數(shù)組中的ID對(duì)應(yīng)記錄還存在于中間表:
$user->roles()->sync([1, 2, 3]);
你還可以和ID一起傳遞額外的中間表值:
$user->roles()->sync([1 => ['expires' => true], 2, 3]);
當(dāng)一個(gè)模型屬于另外一個(gè)時(shí),例如Comment
屬于Post
,子模型更新時(shí)父模型的時(shí)間戳也被更新將很有用,例如,當(dāng)Comment
模型被更新時(shí),你可能想要”觸發(fā)“創(chuàng)建其所屬模型Post
的updated_at
時(shí)間戳。Eloquent使得這項(xiàng)操作變得簡(jiǎn)單,只需要添加包含關(guān)聯(lián)關(guān)系名稱的touches
屬性到子模型中即可:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model{
/**
* 要觸發(fā)的所有關(guān)聯(lián)關(guān)系
*
* @var array
*/
protected $touches = ['post'];
/**
* 評(píng)論所屬文章
*/
public function post()
{
return $this->belongsTo('App\Post');
}
}
現(xiàn)在,當(dāng)你更新Comment
時(shí),所屬模型Post
將也會(huì)更新其updated_at
值:
$comment = App\Comment::find(1);
$comment->text = 'Edit to this comment!';
$comment->save();
更多建議: