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(): HasOne14 {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(): BelongsTo14 {15 return $this->belongsTo(User::class);16 }17}
當呼叫 user
方法時,Eloquent 將嘗試尋找一個 User
模型,其 id
與 Phone
模型上的 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(): BelongsTo5{6 return $this->belongsTo(User::class, 'foreign_key');7}
如果父模型未使用 id
作為其主鍵,或者您希望使用不同的欄位尋找關聯的模型,您可以將第三個參數傳遞給 belongsTo
方法,指定父表格的自訂鍵
1/**2 * Get the user that owns the phone.3 */4public function user(): BelongsTo5{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(): HasMany14 {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(): HasMany14 {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(): BelongsTo14 {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
模型,其 id
與 Comment
模型上的 post_id
欄位匹配。
Eloquent 透過檢查關聯方法的名稱,並在方法名稱後綴加上 _
以及父模型主鍵欄位的名稱,來決定預設的外鍵名稱。因此,在這個範例中,Eloquent 會假設 Post
模型在 comments
資料表上的外鍵是 post_id
。
然而,如果您的關聯外鍵不遵循這些慣例,您可以將自訂外鍵名稱作為第二個參數傳遞給 belongsTo
方法
1/**2 * Get the post that owns the comment.3 */4public function post(): BelongsTo5{6 return $this->belongsTo(Post::class, 'foreign_key');7}
如果您的父模型未使用 id
作為其主鍵,或者您希望使用不同的欄位來尋找關聯模型,您可以將第三個參數傳遞給 belongsTo
方法,指定您父資料表的自訂鍵
1/**2 * Get the post that owns the comment.3 */4public function post(): BelongsTo5{6 return $this->belongsTo(Post::class, 'foreign_key', 'owner_key');7}
預設模型
belongsTo
、hasOne
、hasOneThrough
和 morphOne
關聯允許您定義一個預設模型,如果給定的關聯為 null
,將會返回該模型。這種模式通常被稱為 Null Object 模式,並且可以幫助移除您程式碼中的條件檢查。在以下範例中,如果沒有使用者附加到 Post
模型,user
關聯將返回一個空的 App\Models\User
模型
1/**2 * Get the author of the post.3 */4public function user(): BelongsTo5{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(): BelongsTo15{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(): HasOne5{6 return $this->hasOne(Order::class)->latestOfMany();7}
同樣地,您可以定義一個方法來檢索關聯的「最舊」或第一個相關模型
1/**2 * Get the user's oldest order.3 */4public function oldestOrder(): HasOne5{6 return $this->hasOne(Order::class)->oldestOfMany();7}
預設情況下,latestOfMany
和 oldestOfMany
方法將根據模型的主鍵檢索最新或最舊的相關模型,該主鍵必須是可排序的。然而,有時您可能希望使用不同的排序標準從更大的關聯中檢索單一模型。
例如,使用 ofMany
方法,您可以檢索使用者最昂貴的訂單。ofMany
方法接受可排序的欄位作為其第一個參數,以及在查詢相關模型時要應用的聚合函數(min
或 max
)
1/**2 * Get the user's largest order.3 */4public function largestOrder(): HasOne5{6 return $this->hasOne(Order::class)->ofMany('price', 'max');7}
由於 PostgreSQL 不支援對 UUID 欄位執行 MAX
函數,因此目前無法將 one-of-many 關聯與 PostgreSQL UUID 欄位結合使用。
將「Many」關聯轉換為 Has One 關聯
通常,當使用 latestOfMany
、oldestOfMany
或 ofMany
方法檢索單一模型時,您已經為同一個模型定義了「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(): HasOne13{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 910owners11 id - integer12 name - string13 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(): HasOneThrough14 {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 910deployments11 id - integer12 environment_id - integer13 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(): HasManyThrough14 {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(): HasMany14 {15 return $this->hasMany(Post::class)->latest();16 }17 18 /**19 * Get the user's featured posts.20 */21 public function featuredPosts(): HasMany22 {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(): HasMany5{6 return $this->posts()->withAttributes(['featured' => true]);7}
withAttributes
方法將使用給定的屬性將 where
子句約束新增到查詢中,並且它還會將給定的屬性新增到透過關聯方法建立的任何模型
1$post = $user->featuredPosts()->create(['title' => 'Featured Post']);2 3$post->featured; // true
多對多關聯
多對多關聯比 hasOne
和 hasMany
關聯稍微複雜。多對多關聯的一個範例是使用者擁有許多角色,而這些角色也由應用程式中的其他使用者共享。例如,使用者可能會被指派「作者」和「編輯」的角色;然而,這些角色也可能被指派給其他使用者。因此,一個使用者擁有許多角色,而一個角色擁有許多使用者。
資料表結構
若要定義這種關聯,需要三個資料庫表:users
、roles
和 role_user
。role_user
資料表從相關模型名稱的字母順序衍生而來,並且包含 user_id
和 role_id
欄位。此資料表用作連結使用者和角色的中間表。
請記住,由於一個角色可以屬於許多使用者,我們不能簡單地在 roles
資料表上放置 user_id
欄位。這將意味著一個角色只能屬於一個使用者。為了提供角色可以指派給多個使用者的支援,需要 role_user
資料表。我們可以像這樣總結關聯的資料表結構
1users 2 id - integer 3 name - string 4 5roles 6 id - integer 7 name - string 8 9role_user10 user_id - integer11 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(): BelongsToMany14 {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(): BelongsToMany14 {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_at
和 updated_at
時間戳記,並且由 Eloquent 自動維護,請在定義關聯時呼叫 withTimestamps
方法。
1return $this->belongsToMany(Role::class)->withTimestamps();
使用 Eloquent 自動維護時間戳記的中介資料表,必須同時擁有 created_at
和 updated_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}
透過中間表欄位過濾查詢
您也可以使用 wherePivot
、wherePivotIn
、wherePivotNotIn
、wherePivotBetween
、wherePivotNotBetween
、wherePivotNull
和 wherePivotNotNull
方法在定義關聯時,過濾 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(): BelongsToMany14 {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
如果您已定義一個使用自訂樞紐模型的多對多關聯,並且該樞紐模型具有自動遞增的主鍵,您應確保您的自訂樞紐模型類別定義一個設定為 true
的 incrementing
屬性。
1/**2 * Indicates if the IDs are auto-incrementing.3 *4 * @var bool5 */6public $incrementing = true;
多態關聯
多型關聯允許子模型使用單一關聯屬於多種類型的模型。例如,假設您正在建構一個允許使用者分享部落格文章和影片的應用程式。在這樣的應用程式中,Comment
模型可能同時屬於 Post
和 Video
模型。
一對一(多型)
資料表結構
一對一多型關聯與典型的一對一關聯類似;但是,子模型可以使用單一關聯屬於多種類型的模型。例如,部落格 Post
和 User
可能與 Image
模型共享多型關聯。使用一對一多型關聯可讓您擁有一個包含唯一圖片的單一資料表,該資料表可以與文章和使用者關聯。首先,讓我們檢查資料表結構。
1posts 2 id - integer 3 name - string 4 5users 6 id - integer 7 name - string 8 9images10 id - integer11 url - string12 imageable_id - integer13 imageable_type - string
請注意 images
資料表上的 imageable_id
和 imageable_type
欄位。imageable_id
欄位將包含文章或使用者的 ID 值,而 imageable_type
欄位將包含父模型類別名稱。imageable_type
欄位供 Eloquent 用於決定在存取 imageable
關聯時要傳回哪種「類型」的父模型。在這種情況下,該欄位將包含 App\Models\Post
或 App\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(): MorphTo14 {15 return $this->morphTo();16 }17}18 19use Illuminate\Database\Eloquent\Model;20use Illuminate\Database\Eloquent\Relations\MorphOne;21 22class Post extends Model23{24 /**25 * Get the post's image.26 */27 public function image(): MorphOne28 {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 Model37{38 /**39 * Get the user's image.40 */41 public function image(): MorphOne42 {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
關聯將傳回 Post
或 User
實例,具體取決於哪種類型的模型擁有該圖片。
鍵慣例
如有必要,您可以指定多型子模型使用的「id」和「type」欄位的名稱。如果您這樣做,請確保您始終將關聯的名稱作為 morphTo
方法的第一個參數傳遞。通常,此值應與方法名稱匹配,因此您可以使用 PHP 的 __FUNCTION__
常數。
1/**2 * Get the model that the image belongs to.3 */4public function imageable(): MorphTo5{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 - string1011comments12 id - integer13 body - text14 commentable_id - integer15 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(): MorphTo14 {15 return $this->morphTo();16 }17}18 19use Illuminate\Database\Eloquent\Model;20use Illuminate\Database\Eloquent\Relations\MorphMany;21 22class Post extends Model23{24 /**25 * Get all of the post's comments.26 */27 public function comments(): MorphMany28 {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 Model37{38 /**39 * Get all of the video's comments.40 */41 public function comments(): MorphMany42 {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
關聯將傳回 Post
或 Video
實例,具體取決於哪種類型的模型是評論的父模型。
在子模型上自動填充父模型
即使在使用 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(): MorphOne5{6 return $this->morphOne(Image::class, 'imageable')->latestOfMany();7}
同樣地,您可以定義一個方法來檢索關聯的「最舊」或第一個相關模型
1/**2 * Get the user's oldest image.3 */4public function oldestImage(): MorphOne5{6 return $this->morphOne(Image::class, 'imageable')->oldestOfMany();7}
預設情況下,latestOfMany
和 oldestOfMany
方法將根據模型的主鍵檢索最新或最舊的相關模型,該主鍵必須是可排序的。然而,有時您可能希望使用不同的排序標準從更大的關聯中檢索單一模型。
例如,使用 ofMany
方法,您可以檢索使用者最「喜歡」的圖片。ofMany
方法接受可排序的欄位作為其第一個參數,以及在查詢相關模型時要套用的聚合函數(min
或 max
)。
1/**2 * Get the user's most popular image.3 */4public function bestImage(): MorphOne5{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 9tags10 id - integer11 name - string1213taggables14 tag_id - integer15 taggable_id - integer16 taggable_type - string
在深入探討多型多對多關聯之前,您可能會受益於閱讀關於典型多對多關聯的文件。
模型結構
接下來,我們準備好在模型上定義關聯。Post
和 Video
模型都將包含一個呼叫基礎 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(): MorphToMany14 {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(): MorphToMany14 {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(): MorphToMany22 {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
模型上的 posts
或 videos
方法。
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
模型可能屬於 Post
或 Video
模型,預設的 commentable_type
將分別為 App\Models\Post
或 App\Models\Video
。但是,您可能希望將這些值與應用程式的內部結構分離。
例如,我們可以改用簡單的字串(例如 post
和 video
)而不是使用模型名稱作為「類型」。這樣做,即使模型被重新命名,資料庫中多型「類型」欄位值仍然有效。
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(): HasMany14 {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 posts3where 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 posts3where user_id = ? and (active = 1 or votes >= 100)
關聯方法與動態屬性
如果您不需要向 Eloquent 關聯查詢新增額外的約束,您可以像存取屬性一樣存取關聯。例如,繼續使用我們的 User
和 Post
範例模型,我們可以像這樣存取使用者的所有文章。
1use App\Models\User;2 3$user = User::find(1);4 5foreach ($user->posts as $post) {6 // ...7}
動態關聯屬性執行「延遲載入」,這表示它們只會在您實際存取它們時載入其關聯資料。因此,開發人員通常使用預先載入來預先載入他們知道在載入模型後會被存取的關聯。預先載入顯著減少了載入模型關聯時必須執行的 SQL 查詢。
查詢關聯是否存在
在檢索模型記錄時,您可能希望根據關聯的存在來限制您的結果。例如,假設您想要檢索所有至少有一個評論的部落格文章。為此,您可以將關聯的名稱傳遞給 has
和 orHas
方法。
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();
如果您需要更大的彈性,您可以使用 whereHas
和 orWhereHas
方法在您的 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 條件來查詢關聯是否存在,您可能會發現使用 whereRelation
、orWhereRelation
、whereMorphRelation
和 orWhereMorphRelation
方法更方便。例如,我們可以查詢所有具有未批准評論的文章。
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();
查詢關聯是否不存在
在檢索模型記錄時,您可能希望根據缺少關聯性來限制結果。例如,假設您想要檢索所有沒有任何評論的部落格文章。為此,您可以將關聯性的名稱傳遞給 doesntHave
和 orDoesntHave
方法
1use App\Models\Post;2 3$posts = Post::doesntHave('comments')->get();
如果您需要更強大的功能,可以使用 whereDoesntHave
和 orWhereDoesntHave
方法,在您的 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)」關係的存在性,您可以使用 whereHasMorph
和 whereDoesntHaveMorph
方法。這些方法的第一個參數接受關聯性的名稱。接下來,這些方法接受您希望包含在查詢中的相關模型名稱。最後,您可以提供一個閉包 (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)」關係的父模型的子項。您可以使用 whereMorphedTo
和 whereNotMorphedTo
方法來完成此操作,這些方法將自動為給定的模型確定正確的 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 語句
如果您將 withCount
與 select
語句結合使用,請確保在 select
方法之後呼叫 withCount
1$posts = Post::select(['title', 'body'])2 ->withCount('comments')3 ->get();
其他聚合函式
除了 withCount
方法之外,Eloquent 還提供了 withMin
、withMax
、withAvg
、withSum
和 withExists
方法。這些方法將在您的結果模型上放置一個 {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 關聯上的關聯模型數量
如果您想要預先載入「多態關聯 (morph to)」關係,以及該關係可能傳回的各種實體的相關模型計數,您可以結合使用 with
方法和 morphTo
關係的 morphWithCount
方法。
在這個範例中,假設 Photo
和 Post
模型可以建立 ActivityFeed
模型。我們將假設 ActivityFeed
模型定義了一個名為 parentable
的「多態關聯 (morph to)」關係,這允許我們檢索給定 ActivityFeed
實例的父 Photo
或 Post
模型。此外,讓我們假設 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(): BelongsTo14 {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 books2 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(): MorphTo12 {13 return $this->morphTo();14 }15}
在這個範例中,假設 Event
、Photo
和 Post
模型可以建立 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 array14 */15 protected $with = ['author'];16 17 /**18 * Get the author that wrote the book.19 */20 public function author(): BelongsTo21 {22 return $this->belongsTo(Author::class);23 }24 25 /**26 * Get the genre of the book.27 */28 public function genre(): BelongsTo29 {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(): MorphTo12 {13 return $this->morphTo();14 }15}
在這個範例中,假設 Event
、Photo
和 Post
模型可以建立 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(): void7{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]);
save
和 saveMany
方法將持久化給定的模型實例,但不會將新持久化的模型新增到已載入到父模型上的任何記憶體中關聯性。如果您計劃在使用 save
或 saveMany
方法後存取關聯性,您可能希望使用 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
方法
除了 save
和 saveMany
方法之外,您也可以使用 create
方法,它接受屬性陣列,建立一個模型,並將其插入資料庫。save
和 create
之間的區別在於,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]);
createQuietly
和 createManyQuietly
方法可以用於建立模型,而不會派發任何事件。
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]);
您也可以使用 findOrNew
、firstOrNew
、firstOrCreate
和 updateOrCreate
方法來在關聯上建立和更新模型。
在使用 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();
為了方便起見,attach
和 detach
也接受 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]);
觸碰父時間戳記
當模型定義了與另一個模型的 belongsTo
或 belongsToMany
關聯時,例如屬於 Post
的 Comment
,有時在子模型被更新時更新父模型的時間戳記會很有幫助。
例如,當 Comment
模型被更新時,您可能想要自動「更新」擁有 Post
的 updated_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 array14 */15 protected $touches = ['post'];16 17 /**18 * Get the post that the comment belongs to.19 */20 public function post(): BelongsTo21 {22 return $this->belongsTo(Post::class);23 }24}
只有在使用 Eloquent 的 save
方法更新子模型時,父模型時間戳記才會被更新。