鍍金池/ 教程/ PHP/ 關(guān)聯(lián)關(guān)系
門面
Laravel Homestead
安裝及配置
測試
HTTP 中間件
加密
升級指南
幫助函數(shù)
應(yīng)用目錄結(jié)構(gòu)
集合
新手入門指南-簡單任務(wù)管理系統(tǒng)
任務(wù)調(diào)度
查詢構(gòu)建器
視圖
驗證
Laravel Cashier(訂購&支付&發(fā)票)
本地化
隊列
調(diào)整器
分頁
文件系統(tǒng)/云存儲
貢獻代碼
哈希
HTTP 控制器
緩存
遷移
HTTP 請求
Laravel Elixir
發(fā)行版本說明
Envoy 任務(wù)運行器(SSH任務(wù))
序列化
Session
起步
帶用戶功能的任務(wù)管理系統(tǒng)
起步
用戶授權(quán)
郵件
事件
填充數(shù)據(jù)
HTTP 路由
服務(wù)提供者
Blade 模板引擎
包開發(fā)
用戶認證
Artisan 控制臺
HTTP 響應(yīng)
集合
服務(wù)容器
關(guān)聯(lián)關(guān)系
一次請求的生命周期
契約
Redis
錯誤&日志

關(guān)聯(lián)關(guān)系

1、簡介

數(shù)據(jù)表經(jīng)常要與其它表做關(guān)聯(lián),比如一篇博客文章可能有很多評論,或者一個訂單會被關(guān)聯(lián)到下單用戶,Eloquent 使得組織和處理這些關(guān)聯(lián)關(guān)系變得簡單,并且支持多種不同類型的關(guān)聯(lián)關(guān)系:

  • 一對一
  • 一對多
  • 多對多
  • 遠層多對多
  • 多態(tài)關(guān)聯(lián)
  • 多對多的多態(tài)關(guān)聯(lián)

2、定義關(guān)聯(lián)關(guān)系

Eloquent 關(guān)聯(lián)關(guān)系以 Eloquent 模型類方法的形式被定義。和 Eloquent 模型本身一樣,關(guān)聯(lián)關(guān)系也是強大的查詢構(gòu)建器,定義關(guān)聯(lián)關(guān)系為函數(shù)能夠提供功能強大的方法鏈和查詢能力。例如:

$user->posts()->where('active', 1)->get();

但是,在深入使用關(guān)聯(lián)關(guān)系之前,讓我們先學習如何定義每種關(guān)聯(lián)類型:

2.1 一對一

一對一關(guān)聯(lián)是一個非常簡單的關(guān)聯(lián)關(guān)系,例如,一個 User 模型有一個與之對應(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)到用戶的手機
     */
    public function phone()
    {
        return $this->hasOne('App\Phone');
    }
}

傳遞給 hasOne 方法的第一個參數(shù)是關(guān)聯(lián)模型的名稱,關(guān)聯(lián)關(guān)系被定義后,我們可以使用 Eloquent 的動態(tài)屬性獲取關(guān)聯(lián)記錄。動態(tài)屬性允許我們訪問關(guān)聯(lián)函數(shù)就像它們是定義在模型上的屬性一樣:

$phone = User::find(1)->phone;

Eloquent 默認關(guān)聯(lián)關(guān)系的外鍵基于模型名稱,在本例中,Phone 模型默認有一個 user_id 外鍵,如果你希望重寫這種約定,可以傳遞第二個參數(shù)到 hasOne 方法:

return $this->hasOne('App\Phone', 'foreign_key');

此外,Eloquent 假設(shè)外鍵應(yīng)該在父級上有一個與之匹配的 id,換句話說,Eloquent 將會通過 user 表的 id 值去 phone 表中查詢 user_id 與之匹配的 Phone 記錄。如果你想要關(guān)聯(lián)關(guān)系使用其他值而不是 id,可以傳遞第三個參數(shù)到 hasOne 來指定自定義的主鍵:

return $this->hasOne('App\Phone', 'foreign_key', 'local_key');

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

