数据表之间通常有一定的关联。例如,一篇博客文章可能有多条评论,或者一个订单对应一个下单用户。Eloquent 让我们更容易管理和使用这些关联,以下是支持的几种不同类型的关联:
可以在 Eloquent 模型类上用方法定义 Eloquent 关联。因此,与 Eloquent 模型自身一样,关联也可以作为强大的 查询构造器 使用,用方法定义关联提供了强大的链式方法和查询功能。例如,我们可以在 posts 关联上链式添加其它约束:
$user->posts()->where('active', 1)->get();但是,在深入使用关联之前,我们先来看看如何定义每种关联类型。

一对一关联是非常基础的关系。例如,一个 User 模型可能被关联到一个 Phone。要定义此关联,可以在 User 模型上添加一个 phone 方法。phone 方法应调用 hasOne 方法并返回其结果:
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 相对的关联:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Phone extends Model
{
/**
* 获取拥有此电话的用户
*/
public function user()
{
return $this->belongsTo('App\User');
}
}在上述示例中,Eloquent 会尝试在 User 模型的 id 上查找和 user_id 相匹配的值。Eloquent 通过检查关联方法名并添加 _id 后缀决定默认的外键名。当然,如果 Phone 模型的外键不是 user_id,可以将自定义键名作为第二个参数传递给 belongsTo 方法:
/**
* 获取拥有此电话的用户
*/
public function user()
{
return $this->belongsTo('App\User', 'foreign_key');
}如果父模型不使用 id 作为主键,或者希望用不同的字段连接子模型,可以传递第三个参数给 belongsTo 方法指定父表的自定义键:
/**
* 获取拥有此电话的用户
*/
public function user()
{
return $this->belongsTo('App\User', 'foreign_key', 'other_key');
}
「一对多」关联用于定义单个模型拥有任何数量的其它模型。例如,一篇博客文章可能有任意数量的评论。与所有其它 Eloquent 关联一样,通过在模型上添加一个函数定义一对多关联:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
/**
* 获取博客文章的评论
*/
public function comments()
{
return $this->hasMany('App\Comment');
}
}需要注意的是,Eloquent 会自动决定 Comment 模型的外键字段。按照约定,Eloquent 会使用父模型名的「蛇形命名」并添加 _id 后缀。因此,在此示例中,Eloquent 会假定 Comment 模型上的外键是 post_id。
定义关联后,就可以通过访问 comments 属性获取评论的集合了。要注意的是,由于 Eloquent 提供了「动态属性」,因此我们访问关联方法就像它们是模型中定义的属性一样:
$comments = App\Post::find(1)->comments;
foreach ($comments as $comment) {
//
}当然,由于所有关联还可以作为查询构造器使用,因此可以在调用 comments 获取评论时继续在关联上链式添加其它约束:
$comment = 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 方法:
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 会尝试在 Post 模型的 id 上查找和 Comment 的 post_id 相匹配的值。Eloquent 通过检查关联方法名并添加 _ 后缀,再跟上主键名决定默认的外键名。当然,如果 Comment 模型的外键不是 post_id,可以将自定义键名作为第二个参数传递给 belongsTo 方法:
/**
* 获取拥有此评论的文章
*/
public function post()
{
return $this->belongsTo('App\Post', 'foreign_key');
}如果父模型不使用 id 作为主键,或者希望用不同的字段连接子模型,可以传递第三个参数给 belongsTo 方法指定父表的自定义键:
/**
* 获取拥有此评论的文章
*/
public function post()
{
return $this->belongsTo('App\Post', 'foreign_key', 'other_key');
}
多对多关联比 hasOne 和 hasMany 关系稍微复杂一点。一个示例是,一个用户有多个角色,而角色也被其他用户共享。例如,很多用户都有「管理员」角色。要定义这种关联,需要三张数据表:users,roles 和 role_user。role_user 表名由按照字母顺序连接两个关联模型的名称决定,并且包含 user_id 和 role_id 字段。
通过编写一个方法并返回 belongsToMany 的结果定义多对多关联。例如,我们在 User 模型上定义 roles 方法:
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 方法:
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 对象中只存在模型的键。如果中间表包含其它属性,必须在定义关联时指定它们:
return $this->belongsToMany('App\Role')->withPivot('column1', 'column2');如果想要中间表自动维护 created_at 和 updated_at 时间戳,可以在定义关联时使用 withTimestamps 方法:
return $this->belongsToMany('App\Role')->withTimestamps();pivot 名称如之前所述,可以在模型上使用 pivot 属性访问中间表属性。当然,您可以自由定义此属性名来更好地反映其在应用中的用途。
例如,如果应用中包含可能订阅播客的用户,那么用户和播客之间可能就是多对多关联。这种情况下,可能希望将中间表 pivot 属性重命名为 subscription。可以在定义关联时使用 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 方法对 belongsToMany 返回的结果进行过滤:
return $this->belongsToMany('App\Role')->wherePivot('approved', 1);
return $this->belongsToMany('App\Role')->wherePivotIn('priority', [1, 2]);如果要定义一个自定义模型表示关联的中间表,可以在定义关联时调用 using 方法。自定义多对多中间表模型应该继承 Illuminate\Database\Eloquent\Relations\Pivot 类,而自定义多态多对多中间表模型应该继承 Illuminate\Database\Eloquent\Relations\MorphPivot 类。例如,我们定义一个使用自定义 UserRole 中间表模型的 Role:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Role extends Model
{
/**
* 属于角色的用户
*/
public function users()
{
return $this->belongsToMany('App\User')->using('App\UserRole');
}
}当定义 UserRole 模型时,我们将继承 Pivot 类:
namespace App;
use Illuminate\Database\Eloquent\Relations\Pivot;
class UserRole extends Pivot
{
//
}
「远程一对多」关联为通过中间关联访问远程关联提供了方便的快捷操作。例如,一个 Country 模型可能通过中间的 User 模型拥有很多 Post 模型。在此示例中,您可以轻松聚集给定国家下的所有博客文章。我们看一下定义此关联所需的数据表:
countries
id - integer
name - string
users
id - integer
country_id - integer
name - string
posts
id - integer
user_id - integer
title - string尽管 posts 不包含 country_id 字段,但 hasManyThrough 关联仍可以通过 $country->posts 获取一个国家的文章。要完成此查询,Eloquent 会在中间表 users 上检查 country_id。找到匹配的用户 ID 后,用它们查询 posts 表。
既然我们已经查看了关联的数据表结构,接着我们在 Country 模型上定义:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Country extends Model
{
/**
* 获取国家的所有文章
*/
public function posts()
{
return $this->hasManyThrough('App\Post', 'App\User');
}
}第一个传递给 hasManyThrough 方法的参数是最终我们希望获取的模型名,而第二个参数是中间模型名。
通常情况下,执行关联查询时会使用约定的 Eloquent 外键。如果要自定义关联的键,可以将其作为第三个和第四个参数传递给 hasManyThrough 方法。第三个参数是中间模型的外键名。第四个参数是最终模型的外键名。第五个参数是本地键,而第六个参数是本地键的中间模型:
class Country extends Model
{
public function posts()
{
return $this->hasManyThrough(
'App\Post',
'App\User',
'country_id', // 用户表的外键
'user_id', // 文章表的外键
'id', // 国家表的本地键
'id' // 用户表的本地键
);
}
}
多态关联允许一个模型在单个关联中属于多个其它模型。例如,假设应用中用户可以同时「评论」文章和视频。使用多态关联,可以使用单张 comments 数据表同时满足这些场景。首先,我们查看创建此关联所需的数据表结构:
posts
id - integer
title - string
body - text
videos
id - integer
title - string
url - string
comments
id - integer
body - text
commentable_id - integer
commentable_type - string需要注意的两个字段是 comments 数据表的 commentable_id 和 commentable_type 字段。commentable_id 字段会包含文章或视频的 ID,而 commentable_type 字段会包含父模型的类名。commentable_type 字段会在 ORM 获取 commentable 关联时用于确定返回哪个「类型」的父模型。
接下来,我们查看创建此关联所需的模型定义:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
/**
* 获取所有可评论的父模型
*/
public function commentable()
{
return $this->morphTo();
}
}
class Post extends Model
{
/**
* 获取文章的所有评论
*/
public function comments()
{
return $this->morphMany('App\Comment', 'commentable');
}
}
class Video extends Model
{
/**
* 获取视频的所有评论
*/
public function comments()
{
return $this->morphMany('App\Comment', 'commentable');
}
}定义数据表和模型后,就可以通过模型获取关联了。例如,要获取文章的所有评论,我们可以使用 comments 动态属性:
$post = App\Post::find(1);
foreach ($post->comments as $comment) {
//
}还可以通过执行调用了 morphTo 的方法名在多态模型中获取多态关联的拥有者。在我们的示例中,就是 Comment 模型上的 commentable 方法。因此,我们可以像动态属性一样访问此方法:
$comment = App\Comment::find(1);
$commentable = $comment->commentable;Comment 模型上的 commentable 关联会返回一个 Post 或 Video 实例,具体取决于拥有评论的模型类型。
默认情况下,Laravel 会使用完整的类名存储关联模型的类型。举例说明,上述示例中 Comment 可能属于一个 Post 或 Video,因此默认的 commentable_type 会分别是 App\Post 或 App\Video。但是,您可能希望将数据库从应用的内部结构中解耦。这种情况下,可以定义一个关联的「多态映射」指示 Eloquent 为每个模型使用自定义名称而不是类名:
use Illuminate\Database\Eloquent\Relations\Relation;
Relation::morphMap([
'posts' => 'App\Post',
'videos' => 'App\Video',
]);可以在 AppServiceProvider 的 boot 函数中注册 morphMap,或者也可以创建一个单独的服务提供者。

