Eloquent: 關聯模型
簡介
數據庫中的表經常性的關聯其它的表。比如,一個博客文章可以有很多的評論,或者一個訂單會關聯一個用戶。Eloquent 使管理和協作這些關系變的非常的容易,并且支持多種不同類型的關聯:
- 一對一
- 一對多
- 多對多
- 遠程一對多
- 多態關聯
- 多態多對多關聯
定義關聯
Eloquent 關聯可以像定義方法一樣在 Eloquent 模型類中進行定義。同時,它就像 Eloquent 模型自身一樣也提供了強大的查詢生成器。這允許關聯模型可以鏈式的執行查詢能力。比如:
$user->posts()->where('active', 1)->get();
但是,在更深入的使用關聯之前,讓我們先來學習一下如何定義各種類型的關聯。
一對一
一對一的關聯是最基礎的關聯。比如,一個 User
模型可能關聯一個 Phone
。我們需要在 User
模型上放置一個 phone
方法來定義這種關聯。phone
方法應該返回一個基類 Eloquent 模型上 hasOne
方法的結果:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Get the phone record associated with the user.
*/
public function phone()
{
return $this->hasOne('App\Phone');
}
}
傳遞到 hasOne
方法的第一個參數應該是關聯模型的名稱。一旦關聯被定義完成,我們可以使用 Eloquent 的動態屬性來訪問關聯模型的記錄。動態屬性允許你訪問關聯函數,就像是它們是定義在模型中的屬性一樣:
$phone = User::find(1)->phone;
Eloquent 假定所關聯的外鍵是基于模型的名稱的。在這個前提下,Phone
模型會自動的假定其擁有一個 user_id
外鍵。如果你希望修改這個慣例,你可以傳遞第二個參數到 hasOne
方法中:
return $this->hasOne('App\Phone', 'foreign_key');
另外,Eloquent 也會假定外鍵應該在其上層模型上擁有一個匹配的 id
(或者自定義的 $primaryKey
)值。換句話說,Eloquent 會查詢 Phone
記錄中的 user_id
列所對應的用戶的 id
列的記錄。如果你希望關聯使用 id
以外的值,你可以傳遞第三個參數到 hasOne
方法來指定自定義的鍵:
return $this->hasOne('App\Phone', 'foreign_key', 'local_key');
定義相對的關聯
那么,我們可以從我們的 User
中訪問 Phone
模型。現在,讓我們在 Phone
模型上定義一個關聯,讓我們可以從 Phone
模型中訪問其所屬的 User
。我們使用 belongsTo
方法來定義 hasOne
相對的關聯:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Phone extends Model
{
/**
* Get the user that owns the phone.
*/
public function user()
{
return $this->belongsTo('App\User');
}
}
在上面的例子中,Eloquent 將會嘗試從 Phone
模型中的 user_id
字段中匹配查找 id
相同的 User
。Eloquent 會依據所關聯的模型的蛇形命名和 _id
來假定默認的外鍵名。事實上,如果在 Phone
模型上的外鍵不是 user_id
,那么你可以傳遞自定義的外鍵名到 belongsTo
方法的第二個參數:
/**
* Get the user that owns the phone.
*/
public function user()
{
return $this->belongsTo('App\User', 'foreign_key');
}
如果你的上級模型并沒有使用 id
作為主鍵名,或者你希望下級模型關聯一個不同的列。你可以傳遞第三個參數到 belongsTo
方法來指定上級模型表中的自定義鍵:
/**
* Get the user that owns the phone.
*/
public function user()
{
return $this->belongsTo('App\User', 'foreign_key', 'other_key');
}
一對多
一個一對多的關聯常常用來定義一個模型擁有其他任意數目的模型。比如,一個博客文章可以擁有很多條評論。就像其他的 Eloquent 關聯一樣,一對多關聯在 Eloquent 模型中通過方法來進行定義:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
/**
* Get the comments for the blog post.
*/
public function comments()
{
return $this->hasMany('App\Comment');
}
}
記住,Eloquent 會自動的根據 Comment
模型來判斷合適的外鍵。依據慣例,Eloquent 會使用自身模型的蛇形命名和 _id
來作為外鍵。所以,在這個例子中,Eloquent 會假定 Comment
模型的外鍵是 post_id
。
一旦關聯定義完成之后,我們可以通過 comments
屬性來訪問所有關聯的評論的集合。記住,由于 Eloquent 提供了動態屬性,我們可以對關聯函數進行訪問,就像他們是在模型中定義的屬性一樣:
$comments = App\Post::find(1)->comments;
foreach ($comments as $comment) {
//
}
當然,由于所有的關聯都提供了查詢生成器的功能,所以你可以在調用 comments
方法時繼續的添加一些限制條件,你可以通過鏈式的調用進行查詢條件的添加:
$comments = App\Post::find(1)->comments()->where('title', 'foo')->first();
就像 hasOne
方法,你可以通過添加額外的參數到 hasMany
方法中來重置外鍵和主鍵:
return $this->hasMany('App\Comment', 'foreign_key');
return $this->hasMany('App\Comment', 'foreign_key', 'local_key');
定義相對的關聯
現在我們可以訪問文章中所有的評論了,讓我們為評論定義一個關聯使其可以訪問它的上層文章模型。為了定義一個 hasMany
相對的關聯,你需要在下層模型中定義一個關聯方法并調用 belongsTo
方法:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
/**
* Get the post that owns the comment.
*/
public function post()
{
return $this->belongsTo('App\Post');
}
}
一旦關聯被定義完成,我們就可以通過 Comment
模型的 post
動態屬性來檢索到其對應的 Post
模型:
$comment = App\Comment::find(1);
echo $comment->post->title;
在上面的例子中,Eloquent 會嘗試從 Comment
模型中的 post_id
字段檢索與其相對應 id
的 Post
模型。Eloquent 會使用關聯模型的蛇形命名和 _id
來作為默認的外鍵。如果 Comment
模型的外鍵不是 post_id
,你可以傳遞一個自定義的鍵名到 belongsTo
方法的第二個參數:
/**
* Get the post that owns the comment.
*/
public function post()
{
return $this->belongsTo('App\Post', 'foreign_key');
}
如果上層模型并沒有使用 id
作為主鍵,或者你想在下層模型中關聯其他的列,你可以傳遞第三個參數到 belongsTo
方法中:
/**
* Get the post that owns the comment.
*/
public function post()
{
return $this->belongsTo('App\Post', 'foreign_key', 'other_key');
}
多對多
多對多的關聯比 hasOne
和 hasMany
關聯要稍微復雜一些。假如一個用戶擁有多個角色,而角色又可以被其他的用戶所共享。比如,多個用戶可以擁有管理員的角色。如果定義這種關聯,我們需要定義三個數據庫表:users
,roles
,和 role_user
。role_user
表的命名是以相關聯的兩個模型數據表來依照字母順序命名,并且表中包含了 user_id
和 role_id
列。
多對多關聯需要編寫一個方法調用基礎 Eloquent 類 belongsToMany
方法。比如,讓我們在 User
模型中定義一個 roles
方法:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* The roles that belong to the user.
*/
public function roles()
{
return $this->belongsToMany('App\Role');
}
}
一旦關聯被定義,你可以通過 roles
動態屬性來訪問用戶的角色:
$user = App\User::find(1);
foreach ($user->roles as $role) {
//
}
當然,就像其他類型的關聯,你可以調用 roles
方法并且鏈式調用查詢條件:
$roles = App\User::find(1)->roles()->orderBy('name')->get();
就如先前所提到的,Eloquent 會合并兩個關聯模型并依照字母順序進行命名。當然你也可以隨意的重寫這個約定,你可以傳遞第二個參數到 belongsToMany
方法:
return $this->belongsToMany('App\Role', 'role_user');
除了自定義合并數據表的名稱之外,你也可以通過往 belongsToMany
方法傳傳遞額外參數來自定義數據表里的鍵的字段名稱。第三個參數是你定義在關聯中模型外鍵的名稱。第四個參數則是你要合并的模型外鍵的名稱:
return $this->belongsToMany('App\Role', 'role_user', 'user_id', 'role_id');
定義相對關聯
你只需要在相對應的關聯模型里放置其他的方法來調用 belongsToMany
方法就可以定義相對關聯。繼續我們上面的用戶角色示例,讓我們在 Role
模型中定義一個 users
方法:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Role extends Model
{
/**
* The users that belongs to the role.
*/
public function users()
{
return $this->belongsToMany('App\User');
}
}
就如你所看到的,這個關聯的定義與用戶的關聯定義完全相同。因為我們重復的使用了 belongsToMany
方法,當定義相對于多對多的關聯時,所有常用的自定義數據表和鍵的選項都是可用的。
檢索中間表字段
正如你已經了解到的。定義多對多的關聯需要引入一個中間表。Eloquent 提供了幾種非常有幫助的方式來與這個表進行交互。比如,讓我們假定我們的 User
對象關聯到了很多 Role
對象。在訪問這些關聯對象時,我們可以通過在模型上使用 pivot
屬性來訪問中間表:
$user = App\User::find(1);
foreach ($user->roles as $role) {
echo $role->pivot->created_at;
}
注意我們取出的每個 Role
對象,都會被自動的分配 pivot
屬性。這個屬性包含了一個代表中間表的模型,并且可以像其他 Eloquent 模型一樣被使用。
默認的,只有模型的鍵會被 pivot
對象提供,如果你的中間表包含了額外的屬性,你必須在定義關聯時指定它們:
return $this->belongsToMany('App\Role')->withPivot('column1', 'column2');
如果你想要中間表自動維護 created_at
和 updated_at
時間戳,你可以在定義關聯時使用 withTimestamps
方法:
return $this->belongsToMany('App\Role')->withTimestamps();
通過中間表字段過濾關系
你可以通過在定義關聯時使用 wherePrivot
和 wherePivotIn
方法來在返回的結果中進行過濾:
return $this->belongsToMany('App\Role')->wherePivot('approved', 1);
return $this->belongsToMany('App\Role')->wherePivotIn('approved', [1, 2]);
遠程一對多
遠程一對多關聯提供了簡短便捷的方法通過中間關聯件來訪問遠端的關聯。比如,一個 Country
模型應該通過 User
模型可以擁有很多的 Post
模型。在這個例子中,你可以非常容易的就檢索出一個國家中的所有的文章。讓我們來看一下定義這些關聯所需要的表:
countries
id - integer
name - string
users
id - integer
country_id - integer
name - string
posts
id - integer
user_id - integer
title - string
遠端的 posts
并沒有包含 country_id
列,hasManyThrough
關聯可以通過 $country->posts
來訪問一個國家的文章。為了執行這個查詢,Eloquent 會通過中間表 users
的 country_id
來檢索 posts
表中用戶 ID 相匹配的記錄。
現在我們已經明確了關聯表的結構,那么讓我們來在 Country
模型上定義關聯:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Country extends Model
{
/**
* Get all of the posts for the country.
*/
public function posts()
{
return $this->hasManyThrough('App\Post', 'App\User');
}
}
傳遞到 hasManyThrough
方法的第一個參數是我們最終想要訪問到的模型,而第二個參數則是中間層的模型名稱。
當使用關聯查詢時,通常 Eloquent 會遵循外鍵約定。如果你希望對關聯的鍵進行自定義,你可以傳遞第三和第四個參數到 hasManyThrough
方法。第三個參數是中間層模型的外鍵名稱,第四個參數是最終想要獲取的模型中的所對應的中間層的外鍵, 而第五個參數則是當前模型的主鍵:
class Country extends Model
{
public function posts()
{
return $this->hasManyThrough(
'App\Post', 'App\User',
'country_id', 'user_id', 'id'
);
}
}
多態關聯
表結構
多態關聯允許一個模型在單個關聯中從屬一個或多個其它模型。比如,想象一下應用中的用戶可以喜歡文章及其評論。如果使用多態關聯,那么你就可以使用一個單獨的 likes
表來關聯這兩個場景。首先,讓我們確定定義這種關聯所需要的表結構:
posts
id - integer
title - string
body - text
comments
id - integer
post_id - integer
body - text
likes
id - integer
likeable_id - integer
likeable_type - string
你需要注意到的兩個在 likes
表中重要的字段 likeable_id
和 likeable_type
。likeable_id
字段會包含文章或者評論的 ID 值,而 likeable_type
字段會包含其所屬的模型的類名。likeable_type
就是當訪問 likeable
關聯時 ORM 用來判斷所屬的模型是哪個類型。
模型結構
接著,讓我們檢查一下這個關聯所需要的模型定義:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class like extends Model
{
/**
* Get all of the owning likeable models.
*/
public function likeable()
{
return $this->morphTo();
}
}
class Post extends Model
{
/**
* Get all of the post's likes.
*/
public function likes()
{
return $this->morphMany('App\Like', 'likeable');
}
}
class Comment extends Model
{
/**
* Get all of the comment's likes.
*/
public function likes()
{
return $this->morphMany('App\Like', 'likeable');
}
}
獲取多態關聯
一旦數據庫表和模型都定義完成,你就可以在你的模型中訪問這些關聯。比如,你可以使用 likes
動態屬性來訪問文章中所有關聯的 likes 模型:
$post = App\Post::find(1);
foreach ($post->likes as $like) {
//
}
你也可以通過在模型上調用提供 morphTo
的方法來獲取多態模型其關系所有者。在上面的例子中,指的就是 Like
模型中的 likeable
方法。所以,我們可以像使用動態屬性一樣使用方法來進行訪問:
$like = App\Like::find(1);
$likeable = $like->likeable;
Like
模型的 likeable
關聯將會返回一個 Post
或者 Comment
實例,這取決于其所屬者的類型。
自定義多態類型
默認的,Laravel 會使用包完全限定類名來存儲所關聯模型的類型。比如,上面的例子中 Like
可以屬于 Post
或者 Comment
。默認的 likeable_type
應該是 App\Post
或者 App\Comment
。事實上,你可能希望從你的應用程序的內部結構分離數據庫。在這個例子中,你可以定義一個關聯的多態映射來指導 Eloquent 使用模型關聯的表名稱來替代類名:
use Illuminate\Database\Eloquent\Relations\Relation;
Relation::morphMap([
App\Post::class,
App\Comment::class,
]);
或者,你可以指定一個自定的字符串與每個模型進行關聯:
use Illuminate\Database\Eloquent\Relations\Relation;
Relation::morphMap([
'posts' => App\Post::class,
'likes' => App\Like::class,
]);
你可以在你的 AppServiceProvider
或者一個分離的服務提供者的 boot
方法中注冊你的 morphMap
。
多態多對多關聯
表結構
除了傳統的多態關聯,你也可以定義多對多的多態關聯。比如,一個博客的 Post
和 Video
模型應該可以共享一個多態關聯的 Tag
模型。使用多對多的多態關聯可以允許你的博客文章和視頻能夠共享獨特標簽的單個列表。首先,讓我們來看一下表結構:
posts
id - integer
name - string
videos
id - integer
name - string
tags
id - integer
name - string
taggables
tag_id - integer
taggable_id - integer
taggable_type - string
模型結構
接著,我們來定義模型中的關聯。Post
和 Video
模型將都會包含調用基礎 Eloquent 類的 morphToMany
方法的 tags
方法:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
/**
* Get all of the tags for the post.
*/
public function tags()
{
return $this->morphToMany('App\Tag', 'taggable');
}
}
定義相對的關聯
接著,在 Tag
模型中,你應該為所有關聯模型定義相應的方法。所以,在這個例子中,我們將定義 posts
方法和 videos
方法:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Tag extends Model
{
/**
* Get all of the posts that are assigned this tag.
*/
public function posts()
{
return $this->morphedByMany('App\Post', 'taggable');
}
/**
* Get all of the videos that are assigned this tag.
*/
public function videos()
{
return $this->morphedByMany('App\Video', 'taggable');
}
}
獲取關聯
當定義完成數據表和模型之后,你就可以通過模型來訪問其關聯。比如,你可以簡單的使用 tags
動態屬性來訪問文章的所有標簽模型:
$post = App\Post::find(1);
foreach ($post->tags as $tag) {
//
}
你也可以通過訪問模型中提供執行 morphedByMany
方法的方法來獲取關聯模型的所屬模型。在上面的例子中,就是 Tag
模型上的 posts
或者 videos
方法。所以,你可以像動態屬性一樣訪問這些方法:
$tab = App\Tag::find(1);
foreach ($tag->videos as $video) {
//
}
關聯查詢
由于所有的 Eloquent 關聯類型都是通過方法定義的,所以你可以調用這些方法來獲取所關聯的模型的實例而無需實際的執行關聯查詢。另外,所有的 Eloquent 關聯也都提供了查詢生成器服務,這允許你可以繼續的鏈式執行查詢操作。
比如,想象一下博客系統中 User
模型擁有很多 Post
關聯的模型:
<?ph
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Get all of the posts for the user.
*/
public function posts()
{
return $this->hasMany('App\Post');
}
}
你可以查詢 posts
關聯的同時添加一些額外的查詢約束:
$user = App\User::find(1);
$user->posts()->where('active', 1)->get();
你應該注意到了,你可以在關聯中使用任何的查詢生成器的方法。
關聯方法 Vs. 動態屬性
如果你不需要在進行 Eloquent 關聯查詢時添加額外的約束,你可以簡單的像它的屬性一樣進行訪問。比如,我們繼續使用 User
和 Post
示例模型。我們可以像這樣來訪問用戶的所有文章:
$user = App\User::find(1);
foreach ($user->posts as $post) {
//
}
動態屬性是惰性加載的,這意味著在你實際訪問他們之前,其關聯數據是不會加載的。正因為如此,開發的時候通常使用預加載來進行加載一些即將用到的關聯模型。預加載要求必須加載一個模型的關系,這有效的減少了查詢的次數。
查詢關聯是否存在
當訪問一個模型的記錄時,你可能會希望基于關聯的記錄是否存在來對結果進行限制。比如,想象一下你希望獲取最少有一條評論的博客文章。你可以傳遞關聯的名稱到 has
方法來做這些:
// Retrieve all posts that have at least one comment...
$posts = App\Post::has('comments')->get();
你也可以指定操作符,和數量來進一步定制查詢:
// Retrieve all posts that have three or more comments...
$posts = Post::has('comments', '>=', 3)->get();
你也可以使用 .
語法來構造嵌套的 has
語句。比如,你可以獲取所有包含至少一條評論和投票的文章:
// Retrieve all posts that hava at least one comment with votes...
$posts = Post::has('comments.votes')->get();
如果你需要更高的控制,你可以使用 whereHas
和 orWhereHas
方法來在 has
查詢中插入 where
子句。這些方法允許你為關聯進行自定義的約束查詢。比如檢查評論的內容:
// Retrieve all posts with at least one comment containing words like foo%
$posts = Post::whereHas('comments', function ($query) {
$query->where('content', 'like', 'foo%');
})->get();
統計關聯結果
如果你希望統計關聯的結果而不實際的加載它們,你可以使用 withCount
方法,這將在你的結果模型中添加 {relation}_count
列。比如:
$posts = App\Post::withCount('comments')->get();
foreach ($posts as $post) {
echo $post->comments_count;
}
你也可以同時檢索多個關聯的統計,以及添加查詢約束:
$posts = Post::withCount(['votes', 'comments' => function ($query) {
$query->where('content', 'like', 'foo%');
}])->get();
echo $posts[0]->votes_count;
echo $posts[0]->comments_count;
預加載
當通過屬性訪問 Eloquent 關聯時,該關聯的數據會被延遲加載。這意味著該關聯數據只有在你真實的訪問屬性時才會進行加載。事實上,Eloquent 可以在上層模型中一次性預加載的。預加載有效避免了 N + 1 的查找問題。要說明 N + 1 查找問題,我們可以來看一個 Author
關聯 Book
的示例:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Book extends Model
{
/**
* Get the author that wrote the book.
*/
public function author()
{
return $this->belongsTo('App\Author');
}
}
現在,讓我們檢索所有的書籍和他們的作者:
$books = App\Book::all();
foreach ($books as $book) {
echo $book->author->name;
}
這個循環會執行一次查找回所有的書籍,接著每本書會運行一次查找作者的操作。所以,如果我們擁有 25 本書,那么循環將會進行 26 次查詢:1 次查詢所有的書籍,25 次查詢相關書籍的作者。
非常幸運的,我們可以使用預加載來將查詢有效的控制在 2 次。當查詢時,使用 with
方法來指定關聯的預加載:
$books = App\Book::with('author')->get();
foreach ($books as $book) {
echo $book->author->name;
}
對于這個操作,只會執行兩個查詢:
select * from books
select * from authors where id in (1, 2, 3, 4, 5, ...)
預加載多個關聯
有時候你可能需要在一個操作中預加載多個關聯,你只需要傳遞額外的參數到 with
方法中就可以:
$books = App\Book::with('author', 'publisher')->get();
嵌套的預加載
你可以使用 .
語法來加載嵌套的關聯。比如,讓我們在一個 Eloquent 語句中一次加載所有書籍的作者以及作者的死人通訊簿:
$books = App\Book::with('author.contacts')->get();
預加載約束
有時候你可能希望預加載一些關聯,但是也需要對預加載查詢指定額外的約束,這里有個示例:
$users = App\User::with(['posts' => function ($query) {
$query->where('title', 'like', '%first%');
}])->get();
在這個例子中,Eloquent 會值預加載文章的 title
列包含 first
單詞的記錄。當然,你也可以調用其他查詢生成器可用的方法:
$users = App\User::with(['posts' => function ($query) {
$query->orderBy('created_at', 'desc');
}])->get();
延遲預加載
有時候你可能需要在上層模型被獲取后才預加載其關聯。當你需要來動態決定是否加載關聯模型時尤其有用:
$books = App\Book::all();
if ($someCondition) {
$books->load('author', 'publisher');
}
如果你需要對預加載做一些查詢約束,你可以傳遞 Closure
到 load
方法:
$books->load(['author' => function ($query) {
$query->orderBy('published_date', 'asc');
}]);
插入關系模型
Save 方法
Eloquent 提供了方便的方法來為模型添加一個關聯。比如,也許你需要為 Post
模型新增一個 Comment
。除了手動的設置 Comment
的 post_id
屬性,你也可以直接在關聯模型中調用 save
方法來插入 Comment
:
$comment = new App\Comment(['message' => 'A new comment.']);
$post = App\Post::find(1);
$post->comments()->save($comment);
注意上面我們并沒有使用關聯模型的動態屬性的方式來訪問 comments
,而是使用 comments
方法的形式來獲取關聯模型的實例。save
方法會自動的添加相應的 post_id
值到新的 Comment
模型上。
如果你需要一次添加多個關聯模型,你需要使用 saveMany
方法:
$post = App\Post::find(1);
$post->comments()->saveMany([
new App\Comment(['message' => 'A new comment.']),
new App\Comment(['message' => 'Another comment.']),
]);
Save & 多對多關聯
當與多對多關聯互動時,save
方法接收一個中間層表屬性的額外參數數組作為第二個參數:
App\User::find(1)->roles()->save($role, ['expires' => $expires]);
Create 方法
除了 save
和 saveMany
方法之外,你也可以使用 create
方法,它可以接收屬性組成的數組,創建一個模型并且將其存儲到數據庫。這一次,save
和 create
方法的區別是 save
接收一個完整的 Eloquent 模型實例,而 create
接收的是一個原生的 PHP array
:
$post = App\Post::find(1);
$comment = $post->comments()->create([
'message' => 'A new comment.',
]);
在使用 create
方法之前,你應該確保已經閱讀了屬性的 批量賦值文檔。
更新從屬關聯模型
當更新一個 belongsTo
關聯時,你應該使用 associate
方法。這個方法會在下層模型中設置外鍵:
$account = App\Account::find(10);
$user->account()->associate($account);
$user->save();
當刪除 belongsTo
關聯時,你應該使用 dissociate
方法,該方法會重置下層模型所關聯的外鍵:
$user->account()->dissociate();
$user->save();
多對多關聯
附加 / 抽離
當使用多對多關聯時,Eloquent 提供了一些額外的幫助方法來更方便的管理關聯模型。比如,讓我們想象一下用戶可以有很多角色并且角色可以有很多用戶。你可以使用 attach
方法來附加一個角色到用戶并且在中間表中加入這條記錄:
$user = App\User::find(1);
$user->roles()->attach($roleId);
當附加關聯到模型時,你也可以傳遞一個含有額外數據的數組來將其添加到中間表中:
$user->roles()->attach($roleId, ['expires' => $expires]);
當然,有時候你可能需要從用戶中刪除一個角色。你可以使用 detach
方法來刪除多對多關聯的記錄。datech
方法將從中間表中刪除相應的記錄。但是,除了中間表,其它兩個模型的記錄都還會被保留:
// Detach a single role from the user...
$user->roles()->detach($roleId);
// Detach all roles from the user...
$user->roles()->detach();
為了更加的便捷,attach
和 detach
也可以接收 IDs 所組成的數組作為輸入:
$user = App\User::find(1);
$user->roles()->detach([1, 2, 3]);
$user->roles()->attach([1 => ['expires' => $expires], 2, 3]);
更新中間表的記錄
如果你需要更新中間表中存在的行,你可以使用 updateExistingPivot
方法:
$user = App\User::find(1);
$user->roles()->updateExistingPivot($roleId, $attributes);
便利的同步
你也可以使用 sync
方法來構建多對多的關聯。sync
方法接收放置中間表 IDs 所組成的數組。任意 IDs 如果沒有在所給定的數組中,那么其將會從中間表中進行刪除。所以,在操作完成之后,只有存在于給定數組里的 IDs 才會存在于中間表中:
$user->roles()->sync([1, 2, 3]);
你也可以同時傳遞額外的中間表的鍵值對:
$user->roles()->sync([1 => ['expires' => true], 2, 3]);
聯動上層模型時間戳
當一個模型 belongsTo
或者 belongsToMany
另外一個模型時,比如 Comment
從屬于 Post
,這對下層模型更新時同時要求更新上層模型的時間戳時很有幫助。比如,當 Comment
模型更新了,你想要自動的更新其所屬的 Post
模型的 updated_at
時間戳。Eloquent 使之變的非常容易。你只需要在下層模型中添加一個 touches
屬性來包含關聯的名稱就可以了:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
/**
* All of the relationships to be touched.
*
* @var array
*/
protected $touches = ['post'];
/**
* Get the post that the comment belongs to.
*/
public function post()
{
return $this->belongsTo('App\Post');
}
}
現在,當你更新 Comment
時,其所屬的 Post
將會同時更新 updated_at
列:
$comment = App\Comment::find(1);
$comment->text = 'Edit to this comment!';
$comment->save();