Eloquent ORM ―― 關(guān)聯(lián)關(guān)系

2018-02-24 15:38 更新

Eloquent ORM —— 關(guān)聯(lián)關(guān)系

1、簡(jiǎn)介

數(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)系:

2、定義關(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)類型:

2.1?一對(duì)一

一對(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');

2.1.1 定義相對(duì)的關(guān)聯(lián)

我們可以從User中訪問(wèn)Phone模型,相應(yīng)的,我們也可以在Phone模型中定義關(guān)聯(lián)關(guān)系從而讓我們可以擁有該phoneUser。我們可以使用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_idUser模型查找與之匹配的記錄。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');
}

2.2?一對(duì)多

“一對(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');

2.2.1 定義相對(duì)的關(guān)聯(lián)

現(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_idPost模型的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');
}

2.3?多對(duì)多

多對(duì)多關(guān)系比hasOnehasMany關(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ù)表:usersrolesrole_user,role_user表按照關(guān)聯(lián)模型名的字母順序命名,并且包含user_idrole_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');

2.3.1 定義相對(duì)的關(guān)聯(lián)關(guān)系

要定義與多對(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í)都是可用的。

2.3.2 獲取中間表的列

正如你已經(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_atupdated_at時(shí)間戳,在關(guān)聯(lián)關(guān)系定義時(shí)使用withTimestamps方法:

return $this->belongsToMany('App\Role')->withTimestamps();

2.4 遠(yuǎn)層的一對(duì)多

“遠(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');
    }
}

2.5 多態(tài)關(guān)聯(lián)

2.5.1?表結(jié)構(gòu)

多態(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_idimageable_type。imageable_id列包含staffproduct的ID值,而imageable_type列包含所屬模型的類名。當(dāng)訪問(wèn)imageable關(guān)聯(lián)時(shí),ORM根據(jù)imageable_type列來(lái)判斷所屬模型的類型并返回相應(yīng)模型實(shí)例。

2.5.2 ?模型結(jié)構(gòu)

接下來(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');
    }
}

2.5.3 獲取多態(tài)關(guān)聯(lián)

數(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)返回StaffProduct實(shí)例,這取決于那個(gè)類型的模型擁有該照片。

2.6 多對(duì)多多態(tài)關(guān)聯(lián)

2.6.1 表結(jié)構(gòu)

除了傳統(tǒng)的多態(tài)關(guān)聯(lián),還可以定義“多對(duì)多”的多態(tài)關(guān)聯(lián),例如,一個(gè)博客的PostVideo模型可能共享一個(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

2.6.2 模型結(jié)構(gòu)

接下來(lái),我們準(zhǔn)備在模型中定義該關(guān)聯(lián)關(guān)系。PostVideo模型都有一個(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');
    }
}

2.6.3 定義相對(duì)的關(guān)聯(lián)關(guān)系

接下來(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');
    }
}

2.6.4 獲取關(guān)聯(lián)關(guān)系

定義好數(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) {
    //
}

3、關(guān)聯(lián)查詢

由于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ì)象,例如,還是拿UserPost模型作為例子,你可以像這樣訪問(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)大的功能,可以使用whereHasorWhereHas方法將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();

3.1 渴求式加載

當(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)到AuthorBook模型:

<?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, ...)

3.1.1 渴求式加載多個(gè)關(guān)聯(lián)關(guān)系

有時(shí)候你需要在單個(gè)操作中渴求式加載幾個(gè)不同的關(guān)聯(lián)關(guān)系。要實(shí)現(xiàn)這個(gè),只需要添加額外的參數(shù)到with方法即可:

$books = App\Book::with('author', 'publisher')->get();

3.1.2 嵌套的渴求式加載

要渴求式加載嵌套的關(guān)聯(lián)關(guān)系,可以使用”.“語(yǔ)法。例如,讓我們?cè)谝粋€(gè)Eloquent語(yǔ)句中渴求式加載所有書的作者及所有作者的個(gè)人聯(lián)系方式:

$books = App\Book::with('author.contacts')->get();

3.2 帶條件約束的渴求式加載

有時(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();

3.3 懶惰渴求式加載

有時(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');
}]);

4、插入關(guān)聯(lián)模型

4.1 基本使用

4.1.1 save方法

Eloquent提供了便利的方法來(lái)添加新模型到關(guān)聯(lián)關(guān)系。例如,也許你需要插入新的CommentPost模型,你可以從關(guān)聯(lián)關(guān)系的save方法直接插入Comment而不是手動(dòng)設(shè)置Commentpost_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.']),
]);

4.1.2 save & 多對(duì)多關(guān)聯(lián)

當(dāng)處理多對(duì)多關(guān)聯(lián)的時(shí)候,save方法以數(shù)組形式接收額外的中間表屬性作為第二個(gè)參數(shù):

App\User::find(1)->roles()->save($role, ['expires' => $expires]);

4.1.3 create方法

除了savesaveMany方法外,還可以使用create方法,該方法接收屬性數(shù)組、創(chuàng)建模型、然后插入數(shù)據(jù)庫(kù)。savecreate的不同之處在于save接收整個(gè)Eloquent模型實(shí)例而create接收原生PHP數(shù)組:

$post = App\Post::find(1);

$comment = $post->comments()->create([
    'message' => 'A new comment.',
]);

使用create方法之前確保先瀏覽屬性批量賦值文檔。

4.1.4 更新”屬于“關(guān)聯(lián)

更新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();

4.2 多對(duì)多關(guān)聯(lián)

4.2.1 附加/分離

處理多對(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();

為了方便,attachdetach還接收數(shù)組形式的ID作為輸入:

$user = App\User::find(1);
$user->roles()->detach([1, 2, 3]);
$user->roles()->attach([1 => ['expires' => $expires], 2, 3]);

4.2.2 同步

你還可以使用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]);

4.3 觸發(fā)父級(jí)時(shí)間戳

當(dāng)一個(gè)模型屬于另外一個(gè)時(shí),例如Comment屬于Post,子模型更新時(shí)父模型的時(shí)間戳也被更新將很有用,例如,當(dāng)Comment模型被更新時(shí),你可能想要”觸發(fā)“創(chuàng)建其所屬模型Postupdated_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();

擴(kuò)展閱讀1:實(shí)例教程 ——?關(guān)聯(lián)關(guān)系及其在模型中的定義(一)

擴(kuò)展閱讀2:實(shí)例教程 —— 關(guān)聯(lián)關(guān)系及其在模型中的定義(二)

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)