Laravel的Eloquent ORM提供了便捷的模型关联功能,能够将数据库表之间的关系映射到模型层面,避免手动编写复杂的联表SQL语句,大幅降低数据操作的复杂度。

常见关联关系类型及定义方法
一对一关联
一对一关联通常用于两个表之间存在唯一对应关系,例如用户表和用户信息扩展表。假设User模型对应用户主表,UserProfile模型对应用户扩展表,两个表通过user_id字段关联。
首先在User模型中定义关联方法:
<?php
namespace AppModels;
use IlluminateDatabaseEloquentModel;
class User extends Model
{
// 定义一对一关联,一个用户对应一个用户资料
public function profile()
{
// 第一个参数是关联模型类名,第二个参数是关联模型的外键,第三个参数是当前模型的主键
return $this->hasOne(UserProfile::class, 'user_id', 'id');
}
}
在UserProfile模型中定义反向关联:
<?php
namespace AppModels;
use IlluminateDatabaseEloquentModel;
class UserProfile extends Model
{
// 定义反向一对一关联,用户资料属于一个用户
public function user()
{
// 第一个参数是关联模型类名,第二个参数是当前模型的外键,第三个参数是关联模型的主键
return $this->belongsTo(User::class, 'user_id', 'id');
}
}
一对多关联
一对多关联适用于一个模型对应多个其他模型的情况,例如用户表和文章表,一个用户可以发布多篇文章。
在User模型中定义一对多关联:
<?php
namespace AppModels;
use IlluminateDatabaseEloquentModel;
class User extends Model
{
// 定义一对多关联,一个用户有多篇文章
public function posts()
{
return $this->hasMany(Post::class, 'user_id', 'id');
}
}
在Post模型中定义反向关联:
<?php
namespace AppModels;
use IlluminateDatabaseEloquentModel;
class Post extends Model
{
// 文章属于一个用户
public function user()
{
return $this->belongsTo(User::class, 'user_id', 'id');
}
}
多对多关联
多对多关联需要中间表来维护关系,例如文章表和标签表,一篇文章可以有多个标签,一个标签也可以属于多篇文章,中间表通常为article_tag,包含article_id和tag_id字段。
在Post模型中定义多对多关联:
<?php
namespace AppModels;
use IlluminateDatabaseEloquentModel;
class Post extends Model
{
// 定义多对多关联,文章有多个标签
public function tags()
{
// 第二个参数是中间表名,第三个参数是当前模型在中间表的外键,第四个参数是关联模型在中间表的外键
return $this->belongsToMany(Tag::class, 'article_tag', 'article_id', 'tag_id');
}
}
在Tag模型中定义反向多对多关联:
<?php
namespace AppModels;
use IlluminateDatabaseEloquentModel;
class Tag extends Model
{
// 标签属于多个文章
public function posts()
{
return $this->belongsToMany(Post::class, 'article_tag', 'tag_id', 'article_id');
}
}
关联查询常用操作
基础关联查询
定义好关联之后,可以直接通过动态属性访问关联数据:
<?php // 查询id为1的用户及其关联的资料 $user = User::find(1); // 访问关联的动态属性,会自动执行关联查询 $profile = $user->profile; // 查询id为1的文章及其关联的用户 $post = Post::find(1); $author = $post->user; // 查询id为1的文章及其所有标签 $post = Post::find(1); $tags = $post->tags;
预加载关联
如果直接循环访问关联属性,会产生N+1查询问题,例如查询所有用户再获取每个用户的资料,会执行1次查询用户加N次查询资料的SQL。使用with方法预加载可以解决这个问题:
<?php
// 预加载用户关联的资料,只会执行2次SQL查询
$users = User::with('profile')->get();
foreach ($users as $user) {
// 这里不会额外执行SQL查询
echo $user->profile->nickname;
}
// 预加载多个关联
$posts = Post::with(['user', 'tags'])->get();
关联查询条件筛选
可以在查询时添加关联模型的条件筛选,例如查询发布过文章的用户:
<?php
// 使用whereHas筛选存在关联文章的的用户
$usersWithPosts = User::whereHas('posts', function ($query) {
// 可以添加更多条件,例如查询发布过状态为已发布文章的用户
$query->where('status', 1);
})->get();
// 查询包含指定标签的文章
$posts = Post::whereHas('tags', function ($query) {
$query->where('name', 'Laravel');
})->get();
关联数据新增与更新
除了查询,关联还支持便捷的新增和更新操作:
<?php
// 给id为1的用户新增关联资料
$user = User::find(1);
$user->profile()->create([
'nickname' => '测试用户',
'avatar' => 'avatar.jpg'
]);
// 给id为1的文章添加标签,attach方法会往中间表插入记录
$post = Post::find(1);
$post->tags()->attach([1, 2, 3]); // 传入标签id数组
// 同步标签,会移除中间表中不在数组里的记录
$post->tags()->sync([2, 3]);
注意事项
- 关联定义时外键和主键参数如果符合Laravel的命名约定(外键为模型名小写加_id,主键为id),可以省略后面两个参数
- 多对多关联的中间表名默认是两个模型名的单数形式按字母顺序拼接,例如用户和角色的中间表默认是role_user,如果不符合约定需要手动指定
- 预加载时如果关联数据量很大,可以考虑分块加载避免内存占用过高
- 关联查询的条件筛选中,闭包里的查询构造器和普通查询构造器用法一致,可以添加排序、分页等条件