跳到內容

Eloquent:關聯

簡介

資料庫表格通常彼此關聯。例如,一篇部落格文章可能有多個評論,或者一個訂單可能與下訂單的使用者相關聯。Eloquent 使管理和使用這些關聯變得容易,並支援各種常見的關聯

定義關聯

Eloquent 關聯定義為 Eloquent 模型類別上的方法。由於關聯也充當強大的查詢建構器,因此將關聯定義為方法可提供強大的方法鏈接和查詢功能。例如,我們可以在此 posts 關聯上鏈接額外的查詢約束

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

但是,在深入探討如何使用關聯之前,讓我們先學習如何定義 Eloquent 支援的每種類型關聯。

一對一 / Has One

一對一關聯是一種非常基礎的資料庫關聯類型。例如,一個 User 模型可能與一個 Phone 模型關聯。要定義此關聯,我們將在 User 模型上放置一個 phone 方法。phone 方法應呼叫 hasOne 方法並傳回其結果。hasOne 方法可透過模型的 Illuminate\Database\Eloquent\Model 基礎類別在您的模型中使用

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\HasOne;
7 
8class User extends Model
9{
10 /**
11 * Get the phone associated with the user.
12 */
13 public function phone(): HasOne
14 {
15 return $this->hasOne(Phone::class);
16 }
17}

傳遞給 hasOne 方法的第一個參數是相關模型類別的名稱。一旦定義了關聯,我們就可以使用 Eloquent 的動態屬性檢索相關記錄。動態屬性允許您存取關聯方法,就像它們是模型上定義的屬性一樣

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

Eloquent 會根據父模型名稱判斷關聯的外鍵。在本例中,Phone 模型自動假定具有 user_id 外鍵。如果您希望覆寫此慣例,您可以將第二個參數傳遞給 hasOne 方法

1return $this->hasOne(Phone::class, 'foreign_key');

此外,Eloquent 假設外鍵應具有與父項主鍵欄位相符的值。換句話說,Eloquent 將在 Phone 記錄的 user_id 欄位中尋找使用者的 id 欄位的值。如果您希望關聯使用 id 或模型的 $primaryKey 屬性以外的主鍵值,您可以將第三個參數傳遞給 hasOne 方法

1return $this->hasOne(Phone::class, 'foreign_key', 'local_key');

定義關聯的反向

因此,我們可以從 User 模型存取 Phone 模型。接下來,讓我們在 Phone 模型上定義一個關聯,以便我們可以存取擁有手機的使用者。我們可以使用 belongsTo 方法定義 hasOne 關聯的反向

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\BelongsTo;
7 
8class Phone extends Model
9{
10 /**
11 * Get the user that owns the phone.
12 */
13 public function user(): BelongsTo
14 {
15 return $this->belongsTo(User::class);
16 }
17}

當呼叫 user 方法時,Eloquent 將嘗試尋找一個 User 模型,其 idPhone 模型上的 user_id 欄位相符。

Eloquent 透過檢查關聯方法的名稱並在方法名稱後加上 _id 來判斷外鍵名稱。因此,在本例中,Eloquent 假設 Phone 模型具有 user_id 欄位。但是,如果 Phone 模型上的外鍵不是 user_id,您可以將自訂鍵名稱作為第二個參數傳遞給 belongsTo 方法

1/**
2 * Get the user that owns the phone.
3 */
4public function user(): BelongsTo
5{
6 return $this->belongsTo(User::class, 'foreign_key');
7}

如果父模型未使用 id 作為其主鍵,或者您希望使用不同的欄位尋找關聯的模型,您可以將第三個參數傳遞給 belongsTo 方法,指定父表格的自訂鍵

1/**
2 * Get the user that owns the phone.
3 */
4public function user(): BelongsTo
5{
6 return $this->belongsTo(User::class, 'foreign_key', 'owner_key');
7}

一對多 / Has Many

一對多關聯用於定義單一模型是多個子模型的父項的關聯。例如,一篇部落格文章可能有無數個評論。與所有其他 Eloquent 關聯一樣,一對多關聯是透過在您的 Eloquent 模型上定義方法來定義的

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\HasMany;
7 
8class Post extends Model
9{
10 /**
11 * Get the comments for the blog post.
12 */
13 public function comments(): HasMany
14 {
15 return $this->hasMany(Comment::class);
16 }
17}

請記住,Eloquent 將自動為 Comment 模型判斷正確的外鍵欄位。依照慣例,Eloquent 將採用父模型的「蛇式命名」名稱,並在其後加上 _id。因此,在本例中,Eloquent 將假設 Comment 模型上的外鍵欄位是 post_id

一旦定義了關聯方法,我們就可以透過存取 comments 屬性來存取相關評論的集合。請記住,由於 Eloquent 提供「動態關聯屬性」,我們可以存取關聯方法,就像它們被定義為模型上的屬性一樣

1use App\Models\Post;
2 
3$comments = Post::find(1)->comments;
4 
5foreach ($comments as $comment) {
6 // ...
7}

由於所有關聯也充當查詢建構器,您可以透過呼叫 comments 方法並繼續將條件鏈接到查詢,從而向關聯查詢新增更多約束

1$comment = Post::find(1)->comments()
2 ->where('title', 'foo')
3 ->first();

hasOne 方法類似,您也可以透過將額外參數傳遞給 hasMany 方法來覆寫外鍵和本地鍵

1return $this->hasMany(Comment::class, 'foreign_key');
2 
3return $this->hasMany(Comment::class, 'foreign_key', 'local_key');

在子模型上自動填充父模型

即使在使用 Eloquent 預先載入時,如果您在循環遍歷子模型的同時嘗試從子模型存取父模型,也可能會出現「N + 1」查詢問題

1$posts = Post::with('comments')->get();
2 
3foreach ($posts as $post) {
4 foreach ($post->comments as $comment) {
5 echo $comment->post->title;
6 }
7}

在上面的範例中,引入了「N + 1」查詢問題,因為即使為每個 Post 模型預先載入了評論,Eloquent 也不會自動在每個子 Comment 模型上填充父 Post

如果您希望 Eloquent 自動將父模型填充到其子模型上,您可以在定義 hasMany 關聯時呼叫 chaperone 方法

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\HasMany;
7 
8class Post extends Model
9{
10 /**
11 * Get the comments for the blog post.
12 */
13 public function comments(): HasMany
14 {
15 return $this->hasMany(Comment::class)->chaperone();
16 }
17}

或者,如果您想在執行時選擇加入自動父項填充,您可以在預先載入關聯時呼叫 chaperone 模型

1use App\Models\Post;
2 
3$posts = Post::with([
4 'comments' => fn ($comments) => $comments->chaperone(),
5])->get();

一對多(反向)/ Belongs To

現在我們已經可以存取一篇貼文的所有留言,讓我們定義一個關聯,讓留言可以存取其父貼文。若要定義 hasMany 關聯的反向關聯,請在子模型上定義一個關聯方法,該方法呼叫 belongsTo 方法

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\BelongsTo;
7 
8class Comment extends Model
9{
10 /**
11 * Get the post that owns the comment.
12 */
13 public function post(): BelongsTo
14 {
15 return $this->belongsTo(Post::class);
16 }
17}

一旦關聯被定義,我們可以透過存取 post「動態關聯屬性」來檢索留言的父貼文

1use App\Models\Comment;
2 
3$comment = Comment::find(1);
4 
5return $comment->post->title;

在上面的範例中,Eloquent 將嘗試尋找一個 Post 模型,其 idComment 模型上的 post_id 欄位匹配。

Eloquent 透過檢查關聯方法的名稱,並在方法名稱後綴加上 _ 以及父模型主鍵欄位的名稱,來決定預設的外鍵名稱。因此,在這個範例中,Eloquent 會假設 Post 模型在 comments 資料表上的外鍵是 post_id

然而,如果您的關聯外鍵不遵循這些慣例,您可以將自訂外鍵名稱作為第二個參數傳遞給 belongsTo 方法

1/**
2 * Get the post that owns the comment.
3 */
4public function post(): BelongsTo
5{
6 return $this->belongsTo(Post::class, 'foreign_key');
7}

如果您的父模型未使用 id 作為其主鍵,或者您希望使用不同的欄位來尋找關聯模型,您可以將第三個參數傳遞給 belongsTo 方法,指定您父資料表的自訂鍵

1/**
2 * Get the post that owns the comment.
3 */
4public function post(): BelongsTo
5{
6 return $this->belongsTo(Post::class, 'foreign_key', 'owner_key');
7}

預設模型

belongsTohasOnehasOneThroughmorphOne 關聯允許您定義一個預設模型,如果給定的關聯為 null,將會返回該模型。這種模式通常被稱為 Null Object 模式,並且可以幫助移除您程式碼中的條件檢查。在以下範例中,如果沒有使用者附加到 Post 模型,user 關聯將返回一個空的 App\Models\User 模型

1/**
2 * Get the author of the post.
3 */
4public function user(): BelongsTo
5{
6 return $this->belongsTo(User::class)->withDefault();
7}

若要使用屬性填充預設模型,您可以將陣列或閉包傳遞給 withDefault 方法

