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