You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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');
    }
}

数据库结构示例:

idparent_comment_idtext
1nullText 1
21Text 2
32Text 3
43Text 4
54Text 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集合递归遍历统计:

  1. 先在模型中定义递归关联:
public function allReplies()
{
    return $this->replies()->with('allReplies');
}
  1. 获取评论后递归统计:
// 获取评论并预加载所有嵌套回复
$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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 17:43:21