如何使用Laravel Eloquent统计评论的所有嵌套回复总数?
统计嵌套评论的所有回复总数
问题背景
我正在为文章构建评论系统,现有Comment模型代码如下:
class Comment extends Model { use HasFactory; protected $fillable = ['user_id', 'post_id', 'parent_comment_id', 'text', 'is_approved', 'upvotes', 'downvotes']; protected $with = ['post']; public function post() { return $this->belongsTo(Post::class, 'post_id'); } public function user() { return $this->belongsTo(User::class, 'user_id'); } public function replies() { return $this->hasMany(Comment::class, 'parent_comment_id'); } }
数据库结构示例:
| id | parent_comment_id | text |
|---|---|---|
| 1 | null | Text 1 |
| 2 | 1 | Text 2 |
| 3 | 2 | Text 3 |
| 4 | 3 | Text 4 |
| 5 | 4 | Text 5 |
需求是统计评论的所有嵌套子评论总数:比如评论ID=1时总回复数为4,评论ID=2时总回复数为3。但当前查询仅能统计直接子评论数:
Comment::query() ->without('post') ->with('user:id,name,email,role,picture') ->withCount('replies') ->where('parent_comment_id', request()->input('comment_id', null)) ->where('post_id', $id) ->orderByDesc('replies_count') ->paginate(2) ->withQueryString() ->fragment('post-comments');
解决方案
方法一:递归CTE查询(推荐,性能优)
利用SQL的公共表表达式(CTE)递归遍历所有子评论,一次性计算总数,适合大数据量场景。
批量查询时直接计算总数
修改原有查询,通过selectSub添加嵌套回复数字段:
$comments = Comment::query() ->without('post') ->with('user:id,name,email,role,picture') ->select('comments.*') // 新增子查询计算所有嵌套回复数 ->selectSub(function ($query) { $query->from('comments') ->withRecursive('comment_tree', function ($q) { // 起始节点:当前评论的直接子评论 $q->select('id', 'parent_comment_id') ->from('comments') ->whereColumn('parent_comment_id', 'comments.id') // 递归遍历所有子评论 ->unionAll(function ($q) { $q->select('c.id', 'c.parent_comment_id') ->from('comments as c') ->join('comment_tree as ct', 'ct.id', '=', 'c.parent_comment_id'); }); }) ->count(); }, 'total_replies_count') ->where('parent_comment_id', request()->input('comment_id', null)) ->where('post_id', $id) ->orderByDesc('total_replies_count') ->paginate(2) ->withQueryString() ->fragment('post-comments');
执行后每条评论会带上total_replies_count字段,即为该评论的所有嵌套子评论总数。
单个评论获取总数
如果只需获取单条评论的嵌套回复数,可在模型中添加方法:
public function getAllRepliesCount() { return \DB::table('comments') ->withRecursive('comment_tree', function ($query) { $query->select('id', 'parent_comment_id') ->from('comments') ->where('parent_comment_id', $this->id) ->unionAll(function ($query) { $query->select('c.id', 'c.parent_comment_id') ->from('comments as c') ->join('comment_tree as ct', 'ct.id', '=', 'c.parent_comment_id'); }); }) ->count(); }
使用时直接调用:$comment->getAllRepliesCount()
方法二:集合递归统计(适合小数据量)
若评论数据量小、嵌套层级浅,可通过Laravel集合递归遍历统计:
- 先在模型中定义递归关联:
public function allReplies() { return $this->replies()->with('allReplies'); }
- 获取评论后递归统计:
// 获取评论并预加载所有嵌套回复 $comments = Comment::query() ->without('post') ->with('user:id,name,email,role,picture') ->with('allReplies') ->where('parent_comment_id', request()->input('comment_id', null)) ->where('post_id', $id) ->get(); // 遍历计算总数 $comments->each(function ($comment) { $comment->total_replies_count = $this->countNestedReplies($comment); }); // 辅助统计方法 private function countNestedReplies($comment) { $count = $comment->replies->count(); foreach ($comment->replies as $reply) { $count += $this->countNestedReplies($reply); } return $count; }
注意:此方法在数据量大时会有严重性能问题,仅适合测试或小流量场景。
方法三:缓存优化(高并发场景)
若评论更新频率低,可在评论创建/删除时递归更新父评论的缓存计数,避免每次查询都计算:
- 新增评论时,向上遍历所有父评论,更新其缓存的回复数
- 删除评论时,同样递归更新所有父评论的缓存计数
这种方法能大幅提升查询性能,但需要额外维护缓存更新逻辑。
内容的提问来源于stack exchange,提问作者Moxley
相关产品推荐
相关产品推荐

