
本文介绍在 laravel 中如何通过递归关系准确统计某个分类及其所有下级子分类(任意深度)所关联的商品总数,适用于树形分类结构与末端商品绑定的场景。
本文介绍在 laravel 中如何通过递归关系准确统计某个分类及其所有下级子分类(任意深度)所关联的商品总数,适用于树形分类结构与末端商品绑定的场景。
在典型的电商或内容管理系统中,分类常以树形结构组织(如 category 表中的 parent 字段),而商品(Product)仅直接关联到最深层级的叶子分类(例如题中第 3 级分类 C-1、C-2)。此时,若需向上聚合统计——例如“顶级分类 A 下共有多少商品”,就必须递归遍历其全部后代分类(不限层级),并汇总这些后代分类所关联的商品数量。
单纯使用 with('subCategories') 加 count()(如答案中所示)是错误且无效的:它仅统计直接子分类数量,而非商品数,也未递归遍历深层关系。正确解法需分两步实现:
✅ 步骤一:定义递归关系(已具备)
在 Category 模型中,你已正确定义了自关联的递归关系:
// app/Models/Category.php
public function subCategories()
{
return $this->hasMany(self::class, 'parent')->with('subCategories');
}
但注意:此关系仅用于查询时预加载,不参与计数逻辑。
✅ 步骤二:构建递归商品计数方法
新增一个模型方法 getProductCountAttribute(),利用 Laravel 的 whereDescendantOf()(需安装 kalnoy/nestedset)或手动递归 ID 收集(更通用):
✅ 推荐方案:使用 withCount() + 递归 ID 集合(无需额外扩展包)
// app/Models/Category.php
public function products()
{
return $this->belongsToMany(Product::class, 'category_product'); // 假设多对多中间表
}
public function getProductCountAttribute()
{
// 获取当前分类及其所有后代分类的 ID(含自身)
$allIds = $this->descendants()->pluck('id')->push($this->id)->unique()->toArray();
// 统计这些分类下关联的所有商品总数(去重避免中间表重复计数)
return Product::whereHas('categories', fn ($q) => $q->whereIn('category_id', $allIds))
->distinct('products.id') // 防止同一商品被多个子分类重复计入
->count();
}
⚠️ 注意:若商品仅属于单个叶子分类(即无多对多,而是
product.category_id外键直连),则改为:return Product::whereIn('category_id', $allIds)->count();
✅ 调用示例(控制器中):
$categoryA = Category::findOrFail(1); echo $categoryA->product_count; // 输出 8 $categoryB1 = Category::findOrFail(2); echo $categoryB1->product_count; // 输出 3
? 补充说明与优化建议
-
性能关键:对深树结构频繁调用时,建议添加缓存(如
Cache::remember("category_{$id}_product_count", 3600, ...))。 -
避免 N+1:切勿在循环中逐个调用
$cat->product_count;应批量预加载或使用数据库聚合(如WITH RECURSIVE查询,MySQL 8.0+/PostgreSQL 支持)。 - 数据一致性:当商品或分类关系变更时,需主动刷新缓存或使用模型事件监听更新。
通过该方案,你可精准、高效、可维护地实现任意深度分类的商品总数聚合统计,彻底解决“拉头发式”难题。










