递归函数必须有明确终止条件,需校验数据类型、避免重复查库、提前排序、返回纯数组。常见错误包括漏return、无限调用、每层查库、内部usort、对象未转数组等。

递归函数必须有明确的终止条件
写 PHP 递归最常犯的错,就是忘了 return 或漏掉终止判断,导致无限调用、爆栈、500 错误。无限级分类里,终止条件通常是「当前节点没有子节点」——但不能只靠 count($children) === 0 判断,因为查库可能返回空数组或 null,得先做类型校验。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 入口函数里先检查传入的
$data是否为数组且非空,否则直接return [] - 每层递归前用
isset($item['id']) && is_numeric($item['id'])过滤脏数据,避免后续报 Notice - 别在递归体里用
echo或var_dump调试,容易被缓冲干扰,改用error_log(print_r($item, true), 3, '/tmp/recursion.log')
用引用传递避免重复查库
无限级分类若每次递归都查一次数据库(比如 SELECT * FROM category WHERE parent_id = ?),N 层深度就会触发 N 次查询,性能雪崩。正确做法是一次性查出全部分类,再用 PHP 建立父子映射关系,递归时只从内存取数。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 查库后用
foreach构建$map:键为parent_id,值为该父级下的所有子项数组 - 递归函数接收
&$map和当前$parentId,避免深拷贝大数组 - 注意 PHP 7.4+ 对引用传递的严格性:传参时写
buildTree($map, $parentId, &$result),函数定义里也得带&$result
usort 排序要在递归前完成
很多人把 usort 放在递归内部,结果每层都重排一次子节点,时间复杂度从 O(n log n) 变成 O(n² log n)。无限级菜单通常要求同级按 sort_order 或 created_at 排,这个排序只需做一次——在构建好完整树形结构后,或更优地,在分组到 $map 后,对每个子数组单独排序。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 查库 SQL 加
ORDER BY parent_id, sort_order,让结果天然有序,减少 PHP 层排序压力 - 填充
$map时,用foreach ($rows as $row) { $map[$row['parent_id']][] = $row; },再遍历$map对每个子数组调用usort - 排序回调函数里别用
strcmp直接比数字字段,要转成整型:return (int)$a['sort_order'] (int)$b['sort_order'];
JSON 输出时注意循环引用和资源泄漏
递归生成的树结构如果包含对象(比如 Laravel 的 Eloquent Model),直接 json_encode 可能报 Recursion detected,或者因模型关联未卸载导致内存暴涨。这不是递归函数本身的问题,而是输出环节的陷阱。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 递归函数只返回纯数组(
['id'=>1, 'name'=>'foo', 'children'=>[...]]),不混入对象或资源句柄 - 如果原始数据来自 ORM,提前调用
$model->toArray()或$collection->makeHidden([...])->toArray() - 调试时用
json_last_error_msg()查具体错误,常见是Resource id #123没清理干净
递归本身不难,难的是边界控制、数据纯净度和执行上下文。尤其是无限级分类,90% 的线上问题出在没过滤 null 数据、没限制最大递归深度、或把 DB 查询塞进了递归体——这些点比语法细节更容易让服务突然变慢甚至挂掉。



















