简介
数据表经常要与其它表做关联,比如一篇博客文章可能有很多评论,或者一个订单会被关联到下单用户。
Eloquent 让组织和处理这些关联关系变得简单,并且支持多种不同类型的关联关系:
- 一对一
- 一对多
- 多对多
- 远层一对多
- 多态关联
- 多对多的多态关联
定义关联关系
关联关系以 Eloquent 模型类方法的方式定义。
和 Eloquent 模型本身一样,关联关系也是强大的查询构建器,定义关联关系为方法可以提供功能强大的方法链和查询能力。
例如,我们可以添加更多约束条件到 post 关联关系:
$user->posts()->where('active', 1)->get();
在深入使用关联关系之前,让我们先学习如何定义每种关联关系。
一对一
一对一关联是一个最简单的关联关系。
例如,一个 User 模型有一个与之关联的 Phone 模型。
要定义这种关联关系,我们需要将 phone 方法置于User 模型中,phone 方法调用 Illuminate\Database\Eloquent\Concerns\HasRelationships trait 中的 hasOne 方法并返回其结果。
<?php
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 将会通过 user 表的 id 值去 phone 表中查询 user_id 与之匹配的 Phone 记录。如果你想要关联关系使用其他值而不是 id,可以传递第三个参数到hasOne 来指定自定义的主键:
return $this->hasOne('App\Phone', 'foreign_key', 'local_key');
我们通过传递完整参数改写上述示例代码:
return $this->hasOne('App\Phone', 'user_id', 'id');
定义相对的关联
我们可以从 User 中访问 Phone 模型,相应地,也可以在 Phone 模型中定义关联关系从而让我们可以拥有该手机的 User。
可以使用 belongsTo 方法定义与 hasOne 关联关系相对的关联:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Phone extends Model{
/**
* 获取拥有该手机的用户
*/
public function user()
{
return $this->belongsTo('App\User');
}
}
Eloquent 默认将会尝试通过 Phone 模型的 user_id 去 User 模型查找与之匹配的记录。
Eloquent 通过在关联关系方法名后加 _id 后缀来生成默认的外键名。不过,如果 Phone 模型上的外键不是 user_id,也可以将自定义的键名作为第二个参数传递到 belongsTo 方法:
return $this->belongsTo('App\User', 'foreign_key');
如果父模型不使用 id 作为主键,或者你希望使用别的数据列来连接子模型,可以将父表自定义键作为第三个参数传递给 belongsTo 方法:
return $this->belongsTo('App\User', 'foreign_key', 'other_key');
同样,我们通过传递完整的参数来改写上述示例代码:
return $this->belongsTo('App\User', 'user_id', 'id');
默认模型
belongsTo 关联关系允许你在给定关联关系为 null 的情况下,定义一个默认的返回模型,我们将这种模式称之为空对象模式,使用这种模式的好处是不用在代码中编写大量的判断检查逻辑。
在下面的例子中,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 模型中定义方法来定义:
<?php
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 方法获取到的评论上:
$comments = 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');
// 在本例中,传递完整参数代码如下
return $this->hasMany('App\Comment', 'post_id', 'id');
一对多(逆向)
现在我们可以访问文章的所有评论了,接下来让我们定义一个关联关系允许通过评论访问所属文章。
要定义与 hasMany 相对的关联关系,需要在子模型中定义一个关联方法去调用 belongsTo 方法:
<?php
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::fin