數(shù)據(jù)表經(jīng)常要與其它表做關(guān)聯(lián),比如一篇博客文章可能有很多評論,或者一個訂單會被關(guān)聯(lián)到下單用戶,Eloquent 使得組織和處理這些關(guān)聯(lián)關(guān)系變得簡單,并且支持多種不同類型的關(guān)聯(lián)關(guān)系:
Eloquent 關(guān)聯(lián)關(guān)系以 Eloquent 模型類方法的形式被定義。和 Eloquent 模型本身一樣,關(guān)聯(lián)關(guān)系也是強大的查詢構(gòu)建器,定義關(guān)聯(lián)關(guān)系為函數(shù)能夠提供功能強大的方法鏈和查詢能力。例如:
$user->posts()->where('active', 1)->get();
但是,在深入使用關(guān)聯(lián)關(guān)系之前,讓我們先學習如何定義每種關(guān)聯(lián)類型:
一對一關(guān)聯(lián)是一個非常簡單的關(guān)聯(lián)關(guān)系,例如,一個 User
模型有一個與之對應(yīng)的 Phone
模型。要定義這種模型,我們需要將 phone
方法置于 User
模型中,phone
方法應(yīng)該返回 Eloquent 模型基類上 hasOne
方法的結(jié)果:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model{
/**
* 獲取關(guān)聯(lián)到用戶的手機
*/
public function phone()
{
return $this->hasOne('App\Phone');
}
}
傳遞給 hasOne
方法的第一個參數(shù)是關(guān)聯(lián)模型的名稱,關(guān)聯(lián)關(guān)系被定義后,我們可以使用 Eloquent 的動態(tài)屬性獲取關(guān)聯(lián)記錄。動態(tài)屬性允許我們訪問關(guān)聯(lián)函數(shù)就像它們是定義在模型上的屬性一樣:
$phone = User::find(1)->phone;
Eloquent 默認關(guān)聯(lián)關(guān)系的外鍵基于模型名稱,在本例中,Phone
模型默認有一個 user_id
外鍵,如果你希望重寫這種約定,可以傳遞第二個參數(shù)到 hasOne
方法:
return $this->hasOne('App\Phone', 'foreign_key');
此外,Eloquent 假設(shè)外鍵應(yīng)該在父級上有一個與之匹配的 id
,換句話說,Eloquent 將會通過 user
表的 id
值去 phone
表中查詢 user_id
與之匹配的 Phone
記錄。如果你想要關(guān)聯(lián)關(guān)系使用其他值而不是 id
,可以傳遞第三個參數(shù)到 hasOne
來指定自定義的主鍵:
return $this->hasOne('App\Phone', 'foreign_key', 'local_key');
我們可以從 User
中訪問Phone
模型,相應(yīng)的,我們也可以在 Phone
模型中定義關(guān)聯(lián)關(guān)系從而讓我們可以擁有該phone
的User
。我們可以使用belongsTo
方法定義與 hasOne
關(guān)聯(lián)關(guān)系相對的關(guān)聯(lián):
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Phone extends Model{
/**
* 獲取手機對應(yīng)的用戶
*/
public function user()
{
return $this->belongsTo('App\User');
}
}
在上面的例子中,Eloquent 將會嘗試通過 Phone
模型的 user_id
去 User
模型查找與之匹配的記錄。Eloquent 通過關(guān)聯(lián)關(guān)系方法名并在方法名后加_id
后綴來生成默認的外鍵名。然而,如果 Phone
模型上的外鍵不是user_id
,也可以將自定義的鍵名作為第二個參數(shù)傳遞到belongsTo
方法:
/**
* 獲取手機對應(yīng)的用戶
*/
public function user(){
return $this->belongsTo('App\User', 'foreign_key');
}
如果父模型不使用 id
作為主鍵,或者你希望使用別的列來連接子模型,可以將父表自定義鍵作為第三個參數(shù)傳遞給belongsTo
方法:
/**
* 獲取手機對應(yīng)的用戶
*/
public function user(){
return $this->belongsTo('App\User', 'foreign_key', 'other_key');
}
“一對多”是用于定義單個模型擁有多個其它模型的關(guān)聯(lián)關(guān)系。例如,一篇博客文章?lián)碛袩o數(shù)評論,和其他關(guān)聯(lián)關(guān)系一樣,一對多關(guān)聯(lián)通過在 Eloquent 模型中定義方法來定義:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model{
/**
* 獲取博客文章的評論
*/
public function comments()
{
return $this->hasMany('App\Comment');
}
}
記住,Eloquent 會自動判斷 Comment
模型的外鍵,為方便起見,Eloquent 將擁有者模型名稱加上 id
后綴作為外鍵。因此,在本例中,Eloquent 假設(shè) Comment
模型上的外鍵是 post_id
。
關(guān)聯(lián)關(guān)系被定義后,我們就可以通過訪問comments
屬性來訪問評論集合。記住,由于 Eloquent 提供“動態(tài)屬性”,我們可以像訪問模型的屬性一樣訪問關(guān)聯(lián)方法:
$comments = App\Post::find(1)->comments;
foreach ($comments as $comment) {
//
}
當然,由于所有關(guān)聯(lián)同時也是查詢構(gòu)建器,我們可以添加更多的條件約束到通過調(diào)用 comments
方法獲取到的評論上:
$comments = App\Post::find(1)->comments()->where('title', 'foo')->first();
和 hasOne
方法一樣,你還可以通過傳遞額外參數(shù)到 hasMany
方法來重新設(shè)置外鍵和本地主鍵:
return $this->hasMany('App\Comment', 'foreign_key');
return $this->hasMany('App\Comment', 'foreign_key', 'local_key');
現(xiàn)在我們可以訪問文章的所有評論了,接下來讓我們定義一個關(guān)聯(lián)關(guān)系允許通過評論訪問所屬文章。要定義與 hasMany
相對的關(guān)聯(lián)關(guān)系,需要在子模型中定義一個關(guān)聯(lián)方法去調(diào)用 belongsTo
方法:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model{
/**
* 獲取評論對應(yīng)的博客文章
*/
public function post()
{
return $this->belongsTo('App\Post');
}
}
關(guān)聯(lián)關(guān)系定義好之后,我們可以通過訪問動態(tài)屬性post
來獲取一條 Comment
對應(yīng)的 Post
:
$comment = App\Comment::find(1);
echo $comment->post->title;
在上面這個例子中,Eloquent 嘗試匹配 Comment
模型的post_id
與 Post
模型的 id
,Eloquent 通過關(guān)聯(lián)方法名加上_id
后綴生成默認外鍵,當然,你也可以通過傳遞自定義外鍵名作為第二個參數(shù)傳遞到 belongsTo
方法,如果你的外鍵不是post_id
,或者你想自定義的話:
/**
* 獲取評論對應(yīng)的博客文章
*/
public function post(){
return $this->belongsTo('App\Post', 'foreign_key');
}
如果你的父模型不使用 id
作為主鍵,或者你希望通過其他列來連接子模型,可以將自定義鍵名作為第三個參數(shù)傳遞給 belongsTo
方法:
/**
* 獲取評論對應(yīng)的博客文章
*/
public function post(){
return $this->belongsTo('App\Post', 'foreign_key', 'other_key');
}
多對多關(guān)系比 hasOne
和 hasMany
關(guān)聯(lián)關(guān)系要稍微復(fù)雜一些。這種關(guān)聯(lián)關(guān)系的一個例子就是一個用戶有多個角色,同時一個角色被多個用戶共用。例如,很多用戶可能都有一個“Admin”角色。要定義這樣的關(guān)聯(lián)關(guān)系,需要三個數(shù)據(jù)表:users
、roles
和 role_user
,role_user
表按照關(guān)聯(lián)模型名的字母順序命名,并且包含 user_id
和 role_id
兩個列。
多對多關(guān)聯(lián)通過編寫一個調(diào)用 Eloquent 基類上的 belongsToMany
方法的函數(shù)來定義:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model{
/**
* 用戶角色
*/
public function roles()
{
return $this->belongsToMany('App\Role');
}
}
關(guān)聯(lián)關(guān)系被定義之后,可以使用動態(tài)屬性 roles
來訪問用戶的角色:
$user = App\User::find(1);
foreach ($user->roles as $role) {
//
}
當然,和所有其它關(guān)聯(lián)關(guān)系類型一樣,你可以調(diào)用 roles
方法來添加條件約束到關(guān)聯(lián)查詢上:
$roles = App\User::find(1)->roles()->orderBy('name')->get();
正如前面所提到的,為了決定關(guān)聯(lián)關(guān)系連接表的表名,Eloquent 以字母順序連接兩個關(guān)聯(lián)模型的名字。然而,你可以重寫這種約定——通過傳遞第二個參數(shù)到 belongsToMany
方法:
return $this->belongsToMany('App\Role', 'user_roles');
除了自定義連接表的表名,你還可以通過傳遞額外參數(shù)到belongsToMany
方法來自定義該表中字段的列名。第三個參數(shù)是你定義的關(guān)系模型的外鍵名稱,第四個參數(shù)你要連接到的模型的外鍵名稱:
return $this->belongsToMany('App\Role', 'user_roles', 'user_id', 'role_id');
要定義與多對多關(guān)聯(lián)相對的關(guān)聯(lián)關(guān)系,只需在關(guān)聯(lián)模型中在調(diào)用一下 belongsToMany
方法即可。讓我們在 Role
模型中定義 users
方法:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Role extends Model{
/**
* 角色用戶
*/
public function users()
{
return $this->belongsToMany('App\User');
}
}
正如你所看到的,定義的關(guān)聯(lián)關(guān)系和與其對應(yīng)的 User
中定義的一模一樣,只是前者引用App\Role
,后者引用App\User
,由于我們再次使用了 belongsToMany
方法,所有的常用表和鍵自定義選項在定義與多對多相對的關(guān)聯(lián)關(guān)系時都是可用的。
正如你已經(jīng)學習到的,處理多對多關(guān)聯(lián)要求一個中間表。Eloquent 提供了一些有用的方法來與其進行交互,例如,我們假設(shè) User
對象有很多與之關(guān)聯(lián)的Role
對象,訪問這些關(guān)聯(lián)關(guān)系之后,我們可以使用模型上的 pivot
屬性訪問中間表:
$user = App\User::find(1);
foreach ($user->roles as $role) {
echo $role->pivot->created_at;
}
注意我們獲取到的每一個Role
模型都被自動賦上了pivot
屬性。該屬性包含一個代表中間表的模型,并且可以像其它 Eloquent 模型一樣使用。
默認情況下,只有模型鍵才能用在 privot
對象上,如果你的 privot
表包含額外的屬性,必須在定義關(guān)聯(lián)關(guān)系時進行指定:
return $this->belongsToMany('App\Role')->withPivot('column1', 'column2');
如果你想要你的 privot
表自動包含 created_at
和 updated_at
時間戳,在關(guān)聯(lián)關(guān)系定義時使用withTimestamps
方法:
return $this->belongsToMany('App\Role')->withTimestamps();
“遠層多對多”關(guān)聯(lián)為通過中間關(guān)聯(lián)訪問遠層的關(guān)聯(lián)關(guān)系提供了一個便利之道。例如,Country
模型通過中間的User
模型可能擁有多個Post
模型。在這個例子中,你可以輕易的聚合給定國家的所有文章,讓我們看看定義這個關(guān)聯(lián)關(guān)系需要哪些表:
countries
id - integer
name - string
users
id - integer
country_id - integer
name - string
posts
id - integer
user_id - integer
title - string
盡管 posts
表不包含 country_id
列,hasManyThrough
關(guān)聯(lián)提供了通過$country->posts
來訪問一個國家的所有文章。要執(zhí)行該查詢,Eloquent 在中間表$users
上檢查 country_id
,查找到相匹配的用戶 ID 后,通過用戶 ID 來查詢 posts
表。
既然我們已經(jīng)查看了該關(guān)聯(lián)關(guān)系的數(shù)據(jù)表結(jié)構(gòu),接下來讓我們在Country
模型上進行定義:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Country extends Model{
/**
* 獲取指定國家的所有文章
*/
public function posts()
{
return $this->hasManyThrough('App\Post', 'App\User');
}
}
第一個傳遞到 hasManyThrough
方法的參數(shù)是最終我們希望訪問的模型的名稱,第二個參數(shù)是中間模型名稱。
當執(zhí)行這種關(guān)聯(lián)查詢時通常 Eloquent 外鍵規(guī)則會被使用,如果你想要自定義該關(guān)聯(lián)關(guān)系的外鍵,可以將它們作為第三個、第四個參數(shù)傳遞給 hasManyThrough
方法。第三個參數(shù)是中間模型的外鍵名,第四個參數(shù)是最終模型的外鍵名。
class Country extends Model{
public function posts()
{
return $this->hasManyThrough('App\Post', 'App\User', 'country_id', 'user_id');
}
}
多態(tài)關(guān)聯(lián)允許一個模型在單個關(guān)聯(lián)下屬于多個不同模型。例如,假如你想要為產(chǎn)品和職工存儲照片,使用多態(tài)關(guān)聯(lián),你可以在這兩種場景下使用單個 photos
表,首先,讓我們看看構(gòu)建這種關(guān)聯(lián)關(guān)系需要的表結(jié)構(gòu):
staff
id - integer
name - string
products
id - integer
price - integer
photos
id - integer
path - string
imageable_id - integer
imageable_type - string
兩個重要的列需要注意的是 photos
表上的 imageable_id
和 imageable_type
。imageable_id
列包含 staff
或 product
的 ID 值,而 imageable_type
列包含所屬模型的類名。當訪問 imageable
關(guān)聯(lián)時,ORM 根據(jù) imageable_type
列來判斷所屬模型的類型并返回相應(yīng)模型實例。
接下來,讓我們看看構(gòu)建這種關(guān)聯(lián)關(guān)系需要在模型中定義什么:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Photo extends Model{
/**
* 獲取所有擁有的 imageable 模型
*/
public function imageable()
{
return $this->morphTo();
}
}
class Staff extends Model{
/**
* 獲取所有職員照片
*/
public function photos()
{
return $this->morphMany('App\Photo', 'imageable');
}
}
class Product extends Model{
/**
* 獲取所有產(chǎn)品照片
*/
public function photos()
{
return $this->morphMany('App\Photo', 'imageable');
}
}
數(shù)據(jù)表和模型定義好以后,可以通過模型訪問關(guān)聯(lián)關(guān)系。例如,要訪問一個職員的所有照片,可以通過使用 photos
的動態(tài)屬性:
$staff = App\Staff::find(1);
foreach ($staff->photos as $photo) {
//
}
你還可以通過訪問調(diào)用 morphTo
方法名來從多態(tài)模型中獲取多態(tài)關(guān)聯(lián)的所屬對象。在本例中,就是 Photo
模型中的 imageable
方法。因此,我們可以用動態(tài)屬性的方式訪問該方法:
$photo = App\Photo::find(1);
$imageable = $photo->imageable;
Photo
模型上的 imageable
關(guān)聯(lián)返回 Staff
或 Product
實例,這取決于那個類型的模型擁有該照片。
除了傳統(tǒng)的多態(tài)關(guān)聯(lián),還可以定義“多對多”的多態(tài)關(guān)聯(lián),例如,一個博客的 Post
和 Video
模型可能共享一個 Tag
模型的多態(tài)關(guān)聯(lián)。使用對多對的多態(tài)關(guān)聯(lián)允許你在博客文章和視頻之間有唯一的標簽列表。首先,讓我們看看表結(jié)構(gòu):
posts
id - integer
name - string
videos
id - integer
name - string
tags
id - integer
name - string
taggables
tag_id - integer
taggable_id - integer
taggable_type - string
接下來,我們準備在模型中定義該關(guān)聯(lián)關(guān)系。Post
和 Video
模型都有一個 tags
方法調(diào)用 Eloquent 基類的 morphToMany
方法:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model{
/**
* 獲取指定文章所有標簽
*/
public function tags()
{
return $this->morphToMany('App\Tag', 'taggable');
}
}
接下來,在 Tag
模型中,應(yīng)該為每一個關(guān)聯(lián)模型定義一個方法,例如,我們定義一個 posts
方法和 videos
方法:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Tag extends Model{
/**
* 獲取所有分配該標簽的文章
*/
public function posts()
{
return $this->morphedByMany('App\Post', 'taggable');
}
/**
* 獲取分配該標簽的所有視頻
*/
public function videos()
{
return $this->morphedByMany('App\Video', 'taggable');
}
}
定義好數(shù)據(jù)庫和模型后可以通過模型訪問關(guān)聯(lián)關(guān)系。例如,要訪問一篇文章的所有標簽,可以使用動態(tài)屬性 tags
:
$post = App\Post::find(1);
foreach ($post->tags as $tag) {
//
}
還可以通過訪問調(diào)用 morphedByMany
的方法名從多態(tài)模型中獲取多態(tài)關(guān)聯(lián)的所屬對象。在本例中,就是 Tag 模型中的 posts
或者 videos
方法:
$tag = App\Tag::find(1);
foreach ($tag->videos as $video) {
//
}
由于 Eloquent 所有關(guān)聯(lián)關(guān)系都是通過函數(shù)定義,你可以調(diào)用這些方法來獲取關(guān)聯(lián)關(guān)系的實例而不需要再去手動執(zhí)行關(guān)聯(lián)查詢。此外,所有 Eloquent 關(guān)聯(lián)關(guān)系類型同時也是查詢構(gòu)建器,允許你在最終在數(shù)據(jù)庫執(zhí)行 SQL 之前繼續(xù)添加條件約束到關(guān)聯(lián)查詢上。 例如,假定在一個博客系統(tǒng)中一個 User 模型有很多相關(guān)的 Post 模型:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model{
/**
* 獲取指定用戶的所有文章
*/
public function posts()
{
return $this->hasMany('App\Post');
}
}
你可以像這樣查詢 posts
關(guān)聯(lián)并添加額外的條件約束到該關(guān)聯(lián)關(guān)系上:
$user = App\User::find(1);
$user->posts()->where('active', 1)->get();
你可以在關(guān)聯(lián)關(guān)系上使用任何查詢構(gòu)建器!
關(guān)聯(lián)關(guān)系方法 VS 動態(tài)屬性
如果你不需要添加額外的條件約束到 Eloquent 關(guān)聯(lián)查詢,你可以簡單通過動態(tài)屬性來訪問關(guān)聯(lián)對象,例如,還是拿 User
和 Post
模型作為例子,你可以像這樣訪問所有用戶的文章:
$user = App\User::find(1);
foreach ($user->posts as $post) {
//
}
動態(tài)屬性就是”懶惰式加載“,意味著當你真正訪問它們的時候才會加載關(guān)聯(lián)數(shù)據(jù)。正因為如此,開發(fā)者經(jīng)常使用渴求式加載來預(yù)加載他們知道在加載模型時要被訪問的關(guān)聯(lián)關(guān)系。渴求式加載有效減少了必須要被執(zhí)以加載模型關(guān)聯(lián)的 SQL 查詢。
查詢已存在的關(guān)聯(lián)關(guān)系 訪問一個模型的記錄的時候,你可能希望基于關(guān)聯(lián)關(guān)系是否存在來限制查詢結(jié)果的數(shù)目。例如,假設(shè)你想要獲取所有至少有一個評論的博客文章,要實現(xiàn)這個,可以傳遞關(guān)聯(lián)關(guān)系的名稱到 has 方法:
// 獲取所有至少有一條評論的文章...
$posts = App\Post::has('comments')->get();
你還可以指定操作符和大小來自定義查詢:
// 獲取所有至少有三條評論的文章...
$posts = Post::has('comments', '>=', 3)->get();
還可以使用”.“來構(gòu)造嵌套 has 語句,例如,你要獲取所有至少有一條評論及投票的所有文章:
// 獲取所有至少有一條評論獲得投票的文章...
$posts = Post::has('comments.votes')->get();
如果你需要更強大的功能,可以使用 whereHas
和 orWhereHas
方法將 where
條件放到 has
查詢上,這些方法允許你添加自定義條件約束到關(guān)聯(lián)關(guān)系條件約束,例如檢查一條評論的內(nèi)容:
// 獲取所有至少有一條評論包含 foo 字樣的文章
$posts = Post::whereHas('comments', function ($query) {
$query->where('content', 'like', 'foo%');
})->get();
當以屬性方式訪問數(shù)據(jù)庫關(guān)聯(lián)關(guān)系的時候,關(guān)聯(lián)關(guān)系數(shù)據(jù)時”懶惰式加載“的,這意味著關(guān)聯(lián)關(guān)系數(shù)據(jù)直到第一次訪問的時候才被加載。然而,Eloquent 可以在查詢父級模型的同時”渴求式加載“關(guān)聯(lián)關(guān)系。渴求式加載緩解了 N+1
查詢問題,要闡明 N+1
查詢問題,考慮下關(guān)聯(lián)到 Author
的 Book
模型:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Book extends Model{
/**
* 獲取寫這本書的作者
*/
public function author()
{
return $this->belongsTo('App\Author');
}
}
現(xiàn)在,讓我們獲取所有書及其作者:
$books = App\Book::all();
foreach ($books as $book) {
echo $book->author->name;
}
該循環(huán)先執(zhí)行 1 次查詢獲取表中的所有書,然后另一個查詢獲取每一本書的作者,因此,如果有 25 本書,要執(zhí)行 26 次查詢:1 次是獲取書本身,剩下的 25 次查詢是為每一本書獲取其作者。
謝天謝地,我們可以使用渴求式加載來減少該操作到 2 次查詢。當查詢的時候,可以使用 with
方法指定應(yīng)該被渴求式加載的關(guān)聯(lián)關(guān)系:
$books = App\Book::with('author')->get();
foreach ($books as $book) {
echo $book->author->name;
}
在該操作中,只執(zhí)行兩次查詢即可:
select * from books
select * from authors where id in (1, 2, 3, 4, 5, ...)
有時候你需要在單個操作中渴求式加載幾個不同的關(guān)聯(lián)關(guān)系。要實現(xiàn)這個,只需要添加額外的參數(shù)到 with
方法即可:
$books = App\Book::with('author', 'publisher')->get();
要渴求式加載嵌套的關(guān)聯(lián)關(guān)系,可以使用”.“語法。例如,讓我們在一個 Eloquent 語句中渴求式加載所有書的作者及所有作者的個人聯(lián)系方式:
$books = App\Book::with('author.contacts')->get();
有時候我們希望渴求式加載一個關(guān)聯(lián)關(guān)系,但還想為渴求式加載指定更多的查詢條件:
$users = App\User::with(['posts' => function ($query) {
$query->where('title', 'like', '%first%');
}])->get();
在這個例子中,Eloquent 只渴求式加載 title 包含 first 的文章。當然,你可以調(diào)用其它查詢構(gòu)建器來自定義渴求式加載操作:
$users = App\User::with(['posts' => function ($query) {
$query->orderBy('created_at', 'desc');
}])->get();
有時候你需要在父模型已經(jīng)被獲取后渴求式加載一個關(guān)聯(lián)關(guān)系。例如,這在你需要動態(tài)決定是否加載關(guān)聯(lián)模型時可能很有用:
$books = App\Book::all();
if ($someCondition) {
$books->load('author', 'publisher');
}
如果你需要設(shè)置更多的查詢條件到渴求式加載查詢上,可以傳遞一個閉包到 load
方法:
$books->load(['author' => function ($query) {
$query->orderBy('published_date', 'asc');
}]);
Eloquent 提供了便利的方法來添加新模型到關(guān)聯(lián)關(guān)系。例如,也許你需要插入新的 Comment
到 Post
模型,你可以從關(guān)聯(lián)關(guān)系的save
方法直接插入Comment
而不是手動設(shè)置 Comment
的 post_id
屬性:
$comment = new App\Comment(['message' => 'A new comment.']);
$post = App\Post::find(1);
$comment = $post->comments()->save($comment);
注意我們沒有用動態(tài)屬性方式訪問 comments
,而是調(diào)用 comments
方法獲取關(guān)聯(lián)關(guān)系實例。save
方法會自動添加 post_id
值到新的 Comment
模型。
如果你需要保存多個關(guān)聯(lián)模型,可以使用 saveMany
方法:
$post = App\Post::find(1);
$post->comments()->saveMany([
new App\Comment(['message' => 'A new comment.']),
new App\Comment(['message' => 'Another comment.']),
]);
當處理多對多關(guān)聯(lián)的時候,save
方法以數(shù)組形式接收額外的中間表屬性作為第二個參數(shù):
App\User::find(1)->roles()->save($role, ['expires' => $expires]);
除了 save
和 saveMany
方法外,還可以使用 create
方法,該方法接收屬性數(shù)組、創(chuàng)建模型、然后插入數(shù)據(jù)庫。save
和 create
的不同之處在于 save
接收整個 Eloquent 模型實例而create
接收原生 PHP 數(shù)組:
$post = App\Post::find(1);
$comment = $post->comments()->create([
'message' => 'A new comment.',
]);
使用 create
方法之前確保先瀏覽屬性批量賦值文檔。
更新 belongsTo
關(guān)聯(lián)的時候,可以使用 associate
方法,該方法會在子模型設(shè)置外鍵:
$account = App\Account::find(10);
$user->account()->associate($account);
$user->save();
移除belongsTo
關(guān)聯(lián)的時候,可以使用 dissociate
方法。該方法在子模型上取消外鍵和關(guān)聯(lián):
$user->account()->dissociate();
$user->save();
處理多對多關(guān)聯(lián)的時候,Eloquent 提供了一些額外的幫助函數(shù)來使得處理關(guān)聯(lián)模型變得更加方便。例如,讓我們假定一個用戶可能有多個角色同時一個角色屬于多個用戶,要通過在連接模型的中間表中插入記錄附加角色到用戶上,可以使用 attach
方法:
$user = App\User::find(1);
$user->roles()->attach($roleId);
附加關(guān)聯(lián)關(guān)系到模型,還可以以數(shù)組形式傳遞額外被插入數(shù)據(jù)到中間表:
$user->roles()->attach($roleId, ['expires' => $expires]);
當然,有時候有必要從用戶中移除角色,要移除一個多對多關(guān)聯(lián)記錄,使用 detach
方法。detach
方法將會從中間表中移除相應(yīng)的記錄;然而,兩個模型在數(shù)據(jù)庫中都保持不變:
// 從指定用戶中移除角色...
$user->roles()->detach($roleId);
// 從指定用戶移除所有角色...
$user->roles()->detach();
為了方便,attach
和 detach
還接收數(shù)組形式的 ID 作為輸入:
$user = App\User::find(1);
$user->roles()->detach([1, 2, 3]);
$user->roles()->attach([1 => ['expires' => $expires], 2, 3]);
你還可以使用 sync
方法構(gòu)建多對多關(guān)聯(lián)。sync
方法接收數(shù)組形式的 ID 并將其放置到中間表。任何不在該數(shù)組中的 ID 對應(yīng)記錄將會從中間表中移除。因此,該操作完成后,只有在數(shù)組中的 ID 對應(yīng)記錄還存在于中間表:
$user->roles()->sync([1, 2, 3]);
你還可以和 ID 一起傳遞額外的中間表值:
$user->roles()->sync([1 => ['expires' => true], 2, 3]);
當一個模型屬于另外一個時,例如 Comment
屬于 Post
,子模型更新時父模型的時間戳也被更新將很有用,例如,當Comment
模型被更新時,你可能想要”觸發(fā)“創(chuàng)建其所屬模型 Post
的 updated_at
時間戳。Eloquent 使得這項操作變得簡單,只需要添加包含關(guān)聯(lián)關(guān)系名稱的 touches
屬性到子模型中即可:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model{
/**
* 要觸發(fā)的所有關(guān)聯(lián)關(guān)系
*
* @var array
*/
protected $touches = ['post'];
/**
* 評論所屬文章
*/
public function post()
{
return $this->belongsTo('App\Post');
}
}
現(xiàn)在,當你更新 Comment
時,所屬模型 Post
將也會更新其 updated_at
值:
$comment = App\Comment::find(1);
$comment->text = 'Edit to this comment!';
$comment->save();