Laravel 9.x 中实现无限级分类搜索子分类,推荐父子关系模型+递归Eloquent关系+whereHas;需定义parent/children关系,添加scopeWithDescendants作用域递归查后代,控制器中调用并配合数据库索引优化。

在 Laravel 9.x 中实现无限级分类的搜索子分类功能,核心在于:利用嵌套集(Nested Set)或父子关系(Adjacency List)模型 + 递归查询 + 全文/关键词匹配。推荐使用 父子关系模型 + 递归 Eloquent 关系 + whereHas 或 withWhereHas(Laravel 9.23+),兼顾可读性、维护性和性能(中小数据量场景)。
1. 确保分类模型支持父子关系
你的分类模型(如 Category)需定义 parent_id 字段,并声明两个关键关系:
-
parent():指向父分类(一对一) -
children():指向子分类集合(一对多)
示例代码:
class Category extends Model
{
protected $fillable = ['name', 'slug', 'parent_id'];
public function parent()
{
return $this->belongsTo(Category::class, 'parent_id');
}
public function children()
{
return $this->hasMany(Category::class, 'parent_id');
}
}
2. 实现“搜索并包含所有后代”的逻辑
目标是:输入关键词(如 “手机”),返回所有名称匹配的分类及其全部子分类(无论几级)。不建议用纯递归 PHP 处理,应尽量交由数据库完成。
推荐方案:先查出匹配关键词的分类 ID 列表,再递归查出这些分类的所有后代(含自身)。
可在模型中添加一个作用域方法:
public function scopeWithDescendants($query, $keyword)
{
// 步骤1:查出所有匹配关键词的分类(含自身)
$matchingIds = self::where('name', 'like', "%{$keyword}%")
->pluck('id')
->toArray();
if (empty($matchingIds)) {
return $query->whereRaw('0');
}
// 步骤2:用递归 CTE(MySQL 8.0+/PostgreSQL)或多次查询模拟后代
// Laravel 9 不原生支持 CTE,这里用较通用的「迭代查询」方式(适合层级 ≤ 10)
$allIds = array_unique(array_merge(
$matchingIds,
$this->getDescendantIds($matchingIds)
));
return $query->whereIn('id', $allIds);
}
protected function getDescendantIds(array $ids): array
{
$descendants = [];
$currentLevel = $ids;
while (!empty($currentLevel)) {
$children = self::whereIn('parent_id', $currentLevel)
->pluck('id')
->toArray();
if (empty($children)) break;
$descendants = array_merge($descendants, $children);
$currentLevel = $children;
}
return $descendants;
}
3. 在控制器中调用搜索
例如在 CategoryController@index 中:
public function index(Request $request)
{
$keyword = $request->input('q', '');
$categories = Category::with(['parent', 'children'])
->when($keyword, fn ($q) => $q->withDescendants($keyword))
->orderBy('parent_id')
->orderBy('sort_order')
->get();
return view('categories.index', compact('categories', 'keyword'));
}
前端可加简单搜索表单:
<form method="GET">
<input type="text" name="q" value="{{ request('q') }}" placeholder="搜索分类名...">
<button type="submit">搜索</button>
</form>
4. 可选优化:添加索引与模糊策略
提升搜索效率和体验:
- 为
name字段添加数据库索引:php artisan make:migration add_index_to_categories_name→$table->index('name'); - 如需更精准匹配,改用
where('name', 'ilike', "%{$keyword}%")(PostgreSQL)或全文索引(MySQLFULLTEXT) - 对高频搜索词可缓存结果(如用
Cache::remember包裹getDescendantIds)
不复杂但容易忽略。


