我們可以從 User 中訪問Phone 模型,相應(yīng)的,我們也可以在 Phone 模型中定義關(guān)聯(lián)關(guān)系從而讓我們可以擁有該phoneUser。我們可以使用belongsTo 方法定義與 hasOne 關(guān)聯(lián)關(guān)系相對的關(guān)聯(lián):

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Phone extends Model{
    /**
     * 獲取手機對應(yīng)的用戶
     */
    public function user()
    {
        return $this->belongsTo('App\User');
    }
}

在上面的例子中,Eloquent 將會嘗試通過 Phone 模型的 user_idUser模型查找與之匹配的記錄。Eloquent 通過關(guān)聯(lián)關(guān)系方法名并在方法名后加_id 后綴來生成默認的外鍵名。然而,如果 Phone 模型上的外鍵不是user_id,也可以將自定義的鍵名作為第二個參數(shù)傳遞到belongsTo方法:

/**
 * 獲取手機對應(yīng)的用戶
 */
public function user(){
    return $this->belongsTo('App\User', 'foreign_key');
}

如果父模型不使用 id 作為主鍵,或者你希望使用別的列來連接子模型,可以將父表自定義鍵作為第三個參數(shù)傳遞給belongsTo方法:

/**
 * 獲取手機對應(yīng)的用戶
 */
public function user(){
    return $this->belongsTo('App\User', 'foreign_key', 'other_key');
}

2.2 一對多

“一對多”是用于定義單個模型擁有多個其它模型的關(guān)聯(lián)關(guān)系。例如,一篇博客文章?lián)碛袩o數(shù)評論,和其他關(guān)聯(lián)關(guān)系一樣,一對多關(guān)聯(lián)通過在 Eloquent 模型中定義方法來定義:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model{
    /**
     * 獲取博客文章的評論
     */
    public function comments()
    {
        return $this->hasMany('App\Comment');
    }
}

記住,Eloquent 會自動判斷 Comment 模型的外鍵,為方便起見,Eloquent 將擁有者模型名稱加上 id后綴作為外鍵。因此,在本例中,Eloquent 假設(shè) Comment模型上的外鍵是 post_id。 關(guān)聯(lián)關(guān)系被定義后,我們就可以通過訪問comments 屬性來訪問評論集合。記住,由于 Eloquent 提供“動態(tài)屬性”,我們可以像訪問模型的屬性一樣訪問關(guān)聯(lián)方法:

$comments = App\Post::find(1)->comments;

foreach ($comments as $comment) {
    //
}

當然,由于所有關(guān)聯(lián)同時也是查詢構(gòu)建器,我們可以添加更多的條件約束到通過調(diào)用 comments 方法獲取到的評論上:

$comments = App\Post::find(1)->comments()->where('title', 'foo')->first();

hasOne 方法一樣,你還可以通過傳遞額外參數(shù)到 hasMany方法來重新設(shè)置外鍵和本地主鍵:

return $this->hasMany('App\Comment', 'foreign_key');
return $this->hasMany('App\Comment', 'foreign_key', 'local_key');

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

現(xiàn)在我們可以訪問文章的所有評論了,接下來讓我們定義一個關(guān)聯(lián)關(guān)系允許通過評論訪問所屬文章。要定義與 hasMany 相對的關(guān)聯(lián)關(guān)系,需要在子模型中定義一個關(guān)聯(lián)方法去調(diào)用 belongsTo 方法:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model{
    /**
     * 獲取評論對應(yīng)的博客文章
     */
    public function post()
    {
        return $this->belongsTo('App\Post');
    }
}

關(guān)聯(lián)關(guān)系定義好之后,我們可以通過訪問動態(tài)屬性post 來獲取一條 Comment對應(yīng)的 Post

$comment = App\Comment::find(1);
echo $comment->post->title;

在上面這個例子中,Eloquent 嘗試匹配 Comment 模型的post_idPost 模型的 id,Eloquent 通過關(guān)聯(lián)方法名加上_id后綴生成默認外鍵,當然,你也可以通過傳遞自定義外鍵名作為第二個參數(shù)傳遞到 belongsTo方法,如果你的外鍵不是post_id,或者你想自定義的話:

/**
 * 獲取評論對應(yīng)的博客文章
 */
public function post(){
    return $this->belongsTo('App\Post', 'foreign_key');
}

