假设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();