递归函数必须设终止条件,否则会因嵌套过深崩溃;应按需查子集、用引用传参收集结果、避免重复查全表或sql拼接,必要时用with预加载或缓存优化。

递归函数必须带终止条件,否则会无限调用崩溃
PHP 递归查询分类最常见错误是没设好退出逻辑,比如用 while 或 foreach 遍历子集时,忘了判断当前节点是否还有子节点。一旦某层数据为空但代码仍继续调用自身,就会触发 Fatal error: Maximum function nesting level of '256' reached。
正确做法是:每次递归前先查该分类 ID 对应的子分类,如果结果为空数组,直接 return;否则才对每个子项再调用自己。
- 数据库表需有
id、parent_id字段,且根分类的parent_id为 0 或 NULL - 不要在递归函数里重复查全表,应按需查子集(如
WHERE parent_id = ?) - 避免在循环体内拼接 SQL 字符串,防止注入;用 PDO 预处理或 Laravel 的
where()更安全
用引用传参收集结果,比拼接字符串更可控
很多人习惯在递归中用 return $result . $childData 拼接 HTML 或数组,但 PHP 默认按值传递,每层递归都会复制一份变量,内存占用大、结构难维护。改用引用传参(&$result)能直接往一个数组里追加数据,层级关系也容易保留。
示例关键片段:
function buildTree($parentId = 0, &$result = [], $level = 0) {
$children = query("SELECT id, name FROM category WHERE parent_id = ?", [$parentId]);
foreach ($children as $row) {
$row['level'] = $level;
$result[] = $row;
buildTree($row['id'], $result, $level + 1); // 注意:$result 是引用,不用 return 接收
}
}
- 传入空数组
[]给$result,函数执行完它就已含全部数据 -
$level参数可选,用于标记深度,方便前端缩进或限制最大层数 - 若需返回树形嵌套结构(子集作为父项的
children键),则不能用扁平数组,得改用“先查全部再递归组装”策略
查一次全量数据再本地递归,比多次数据库查询更快
当分类总数不大(比如
核心思路:把结果集转成以 id 为键的关联数组,再遍历一遍,按 parent_id 归位子项。
$all = query("SELECT id, parent_id, name FROM category");
$indexed = [];
foreach ($all as $row) {
$indexed[$row['id']] = $row;
}
$tree = [];
foreach ($all as $row) {
if ($row['parent_id'] == 0) {
$tree[] = &$indexed[$row['id']];
} else {
$indexed[$row['parent_id']]['children'][] = &$indexed[$row['id']];
}
}
- 注意
&$indexed[...]中的引用符号,否则children里存的是副本,修改不会反映到原数组 - 此法不依赖函数递归,无栈溢出风险,适合 CLI 脚本或后台导出场景
- 如果分类表有排序字段(如
sort_order),记得在foreach ($all)前先usort,否则父子顺序可能错乱
Laravel 中用 Eloquent 的 with() 和 accessor 处理层级,别硬写递归
如果你项目已用 Laravel,别急着手写递归函数。Eloquent 支持无限极分类的常见模式是:定义 children 关系 + 使用 with('children') 预加载,再配合访问器(accessor)生成带层级的数组。
模型中这样写:
class Category extends Model
{
protected $with = ['children'];
public function children()
{
return $this->hasMany(Category::class, 'parent_id');
}
protected $appends = ['full_name'];
public function getFullNameAttribute()
{
return str_repeat('— ', $this->level) . $this->name;
}
}
-
$categories = Category::where('parent_id', 0)->with('children')->get();一条语句拉出完整树 - 避免 N+1 查询的关键是
with(),不是递归函数本身 - 真实项目中常需缓存整棵树(如 Redis 存 JSON),因为分类变动频率低,但读取频繁
真正需要手写递归的场景,往往是 legacy 系统、定制化导出、或要兼容非标准字段结构的时候——这时候得盯紧终止条件和数据引用。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