如果你的父模型不使用 id 作為主鍵,或者你希望通過其他列來連接子模型,可以將自定義鍵名作為第三個參數(shù)傳遞給 belongsTo 方法:

/**
 * 獲取評論對應(yīng)的博客文章
 */
public function post(){
    return $this->belongsTo('App\Post', 'foreign_key', 'other_key');
}

2.3 多對多

多對多關(guān)系比 hasOnehasMany 關(guān)聯(lián)關(guān)系要稍微復(fù)雜一些。這種關(guān)聯(lián)關(guān)系的一個例子就是一個用戶有多個角色,同時一個角色被多個用戶共用。例如,很多用戶可能都有一個“Admin”角色。要定義這樣的關(guān)聯(lián)關(guān)系,需要三個數(shù)據(jù)表:usersrolesrole_userrole_user 表按照關(guān)聯(lián)模型名的字母順序命名,并且包含 user_idrole_id 兩個列。 多對多關(guān)聯(lián)通過編寫一個調(diào)用 Eloquent 基類上的 belongsToMany 方法的函數(shù)來定義:

<?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)系被定義之后,可以使用動態(tài)屬性 roles來訪問用戶的角色:

$user = App\User::find(1);

foreach ($user->roles as $role) {
    //
}

當然,和所有其它關(guān)聯(lián)關(guān)系類型一樣,你可以調(diào)用 roles方法來添加條件約束到關(guān)聯(lián)查詢上:

$roles = App\User::find(1)->roles()->orderBy('name')->get();

正如前面所提到的,為了決定關(guān)聯(lián)關(guān)系連接表的表名,Eloquent 以字母順序連接兩個關(guān)聯(lián)模型的名字。然而,你可以重寫這種約定——通過傳遞第二個參數(shù)到 belongsToMany 方法:

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

除了自定義連接表的表名,你還可以通過傳遞額外參數(shù)到belongsToMany 方法來自定義該表中字段的列名。第三個參數(shù)是你定義的關(guān)系模型的外鍵名稱,第四個參數(shù)你要連接到的模型的外鍵名稱:

return $this->belongsToMany('App\Role', 'user_roles', 'user_id', 'role_id');

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

要定義與多對多關(guān)聯(lián)相對的關(guān)聯(lián)關(guān)系,只需在關(guān)聯(lián)模型中在調(diào)用一下 belongsToMany 方法即可。讓我們在 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)系和與其對應(yīng)的 User 中定義的一模一樣,只是前者引用App\Role,后者引用App\User,由于我們再次使用了 belongsToMany 方法,所有的常用表和鍵自定義選項在定義與多對多相對的關(guān)聯(lián)關(guān)系時都是可用的。

2.3.2 獲取中間表的列

正如你已經(jīng)學習到的,處理多對多關(guān)聯(lián)要求一個中間表。Eloquent 提供了一些有用的方法來與其進行交互,例如,我們假設(shè) User 對象有很多與之關(guān)聯(lián)的Role對象,訪問這些關(guān)聯(lián)關(guān)系之后,我們可以使用模型上的 pivot 屬性訪問中間表:

$user = App\User::find(1);

foreach ($user->roles as $role) {
    echo $role->pivot->created_at;
}

注意我們獲取到的每一個Role 模型都被自動賦上了pivot 屬性。該屬性包含一個代表中間表的模型,并且可以像其它 Eloquent 模型一樣使用。

默認情況下,只有模型鍵才能用在 privot 對象上,如果你的 privot 表包含額外的屬性,必須在定義關(guān)聯(lián)關(guān)系時進行指定:

return $this->belongsToMany('App\Role')->withPivot('column1', 'column2');

如果你想要你的 privot表自動包含 created_atupdated_at 時間戳,在關(guān)聯(lián)關(guān)系定義時使用withTimestamps 方法:

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

2.4 遠層的多對多

“遠層多對多”關(guān)聯(lián)為通過中間關(guān)聯(lián)訪問遠層的關(guān)聯(lián)關(guān)系提供了一個便利之道。例如,Country 模型通過中間的User 模型可能擁有多個Post 模型。在這個例子中,你可以輕易的聚合給定國家的所有文章,讓我們看看定義這個關(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)提供了通過$country->posts 來訪問一個國家的所有文章。要執(zhí)行該查詢,Eloquent 在中間表$users 上檢查 country_id,查找到相匹配的用戶 ID 后,通過用戶 ID 來查詢 posts 表。

