關聯關系
One To One
假設User模型關聯了Phone模型,要定義這樣一個關聯,需要在User模型中定義一個phone方法,該方法傳回一個
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
方法的第一個參數為要關聯的模型,定義好之後,可以使用下列文法查詢到關聯屬性了
$phone = User::find(1)->phone;
複制
Eloquent會假定關聯的外鍵是基于模型名稱的,是以Phone模型會自動使用
user_id
字段作為外鍵,可以使用第二個參數和第三個參數覆寫
return $this->hasOne('App\Phone', 'foreign_key');
return $this->hasOne('App\Phone', 'foreign_key', 'local_key');
複制
定義反向關系
定義上述的模型之後,就可以使用User模型擷取Phone模型了,當然也可以通過Phone模型擷取所屬的User了,這就用到了
belongsTo
方法了
<?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');
// return $this->belongsTo('App\User', 'foreign_key');
// return $this->belongsTo('App\User', 'foreign_key', 'other_key');
}
}
複制
One To Many
假設有一個文章,它有很多關聯的評論資訊,這種情況下應該使用一對多的關聯,使用
hasMany
方法
<?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');
}
}
複制
查詢操作
$comments = App\Post::find(1)->comments;
foreach ($comments as $comment) {
//
}
$comments = App\Post::find(1)->comments()->where('title', 'foo')->first();
複制
定義反向關聯
反向關聯也是使用
belongsTo
方法,參考One To One部分。
$comment = App\Comment::find(1);
echo $comment->post->title;
複制
Many To Many
多對多關聯因為多了一個中間表,實作起來比
hasOne
和
hasMany
複雜一些。
考慮這樣一個場景,使用者可以屬于多個角色,一個角色也可以屬于多個使用者。這就引入了三個表:
users
,
roles
,
role_user
。其中
role_user
表為關聯表,包含兩個字段
user_id
和
role_id
。
多對多關聯需要使用
belongsToMany
方法
<?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', 'role_user');
// 指定關聯表,關聯字段
// return $this->belongsToMany('App\Role', 'role_user', 'user_id', 'role_id');
return $this->belongsToMany('App\Role');
}
}
複制
上述定義了一個使用者屬于多個角色,一旦該關系确立,就可以查詢了
$user = App\User::find(1);
foreach ($user->roles as $role) {
//
}
$roles = App\User::find(1)->roles()->orderBy('name')->get();
複制
反向關聯關系
反向關系與正向關系實作一樣
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Role extends Model
{
/**
* The users that belong to the role.
*/
public function users()
{
return $this->belongsToMany('App\User');
}
}
複制
檢索中間表的列值
對多對多關系來說,引入了一個中間表,是以需要有方法能夠查詢到中間表的列值,比如關系确立的時間等,使用
pivot
屬性查詢中間表
$user = App\User::find(1);
foreach ($user->roles as $role) {
echo $role->pivot->created_at;
}
複制
上述代碼通路了中間表的
created_at
字段。
注意的是,預設情況下之後模型的鍵可以通過
pivot
對象進行通路,如果中間表包含了額外的屬性,在指定關聯關系的時候,需要使用
withPivot
方法明确的指定列名
return $this->belongsToMany('App\Role')->withPivot('column1', 'column2');
複制
如果希望中間表自動維護
created_at
和
updated_at
字段的話,需要使用
withTimestamps()
return $this->belongsToMany('App\Role')->withTimestamps();
複制
Has Many Through
這種關系比較強大,假設這樣一個場景:Country模型下包含了多個User模型,而每個User模型又包含了多個Post模型,也就是說一個國家有很多使用者,而這些使用者都有很多文章,我們希望查詢某個國家的所有文章,怎麼實作呢,這就用到了Has Many Through關系
countries
id - integer
name - string
users
id - integer
country_id - integer
name - string
posts
id - integer
user_id - integer
title - string
複制
可以看到,posts表中并不直接包含country_id,但是它通過users表與countries表建立了關系
使用Has Many Through關系
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', 'country_id', 'user_id');
return $this->hasManyThrough('App\Post', 'App\User');
}
}
複制
方法
hasManyThrough
的第一個參數是我們希望通路的模型名稱,第二個參數是中間模型名稱。
HasManyThrough hasManyThrough(
string $related,
string $through,
string|null $firstKey = null,
string|null $secondKey = null,
string|null $localKey = null
)
複制
Polymorphic Relations (多态關聯)
多态關聯使得同一個模型使用一個關聯就可以屬于多個不同的模型,假設這樣一個場景,我們有一個文章表和一個評論表,使用者既可以對文章執行喜歡操作,也可以對評論執行喜歡操作,這樣的情況下該怎麼處理呢?
表結構如下
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_type字段判斷該記錄喜歡的是文章還是評論,表結構有了,接下來就該定義模型了
<?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 product'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');
}
}
複制
預設情況下,
likeable_type
的類型是關聯的模型的完整名稱,比如這裡就是
App\Post
和
App\Comment
。
通常情況下我們可能會使用自定義的值辨別關聯的表名,是以,這就需要自定義這個值了,我們需要在項目的服務提供者對象的
boot
方法中注冊關聯關系,比如
AppServiceProvider
的
boot
方法中
use Illuminate\Database\Eloquent\Relations\Relation;
Relation::morphMap([
'posts' => App\Post::class,
'likes' => App\Like::class,
]);
複制
檢索多态關系
通路一個文章所有的喜歡
$post = App\Post::find(1);
foreach ($post->likes as $like) {
//
}
複制
通路一個喜歡的文章或者評論
$like = App\Like::find(1);
$likeable = $like->likeable;
複制
上面的例子中,傳回的likeable會根據該記錄的類型傳回文章或者評論。
多對多的多态關聯
多對多的關聯使用方法
morphToMany
和
morphedByMany
,這裡就不多廢話了。
關聯關系查詢
在Eloquent中,所有的關系都是使用函數定義的,可以在不執行關聯查詢的情況下擷取關聯的執行個體。假設我們有一個部落格系統,User模型關聯了很多Post模型:
/**
* Get all of the posts for the user.
*/
public function posts()
{
return $this->hasMany('App\Post');
}
複制
你可以像下面這樣查詢關聯并且添加額外的限制
$user = App\User::find(1);
$user->posts()->where('active', 1)->get();
複制
如果不需要對關聯的屬性添加限制,可以直接作為模型的屬性通路,例如上面的例子,我們可以使用下面的方式通路User的Post
$user = App\User::find(1);
foreach ($user->posts as $post) {
//
}
複制
動态的屬性都是延遲加載的,它們隻有在被通路的時候才會去查詢資料庫,與之對應的是預加載,預加載可以使用關聯查詢出所有資料,減少執行sql的數量。
查詢關系存在性
使用
has
方法可以基于關系的存在性傳回結果
// 檢索至少有一個評論的所有文章...
$posts = App\Post::has('comments')->get();
// Retrieve all posts that have three or more comments...
$posts = Post::has('comments', '>=', 3)->get();
// Retrieve all posts that have at least one comment with votes...
$posts = Post::has('comments.votes')->get();
複制
如果需要更加強大的功能,可以使用
whereHas
和
orWhereHas
方法,把where條件放到
has
語句中。
// 檢索所有至少存在一個比對foo%的評論的文章
$posts = Post::whereHas('comments', function ($query) {
$query->where('content', 'like', 'foo%');
})->get();
複制
預加載
在通路Eloquent模型的時候,預設情況下所有的關聯關系都是延遲加載的,在使用的時候才會開始加載,這就造成了需要執行大量的sql的問題,使用預加載功能可以使用關聯查詢出所有結果
<?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;
}
複制
上面的查詢将會執行一個查詢查詢出所有的書,然後在周遊的時候再執行N個查詢查詢出作者資訊,顯然這樣做是非常低效的,幸好我們還有預加載功能,可以将這N+1個查詢減少到2個查詢,在查詢的時候,可以使用
with
方法指定哪個關系需要預加載。
$books = App\Book::with('author')->get();
foreach ($books as $book) {
echo $book->author->name;
}
複制
對于該操作,會執行下列兩個sql
select * from books
select * from authors where id in (1, 2, 3, 4, 5, ...)
複制
預加載多個關系
$books = App\Book::with('author', 'publisher')->get();
複制
嵌套的預加載
$books = App\Book::with('author.contacts')->get();
複制
帶限制的預加載
$users = App\User::with(['posts' => function ($query) {
$query->where('title', 'like', '%first%');
}])->get();
$users = App\User::with(['posts' => function ($query) {
$query->orderBy('created_at', 'desc');
}])->get();
複制
延遲預加載
有時候,在上級模型已經檢索出來之後,可能會需要預加載關聯資料,可以使用
load
方法
$books = App\Book::all();
if ($someCondition) {
$books->load('author', 'publisher');
}
$books->load(['author' => function ($query) {
$query->orderBy('published_date', 'asc');
}]);
複制
關聯模型插入
save方法
儲存單個關聯模型
$comment = new App\Comment(['message' => 'A new comment.']);
$post = App\Post::find(1);
$post->comments()->save($comment);
複制
儲存多個關聯模型
$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]);
複制
上述代碼會更新中間表的expires字段。
create方法
使用
create
方法與
save
方法的不同在于它是使用數組的形式建立關聯模型的
$post = App\Post::find(1);
$comment = $post->comments()->create([
'message' => 'A new comment.',
]);
複制
更新 “Belongs To” 關系
更新
belongsTo
關系的時候,可以使用
associate
方法,該方法會設定子模型的外鍵
$account = App\Account::find(10);
$user->account()->associate($account);
$user->save();
複制
要移除
belongsTo
關系的話,使用
dissociate
方法
$user->account()->dissociate();
$user->save();
複制
Many to Many 關系
中間表查詢條件
當查詢時需要對使用中間表作為查詢條件時,可以使用
wherePivot
,
wherePivotIn
,
orWherePivot
,
orWherePivotIn
添加查詢條件。
$enterprise->with(['favorites' => function($query) {
$query->wherePivot('enterprise_id', '=', 12)->select('id');
}]);
複制
Attaching / Detaching
$user = App\User::find(1);
// 為使用者添加角色
$user->roles()->attach($roleId);
// 為使用者添加角色,更新中間表的expires字段
$user->roles()->attach($roleId, ['expires' => $expires]);
// 移除使用者的單個角色
$user->roles()->detach($roleId);
// 移除使用者的所有角色
$user->roles()->detach();
複制
attach
和
detach
方法支援數組參數,同時添加和移除多個
$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
方法,可以指定兩個模型之間隻存在指定的關聯關系
$user->roles()->sync([1, 2, 3]);
$user->roles()->sync([1 => ['expires' => true], 2, 3]);
複制
上述兩個方法都會讓使用者隻存在1,2,3三個角色,如果使用者之前存在其他角色,則會被删除。
更新父模型的時間戳
假設場景如下,我們為一個文章增加了一個新的評論,我們希望這個時候文章的更新時間會相應的改變,這種行為在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');
}
}
複制
現在,更新評論的時候,文章的
updated_at
字段也會被更新
$comment = App\Comment::find(1);
$comment->text = 'Edit to this comment!';
$comment->save();
複制
參考: Eloquent: Relationships