除了传统的多态关联外,还可以定义「多对多」多态关联。例如,一个博客 Post 和一个 Video 模型可以共享一个到 Tag 模型的多态关联。使用多对多多态关联,可以在博客文章和视频间共享唯一的标签列表。首先,我们查看下一数据表结构:
posts
id - integer
name - string
videos
id - integer
name - string
tags
id - integer
name - string
taggables
tag_id - integer
taggable_id - integer
taggable_type - string接下来,就可以在模型上定义关联了。Post 和 Video 模型都会有一个调用 Eloquent 基类的 morphToMany 方法的 tags 方法:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
/**
* 获取文章的所有标签
*/
public function tags()
{
return $this->morphToMany('App\Tag', 'taggable');
}
}接着,在 Tag 模型上,应该为每个关联的模型定义一个方法。因此,在此示例中,我们会定义一个 posts 和一个 videos 方法:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Tag extends Model
{
/**
* 获取分配了此标签的所有文章
*/
public function posts()
{
return $this->morphedByMany('App\Post', 'taggable');
}
/**
* 获取分配了此标签的所有视频
*/
public function videos()
{
return $this->morphedByMany('App\Video', 'taggable');
}
}定义数据表和模型后,就可以通过模型获取关联了。例如,要获取文章的所有标签,可以使用 tags 动态属性:
$post = App\Post::find(1);
foreach ($post->tags as $tag) {
//
}还可以通过执行调用了 morphedByMany 的方法名在多态模型中获取多态关联的拥有者。在我们的示例中,就是 Tag 模型上的 posts 或 videos 方法。因此,我们可以像动态属性一样访问这些方法:
$tag = App\Tag::find(1);
foreach ($tag->videos as $video) {
//
}由于所有类型的 Eloquent 关联都通过方法定义,因此可以调用这些方法获取一个关联实例,而不用实际执行关联查询。此外,所有类型的 Eloquent 关联都可以作为 查询构造器 使用,所以可以在数据库最终运行 SQL 前继续链式添加约束到关联查询。
例如,假设一个博客系统中 User 模型有很多关联的 Post 模型:
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* 获取用户的所有文章
*/
public function posts()
{
return $this->hasMany('App\Post');
}
}还可以像这样查询 posts 关联并为关联添加其它约束条件:
$user = App\User::find(1);
$user->posts()->where('active', 1)->get();可以在关联上使用任何 查询构造器 方法,因此务必查看查询构造器的文档了解有哪些可用的方法。
如果不需要对 Eloquent 关联查询添加额外约束,可以获取关联就像它们是属性一样。例如,继续使用 User 和 Post 模型为例,我们可以像这样获取用户的所有文章:
$user = App\User::find(1);
foreach ($user->posts as $post) {
//
}动态属性是「懒加载」,意味着只有在实际访问时它们才会加载其关联数据。因此,开发者经常使用「预加载」在加载模型后提前加载要使用的关联。预加载有效减少了加载模型关联必须执行的 SQL 查询。
获取模型记录时,可能希望基于已存在的关联对结果进行限制。例如,假设希望获取至少有一条评论的所有博客文章。要完成此操作,可以传递关联名称给 has 和 orHas 方法:
// 获取至少有一条评论的所有文章
$posts = App\Post::has('comments')->get();也可以指定操作符和数量进行更详细的自定义查询:
// 获取有三条及以上评论的所有文章
$posts = App\Post::has('comments', '>=', 3)->get();还可以使用「点」语法构造嵌套的 has 语句。例如,可以获取至少有一条被点赞评论的所有文章:
// 获取至少有一条被点赞评论的所有文章
$posts = App\Post::has('comments.votes')->get();如果需要更强大的用法,可以使用 whereHas 和 orWhereHas 方法将「where」条件添加到 has 查询。这些方法允许您将自定义约束添加到关联约束,例如检查一条评论的内容:
// 获取至少有一条以 foo 起头的评论的所有文章
$posts = App\Post::whereHas('comments', function ($query) {
$query->where('content', 'like', 'foo%');
})->get();获取模型记录时,可能希望基于不存在的关联对结果进行限制。例如,假设希望获取没有任何评论的所有博客文章。要完成此操作,可以传递关联名称给 doesntHave 或 orDoesntHave 方法:
$posts = App\Post::doesntHave('comments')->get();如果需要更强大的用法,可以使用 whereDoesntHave 和 orWhereDoesntHave 方法将「where」条件添加到 doesntHave 查询。这些方法允许您将自定义约束添加到关联约束,例如检查一条评论的内容:
$posts = App\Post::whereDoesntHave('comments', function ($query) {
$query->where('content', 'like', 'foo%');
})->get();还可以使用「点」语法查询嵌套的关联。例如,下列查询会获取评论作者没有被禁用的所有文章:
$posts = App\Post::whereDoesntHave('comments.author', function ($query) {
$query->where('banned', 1);
})->get();如果要在不实际加载关联结果的情况下统计其数量,可以使用 withCount 方法,它会在结果模型中放一个 {relation}_count 字段。例如:
$posts = App\Post::withCount('comments')->get();
foreach ($posts as $post) {
echo $post->comments_count;
}可以为多个关联添加计数就和添加约束到查询一样:
$posts = App\Post::withCount(['votes', 'comments' => function ($query) {
$query->where('content', 'like', 'foo%');
}])->get();
echo $posts[0]->votes_count;
echo $posts[0]->comments_count;还可以为关联计数结果取别名,在相同关联上进行多个计数:
$posts = App\Post::withCount([
'comments',
'comments as pending_comments_count' => function ($query) {
$query->where('approved', false);
}
])->get();
echo $posts[0]->comments_count;
echo $posts[0]->pending_comments_count;像属性一样获取 Eloquent 关联时,关联数据是「懒加载」。这意味着关联数据只会在初次访问属性时才会实际加载。但是,在查询父模型时 Eloquent 可以同时「预加载」关联。预加载避免了 N+1 查询问题。为了说明 N+1 查询问题,假设 Book 模型关联到了 Author:
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;
}此循环会执行 1 次查询获取数据表的所有书,然后每本书执行另一次查询获取作者。因此,如果我们有 25 本书,此循环会执行 26 次查询:1 次获取书,其它 25 次查询获取每本书的作者。
幸好,我们可以使用预加载将此操作减少到只有 2 次查询。在查询时,可以使用 with 方法指定要预加载的关联:
$books = App\Book::with('author')->get();
foreach ($books as $book) {
echo $book->author->name;
}在此操作中,只会执行 2 次查询:
select * from books
select * from authors where id in (1, 2, 3, 4, 5, ...)有时可能需要在单个操作中预加载多个不同的关联。要完成此操作,只需要传递其它参数给 with 方法:
$books = App\Book::with(['author', 'publisher'])->get();要预加载嵌套的关联,可以使用「点」语法。例如,我们在一条 Eloquent 语句中预加载所有书的作者和所有书作者的个人联系方式:
$books = App\Book::with('author.contacts')->get();获取关联时可能不总需要所有字段。因此,Eloquent 允许您指定要获取的关联字段:
$users = App\Book::with('author:id,name')->get();使用此功能时,应该在要获取的字段列表中始终包括
id字段。
有时可能希望预加载关联,但也希望为预加载查询指定其它查询约束。下面是一个示例:
$users = App\User::with(['posts' => function ($query) {
$query->where('title', 'like', '%first%');
}])->get();在此示例中,Eloquent 只会加载文章的 title 字段包含单词 first 的文章。当然,还可以调用其它 查询构造器 方法进行更多自定义预加载操作:
$users = App\User::with(['posts' => function ($query) {
$query->orderBy('created_at', 'desc');
}])->get();有时在获取父模型后可能需要预加载关联模型。例如,在需要动态决定是否加载关联模型时很有用:
$books = App\Book::all();
if ($someCondition) {
$books->load('author', 'publisher');
}如果需要在预加载上添加其它查询约束,可以传递一个数组,并将要加载的关联作为键。数组的值应是一个接收查询实例的闭包实例:
$books->load(['author' => function ($query) {
$query->orderBy('published_date', 'asc');
}]);如果只是在关联未加载时进行加载,可以使用 loadMissing 方法:
public function format(Book $book)
{
$book->loadMissing('author');
return [
'name' => $book->name,
'author' => $book->author->name
];
}save 方法Eloquent 为添加新模型到关联中提供了便捷的方法。例如,可能需要为 Post 模型插入一个新的 Comment。可以直接使用关联的 save 方法插入 Comment,而不是手动设置 Comment 的 post_id 属性:
$comment = new App\Comment(['message' => 'A new comment.']);
$post = App\Post::find(1);
$post->comments()->save($comment);需要注意的是,我们没有以动态属性的方式访问 comments 关联。而是,调用 comments 方法获取一个关联的实例。save 方法会自动添加对应的 post_id 值到新的 Comment 模型。
如果需要保存多个关联模型,可以使用 saveMany 方法:
$post = App\Post::find(1);
$post->comments()->saveMany([
new App\Comment(['message' => 'A new comment.']),
new App\Comment(['message' => 'Another comment.']),
]);如果要 save 模型和所有相关的关联,可以使用 push 方法:
$post = App\Post::find(1);
$post->comments[0]->message = 'Message';
$post->comments[0]->author->name = 'Author Name';
$post->push();create 方法除了使用 save 和 saveMany 方法外,还可以使用 create 方法,它接收一个属性数组,创建模型并将其插入到数据库。同样,save 和 create 之间的区别是,save 接收一个完整的 Eloquent 模型实例,而 create 接收一个 PHP 数组:
$post = App\Post::find(1);
$comment = $post->comments()->create([
'message' => 'A new comment.',
]);在使用
create方法前,确保查看了属性 批量赋值 文档。
可以使用 createMany 方法创建多个关联模型:
$post = App\Post::find(1);
$post->comments()->createMany([
[
'message' => 'A new comment.',
],
[
'message' => 'Another new comment.',
],
]);还可以使用 findOrNew,firstOrNew,firstOrCreate 和 updateOrCreate 方法 在关联上创建和更新模型。
更新 belongsTo 关联时,可以使用 associate 方法。此方法会设置子模型的外键:
$account = App\Account::find(10);
$user->account()->associate($account);
$user->save();删除 belongsTo 关联时,可以使用 dissociate 方法。此方法会将关联的外键设置为 null:
$user->account()->dissociate();
$user->save();belongsTo 关联允许您定义一个当给定关联为 null 时返回的默认模型。这种模式通常被称为 空对象模式,可以帮助在代码中省去条件判断。在以下示例中,如果没有 user 附加到文章则 user 关联会返回一个空的 App\User 模型:
/**
* 获取文章的作者
*/
public function user()
{
return $this->belongsTo('App\User')->withDefault();
}要为默认模型指定属性,可以传递一个数组或闭包给 withDefault 方法:
/**
* 获取文章的作者
*/
public function user()
{
return $this->belongsTo('App\User')->withDefault([
'name' => 'Guest Author',
]);
}
/**
* 获取文章的作者
*/
public function user()
{
return $this->belongsTo('App\User')->withDefault(function ($user) {
$user->name = 'Guest Author';
});
}Eloquent 也提供了一些额外的辅助方法,来更方便地处理关联模型。例如,我们假设一个用户可以有很多角色并且一个角色可以有很多用户。如果要通过在连接模型的中间表中插入一条记录将角色附加到用户,可以使用 attach 方法:
$user = App\User::find(1);
$user->roles()->attach($roleId);将关联附加到模型时,也可以传递一个要插入到中间表的额外数据的数组:
$user->roles()->attach($roleId, ['expires' => $expires]);当然,有时需要在用户上移除角色。要移除一个多对多关联记录,可以使用 detach 方法。detach 方法会移除中间表中对应的记录;不过,两个模型都会保留在数据库中:
// 从用户上分离一个角色
$user->roles()->detach($roleId);
// 从用户上分离所有角色
$user->roles()->detach();为了方便,attach 和 detach 也接收 ID 数组作为输入:
$user = App\User::find(1);
$user->roles()->detach([1, 2, 3]);
$user->roles()->attach([
1 => ['expires' => $expires],
2 => ['expires' => $expires]
]);也可以使用 sync 方法构造多对多关联。sync 接收一个放到中间表的 ID 数组。任何不在给定数组中的 ID 都会从中间表中移除。因此,此操作完成后,只有给定数组中的 ID 会存在于中间表中:
$user->roles()->sync([1, 2, 3]);还可以和 ID 一起传递额外的中间表值:
$user->roles()->sync([1 => ['expires' => true], 2, 3]);如果不希望分离已存在的 ID,可以使用 syncWithoutDetaching 方法:
$user->roles()->syncWithoutDetaching([1, 2, 3]);多对对关联也提供了一个 toggle 方法,用于「切换」给定 ID 的附加状态。如果给定 ID 当前已附加,则会被分离。同样,如果当前已分离,则会被附加:
$user->roles()->toggle([1, 2, 3]);处理多对多关联时,save 方法接收一个额外的中间表属性数组作为其第二个参数:
App\User::find(1)->roles()->save($role, ['expires' => $expires]);如果需要更新中间表中已存在的行,可以使用 updateExistingPivot 方法。此方法接收中间表记录的外键和要更新的属性数组:
$user = App\User::find(1);
$user->roles()->updateExistingPivot($roleId, $attributes);当一个模型 belongsTo 或 belongsToMany 另一个模型时(例如一个 Comment 属于一个 Post),在子模型更新时更新父模型的时间戳有时很有用。例如,Comment 模型更新后,您可能想要自动「触发」拥有者 Post 的时间戳更新。Eloquent 中很容易实现。只需要添加一个包含关联名的 touches 属性到子模型中:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
/**
* 所有要触发的关联
*
* @var array
*/
protected $touches = ['post'];
/**
* 获取评论所属的文章
*/
public function post()
{
return $this->belongsTo('App\Post');
}
}现在,当更新 Comment 时,拥有者 Post 也会更新其 updated_at 字段,可以更方便地知道何时应该让 Post 模型的缓存失效:
$comment = App\Comment::find(1);
$comment->text = 'Edit to this comment!';
$comment->save();