既然我們已經(jīng)查看了該關(guān)聯(lián)關(guān)系的數(shù)據(jù)表結(jié)構(gòu),接下來讓我們在Country模型上進行定義:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Country extends Model{
    /**
     * 獲取指定國家的所有文章
     */
    public function posts()
    {
        return $this->hasManyThrough('App\Post', 'App\User');
    }
}

第一個傳遞到 hasManyThrough方法的參數(shù)是最終我們希望訪問的模型的名稱,第二個參數(shù)是中間模型名稱。

當執(zhí)行這種關(guān)聯(lián)查詢時通常 Eloquent 外鍵規(guī)則會被使用,如果你想要自定義該關(guān)聯(lián)關(guān)系的外鍵,可以將它們作為第三個、第四個參數(shù)傳遞給 hasManyThrough 方法。第三個參數(shù)是中間模型的外鍵名,第四個參數(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)允許一個模型在單個關(guān)聯(lián)下屬于多個不同模型。例如,假如你想要為產(chǎn)品和職工存儲照片,使用多態(tài)關(guān)聯(lián),你可以在這兩種場景下使用單個 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

兩個重要的列需要注意的是 photos 表上的 imageable_idimageable_typeimageable_id列包含 staffproduct 的 ID 值,而 imageable_type 列包含所屬模型的類名。當訪問 imageable關(guān)聯(lián)時,ORM 根據(jù) imageable_type 列來判斷所屬模型的類型并返回相應(yīng)模型實例。

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

接下來,讓我們看看構(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ān)聯(lián)關(guān)系。例如,要訪問一個職員的所有照片,可以通過使用 photos 的動態(tài)屬性:

$staff = App\Staff::find(1);

foreach ($staff->photos as $photo) {
    //
}

你還可以通過訪問調(diào)用 morphTo 方法名來從多態(tài)模型中獲取多態(tài)關(guān)聯(lián)的所屬對象。在本例中,就是 Photo 模型中的 imageable 方法。因此,我們可以用動態(tài)屬性的方式訪問該方法:

$photo = App\Photo::find(1);
$imageable = $photo->imageable;

Photo 模型上的 imageable 關(guān)聯(lián)返回 StaffProduct 實例,這取決于那個類型的模型擁有該照片。

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

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

除了傳統(tǒng)的多態(tài)關(guān)聯(lián),還可以定義“多對多”的多態(tài)關(guān)聯(lián),例如,一個博客的 PostVideo 模型可能共享一個 Tag 模型的多態(tài)關(guān)聯(lián)。使用對多對的多態(tài)關(guān)聯(lián)允許你在博客文章和視頻之間有唯一的標簽列表。首先,讓我們看看表結(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)

接下來,我們準備在模型中定義該關(guān)聯(lián)關(guān)系。PostVideo 模型都有一個 tags方法調(diào)用 Eloquent 基類的 morphToMany方法:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model{
    /**
     * 獲取指定文章所有標簽
     */
    public function tags()
    {
        return $this->morphToMany('App\Tag', 'taggable');
    }
}

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

接下來,在 Tag 模型中,應(yīng)該為每一個關(guān)聯(lián)模型定義一個方法,例如,我們定義一個 posts 方法和 videos方法:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Tag extends Model{
    /**
     * 獲取所有分配該標簽的文章
     */
    public function posts()
    {
        return $this->morphedByMany('App\Post', 'taggable');
    }

    /**
     * 獲取分配該標簽的所有視頻
     */
    public function videos()
    {
        return $this->morphedByMany('App\Video', 'taggable');
    }
}

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

定義好數(shù)據(jù)庫和模型后可以通過模型訪問關(guān)聯(lián)關(guān)系。例如,要訪問一篇文章的所有標簽,可以使用動態(tài)屬性 tags

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

foreach ($post->tags as $tag) {
    //
}

