今天要做的是建立模型之間的關聯。
資料庫的資料表通常會互相關聯。例如,部落格文章可能有許多評論,或是訂單會與下單的使用者有關聯。Eloquent 使這些關聯變得更容易管理與運用
在這次的專案會使用到以下三種關聯:
一對一關聯是相當基本的關聯。例如,User
模型可與 Phone
關聯。要定義這個關聯,我們先在 User
模型上放置 phone
方法。phone
方法會呼叫 hasOne
方法並回傳它的結果:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* 取得與使用者有關的電話記錄。
*/
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
模型上定義關聯,這可以讓我們存取擁有手機的 User
。我們能使用 belongsTo
來反向定義 hasOne
的關聯:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Phone extends Model
{
/**
* 取得擁有手機的使用者。
*/
public function user()
{
return $this->belongsTo('App\User');
}
}
在範例中,Eloquent 會嘗試匹配 Phone
模型的 user_id
至 User
模型的 id
。Eloquent 透過檢查關聯方法的名稱和使用 _id
後綴方法的名稱來決定預設外鍵名稱。然而,如果在 Phone
上的外鍵不是user_id
,你可以在 belongsTo
方法上傳入自訂鍵作為第二個參數:
/**
* 取得擁有手機的使用者。
*/
public function user()
{
return $this->belongsTo('App\User', 'foreign_key');
}
如果你的上層模型不是使用 id
作為主鍵,或是你希望以不同的欄位 join
下層模型,你可以傳遞第三參數至 belongsTo
方法指定層資料表的自訂鍵:
/**
* 取得擁有手機的使用者。
*/
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
{
/**
* 取得部落格文章的評論
*/
public function comments()
{
return $this->hasMany('App\Comment');
}
}
Eloquent 會在 Comment 模型上自動決定該屬性外鍵欄位。按照慣例,Eloquent 會使用被關聯的模型「snake case」 名稱與後綴 _id
來命名。因此,在這個範例,Eloquent 會假設在Comment
模型上的外鍵是 post_id
。
一旦關聯被定義,我們能透過訪問 comments
屬性來存取 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
{
/**
* 取得擁有該評論的文章。
*/
public function post()
{
return $this->belongsTo('App\Post');
}
}
一旦關聯被定義之後,我們可以透過 post
「動態屬性」取得 Comment
的 Post
模型:
$comment = App\Comment::find(1);
echo $comment->post->title;
在範例中,Eloquent 會嘗試匹配 Comment
模型的 post_id
至 Post
模型的 id
。Eloquent 判斷的預設外鍵名稱參考於關聯模型的方法,並在方法名稱後面加上 _id
。當然,如果 Comment
模型的外鍵不是 post_id
,你可以傳遞自訂的鍵名至 belongsTo
方法的第二個參數:
/**
* 取得擁有該評論的文章。
*/
public function post()
{
return $this->belongsTo('App\Post', 'foreign_key');
}
如果你的上層模型不是使用 id
作為主鍵,或是你希望以不同的欄位 join
下層模型,你可以傳遞第三參數至 belongsTo
方法指定上層資料表的自訂鍵:
/**
* 取得擁有該評論的文章。
*/
public function post()
{
return $this->belongsTo('App\Post', 'foreign_key', 'other_key');
}
多對多關聯稍微比 hasOne
及 hasMany
關聯還複雜。這種關聯的例子如,一位使用者可能用有很多身份,而一種身份可能很多使用者都有。舉例來說,很多使用者都擁有「管理者」的身份。要定義這種關聯,需要使用三個資料表:users
、roles
和 role_use
r。role_user
表命名是以相關聯的兩個模型資料表,依照字母順序命名,並包含了 user_id
和 role_id
欄位。
多對多關聯透過撰寫一個被用來回傳 belongsToMany
方法結果來定義。例如,在 User
模型上定義 roles
方法:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* 屬於該使用者的身份。
*/
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
{
/**
* 屬於該身份的使用者們。
*/
public function users()
{
return $this->belongsToMany('App\User');
}
}
這個定義除了簡單的參考 App\User
模型外,與 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
物件只提供模型的鍵。如果 pivot
資料表包含了其他的屬性,可以在定義關聯方法時指定那些欄位:
return $this->belongsToMany('App\Role')->withPivot('column1', 'column2');
如果你想要樞紐表自動維護 created_at
和 updated_at
時間戳記,在定義關聯方法時加上 withTimestamps
方法:
return $this->belongsToMany('App\Role')->withTimestamps();
pivot
屬性名稱如之前所說的,從中介表中的數行可以在模型上使用 pivot
方法來存取。然而,你可以自由的自訂該屬性的名稱來更好的反映在應用程式中的用途。
例如,如果你的應用程式包含可以訂閱 Podcasts
的使用,使用者與 Podcasts
之間可能存在著多對多的關係。如果遇到這種情況,你可能希望你的中介表來存取器重新命名為 subscription
,而不是 pivot
。在定義關聯時,可以使用 as
方法來做到:
return $this->belongsToMany('App\Podcast')
->as('subscription')
->withTimestamps();
一旦完成了,你就可以存取使用自訂的名稱來存取中介表的資料:
$users = User::with('podcasts')->get();
foreach ($users->flatMap->podcasts as $podcast) {
echo $podcast->subscription->created_at;
}
在你定義關聯時,還能使用 wherePivot 和 wherePivotIn 方法過濾回傳的結果:
return $this->belongsToMany('App\Role')->wherePivot('approved', 1);
return $this->belongsToMany('App\Role')->wherePivotIn('priority', [1, 2]);
如果你想要自訂義模型來表示關聯的中介表,你可以在定義關聯時呼叫 using
方法。所有用於表示關聯的中介表的自訂模型必須繼承 Illuminate\Database\Eloquent\Relations\Pivot
類別。 例如,我們可以選擇使用自訂的 UserRole
中介模型來定義 Role
:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Role extends Model
{
/**
* 屬於該身份的使用者們。
*/
public function users()
{
return $this->belongsToMany('App\User')->using('App\UserRole');
}
}
在定義 UserRole
模型時,我們可以繼承 Pivot
類別:
<?php
namespace App;
use Illuminate\Database\Eloquent\Relations\Pivot;
class UserRole extends Pivot
{
//
}