在数据库设计中,一对多关联是最常见的关系之一。例如,一篇博客文章拥有多条评论,一个用户可以发布多篇文章。
Laravel 的 Eloquent ORM 通过简洁的语法让一对多关联的定义和使用变得非常优雅。
本章将详细介绍如何使用 hasMany 和 belongsTo 方法构建和管理一对多关联。
一对多关联表示一个模型拥有多个子模型。例如:
在数据库层面,通常通过在「多」的一方表中存储「一」的主键来实现(如 comments 表中的 post_id 字段)。
posts表
- id
- title
- content
- user_id
comments表
- id
- content
- post_id
- user_id
一对多关联需要定义两个方向:
hasMany 方法。belongsTo 方法。在 Post 模型中定义与 Comment 的一对多关联:
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);
}
}
也可以指定外键和本地键:
return $this->hasMany(Comment::class, 'foreign_key', 'local_key');
在 Comment 模型中定义反向关联:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Comment extends Model
{
public function post(): BelongsTo
{
return $this->belongsTo(Post::class);
}
}
同样可以自定义外键和所有者键:
return $this->belongsTo(Post::class, 'foreign_key', 'owner_key');
comments() 方法会使用 post_id 作为外键(以关联的模型名加上 _id)。如果不一致,请显式指定。
定义好关联后,就可以方便地访问和操作关联数据。
通过动态属性可以获取所有关联模型(返回集合):
$post = Post::find(1);
$comments = $post->comments; // 集合
foreach ($comments as $comment) {
echo $comment->content;
}
$comment = Comment::find(1);
$post = $comment->post; // 单个模型对象
echo $post->title;
$post = Post::find(1);
$comment = new Comment(['content' => '很棒的文章']);
$post->comments()->save($comment);
也可以批量创建多个:
$post->comments()->saveMany([
new Comment(['content' => '评论一']),
new Comment(['content' => '评论二']),
]);
在 Comment 模型中定义可填充字段:
protected $fillable = ['content'];
然后:
$post->comments()->create(['content' => '通过 create 创建']);
$comment = $post->comments()->firstOrCreate(
['content' => '查找内容'],
['user_id' => 1] // 如果不存在时附加的数据
);
使用 associate 方法:
$comment = Comment::find(1);
$newPost = Post::find(2);
$comment->post()->associate($newPost);
$comment->save();
使用 dissociate 解除关联:
$comment->post()->dissociate();
$comment->save();
你可以在关联上添加额外的查询条件,以获取部分关联数据。
$comments = $post->comments()
->where('is_approved', true)
->orderBy('created_at', 'desc')
->limit(5)
->get();
使用 has 方法获取至少有一条评论的文章:
$posts = Post::has('comments')->get();
可以添加条件:has('comments', '>=', 3) 获取评论数大于等于3的文章。
使用 whereHas 添加更复杂的条件:
$posts = Post::whereHas('comments', function ($query) {
$query->where('content', 'like', '%好评%');
})->get();
使用 withCount 方法在不加载关联数据的情况下获取数量:
$posts = Post::withCount('comments')->get();
foreach ($posts as $post) {
echo $post->comments_count;
}
预加载可以有效解决 N+1 查询问题,提高性能。
$posts = Post::with('comments')->get();
foreach ($posts as $post) {
foreach ($post->comments as $comment) {
echo $comment->content;
}
}
仅需两次查询(一次查文章,一次查所有评论),而不是 N+1 次。
$posts = Post::with(['comments' => function ($query) {
$query->where('is_approved', true)->orderBy('created_at', 'desc');
}])->get();
$users = User::with('posts.comments')->get();
这会同时加载用户、用户的文章以及文章的评论。
如果已经获取了模型集合,可以事后加载关联:
$posts = Post::all();
$posts->load('comments');
下面是一个完整的示例,展示 User、Post、Comment 之间的一对多关系。
模型定义:
// User.php
public function posts()
{
return $this->hasMany(Post::class);
}
// Post.php
public function user()
{
return $this->belongsTo(User::class);
}
public function comments()
{
return $this->hasMany(Comment::class);
}
// Comment.php
public function post()
{
return $this->belongsTo(Post::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
使用示例:
// 获取某个用户的所有文章,并预加载文章的所有评论
$user = User::with('posts.comments')->find(1);
foreach ($user->posts as $post) {
echo $post->title . ' - 评论数:' . $post->comments->count();
}
// 为文章添加评论
$post = Post::find(1);
$post->comments()->create([
'content' => '很好的文章',
'user_id' => auth()->id(),
]);
// 获取带有评论数且评论数大于5的文章
$popularPosts = Post::withCount('comments')
->having('comments_count', '>', 5)
->get();
with() 加载关联,避免视图层引发 N+1 查询。
comments() 方法会使用 commentable_id?不对,对于 belongsTo,默认外键是关联模型名的小写单数加 _id。如 Comment 模型的 post() 方法会使用 post_id。如果不一致,请显式传入第二个参数。
static::deleting(function ($post) { $post->comments()->delete(); });
一对多关联是 Eloquent 中最常用的关联类型之一。通过 hasMany 和 belongsTo,你可以轻松定义模型间的关系,并进行增删改查、预加载、条件查询等操作。
掌握这些技巧,将使你的数据库操作更加简洁高效。
下一章我们将继续学习多对多关联,处理更复杂的数据关系。