還可以通過訪問調(diào)用 morphedByMany的方法名從多態(tài)模型中獲取多態(tài)關(guān)聯(lián)的所屬對象。在本例中,就是 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)系都是通過函數(shù)定義,你可以調(diào)用這些方法來獲取關(guān)聯(lián)關(guān)系的實例而不需要再去手動執(zhí)行關(guān)聯(lián)查詢。此外,所有 Eloquent 關(guān)聯(lián)關(guān)系類型同時也是查詢構(gòu)建器,允許你在最終在數(shù)據(jù)庫執(zhí)行 SQL 之前繼續(xù)添加條件約束到關(guān)聯(lián)查詢上。 例如,假定在一個博客系統(tǒng)中一個 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 動態(tài)屬性 如果你不需要添加額外的條件約束到 Eloquent 關(guān)聯(lián)查詢,你可以簡單通過動態(tài)屬性來訪問關(guān)聯(lián)對象,例如,還是拿 UserPost 模型作為例子,你可以像這樣訪問所有用戶的文章:

$user = App\User::find(1);

foreach ($user->posts as $post) {
    //
}

動態(tài)屬性就是”懶惰式加載“,意味著當你真正訪問它們的時候才會加載關(guān)聯(lián)數(shù)據(jù)。正因為如此,開發(fā)者經(jīng)常使用渴求式加載來預(yù)加載他們知道在加載模型時要被訪問的關(guān)聯(lián)關(guān)系。渴求式加載有效減少了必須要被執(zhí)以加載模型關(guān)聯(lián)的 SQL 查詢。

查詢已存在的關(guān)聯(lián)關(guān)系 訪問一個模型的記錄的時候,你可能希望基于關(guān)聯(lián)關(guān)系是否存在來限制查詢結(jié)果的數(shù)目。例如,假設(shè)你想要獲取所有至少有一個評論的博客文章,要實現(xiàn)這個,可以傳遞關(guān)聯(lián)關(guān)系的名稱到 has 方法:

// 獲取所有至少有一條評論的文章...
$posts = App\Post::has('comments')->get();

你還可以指定操作符和大小來自定義查詢:

// 獲取所有至少有三條評論的文章...
$posts = Post::has('comments', '>=', 3)->get();

還可以使用”.“來構(gòu)造嵌套 has 語句,例如,你要獲取所有至少有一條評論及投票的所有文章:

// 獲取所有至少有一條評論獲得投票的文章...
$posts = Post::has('comments.votes')->get();

如果你需要更強大的功能,可以使用 whereHasorWhereHas 方法將 where 條件放到 has 查詢上,這些方法允許你添加自定義條件約束到關(guān)聯(lián)關(guān)系條件約束,例如檢查一條評論的內(nèi)容:

// 獲取所有至少有一條評論包含 foo 字樣的文章
$posts = Post::whereHas('comments', function ($query) {
    $query->where('content', 'like', 'foo%');
})->get();

3.1 渴求式加載