1/**
2 * Get the author of the post.
3 */
4public function user(): BelongsTo
5{
6 return $this->belongsTo(User::class)->withDefault([
7 'name' => 'Guest Author',
8 ]);
9}
10 
11/**
12 * Get the author of the post.
13 */
14public function user(): BelongsTo
15{
16 return $this->belongsTo(User::class)->withDefault(function (User $user, Post $post) {
17 $user->name = 'Guest Author';
18 });
19}

查詢 Belongs To 關聯

當查詢「belongs to」關聯的子項時,您可以手動建立 where 子句,以檢索對應的 Eloquent 模型

1use App\Models\Post;
2 
3$posts = Post::where('user_id', $user->id)->get();

然而,您可能會發現使用 whereBelongsTo 方法更方便,它會自動為給定的模型確定正確的關聯和外鍵

1$posts = Post::whereBelongsTo($user)->get();

您也可以將 集合 實例提供給 whereBelongsTo 方法。這樣做時,Laravel 將檢索屬於集合中任何父模型的模型

1$users = User::where('vip', true)->get();
2 
3$posts = Post::whereBelongsTo($users)->get();

預設情況下,Laravel 將根據模型的類別名稱確定與給定模型相關聯的關聯;但是,您可以透過將關聯名稱作為第二個參數提供給 whereBelongsTo 方法來手動指定關聯名稱

1$posts = Post::whereBelongsTo($user, 'author')->get();

Has One of Many

有時,一個模型可能有很多相關模型,但您希望輕鬆檢索關聯的「最新」或「最舊」相關模型。例如,User 模型可能與許多 Order 模型相關,但您想要定義一種方便的方式來與使用者下的最新訂單互動。您可以使用 hasOne 關聯類型結合 ofMany 方法來完成此操作

1/**
2 * Get the user's most recent order.
3 */
4public function latestOrder(): HasOne
5{
6 return $this->hasOne(Order::class)->latestOfMany();
7}

同樣地,您可以定義一個方法來檢索關聯的「最舊」或第一個相關模型

1/**
2 * Get the user's oldest order.
3 */
4public function oldestOrder(): HasOne
5{
6 return $this->hasOne(Order::class)->oldestOfMany();
7}

預設情況下,latestOfManyoldestOfMany 方法將根據模型的主鍵檢索最新或最舊的相關模型,該主鍵必須是可排序的。然而,有時您可能希望使用不同的排序標準從更大的關聯中檢索單一模型。

