laravel 的 with 默认不递归加载嵌套关系,with('children') 仅查一级子分类;需定义 childrenrecursive 关系并显式递归调用,避免无限循环。

用 hasMany + with 实现父子关系查询时,为什么只查了一层?
因为 Laravel 的 with 默认不递归加载嵌套关系,with('children') 只会查一级子分类,不会自动继续查子子分类。
常见错误现象:Category::with('children')->get() 返回的数据里,每个子项的 children 属性是空集合或 null,哪怕数据库里明明有下级。
- 必须显式定义递归关系方法,比如叫
childrenRecursive,并在里面用with('childrenRecursive') - 不能在同一个关系方法里直接调用自身(如
return $this->hasMany(...)->with('children')),这会导致无限循环或报错Maximum function nesting level reached - 推荐在模型中定义一个专用的递归访问器,例如:
public function childrenRecursive() { return $this->hasMany(Category::class, 'parent_id')->with('childrenRecursive'); }
用 whereRaw + find 查指定层级路径时,怎么避免 SQL 注入?
当需要根据「完整路径」如 1/5/23 查找某个节点时,直接拼接字符串进 whereRaw 极易被注入——尤其路径来自用户输入。
使用场景:后台编辑某分类时,要定位它在整个树中的位置;或前端点击面包屑导航跳转。
- 不要写
whereRaw("path = '{$path}'"),改用参数绑定:Category::whereRaw('path = ?', [$path])->first(); - 如果用的是 MySQL,且字段类型为
VARCHAR,确保path字段加了索引,否则模糊匹配(如like '1/5/%')会全表扫描 - 注意路径分隔符统一用
/,且开头结尾不带斜杠(即存1/5/23,不是/1/5/23/),否则like匹配逻辑容易出错
递归渲染模板时,@each 和 @include 哪个更合适?
@each 无法处理嵌套结构,它只是对平铺数组做一次循环;真正要渲染多级菜单,必须用 @include 自调用。
性能影响:每次 @include 都是一次视图解析,深度过大会拖慢响应,但一般分类不会超过 10 层,实际影响有限。
- 模板里定义一个
category/tree.blade.php,开头就写:@foreach($categories as $category) <div>{{ $category->name }}</div> @if($category->children->isNotEmpty()) @include('category.tree', ['categories' => $category->children]) @endif @endforeach - 传参必须是
Collection,别传Model实例本身,否则$category->children可能是未加载的 Relation 对象,导致报错Call to a member function isNotEmpty() on null - 避免在循环里反复调用
$category->children,应在控制器中预先用with('childrenRecursive')加载好整棵树
用 scopeWithTree 查询整棵树时,为什么内存暴涨?
因为 with('childrenRecursive') 是 Eager Loading,Laravel 会一次性把所有节点查出来再靠 PHP 组织层级,数据量大时(比如上万分类)很容易 OOM。
容易被忽略的地方:这个写法看似优雅,实则把数据库压力转嫁成了 PHP 内存压力,而且无法分页。
- 如果分类总数超 500 条,建议改用闭包表(Closure Table)或路径前缀(Path Enumeration)方案,用单条 SQL 查出子树
- 若坚持用递归关系,至少加上
select()明确字段,避免select *带来冗余数据:Category::with('childrenRecursive:id,name,parent_id,path')->get(); - 调试时可以用
DB::enableQueryLog()看实际执行了几条 SQL——正常应只有 1 条主查询,但如果关系没定义好,可能变成 N+1










