Laravel Eloquent:获取指定分类及所有嵌套子分类的文章
解决分类及其嵌套子分类ID重复问题,同时优化文章查询方式
嘿,看起来你在递归获取分类ID的时候碰到了重复值的问题,我来给你几个解决方案,从修复现有代码到更高效的数据库查询都有!
一、先修复你的递归方法(快速解决重复问题)
你原来的递归逻辑里,getChildren方法的参数传递和返回逻辑有点混乱,导致同一个ID被多次添加到数组里。我们可以简化递归逻辑,让每个分类只收集自己和子分类的ID,最后统一去重:
修改后的getSubcategoriesIds方法(可以删掉原来的getChildren):
// PostController.php public function getSubcategoriesIds($category) { // 先把当前分类ID加入数组 $ids = [$category->id]; // 递归遍历每个子分类,合并它们的ID数组 foreach ($category->subcategories as $subcategory) { $ids = array_merge($ids, $this->getSubcategoriesIds($subcategory)); } // 去重并重置数组索引,避免重复值和混乱的键名 return array_values(array_unique($ids)); }
这样修改后,每个分类ID只会被添加一次,返回的数组就不会有重复了。
二、更高效的方案:用数据库递归查询(推荐)
上面的方法虽然能解决问题,但如果分类层级很深,会依赖Laravel的集合递归处理,效率不如直接让数据库一次性返回所有需要的ID。我们可以用数据库的递归CTE查询(适合MySQL 8.0+、PostgreSQL等支持的数据库):
在Category模型里添加一个静态方法,专门获取分类及其所有子分类的ID:
// Category.php public static function getDescendantIds($categoryId) { // 用原生SQL的递归查询,一次性拿到所有后代ID(包括自身) $recursiveQuery = "WITH RECURSIVE category_tree AS ( SELECT id, parent_id FROM categories WHERE id = ? UNION ALL SELECT c.id, c.parent_id FROM categories c JOIN category_tree ct ON c.parent_id = ct.id ) SELECT id FROM category_tree"; // 执行查询并提取ID数组 return collect(DB::select($recursiveQuery, [$categoryId]))->pluck('id')->toArray(); }
然后在控制器里直接调用这个方法:
// PostController.php public function show($id) { $categoryIds = Category::getDescendantIds($id); $posts = Post::whereIn('category_id', $categoryIds)->get(); return view('post.index', compact('posts')); }
这种方式只需要一次数据库查询,就能拿到所有需要的分类ID,性能更好,而且天然不会有重复值。
三、终极优化:直接关联查询文章,不用单独拿ID
其实你还可以跳过“先拿分类ID再查文章”的步骤,直接利用Laravel的关联关系,一次性获取目标分类及其所有子分类的文章:
在Category模型里添加一个关联方法:
// Category.php public function allDescendantPosts() { return $this->hasMany(Post::class) ->orWhereIn('category_id', function ($query) { // 子查询获取所有子分类ID $query->select('id') ->from('categories') ->withRecursive(['descendants' => function ($q) { $q->select('id', 'parent_id') ->from('categories') ->where('parent_id', '=', DB::raw('categories.id')); }]) ->where('id', $this->id); }); }
然后控制器里直接调用这个关联:
// PostController.php public function show($id) { $category = Category::findOrFail($id); $posts = $category->allDescendantPosts; return view('post.index', compact('posts')); }
这样一步到位,代码更简洁,也减少了中间步骤的性能损耗。
内容的提问来源于stack exchange,提问作者dav
相关产品推荐
相关产品推荐

