假設User模型關聯了Phone模型,要定義這樣一個關聯,須要在User模型中定義一個phone方法,該方法返回一個hasOne
方法定義的關聯php
<?php namespace App; use Illuminate\Database\Eloquent\Model; class User extends Model { public function phone() { return $this->hasOne('App\Phone'); } }
hasOne
方法的第一個參數爲要關聯的模型,定義好以後,可使用下列語法查詢到關聯屬性了laravel
$phone = User::find(1)->phone;
Eloquent會假定關聯的外鍵是基於模型名稱的,所以Phone模型會自動使用user_id
字段做爲外鍵,可使用第二個參數和第三個參數覆蓋sql
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 { public function user() { return $this->belongsTo('App\User'); // return $this->belongsTo('App\User', 'foreign_key'); // return $this->belongsTo('App\User', 'foreign_key', 'other_key'); } }
假設有一個帖子,它有不少關聯的評論信息,這種狀況下應該使用一對多的關聯,使用hasMany
方法數組
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Post extends Model { public function comments() { return $this->hasMany('App\Comment'); } }
查詢操做ide
$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;
多對多關聯由於多了一箇中間表,實現起來比hasOne
和hasMany
複雜一些。post
考慮這樣一個場景,用戶能夠屬於多個角色,一個角色也能夠屬於多個用戶。這就引入了三個表: users
, roles
, role_user
。其中role_user
表爲關聯表,包含兩個字段user_id
和role_id
。this
多對多關聯須要使用belongsToMany
方法spa
<?php namespace App; use Illuminate\Database\Eloquent\Model; class User extends Model { 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 { 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();
這種關係比較強大,假設這樣一個場景: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 { 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 )
多態關聯使得同一個模型使用一個關聯就能夠屬於多個不一樣的模型,假設這樣一個場景,咱們有一個帖子表和一個評論表,用戶既能夠對帖子執行喜歡操做,也能夠對評論執行喜歡操做,這樣的狀況下該怎麼處理呢?
表結構以下
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 { public function likeable() { return $this->morphTo(); } } class Post extends Model { public function likes() { return $this->morphMany('App\Like', 'likeable'); } } class Comment extends Model { 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模型:
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 { 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'); }]);
保存單個關聯模型
$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的第二個參數指定關聯表中的屬性
App\User::find(1)->roles()->save($role, ['expires' => $expires]);
上述代碼會更新中間表的expires字段。
使用create
方法與save
方法的不一樣在於它是使用數組的形式建立關聯模型的
$post = App\Post::find(1); $comment = $post->comments()->create([ 'message' => 'A new comment.', ]);
更新belongsTo
關係的時候,可使用associate
方法,該方法會設置子模型的外鍵
$account = App\Account::find(10); $user->account()->associate($account); $user->save();
要移除belongsTo
關係的話,使用dissociate
方法
$user->account()->dissociate(); $user->save();
當查詢時須要對使用中間表做爲查詢條件時,可使用wherePivot
, wherePivotIn
,orWherePivot
,orWherePivotIn
添加查詢條件。
$enterprise->with(['favorites' => function($query) { $query->wherePivot('enterprise_id', '=', 12)->select('id'); }]);
$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 { protected $touches = ['post']; public function post() { return $this->belongsTo('App\Post'); } }
如今,更新評論的時候,帖子的updated_at
字段也會被更新
$comment = App\Comment::find(1); $comment->text = 'Edit to this comment!'; $comment->save();