關聯關係
One To One
假設User模型關聯了Phone模型,要定義這樣一個關聯,需要在User模型中定義一個phone方法,該方法返回一個hasOne方法定義的關聯
<?phpnamespace 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方法了
<?phpnamespace 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方法
<?phpnamespace 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方法
<?phpnamespace 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();
反向關聯關係
反向關係與正向關係實現一樣
<?phpnamespace 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');
Has Many Through
這種關係比較強大,假設這樣一個情境:Country模型下包含了多個User模型,而每個User模型又包含了多個Post模型,也就是說一個國家有很多使用者,而這些使用者都有很多文章,我們希望查詢某個國家的所有文章,怎麼實現呢,這就用到了Has Many Through關係
countries id - integer name - stringusers id - integer country_id - integer name - stringposts 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 - textcomments id - integer post_id - integer body - textlikes id - integer likeable_id - integer likeable_type - string
可以看到,我們使用likes表中的likeable_type欄位判斷該記錄喜歡的是文章還是評論,表結構有了,接下來就該定義模型了
<?phpnamespace 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的問題,使用預先載入功能可以使用關聯查詢出所有結果
<?phpnamespace 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 booksselect * 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屬性實現該功能
<?phpnamespace 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();