例如,使用 ofMany 方法,您可以檢索使用者最昂貴的訂單。ofMany 方法接受可排序的欄位作為其第一個參數,以及在查詢相關模型時要應用的聚合函數(minmax

1/**
2 * Get the user's largest order.
3 */
4public function largestOrder(): HasOne
5{
6 return $this->hasOne(Order::class)->ofMany('price', 'max');
7}

由於 PostgreSQL 不支援對 UUID 欄位執行 MAX 函數,因此目前無法將 one-of-many 關聯與 PostgreSQL UUID 欄位結合使用。

將「Many」關聯轉換為 Has One 關聯

通常,當使用 latestOfManyoldestOfManyofMany 方法檢索單一模型時,您已經為同一個模型定義了「has many」關聯。為了方便起見,Laravel 允許您透過在關聯上調用 one 方法,輕鬆地將此關聯轉換為「has one」關聯

1/**
2 * Get the user's orders.
3 */
4public function orders(): HasMany
5{
6 return $this->hasMany(Order::class);
7}
8 
9/**
10 * Get the user's largest order.
11 */
12public function largestOrder(): HasOne
13{
14 return $this->orders()->one()->ofMany('price', 'max');
15}

進階 Has One of Many 關聯

可以建構更進階的「has one of many」關聯。例如,Product 模型可能有很多相關的 Price 模型,即使在發布新價格後,這些模型仍會保留在系統中。此外,產品的新價格資料可能會提前發布,以便在未來日期透過 published_at 欄位生效。

因此,總而言之,我們需要檢索最新的已發布價格,其中發布日期不在未來。此外,如果兩個價格具有相同的發布日期,我們將優先選擇 ID 較大的價格。為了實現這一點,我們必須將一個陣列傳遞給 ofMany 方法,其中包含確定最新價格的可排序欄位。此外,將提供一個閉包作為 ofMany 方法的第二個參數。此閉包將負責將額外的發布日期約束添加到關聯查詢中

1/**
2 * Get the current pricing for the product.
3 */
4public function currentPricing(): HasOne
5{
6 return $this->hasOne(Price::class)->ofMany([
7 'published_at' => 'max',
8 'id' => 'max',
9 ], function (Builder $query) {
10 $query->where('published_at', '<', now());
11 });
12}

Has One Through

「has-one-through」關聯定義了與另一個模型的一對一關聯。然而,這種關聯表示宣告模型可以透過第三個模型與另一個模型的一個實例匹配。

例如,在汽車維修廠應用程式中,每個 Mechanic 模型可能與一個 Car 模型相關聯,並且每個 Car 模型可能與一個 Owner 模型相關聯。雖然技工和車主在資料庫中沒有直接關聯,但技工可以透過 Car 模型存取車主。讓我們看看定義這種關聯所需的資料表

1mechanics
2 id - integer
3 name - string
4
5cars
6 id - integer
7 model - string
8 mechanic_id - integer
9
10owners
11 id - integer
12 name - string
13 car_id - integer

現在我們已經檢視了關聯的資料表結構,讓我們在 Mechanic 模型上定義關聯

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\HasOneThrough;
7 
8class Mechanic extends Model
9{
10 /**
11 * Get the car's owner.
12 */
13 public function carOwner(): HasOneThrough
14 {
15 return $this->hasOneThrough(Owner::class, Car::class);
16 }
17}

傳遞給 hasOneThrough 方法的第一個參數是我們希望存取的最終模型的名稱,而第二個參數是中間模型的名稱。

或者,如果相關關聯已經在關聯中涉及的所有模型上定義,您可以透過調用 through 方法並提供這些關聯的名稱,流暢地定義「has-one-through」關聯。例如,如果 Mechanic 模型具有 cars 關聯,並且 Car 模型具有 owner 關聯,您可以定義一個連接技工和車主的「has-one-through」關聯,如下所示

1// String based syntax...
2return $this->through('cars')->has('owner');
3 
4// Dynamic syntax...
5return $this->throughCars()->hasOwner();

鍵慣例

在執行關聯的查詢時,將使用典型的 Eloquent 外鍵慣例。如果您想自訂關聯的鍵,您可以將它們作為第三個和第四個參數傳遞給 hasOneThrough 方法。第三個參數是中間模型上的外鍵名稱。第四個參數是最終模型上的外鍵名稱。第五個參數是本地鍵,而第六個參數是中間模型的本地鍵

1class Mechanic extends Model
2{
3 /**
4 * Get the car's owner.
5 */
6 public function carOwner(): HasOneThrough
7 {
8 return $this->hasOneThrough(
9 Owner::class,
10 Car::class,
11 'mechanic_id', // Foreign key on the cars table...
12 'car_id', // Foreign key on the owners table...
13 'id', // Local key on the mechanics table...
14 'id' // Local key on the cars table...
15 );
16 }
17}

或者,如前所述,如果相關關聯已經在關聯中涉及的所有模型上定義,您可以透過調用 through 方法並提供這些關聯的名稱,流暢地定義「has-one-through」關聯。這種方法提供了重用已在現有關聯上定義的鍵慣例的優勢

1// String based syntax...
2return $this->through('cars')->has('owner');
3 
4// Dynamic syntax...
5return $this->throughCars()->hasOwner();

Has Many Through

「has-many-through」關聯提供了一種透過中間關聯存取遠端關聯的便捷方式。例如,假設我們正在建構像 Laravel Cloud 這樣的部署平台。Application 模型可以透過中間 Environment 模型存取許多 Deployment 模型。使用此範例,您可以輕鬆收集給定應用程式的所有部署。讓我們看看定義這種關聯所需的資料表

1applications
2 id - integer
3 name - string
4
5environments
6 id - integer
7 application_id - integer
8 name - string
9
10deployments
11 id - integer
12 environment_id - integer
13 commit_hash - string

現在我們已經檢視了關聯的資料表結構,讓我們在 Application 模型上定義關聯

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\HasManyThrough;
7 
8class Application extends Model
9{
10 /**
11 * Get all of the deployments for the application.
12 */
13 public function deployments(): HasManyThrough
14 {
15 return $this->hasManyThrough(Deployment::class, Environment::class);
16 }
17}

傳遞給 hasManyThrough 方法的第一個參數是我們希望存取的最終模型的名稱,而第二個參數是中間模型的名稱。

或者,如果相關關聯已經在關聯中涉及的所有模型上定義,您可以透過調用 through 方法並提供這些關聯的名稱,流暢地定義「has-many-through」關聯。例如,如果 Application 模型具有 environments 關聯,並且 Environment 模型具有 deployments 關聯,您可以定義一個連接應用程式和部署的「has-many-through」關聯,如下所示

1// String based syntax...
2return $this->through('environments')->has('deployments');
3 
4// Dynamic syntax...
5return $this->throughEnvironments()->hasDeployments();

雖然 Deployment 模型的資料表不包含 application_id 欄位,但 hasManyThrough 關聯提供了透過 $application->deployments 存取應用程式部署的功能。為了檢索這些模型,Eloquent 會檢查中間 Environment 模型的資料表上的 application_id 欄位。在找到相關的環境 ID 後,它們會被用於查詢 Deployment 模型的資料表。

鍵慣例

在執行關聯的查詢時,將使用典型的 Eloquent 外鍵慣例。如果您想自訂關聯的鍵,您可以將它們作為第三個和第四個參數傳遞給 hasManyThrough 方法。第三個參數是中間模型上的外鍵名稱。第四個參數是最終模型上的外鍵名稱。第五個參數是本地鍵,而第六個參數是中間模型的本地鍵

1class Application extends Model
2{
3 public function deployments(): HasManyThrough
4 {
5 return $this->hasManyThrough(
6 Deployment::class,
7 Environment::class,
8 'application_id', // Foreign key on the environments table...
9 'environment_id', // Foreign key on the deployments table...
10 'id', // Local key on the applications table...
11 'id' // Local key on the environments table...
12 );
13 }
14}

或者,如前所述,如果相關關聯已經在關聯中涉及的所有模型上定義,您可以透過調用 through 方法並提供這些關聯的名稱,流暢地定義「has-many-through」關聯。這種方法提供了重用已在現有關聯上定義的鍵慣例的優勢

1// String based syntax...
2return $this->through('environments')->has('deployments');
3 
4// Dynamic syntax...
5return $this->throughEnvironments()->hasDeployments();

作用域關聯

在模型中新增額外的方法來約束關聯是很常見的。例如,您可能會在 User 模型中新增一個 featuredPosts 方法,該方法使用額外的 where 約束來約束更廣泛的 posts 關聯

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\HasMany;
7 
8class User extends Model
9{
10 /**
11 * Get the user's posts.
12 */
13 public function posts(): HasMany
14 {
15 return $this->hasMany(Post::class)->latest();
16 }
17 
18 /**
19 * Get the user's featured posts.
20 */
21 public function featuredPosts(): HasMany
22 {
23 return $this->posts()->where('featured', true);
24 }
25}

然而,如果您嘗試透過 featuredPosts 方法建立模型,則其 featured 屬性將不會設定為 true。如果您希望透過關聯方法建立模型,並且還想指定應新增到透過該關聯建立的所有模型的屬性,您可以在建構關聯查詢時使用 withAttributes 方法

1/**
2 * Get the user's featured posts.
3 */
4public function featuredPosts(): HasMany
5{
6 return $this->posts()->withAttributes(['featured' => true]);
7}

withAttributes 方法將使用給定的屬性將 where 子句約束新增到查詢中,並且它還會將給定的屬性新增到透過關聯方法建立的任何模型

1$post = $user->featuredPosts()->create(['title' => 'Featured Post']);
2 
3$post->featured; // true

多對多關聯

多對多關聯比 hasOnehasMany 關聯稍微複雜。多對多關聯的一個範例是使用者擁有許多角色,而這些角色也由應用程式中的其他使用者共享。例如,使用者可能會被指派「作者」和「編輯」的角色;然而,這些角色也可能被指派給其他使用者。因此,一個使用者擁有許多角色,而一個角色擁有許多使用者。

資料表結構

若要定義這種關聯,需要三個資料庫表:usersrolesrole_userrole_user 資料表從相關模型名稱的字母順序衍生而來,並且包含 user_idrole_id 欄位。此資料表用作連結使用者和角色的中間表。

請記住,由於一個角色可以屬於許多使用者,我們不能簡單地在 roles 資料表上放置 user_id 欄位。這將意味著一個角色只能屬於一個使用者。為了提供角色可以指派給多個使用者的支援,需要 role_user 資料表。我們可以像這樣總結關聯的資料表結構

1users
2 id - integer
3 name - string
4
5roles
6 id - integer
7 name - string
8
9role_user
10 user_id - integer
11 role_id - integer

模型結構

多對多關聯透過編寫一個返回 belongsToMany 方法結果的方法來定義。belongsToMany 方法由您應用程式的所有 Eloquent 模型使用的 Illuminate\Database\Eloquent\Model 基礎類別提供。例如,讓我們在我們的 User 模型上定義一個 roles 方法。傳遞給此方法的第一個參數是相關模型類別的名稱

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\BelongsToMany;
7 
8class User extends Model
9{
10 /**
11 * The roles that belong to the user.
12 */
13 public function roles(): BelongsToMany
14 {
15 return $this->belongsToMany(Role::class);
16 }
17}

一旦關聯被定義,您可以使用 roles 動態關聯屬性存取使用者的角色

1use App\Models\User;
2 
3$user = User::find(1);
4 
5foreach ($user->roles as $role) {
6 // ...
7}

由於所有關聯也充當查詢建構器,您可以透過調用 roles 方法並繼續將條件鏈接到查詢中,來對關聯查詢新增進一步的約束

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

為了確定關聯中間資料表的資料表名稱,Eloquent 將按字母順序聯接兩個相關模型名稱。但是,您可以自由覆蓋此慣例。您可以透過將第二個參數傳遞給 belongsToMany 方法來執行此操作

1return $this->belongsToMany(Role::class, 'role_user');

除了自訂中間資料表的名稱之外,您還可以透過將額外的參數傳遞給 belongsToMany 方法來自訂資料表上鍵的欄位名稱。第三個參數是您在其中定義關聯的模型的外鍵名稱,而第四個參數是您要聯接到的模型的外鍵名稱

1return $this->belongsToMany(Role::class, 'role_user', 'user_id', 'role_id');

定義關聯的反向

若要定義多對多關聯的「反向」關聯,您應該在相關模型上定義一個方法,該方法也返回 belongsToMany 方法的結果。為了完成我們的使用者/角色範例,讓我們在 Role 模型上定義 users 方法

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\BelongsToMany;
7 
8class Role extends Model
9{
10 /**
11 * The users that belong to the role.
12 */
13 public function users(): BelongsToMany
14 {
15 return $this->belongsToMany(User::class);
16 }
17}

如您所見,關聯的定義與其 User 模型對應項完全相同,但引用 App\Models\User 模型除外。由於我們正在重複使用 belongsToMany 方法,因此在定義多對多關聯的「反向」關聯時,所有常用的資料表和鍵自訂選項都可用。

檢索中間表欄位

正如您已經了解到的,使用多對多關聯需要中間資料表的存在。Eloquent 提供了一些非常有用的方法來與此資料表互動。例如,假設我們的 User 模型有許多與之相關的 Role 模型。在存取此關聯後,我們可以使用模型上的 pivot 屬性存取中間資料表

1use App\Models\User;
2 
3$user = User::find(1);
4 
5foreach ($user->roles as $role) {
6 echo $role->pivot->created_at;
7}

請注意,我們檢索的每個 Role 模型都會自動被賦予一個 pivot 屬性。此屬性包含一個代表中介資料表的模型。

預設情況下,只有模型鍵會出現在 pivot 模型上。如果您的中介資料表包含額外的屬性,您必須在定義關聯時指定它們。

1return $this->belongsToMany(Role::class)->withPivot('active', 'created_by');

如果您希望您的中介資料表擁有 created_atupdated_at 時間戳記,並且由 Eloquent 自動維護,請在定義關聯時呼叫 withTimestamps 方法。

1return $this->belongsToMany(Role::class)->withTimestamps();

使用 Eloquent 自動維護時間戳記的中介資料表,必須同時擁有 created_atupdated_at 時間戳記欄位。

自訂 pivot 屬性名稱

如先前所述,可以透過 pivot 屬性在模型上存取來自中介資料表的屬性。但是,您可以自由地自訂此屬性的名稱,以更好地反映其在應用程式中的用途。

例如,如果您的應用程式包含可能訂閱 Podcast 的使用者,您可能在使用者和 Podcast 之間存在多對多關聯。在這種情況下,您可能希望將您的中介資料表屬性重新命名為 subscription 而不是 pivot。這可以使用在定義關聯時的 as 方法來完成。

1return $this->belongsToMany(Podcast::class)
2 ->as('subscription')
3 ->withTimestamps();

一旦指定了自訂的中介資料表屬性,您就可以使用自訂的名稱來存取中介資料表資料。

1$users = User::with('podcasts')->get();
2 
3foreach ($users->flatMap->podcasts as $podcast) {
4 echo $podcast->subscription->created_at;
5}

透過中間表欄位過濾查詢

您也可以使用 wherePivotwherePivotInwherePivotNotInwherePivotBetweenwherePivotNotBetweenwherePivotNullwherePivotNotNull 方法在定義關聯時,過濾 belongsToMany 關聯查詢所傳回的結果。

1return $this->belongsToMany(Role::class)
2 ->wherePivot('approved', 1);
3 
4return $this->belongsToMany(Role::class)
5 ->wherePivotIn('priority', [1, 2]);
6 
7return $this->belongsToMany(Role::class)
8 ->wherePivotNotIn('priority', [1, 2]);
9 
10return $this->belongsToMany(Podcast::class)
11 ->as('subscriptions')
12 ->wherePivotBetween('created_at', ['2020-01-01 00:00:00', '2020-12-31 00:00:00']);
13 
14return $this->belongsToMany(Podcast::class)
15 ->as('subscriptions')
16 ->wherePivotNotBetween('created_at', ['2020-01-01 00:00:00', '2020-12-31 00:00:00']);
17 
18return $this->belongsToMany(Podcast::class)
19 ->as('subscriptions')
20 ->wherePivotNull('expired_at');
21 
22return $this->belongsToMany(Podcast::class)
23 ->as('subscriptions')
24 ->wherePivotNotNull('expired_at');

wherePivot 會將 where 子句約束添加到查詢中,但在透過定義的關聯建立新模型時,不會新增指定的值。如果您需要查詢並使用特定的 pivot 值建立關聯,您可以使用 withPivotValue 方法。

1return $this->belongsToMany(Role::class)
2 ->withPivotValue('approved', 1);

透過中間表欄位排序查詢

您可以使用 orderByPivot 方法,排序 belongsToMany 關聯查詢所傳回的結果。在以下範例中,我們將檢索使用者所有最新的徽章。

1return $this->belongsToMany(Badge::class)
2 ->where('rank', 'gold')
3 ->orderByPivot('created_at', 'desc');

定義自訂中間表模型

如果您想定義一個自訂模型來表示您的多對多關聯的中介資料表,您可以在定義關聯時呼叫 using 方法。自訂樞紐模型讓您有機會在樞紐模型上定義額外的行為,例如方法和類型轉換。

自訂多對多樞紐模型應擴展 Illuminate\Database\Eloquent\Relations\Pivot 類別,而自訂多型多對多樞紐模型應擴展 Illuminate\Database\Eloquent\Relations\MorphPivot 類別。例如,我們可以定義一個使用自訂 RoleUser 樞紐模型的 Role 模型。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\BelongsToMany;
7 
8class Role extends Model
9{
10 /**
11 * The users that belong to the role.
12 */
13 public function users(): BelongsToMany
14 {
15 return $this->belongsToMany(User::class)->using(RoleUser::class);
16 }
17}

在定義 RoleUser 模型時,您應該擴展 Illuminate\Database\Eloquent\Relations\Pivot 類別。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Relations\Pivot;
6 
7class RoleUser extends Pivot
8{
9 // ...
10}

樞紐模型可能無法使用 SoftDeletes trait。如果您需要軟刪除樞紐記錄,請考慮將您的樞紐模型轉換為實際的 Eloquent 模型。

自訂樞紐模型和遞增 ID

如果您已定義一個使用自訂樞紐模型的多對多關聯,並且該樞紐模型具有自動遞增的主鍵,您應確保您的自訂樞紐模型類別定義一個設定為 trueincrementing 屬性。

1/**
2 * Indicates if the IDs are auto-incrementing.
3 *
4 * @var bool
5 */
6public $incrementing = true;

多態關聯

多型關聯允許子模型使用單一關聯屬於多種類型的模型。例如,假設您正在建構一個允許使用者分享部落格文章和影片的應用程式。在這樣的應用程式中,Comment 模型可能同時屬於 PostVideo 模型。

一對一(多型)

資料表結構

一對一多型關聯與典型的一對一關聯類似;但是,子模型可以使用單一關聯屬於多種類型的模型。例如,部落格 PostUser 可能與 Image 模型共享多型關聯。使用一對一多型關聯可讓您擁有一個包含唯一圖片的單一資料表,該資料表可以與文章和使用者關聯。首先,讓我們檢查資料表結構。

1posts
2 id - integer
3 name - string
4
5users
6 id - integer
7 name - string
8
9images
10 id - integer
11 url - string
12 imageable_id - integer
13 imageable_type - string

請注意 images 資料表上的 imageable_idimageable_type 欄位。imageable_id 欄位將包含文章或使用者的 ID 值,而 imageable_type 欄位將包含父模型類別名稱。imageable_type 欄位供 Eloquent 用於決定在存取 imageable 關聯時要傳回哪種「類型」的父模型。在這種情況下,該欄位將包含 App\Models\PostApp\Models\User

模型結構

接下來,讓我們檢查建構此關聯所需的模型定義。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\MorphTo;
7 
8class Image extends Model
9{
10 /**
11 * Get the parent imageable model (user or post).
12 */
13 public function imageable(): MorphTo
14 {
15 return $this->morphTo();
16 }
17}
18 
19use Illuminate\Database\Eloquent\Model;
20use Illuminate\Database\Eloquent\Relations\MorphOne;
21 
22class Post extends Model
23{
24 /**
25 * Get the post's image.
26 */
27 public function image(): MorphOne
28 {
29 return $this->morphOne(Image::class, 'imageable');
30 }
31}
32 
33use Illuminate\Database\Eloquent\Model;
34use Illuminate\Database\Eloquent\Relations\MorphOne;
35 
36class User extends Model
37{
38 /**
39 * Get the user's image.
40 */
41 public function image(): MorphOne
42 {
43 return $this->morphOne(Image::class, 'imageable');
44 }
45}

檢索關聯

一旦您的資料庫表格和模型被定義,您可以透過您的模型存取關聯。例如,要檢索文章的圖片,我們可以存取 image 動態關聯屬性。

1use App\Models\Post;
2 
3$post = Post::find(1);
4 
5$image = $post->image;

您可以透過存取執行 morphTo 呼叫的方法名稱來檢索多型模型的父模型。在這種情況下,它是 Image 模型上的 imageable 方法。因此,我們將作為動態關聯屬性存取該方法。

1use App\Models\Image;
2 
3$image = Image::find(1);
4 
5$imageable = $image->imageable;

Image 模型上的 imageable 關聯將傳回 PostUser 實例,具體取決於哪種類型的模型擁有該圖片。

鍵慣例

如有必要,您可以指定多型子模型使用的「id」和「type」欄位的名稱。如果您這樣做,請確保您始終將關聯的名稱作為 morphTo 方法的第一個參數傳遞。通常,此值應與方法名稱匹配,因此您可以使用 PHP 的 __FUNCTION__ 常數。

1/**
2 * Get the model that the image belongs to.
3 */
4public function imageable(): MorphTo
5{
6 return $this->morphTo(__FUNCTION__, 'imageable_type', 'imageable_id');
7}

一對多(多型)

資料表結構

一對多型關聯與典型的一對多關聯類似;但是,子模型可以使用單一關聯屬於多種類型的模型。例如,假設您的應用程式的使用者可以「評論」文章和影片。使用多型關聯,您可以使用單一 comments 資料表來包含文章和影片的評論。首先,讓我們檢查建構此關聯所需的資料表結構。

1posts
2 id - integer
3 title - string
4 body - text
5
6videos
7 id - integer
8 title - string
9 url - string
10
11comments
12 id - integer
13 body - text
14 commentable_id - integer
15 commentable_type - string

模型結構

接下來,讓我們檢查建構此關聯所需的模型定義。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\MorphTo;
7 
8class Comment extends Model
9{
10 /**
11 * Get the parent commentable model (post or video).
12 */
13 public function commentable(): MorphTo
14 {
15 return $this->morphTo();
16 }
17}
18 
19use Illuminate\Database\Eloquent\Model;
20use Illuminate\Database\Eloquent\Relations\MorphMany;
21 
22class Post extends Model
23{
24 /**
25 * Get all of the post's comments.
26 */
27 public function comments(): MorphMany
28 {
29 return $this->morphMany(Comment::class, 'commentable');
30 }
31}
32 
33use Illuminate\Database\Eloquent\Model;
34use Illuminate\Database\Eloquent\Relations\MorphMany;
35 
36class Video extends Model
37{
38 /**
39 * Get all of the video's comments.
40 */
41 public function comments(): MorphMany
42 {
43 return $this->morphMany(Comment::class, 'commentable');
44 }
45}

檢索關聯

一旦您的資料庫表格和模型被定義,您可以透過模型的動態關聯屬性存取關聯。例如,要存取文章的所有評論,我們可以使用 comments 動態屬性。

1use App\Models\Post;
2 
3$post = Post::find(1);
4 
5foreach ($post->comments as $comment) {
6 // ...
7}

您也可以透過存取執行 morphTo 呼叫的方法名稱來檢索多型子模型的父模型。在這種情況下,它是 Comment 模型上的 commentable 方法。因此,我們將作為動態關聯屬性存取該方法,以便存取評論的父模型。

1use App\Models\Comment;
2 
3$comment = Comment::find(1);
4 
5$commentable = $comment->commentable;

Comment 模型上的 commentable 關聯將傳回 PostVideo 實例,具體取決於哪種類型的模型是評論的父模型。

在子模型上自動填充父模型

即使在使用 Eloquent 預先載入時,如果您在循環遍歷子模型的同時嘗試從子模型存取父模型,也可能會出現「N + 1」查詢問題

1$posts = Post::with('comments')->get();
2 
3foreach ($posts as $post) {
4 foreach ($post->comments as $comment) {
5 echo $comment->commentable->title;
6 }
7}

在上面的範例中,引入了「N + 1」查詢問題,因為即使為每個 Post 模型預先載入了評論,Eloquent 也不會自動在每個子 Comment 模型上填充父 Post

如果您希望 Eloquent 自動將父模型 hydration 到其子模型上,您可以在定義 morphMany 關聯時調用 chaperone 方法。

1class Post extends Model
2{
3 /**
4 * Get all of the post's comments.
5 */
6 public function comments(): MorphMany
7 {
8 return $this->morphMany(Comment::class, 'commentable')->chaperone();
9 }
10}

或者,如果您想在執行時選擇加入自動父項填充,您可以在預先載入關聯時呼叫 chaperone 模型

1use App\Models\Post;
2 
3$posts = Post::with([
4 'comments' => fn ($comments) => $comments->chaperone(),
5])->get();

一對多之一(多型)

有時,一個模型可能有多個相關模型,但您希望輕鬆檢索關聯的「最新」或「最舊」相關模型。例如,User 模型可能與多個 Image 模型相關,但您希望定義一種方便的方式來與使用者上傳的最新圖片互動。您可以使用 morphOne 關聯類型與 ofMany 方法來完成此操作。

1/**
2 * Get the user's most recent image.
3 */
4public function latestImage(): MorphOne
5{
6 return $this->morphOne(Image::class, 'imageable')->latestOfMany();
7}

同樣地,您可以定義一個方法來檢索關聯的「最舊」或第一個相關模型

1/**
2 * Get the user's oldest image.
3 */
4public function oldestImage(): MorphOne
5{
6 return $this->morphOne(Image::class, 'imageable')->oldestOfMany();
7}

預設情況下,latestOfManyoldestOfMany 方法將根據模型的主鍵檢索最新或最舊的相關模型,該主鍵必須是可排序的。然而,有時您可能希望使用不同的排序標準從更大的關聯中檢索單一模型。

例如,使用 ofMany 方法,您可以檢索使用者最「喜歡」的圖片。ofMany 方法接受可排序的欄位作為其第一個參數,以及在查詢相關模型時要套用的聚合函數(minmax)。

1/**
2 * Get the user's most popular image.
3 */
4public function bestImage(): MorphOne
5{
6 return $this->morphOne(Image::class, 'imageable')->ofMany('likes', 'max');
7}

可以建構更進階的「一對多之一」關聯。有關更多資訊,請參閱has one of many 文件

多對多(多型)

資料表結構

多對多型關聯比「morph one」和「morph many」關聯稍微複雜。例如,Post 模型和 Video 模型可以與 Tag 模型共享多型關聯。在這種情況下使用多對多型關聯將允許您的應用程式擁有一個包含唯一標籤的單一資料表,該資料表可以與文章或影片關聯。首先,讓我們檢查建構此關聯所需的資料表結構。

1posts
2 id - integer
3 name - string
4
5videos
6 id - integer
7 name - string
8
9tags
10 id - integer
11 name - string
12
13taggables
14 tag_id - integer
15 taggable_id - integer
16 taggable_type - string

在深入探討多型多對多關聯之前,您可能會受益於閱讀關於典型多對多關聯的文件。

模型結構

接下來,我們準備好在模型上定義關聯。PostVideo 模型都將包含一個呼叫基礎 Eloquent 模型類別提供的 morphToMany 方法的 tags 方法。

morphToMany 方法接受相關模型的名稱以及「關聯名稱」。根據我們分配給中介資料表名稱及其包含的鍵的名稱,我們將關聯稱為「taggable」。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\MorphToMany;
7 
8class Post extends Model
9{
10 /**
11 * Get all of the tags for the post.
12 */
13 public function tags(): MorphToMany
14 {
15 return $this->morphToMany(Tag::class, 'taggable');
16 }
17}

定義關聯的反向

接下來,在 Tag 模型上,您應該為其每個可能的父模型定義一個方法。因此,在本範例中,我們將定義一個 posts 方法和一個 videos 方法。這兩種方法都應傳回 morphedByMany 方法的結果。

morphedByMany 方法接受相關模型的名稱以及「關聯名稱」。根據我們分配給中介資料表名稱及其包含的鍵的名稱,我們將關聯稱為「taggable」。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\MorphToMany;
7 
8class Tag extends Model
9{
10 /**
11 * Get all of the posts that are assigned this tag.
12 */
13 public function posts(): MorphToMany
14 {
15 return $this->morphedByMany(Post::class, 'taggable');
16 }
17 
18 /**
19 * Get all of the videos that are assigned this tag.
20 */
21 public function videos(): MorphToMany
22 {
23 return $this->morphedByMany(Video::class, 'taggable');
24 }
25}

檢索關聯

一旦您的資料庫表格和模型被定義,您可以透過您的模型存取關聯。例如,要存取文章的所有標籤,您可以使用 tags 動態關聯屬性。

1use App\Models\Post;
2 
3$post = Post::find(1);
4 
5foreach ($post->tags as $tag) {
6 // ...
7}

您可以透過從多型子模型存取執行 morphedByMany 呼叫的方法名稱來檢索多型關聯的父模型。在這種情況下,它是 Tag 模型上的 postsvideos 方法。

1use App\Models\Tag;
2 
3$tag = Tag::find(1);
4 
5foreach ($tag->posts as $post) {
6 // ...
7}
8 
9foreach ($tag->videos as $video) {
10 // ...
11}

自訂多態型別

預設情況下,Laravel 將使用完全限定的類別名稱來儲存相關模型的「類型」。例如,在上面的多對一關聯範例中,Comment 模型可能屬於 PostVideo 模型,預設的 commentable_type 將分別為 App\Models\PostApp\Models\Video。但是,您可能希望將這些值與應用程式的內部結構分離。

例如,我們可以改用簡單的字串(例如 postvideo)而不是使用模型名稱作為「類型」。這樣做,即使模型被重新命名,資料庫中多型「類型」欄位值仍然有效。

1use Illuminate\Database\Eloquent\Relations\Relation;
2 
3Relation::enforceMorphMap([
4 'post' => 'App\Models\Post',
5 'video' => 'App\Models\Video',
6]);

如果您願意,您可以在 App\Providers\AppServiceProvider 類別的 boot 方法中呼叫 enforceMorphMap 方法,或建立單獨的服務提供者。

您可以使用模型的 getMorphClass 方法在運行時確定給定模型的 morph 別名。相反地,您可以使用 Relation::getMorphedModel 方法確定與 morph 別名關聯的完全限定的類別名稱。

1use Illuminate\Database\Eloquent\Relations\Relation;
2 
3$alias = $post->getMorphClass();
4 
5$class = Relation::getMorphedModel($alias);

當將「morph map」新增到現有應用程式時,資料庫中每個仍然包含完全限定類別的 morphable *_type 欄位值都需要轉換為其「map」名稱。

動態關聯

您可以使用 resolveRelationUsing 方法在運行時定義 Eloquent 模型之間的關聯。雖然通常不建議用於正常的應用程式開發,但在開發 Laravel 套件時,這偶爾可能很有用。

resolveRelationUsing 方法接受所需的關聯名稱作為其第一個參數。傳遞給該方法的第二個參數應是一個閉包,該閉包接受模型實例並傳回有效的 Eloquent 關聯定義。通常,您應該在服務提供者的 boot 方法中配置動態關聯。

1use App\Models\Order;
2use App\Models\Customer;
3 
4Order::resolveRelationUsing('customer', function (Order $orderModel) {
5 return $orderModel->belongsTo(Customer::class, 'customer_id');
6});

在定義動態關聯時,始終為 Eloquent 關聯方法提供明確的鍵名參數。

查詢關聯

由於所有 Eloquent 關聯都是透過方法定義的,因此您可以呼叫這些方法以取得關聯的實例,而無需實際執行查詢來載入相關模型。此外,所有類型的 Eloquent 關聯也充當查詢建構器,讓您可以繼續將約束鏈接到關聯查詢,然後再最終執行針對資料庫的 SQL 查詢。

例如,假設一個部落格應用程式,其中 User 模型有多個相關聯的 Post 模型。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\HasMany;
7 
8class User extends Model
9{
10 /**
11 * Get all of the posts for the user.
12 */
13 public function posts(): HasMany
14 {
15 return $this->hasMany(Post::class);
16 }
17}

您可以查詢 posts 關聯,並向關聯新增額外的約束,如下所示。

1use App\Models\User;
2 
3$user = User::find(1);
4 
5$user->posts()->where('active', 1)->get();

您可以在關聯上使用任何 Laravel 查詢建構器的方法,因此請務必瀏覽查詢建構器文件,以了解所有可用的方法。

在關聯後鏈接 orWhere 子句

如上面的範例所示,您可以自由地在查詢關聯時向關聯新增額外的約束。但是,在將 orWhere 子句鏈接到關聯時要小心,因為 orWhere 子句將在邏輯上與關聯約束位於同一層級。

1$user->posts()
2 ->where('active', 1)
3 ->orWhere('votes', '>=', 100)
4 ->get();

上面的範例將產生以下 SQL。如您所見,or 子句指示查詢傳回任何投票數大於 100 的文章。查詢不再約束於特定使用者。

1select *
2from posts
3where user_id = ? and active = 1 or votes >= 100

在大多數情況下,您應該使用邏輯分組來將括號之間的條件檢查分組。

1use Illuminate\Database\Eloquent\Builder;
2 
3$user->posts()
4 ->where(function (Builder $query) {
5 return $query->where('active', 1)
6 ->orWhere('votes', '>=', 100);
7 })
8 ->get();

上面的範例將產生以下 SQL。請注意,邏輯分組已正確地將約束分組,並且查詢仍然約束於特定使用者。

1select *
2from posts
3where user_id = ? and (active = 1 or votes >= 100)

關聯方法與動態屬性

如果您不需要向 Eloquent 關聯查詢新增額外的約束,您可以像存取屬性一樣存取關聯。例如,繼續使用我們的 UserPost 範例模型,我們可以像這樣存取使用者的所有文章。

1use App\Models\User;
2 
3$user = User::find(1);
4 
5foreach ($user->posts as $post) {
6 // ...
7}

動態關聯屬性執行「延遲載入」,這表示它們只會在您實際存取它們時載入其關聯資料。因此,開發人員通常使用預先載入來預先載入他們知道在載入模型後會被存取的關聯。預先載入顯著減少了載入模型關聯時必須執行的 SQL 查詢。

查詢關聯是否存在

在檢索模型記錄時,您可能希望根據關聯的存在來限制您的結果。例如,假設您想要檢索所有至少有一個評論的部落格文章。為此,您可以將關聯的名稱傳遞給 hasorHas 方法。

1use App\Models\Post;
2 
3// Retrieve all posts that have at least one comment...
4$posts = Post::has('comments')->get();

您也可以指定運算子和計數值,以進一步自訂查詢。

1// Retrieve all posts that have three or more comments...
2$posts = Post::has('comments', '>=', 3)->get();

可以使用「點」表示法建構巢狀 has 陳述式。例如,您可以檢索所有至少有一個評論,且該評論至少有一張圖片的文章。

1// Retrieve posts that have at least one comment with images...
2$posts = Post::has('comments.images')->get();

如果您需要更大的彈性,您可以使用 whereHasorWhereHas 方法在您的 has 查詢上定義額外的查詢約束,例如檢查評論的內容。

1use Illuminate\Database\Eloquent\Builder;
2 
3// Retrieve posts with at least one comment containing words like code%...
4$posts = Post::whereHas('comments', function (Builder $query) {
5 $query->where('content', 'like', 'code%');
6})->get();
7 
8// Retrieve posts with at least ten comments containing words like code%...
9$posts = Post::whereHas('comments', function (Builder $query) {
10 $query->where('content', 'like', 'code%');
11}, '>=', 10)->get();

Eloquent 目前不支援跨資料庫查詢關聯是否存在。關聯必須存在於同一個資料庫中。

內聯關聯存在性查詢

如果您想使用附加到關聯查詢的單一簡單 where 條件來查詢關聯是否存在,您可能會發現使用 whereRelationorWhereRelationwhereMorphRelationorWhereMorphRelation 方法更方便。例如,我們可以查詢所有具有未批准評論的文章。

1use App\Models\Post;
2 
3$posts = Post::whereRelation('comments', 'is_approved', false)->get();

當然,與呼叫查詢建構器的 where 方法一樣,您也可以指定運算子。

1$posts = Post::whereRelation(
2 'comments', 'created_at', '>=', now()->subHour()
3)->get();

查詢關聯是否不存在

在檢索模型記錄時,您可能希望根據缺少關聯性來限制結果。例如,假設您想要檢索所有沒有任何評論的部落格文章。為此,您可以將關聯性的名稱傳遞給 doesntHaveorDoesntHave 方法

1use App\Models\Post;
2 
3$posts = Post::doesntHave('comments')->get();

如果您需要更強大的功能,可以使用 whereDoesntHaveorWhereDoesntHave 方法,在您的 doesntHave 查詢中新增額外的查詢約束,例如檢查評論的內容

1use Illuminate\Database\Eloquent\Builder;
2 
3$posts = Post::whereDoesntHave('comments', function (Builder $query) {
4 $query->where('content', 'like', 'code%');
5})->get();

您可以使用「點」記號 (dot notation) 對巢狀關聯性執行查詢。例如,以下查詢將檢索所有沒有評論的文章;但是,來自未被封鎖作者的評論文章將包含在結果中

1use Illuminate\Database\Eloquent\Builder;
2 
3$posts = Post::whereDoesntHave('comments.author', function (Builder $query) {
4 $query->where('banned', 0);
5})->get();

查詢 Morph To 關聯

若要查詢「多態關聯 (morph to)」關係的存在性,您可以使用 whereHasMorphwhereDoesntHaveMorph 方法。這些方法的第一個參數接受關聯性的名稱。接下來,這些方法接受您希望包含在查詢中的相關模型名稱。最後,您可以提供一個閉包 (closure) 來自訂關聯性查詢

1use App\Models\Comment;
2use App\Models\Post;
3use App\Models\Video;
4use Illuminate\Database\Eloquent\Builder;
5 
6// Retrieve comments associated to posts or videos with a title like code%...
7$comments = Comment::whereHasMorph(
8 'commentable',
9 [Post::class, Video::class],
10 function (Builder $query) {
11 $query->where('title', 'like', 'code%');
12 }
13)->get();
14 
15// Retrieve comments associated to posts with a title not like code%...
16$comments = Comment::whereDoesntHaveMorph(
17 'commentable',
18 Post::class,
19 function (Builder $query) {
20 $query->where('title', 'like', 'code%');
21 }
22)->get();

您有時可能需要根據相關多態模型的「類型」新增查詢約束。傳遞給 whereHasMorph 方法的閉包可以接收一個 $type 值作為其第二個參數。此參數允許您檢查正在建立的查詢「類型」

1use Illuminate\Database\Eloquent\Builder;
2 
3$comments = Comment::whereHasMorph(
4 'commentable',
5 [Post::class, Video::class],
6 function (Builder $query, string $type) {
7 $column = $type === Post::class ? 'content' : 'title';
8 
9 $query->where($column, 'like', 'code%');
10 }
11)->get();

有時您可能想要查詢「多態關聯 (morph to)」關係的父模型的子項。您可以使用 whereMorphedTowhereNotMorphedTo 方法來完成此操作,這些方法將自動為給定的模型確定正確的 Morph Type 映射。這些方法的第一個參數接受 morphTo 關聯性的名稱,第二個參數接受相關的父模型

1$comments = Comment::whereMorphedTo('commentable', $post)
2 ->orWhereMorphedTo('commentable', $video)
3 ->get();

您可以提供 * 作為萬用字元值,而不是傳遞可能的 Polymorphic 模型陣列。這將指示 Laravel 從資料庫中檢索所有可能的多態類型。Laravel 將執行額外的查詢以執行此操作

1use Illuminate\Database\Eloquent\Builder;
2 
3$comments = Comment::whereHasMorph('commentable', '*', function (Builder $query) {
4 $query->where('title', 'like', 'foo%');
5})->get();

有時您可能想要計算給定關聯性的相關模型數量,而無需實際載入模型。為了完成此操作,您可以使用 withCount 方法。withCount 方法將在產生的模型上放置一個 {relation}_count 屬性

1use App\Models\Post;
2 
3$posts = Post::withCount('comments')->get();
4 
5foreach ($posts as $post) {
6 echo $post->comments_count;
7}

透過將陣列傳遞給 withCount 方法,您可以為多個關聯性新增「計數」,以及為查詢新增額外的約束

1use Illuminate\Database\Eloquent\Builder;
2 
3$posts = Post::withCount(['votes', 'comments' => function (Builder $query) {
4 $query->where('content', 'like', 'code%');
5}])->get();
6 
7echo $posts[0]->votes_count;
8echo $posts[0]->comments_count;

您也可以為關聯性計數結果設定別名,允許對同一個關聯性進行多次計數

1use Illuminate\Database\Eloquent\Builder;
2 
3$posts = Post::withCount([
4 'comments',
5 'comments as pending_comments_count' => function (Builder $query) {
6 $query->where('approved', false);
7 },
8])->get();
9 
10echo $posts[0]->comments_count;
11echo $posts[0]->pending_comments_count;

延遲計數載入

使用 loadCount 方法,您可以在父模型已經被檢索後載入關聯性計數

1$book = Book::first();
2 
3$book->loadCount('genres');

如果您需要在計數查詢上設定額外的查詢約束,您可以傳遞一個以您希望計數的關聯性為鍵的陣列。陣列值應該是接收查詢建構器 (query builder) 實例的閉包

1$book->loadCount(['reviews' => function (Builder $query) {
2 $query->where('rating', 5);
3}])

關聯性計數和自訂 Select 語句

如果您將 withCountselect 語句結合使用,請確保在 select 方法之後呼叫 withCount

1$posts = Post::select(['title', 'body'])
2 ->withCount('comments')
3 ->get();

其他聚合函式

除了 withCount 方法之外,Eloquent 還提供了 withMinwithMaxwithAvgwithSumwithExists 方法。這些方法將在您的結果模型上放置一個 {relation}_{function}_{column} 屬性

1use App\Models\Post;
2 
3$posts = Post::withSum('comments', 'votes')->get();
4 
5foreach ($posts as $post) {
6 echo $post->comments_sum_votes;
7}

如果您希望使用另一個名稱來存取彙總函數的結果,您可以指定自己的別名

1$posts = Post::withSum('comments as total_comments', 'votes')->get();
2 
3foreach ($posts as $post) {
4 echo $post->total_comments;
5}

loadCount 方法類似,這些方法的延遲版本也可用。這些額外的彙總操作可以在已經檢索到的 Eloquent 模型上執行

1$post = Post::first();
2 
3$post->loadSum('comments', 'votes');

如果您將這些彙總方法與 select 語句結合使用,請確保在 select 方法之後呼叫彙總方法

1$posts = Post::select(['title', 'body'])
2 ->withExists('comments')
3 ->get();

如果您想要預先載入「多態關聯 (morph to)」關係,以及該關係可能傳回的各種實體的相關模型計數,您可以結合使用 with 方法和 morphTo 關係的 morphWithCount 方法。

在這個範例中,假設 PhotoPost 模型可以建立 ActivityFeed 模型。我們將假設 ActivityFeed 模型定義了一個名為 parentable 的「多態關聯 (morph to)」關係,這允許我們檢索給定 ActivityFeed 實例的父 PhotoPost 模型。此外,讓我們假設 Photo 模型「擁有多個 (have many)」Tag 模型,而 Post 模型「擁有多個 (have many)」Comment 模型。

現在,假設我們想要檢索 ActivityFeed 實例,並預先載入每個 ActivityFeed 實例的 parentable 父模型。此外,我們想要檢索與每個父照片相關聯的標籤 (tag) 數量,以及與每個父文章相關聯的評論 (comment) 數量

1use Illuminate\Database\Eloquent\Relations\MorphTo;
2 
3$activities = ActivityFeed::with([
4 'parentable' => function (MorphTo $morphTo) {
5 $morphTo->morphWithCount([
6 Photo::class => ['tags'],
7 Post::class => ['comments'],
8 ]);
9 }])->get();

延遲計數載入

假設我們已經檢索了一組 ActivityFeed 模型,現在我們想要載入與活動饋送 (activity feed) 相關聯的各種 parentable 模型的巢狀關聯性計數。您可以使用 loadMorphCount 方法來完成此操作

1$activities = ActivityFeed::with('parentable')->get();
2 
3$activities->loadMorphCount('parentable', [
4 Photo::class => ['tags'],
5 Post::class => ['comments'],
6]);

預先載入

當將 Eloquent 關聯性作為屬性存取時,相關模型是「延遲載入 (lazy loaded)」。這表示關聯性資料實際上在您第一次存取該屬性之前不會被載入。但是,Eloquent 可以在您查詢父模型時「預先載入 (eager load)」關聯性。預先載入減輕了「N + 1」查詢問題。為了說明 N + 1 查詢問題,請考慮「屬於 (belongs to)」Author 模型的 Book 模型

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\BelongsTo;
7 
8class Book extends Model
9{
10 /**
11 * Get the author that wrote the book.
12 */
13 public function author(): BelongsTo
14 {
15 return $this->belongsTo(Author::class);
16 }
17}

現在,讓我們檢索所有書籍及其作者

1use App\Models\Book;
2 
3$books = Book::all();
4 
5foreach ($books as $book) {
6 echo $book->author->name;
7}

這個迴圈將執行一個查詢來檢索資料庫表格中的所有書籍,然後為每本書執行另一個查詢,以檢索該書的作者。因此,如果我們有 25 本書,上面的程式碼將運行 26 個查詢:一個用於原始書籍,以及 25 個額外查詢來檢索每本書的作者。

幸運的是,我們可以使用預先載入將此操作減少到僅兩個查詢。在建立查詢時,您可以使用 with 方法指定應該預先載入哪些關聯性

1$books = Book::with('author')->get();
2 
3foreach ($books as $book) {
4 echo $book->author->name;
5}

對於此操作,只會執行兩個查詢 - 一個查詢檢索所有書籍,另一個查詢檢索所有書籍的所有作者

1select * from books
2 
3select * from authors where id in (1, 2, 3, 4, 5, ...)

預先載入多個關聯性

有時您可能需要預先載入幾個不同的關聯性。為此,只需將關聯性陣列傳遞給 with 方法即可

1$books = Book::with(['author', 'publisher'])->get();

巢狀預先載入

若要預先載入關聯性的關聯性,您可以使用「點」語法 (dot syntax)。例如,讓我們預先載入所有書籍的作者以及所有作者的個人聯絡方式

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

或者,您可以透過提供巢狀陣列給 with 方法來指定巢狀預先載入的關聯性,這在預先載入多個巢狀關聯性時可能很方便

1$books = Book::with([
2 'author' => [
3 'contacts',
4 'publisher',
5 ],
6])->get();

巢狀預先載入 morphTo 關聯性

如果您想要預先載入 morphTo 關聯性,以及該關係可能傳回的各種實體的巢狀關聯性,您可以結合使用 with 方法和 morphTo 關聯性的 morphWith 方法。為了幫助說明此方法,讓我們考慮以下模型

1<?php
2 
3use Illuminate\Database\Eloquent\Model;
4use Illuminate\Database\Eloquent\Relations\MorphTo;
5 
6class ActivityFeed extends Model
7{
8 /**
9 * Get the parent of the activity feed record.
10 */
11 public function parentable(): MorphTo
12 {
13 return $this->morphTo();
14 }
15}

在這個範例中,假設 EventPhotoPost 模型可以建立 ActivityFeed 模型。此外,假設 Event 模型屬於 Calendar 模型,Photo 模型與 Tag 模型相關聯,而 Post 模型屬於 Author 模型。

使用這些模型定義和關聯性,我們可以檢索 ActivityFeed 模型實例,並預先載入所有 parentable 模型及其各自的巢狀關聯性

1use Illuminate\Database\Eloquent\Relations\MorphTo;
2 
3$activities = ActivityFeed::query()
4 ->with(['parentable' => function (MorphTo $morphTo) {
5 $morphTo->morphWith([
6 Event::class => ['calendar'],
7 Photo::class => ['tags'],
8 Post::class => ['author'],
9 ]);
10 }])->get();

預先載入特定欄位

您可能不總是需要從您正在檢索的關聯性中取得每個欄位。因此,Eloquent 允許您指定您想要檢索的關聯性的哪些欄位

1$books = Book::with('author:id,name,book_id')->get();

使用此功能時,您應始終在您希望檢索的欄位列表中包含 id 欄位和任何相關的外鍵欄位。

預設預先載入

有時您可能希望在檢索模型時始終載入某些關聯性。為了完成此操作,您可以在模型上定義 $with 屬性

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\BelongsTo;
7 
8class Book extends Model
9{
10 /**
11 * The relationships that should always be loaded.
12 *
13 * @var array
14 */
15 protected $with = ['author'];
16 
17 /**
18 * Get the author that wrote the book.
19 */
20 public function author(): BelongsTo
21 {
22 return $this->belongsTo(Author::class);
23 }
24 
25 /**
26 * Get the genre of the book.
27 */
28 public function genre(): BelongsTo
29 {
30 return $this->belongsTo(Genre::class);
31 }
32}

如果您想從單個查詢的 $with 屬性中移除項目,您可以使用 without 方法

1$books = Book::without('author')->get();

如果您想覆蓋單個查詢中 $with 屬性中的所有項目,您可以使用 withOnly 方法

1$books = Book::withOnly('genre')->get();

約束預先載入

有時您可能希望預先載入關聯性,但也為預先載入查詢指定額外的查詢條件。您可以透過將關聯性陣列傳遞給 with 方法來完成此操作,其中陣列鍵是關聯性名稱,陣列值是一個閉包,用於為預先載入查詢新增額外約束

1use App\Models\User;
2use Illuminate\Contracts\Database\Eloquent\Builder;
3 
4$users = User::with(['posts' => function (Builder $query) {
5 $query->where('title', 'like', '%code%');
6}])->get();

在這個範例中,Eloquent 將僅預先載入文章 (post),其中文章的 title 欄位包含單字 code。您可以呼叫其他 查詢建構器 方法來進一步自訂預先載入操作

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

約束 morphTo 關聯性的預先載入

如果您正在預先載入 morphTo 關聯性,Eloquent 將運行多個查詢來獲取每種類型的相關模型。您可以使用 MorphTo 關聯的 constrain 方法為這些查詢中的每一個新增額外約束

1use Illuminate\Database\Eloquent\Relations\MorphTo;
2 
3$comments = Comment::with(['commentable' => function (MorphTo $morphTo) {
4 $morphTo->constrain([
5 Post::class => function ($query) {
6 $query->whereNull('hidden_at');
7 },
8 Video::class => function ($query) {
9 $query->where('type', 'educational');
10 },
11 ]);
12}])->get();

在這個範例中,Eloquent 將僅預先載入尚未隱藏的文章 (post) 和 type 值為「educational」的影片 (video)。

使用關聯性存在性約束預先載入

您有時可能會發現自己需要檢查關聯性的存在性,同時根據相同的條件載入關聯性。例如,您可能希望僅檢索具有符合給定查詢條件的子 Post 模型的 User 模型,同時也預先載入符合條件的文章。您可以使用 withWhereHas 方法來完成此操作

1use App\Models\User;
2 
3$users = User::withWhereHas('posts', function ($query) {
4 $query->where('featured', true);
5})->get();

延遲預先載入

有時您可能需要在父模型已經被檢索後預先載入關聯性。例如,如果您需要動態決定是否載入相關模型,這可能會很有用

1use App\Models\Book;
2 
3$books = Book::all();
4 
5if ($someCondition) {
6 $books->load('author', 'publisher');
7}

如果您需要在預先載入查詢上設定額外的查詢約束,您可以傳遞一個以您希望載入的關聯性為鍵的陣列。陣列值應該是接收查詢實例的閉包實例

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

若要在尚未載入時才載入關聯性,請使用 loadMissing 方法

1$book->loadMissing('author');

巢狀延遲預先載入和 morphTo

如果您想要預先載入 morphTo 關聯性,以及該關係可能傳回的各種實體的巢狀關聯性,您可以使用 loadMorph 方法。

此方法的第一個參數接受 morphTo 關聯性的名稱,第二個參數接受模型/關聯性配對陣列。為了幫助說明此方法,讓我們考慮以下模型

1<?php
2 
3use Illuminate\Database\Eloquent\Model;
4use Illuminate\Database\Eloquent\Relations\MorphTo;
5 
6class ActivityFeed extends Model
7{
8 /**
9 * Get the parent of the activity feed record.
10 */
11 public function parentable(): MorphTo
12 {
13 return $this->morphTo();
14 }
15}

在這個範例中,假設 EventPhotoPost 模型可以建立 ActivityFeed 模型。此外,假設 Event 模型屬於 Calendar 模型,Photo 模型與 Tag 模型相關聯,而 Post 模型屬於 Author 模型。

使用這些模型定義和關聯性,我們可以檢索 ActivityFeed 模型實例,並預先載入所有 parentable 模型及其各自的巢狀關聯性

1$activities = ActivityFeed::with('parentable')
2 ->get()
3 ->loadMorph('parentable', [
4 Event::class => ['calendar'],
5 Photo::class => ['tags'],
6 Post::class => ['author'],
7 ]);

防止延遲載入

如先前討論,預先載入關聯性通常可以為您的應用程式提供顯著的效能優勢。因此,如果您願意,您可以指示 Laravel 始終阻止關聯性的延遲載入。為了完成此操作,您可以調用基礎 Eloquent 模型類別提供的 preventLazyLoading 方法。通常,您應該在應用程式的 AppServiceProvider 類別的 boot 方法中呼叫此方法。

preventLazyLoading 方法接受一個可選的布林值參數,指示是否應阻止延遲載入。例如,您可能希望僅在非生產環境中停用延遲載入,以便您的生產環境即使在生產程式碼中意外存在延遲載入的關聯性時也能繼續正常運行

1use Illuminate\Database\Eloquent\Model;
2 
3/**
4 * Bootstrap any application services.
5 */
6public function boot(): void
7{
8 Model::preventLazyLoading(! $this->app->isProduction());
9}

在阻止延遲載入後,當您的應用程式嘗試延遲載入任何 Eloquent 關聯性時,Eloquent 將拋出 Illuminate\Database\LazyLoadingViolationException 例外。

您可以使用 handleLazyLoadingViolationsUsing 方法自訂延遲載入違規的行為。例如,使用此方法,您可以指示僅記錄延遲載入違規,而不是使用例外中斷應用程式的執行

1Model::handleLazyLoadingViolationUsing(function (Model $model, string $relation) {
2 $class = $model::class;
3 
4 info("Attempted to lazy load [{$relation}] on model [{$class}].");
5});

save 方法

Eloquent 提供了方便的方法,用於將新模型新增到關聯性。例如,也許您需要為文章新增一個新的評論。您可以透過使用關聯性的 save 方法插入評論,而不是手動設定 Comment 模型上的 post_id 屬性

1use App\Models\Comment;
2use App\Models\Post;
3 
4$comment = new Comment(['message' => 'A new comment.']);
5 
6$post = Post::find(1);
7 
8$post->comments()->save($comment);

請注意,我們沒有將 comments 關聯性作為動態屬性存取。相反,我們呼叫了 comments 方法來取得關聯性的實例。save 方法將自動將適當的 post_id 值新增到新的 Comment 模型。

如果您需要儲存多個相關模型,您可以使用 saveMany 方法

1$post = Post::find(1);
2 
3$post->comments()->saveMany([
4 new Comment(['message' => 'A new comment.']),
5 new Comment(['message' => 'Another new comment.']),
6]);

savesaveMany 方法將持久化給定的模型實例,但不會將新持久化的模型新增到已載入到父模型上的任何記憶體中關聯性。如果您計劃在使用 savesaveMany 方法後存取關聯性,您可能希望使用 refresh 方法重新載入模型及其關聯性

1$post->comments()->save($comment);
2 
3$post->refresh();
4 
5// All comments, including the newly saved comment...
6$post->comments;

遞迴儲存模型和關聯性

如果您想要儲存您的模型及其所有相關的關聯,您可以使用 push 方法。在這個範例中,Post 模型以及其留言和留言的作者都會被儲存。

1$post = Post::find(1);
2 
3$post->comments[0]->message = 'Message';
4$post->comments[0]->author->name = 'Author Name';
5 
6$post->push();

pushQuietly 方法可以用於儲存模型及其相關的關聯,而不會觸發任何事件。

1$post->pushQuietly();

create 方法

除了 savesaveMany 方法之外,您也可以使用 create 方法,它接受屬性陣列,建立一個模型,並將其插入資料庫。savecreate 之間的區別在於,save 接受完整的 Eloquent 模型實例,而 create 接受純 PHP array。新建立的模型將會由 create 方法傳回。

1use App\Models\Post;
2 
3$post = Post::find(1);
4 
5$comment = $post->comments()->create([
6 'message' => 'A new comment.',
7]);

您可以使用 createMany 方法來建立多個相關的模型。

1$post = Post::find(1);
2 
3$post->comments()->createMany([
4 ['message' => 'A new comment.'],
5 ['message' => 'Another new comment.'],
6]);

createQuietlycreateManyQuietly 方法可以用於建立模型,而不會派發任何事件。

1$user = User::find(1);
2 
3$user->posts()->createQuietly([
4 'title' => 'Post title.',
5]);
6 
7$user->posts()->createManyQuietly([
8 ['title' => 'First post.'],
9 ['title' => 'Second post.'],
10]);

您也可以使用 findOrNewfirstOrNewfirstOrCreateupdateOrCreate 方法來在關聯上建立和更新模型

在使用 create 方法之前,請務必查看大量賦值的文件說明。

Belongs To 關聯

如果您想要將子模型指派給新的父模型,您可以使用 associate 方法。在這個範例中,User 模型定義了與 Account 模型的 belongsTo 關聯。這個 associate 方法將會設定子模型上的外鍵。

1use App\Models\Account;
2 
3$account = Account::find(10);
4 
5$user->account()->associate($account);
6 
7$user->save();

若要從子模型中移除父模型,您可以使用 dissociate 方法。這個方法會將關聯的外鍵設定為 null

1$user->account()->dissociate();
2 
3$user->save();

多對多關聯

附加 / 分離

Eloquent 也提供了使多對多關聯的操作更方便的方法。例如,假設一個使用者可以擁有多個角色,而一個角色也可以擁有多個使用者。您可以使用 attach 方法,透過在關聯的關聯表中插入一筆記錄,將角色附加到使用者。

1use App\Models\User;
2 
3$user = User::find(1);
4 
5$user->roles()->attach($roleId);

當將關聯附加到模型時,您也可以傳遞額外資料的陣列,以插入到關聯表中。

1$user->roles()->attach($roleId, ['expires' => $expires]);

有時可能需要從使用者中移除角色。若要移除多對多關聯記錄,請使用 detach 方法。detach 方法將會從關聯表中刪除適當的記錄;然而,兩個模型都會保留在資料庫中。

1// Detach a single role from the user...
2$user->roles()->detach($roleId);
3 
4// Detach all roles from the user...
5$user->roles()->detach();

為了方便起見,attachdetach 也接受 ID 陣列作為輸入。

1$user = User::find(1);
2 
3$user->roles()->detach([1, 2, 3]);
4 
5$user->roles()->attach([
6 1 => ['expires' => $expires],
7 2 => ['expires' => $expires],
8]);

同步關聯

您也可以使用 sync 方法來建構多對多關聯。sync 方法接受 ID 陣列,以放置在關聯表上。任何不在給定陣列中的 ID 都會從關聯表中移除。因此,在這個操作完成後,只有給定陣列中的 ID 會存在於關聯表中。

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

您也可以將額外的關聯表值與 ID 一起傳遞。

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

如果您想要將相同的關聯表值與每個同步模型的 ID 一起插入,您可以使用 syncWithPivotValues 方法。

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

如果您不想要分離給定陣列中不存在的現有 ID,您可以使用 syncWithoutDetaching 方法。

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

切換關聯

多對多關聯也提供了一個 toggle 方法,用於「切換」給定相關模型 ID 的附加狀態。如果給定的 ID 目前已附加,它將會被分離。同樣地,如果它目前已分離,它將會被附加。

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

您也可以將額外的關聯表值與 ID 一起傳遞。

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

更新關聯表上的記錄

如果您需要更新關聯的關聯表中現有的列,您可以使用 updateExistingPivot 方法。這個方法接受關聯記錄外鍵和要更新的屬性陣列。

1$user = User::find(1);
2 
3$user->roles()->updateExistingPivot($roleId, [
4 'active' => false,
5]);

觸碰父時間戳記

當模型定義了與另一個模型的 belongsTobelongsToMany 關聯時,例如屬於 PostComment,有時在子模型被更新時更新父模型的時間戳記會很有幫助。

例如,當 Comment 模型被更新時,您可能想要自動「更新」擁有 Postupdated_at 時間戳記,使其設定為目前的日期和時間。為了達成此目的,您可以將 touches 屬性新增到您的子模型,其中包含當子模型被更新時,應該更新其 updated_at 時間戳記的關聯名稱。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\BelongsTo;
7 
8class Comment extends Model
9{
10 /**
11 * All of the relationships to be touched.
12 *
13 * @var array
14 */
15 protected $touches = ['post'];
16 
17 /**
18 * Get the post that the comment belongs to.
19 */
20 public function post(): BelongsTo
21 {
22 return $this->belongsTo(Post::class);
23 }
24}

只有在使用 Eloquent 的 save 方法更新子模型時,父模型時間戳記才會被更新。