當以屬性方式訪問數(shù)據(jù)庫關(guān)聯(lián)關(guān)系的時候,關(guān)聯(lián)關(guān)系數(shù)據(jù)時”懶惰式加載“的,這意味著關(guān)聯(lián)關(guān)系數(shù)據(jù)直到第一次訪問的時候才被加載。然而,Eloquent 可以在查詢父級模型的同時”渴求式加載“關(guān)聯(lián)關(guān)系。渴求式加載緩解了 N+1 查詢問題,要闡明 N+1 查詢問題,考慮下關(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 次查詢獲取表中的所有書,然后另一個查詢獲取每一本書的作者,因此,如果有 25 本書,要執(zhí)行 26 次查詢:1 次是獲取書本身,剩下的 25 次查詢是為每一本書獲取其作者。 謝天謝地,我們可以使用渴求式加載來減少該操作到 2 次查詢。當查詢的時候,可以使用 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 渴求式加載多個關(guān)聯(lián)關(guān)系

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

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

3.1.2 嵌套的渴求式加載

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

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

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

有時候我們希望渴求式加載一個關(guān)聯(lián)關(guān)系,但還想為渴求式加載指定更多的查詢條件:

$users = App\User::with(['posts' => function ($query) {
    $query->where('title', 'like', '%first%');
}])->get();

在這個例子中,Eloquent 只渴求式加載 title 包含 first 的文章。當然,你可以調(diào)用其它查詢構(gòu)建器來自定義渴求式加載操作:

$users = App\User::with(['posts' => function ($query) {
    $query->orderBy('created_at', 'desc');
}])->get();

3.3 懶惰渴求式加載

有時候你需要在父模型已經(jīng)被獲取后渴求式加載一個關(guān)聯(lián)關(guān)系。例如,這在你需要動態(tài)決定是否加載關(guān)聯(lián)模型時可能很有用:

$books = App\Book::all();

if ($someCondition) {
    $books->load('author', 'publisher');
}

如果你需要設(shè)置更多的查詢條件到渴求式加載查詢上,可以傳遞一個閉包到 load 方法:

$books->load(['author' => function ($query) {
    $query->orderBy('published_date', 'asc');
}]);

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

4.1 基本使用

4.1.1 save 方法

Eloquent 提供了便利的方法來添加新模型到關(guān)聯(lián)關(guān)系。例如,也許你需要插入新的 CommentPost 模型,你可以從關(guān)聯(lián)關(guān)系的save方法直接插入Comment而不是手動設(shè)置 Commentpost_id 屬性:

$comment = new App\Comment(['message' => 'A new comment.']);
$post = App\Post::find(1);
$comment = $post->comments()->save($comment);

注意我們沒有用動態(tài)屬性方式訪問 comments,而是調(diào)用 comments 方法獲取關(guān)聯(lián)關(guān)系實例。save方法會自動添加 post_id值到新的 Comment 模型。

如果你需要保存多個關(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 & 多對多關(guān)聯(lián)

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

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

4.1.3 create 方法

除了 savesaveMany方法外,還可以使用 create 方法,該方法接收屬性數(shù)組、創(chuàng)建模型、然后插入數(shù)據(jù)庫。savecreate 的不同之處在于 save 接收整個 Eloquent 模型實例而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)的時候,可以使用 associate 方法,該方法會在子模型設(shè)置外鍵:

$account = App\Account::find(10);
$user->account()->associate($account);
$user->save();

移除belongsTo 關(guān)聯(lián)的時候,可以使用 dissociate方法。該方法在子模型上取消外鍵和關(guān)聯(lián):

$user->account()->dissociate();
$user->save();

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

4.2.1 附加/分離

處理多對多關(guān)聯(lián)的時候,Eloquent 提供了一些額外的幫助函數(shù)來使得處理關(guān)聯(lián)模型變得更加方便。例如,讓我們假定一個用戶可能有多個角色同時一個角色屬于多個用戶,要通過在連接模型的中間表中插入記錄附加角色到用戶上,可以使用 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]);

當然,有時候有必要從用戶中移除角色,要移除一個多對多關(guān)聯(lián)記錄,使用 detach 方法。detach方法將會從中間表中移除相應(yīng)的記錄;然而,兩個模型在數(shù)據(jù)庫中都保持不變:

// 從指定用戶中移除角色...
$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)建多對多關(guān)聯(lián)。sync 方法接收數(shù)組形式的 ID 并將其放置到中間表。任何不在該數(shù)組中的 ID 對應(yīng)記錄將會從中間表中移除。因此,該操作完成后,只有在數(shù)組中的 ID 對應(yīng)記錄還存在于中間表:

$user->roles()->sync([1, 2, 3]);

你還可以和 ID 一起傳遞額外的中間表值:

$user->roles()->sync([1 => ['expires' => true], 2, 3]);

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

當一個模型屬于另外一個時,例如 Comment 屬于 Post,子模型更新時父模型的時間戳也被更新將很有用,例如,當Comment 模型被更新時,你可能想要”觸發(fā)“創(chuàng)建其所屬模型 Postupdated_at 時間戳。Eloquent 使得這項操作變得簡單,只需要添加包含關(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'];

    /**
     * 評論所屬文章
     */
    public function post()
    {
        return $this->belongsTo('App\Post');
    }
}

現(xiàn)在,當你更新 Comment 時,所屬模型 Post 將也會更新其 updated_at 值:

$comment = App\Comment::find(1);
$comment->text = 'Edit to this comment!';
$comment->save();
上一篇:隊列下一篇:門面