Eloquent: 关联
简介
数据库表通常相互关联。例如,一篇博客文章可能有许多评论,或者一个订单对应一个下单用户。Eloquent
让这些关联的管理和使用变得简单,并支持多种常用的关联类型:
定义关联
Eloquent 关联在 Eloquent 模型类中以方法的形式呈现。如同 Eloquent 模型本身,关联也可以作为强大的查询语句构造器,使用,提供了强大的链式调用和查询功能。例如,我们可以在 posts
关联的链式调用中附加一个约束条件:
$user->posts()->where('active', 1)->get();
不过在深入使用关联之前,让我们先学习如何定义每种关联类型。
一对一
一对一是最基本的数据库关系。 例如,一个 User
模型可能与一个 Phone
模型相关联。为了定义这个关联关系,我们要在 User
模型中写一个 phone
方法。 在 phone
方法中调用 hasOne
方法并返回其结果。 hasOne
方法被定义在 Illuminate\Database\Eloquent\Model
这个模型基类中:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOne;
class User extends Model
{
/**
* 获取与用户相关的电话记录
*/
public function phone(): HasOne
{
return $this->hasOne(Phone::class);
}
}
hasOne
方法的第一个参数是关联模型的类名。一旦定义了模型关联,我们就可以使用 Eloquent 的动态属性获得相关的记录。动态属性允许你访问该关联方法,就像访问模型中定义的属性一样:
$phone = User::find(1)->phone;
Eloquent 基于父模型 User
的名称来确定关联模型 Phone
的外键名称。在本例中,会自动假定 Phone
模型有一个 user_id
的外键。如果你想重写这个约定,可以传递第二个参数给 hasOne
方法:
return $this->hasOne(Phone::class, 'foreign_key');
另外,Eloquent 假设外键的值是与父模型的主键(Primary Key)相同的。换句话说,Eloquent 将会通过 Phone
记录的 user_id
列中查找与用户表的 id
列相匹配的值。如果你希望使用自定义的主键值,而不是使用 id
或者模型中的 $primaryKey
属性,你可以给 hasOne
方法传递第三个参数:
return $this->hasOne(Phone::class, 'foreign_key', 'local_key');
定义反向关联
我们已经能从 User
模型访问到 Phone
模型了。接下来,让我们再在 Phone
模型上定义一个关联,它能让我们访问到拥有该电话的用户。我们可以使用 belongsTo
方法来定义反向关联, belongsTo
方法与 hasOne
方法相对应:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Phone extends Model
{
/**
* 获取拥有此电话的用户
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
在调用 user
方法时,Eloquent 会尝试查找一个 User
模型,该 User
模型上的 id
字段会与 Phone
模型上的 user_id
字段相匹配。
Eloquent 通过关联方法(user
)的名称并使用 _id
作为后缀名来确定外键名称。因此,在本例中,Eloquent 会假设 Phone
模型有一个 user_id
字段。但是,如果 Phone
模型的外键不是 user_id
,这时你可以给 belongsTo
方法的第二个参数传递一个自定义键名:
/**
* 获取拥有此电话的用户
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'foreign_key');
}
如果父模型的主键未使用 id
作为字段名,或者你想要使用其他的字段来匹配相关联的模型,那么你可以向 belongsTo
方法传递第三个参数,这个参数是在父模型中自己定义的字段名称:
/**
* 获取当前手机号的用户
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'foreign_key', 'owner_key');
}
一对多
当要定义一个模型是其他 (一个或者多个)模型的父模型这种关系时,可以使用一对多关联。例如,一篇博客可以有很多条评论。和其他模型关联一样,一对多关联也是在 Eloquent 模型文件中用一个方法来定义的:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Post extends Model
{
/**
* 获取这篇博客的所有评论
*/
public function comments(): HasMany
{
return $this->hasMany(Comment::class);
}
}
注意,Eloquent 将会自动为 Comment
模型选择一个合适的外键。通常,这个外键是通过使用父模型的「蛇形命名」方式,然后再加上 _id
. 的方式来命名的。因此,在上面这个例子中,Eloquent 将会默认 Comment
模型的外键是 post_id
字段。
如果关联方法被定义,那么我们就可以通过 comments
属性来访问相关的评论 集合。注意,由于 Eloquent 提供了「动态属性」,所以我们就可以像访问模型属性一样来访问关联方法:
use App\Models\Post;
$comments = Post::find(1)->comments;
foreach ($comments as $comment) {
// ...
}
由于所有的关系都可以看成是查询构造器,所以你也可以通过链式调用的方式,在 comments
方法中继续添加条件约束:
$comment = Post::find(1)->comments()
->where('title', 'foo')
->first();
像 hasOne
方法一样,你也可以通过将附加参数传递给 hasMany
方法来覆盖外键和本地键:
return $this->hasMany(Comment::class, 'foreign_key');
return $this->hasMany(Comment::class, 'foreign_key', 'local_key');