递归的关键在于明确终止条件、数据来源和结果去向;需设深度限制防爆栈,所有分支必须return,传值优于传引用,且应优先考虑迭代或数据库方案替代递归。

function 递归不是靠背语法学会的,是靠踩坑踩出来的。你写不出能用的递归函数,大概率不是不会“调自己”,而是没想清楚「什么时候该停」「数据从哪来」「结果往哪放」这三件事。
为什么递归函数总报 Maximum function nesting level 错误
这个错误不是 PHP 在卡你,是它在喊“你没设防”。默认xdebug.max_nesting_level 是 256(开发环境),但真实业务里分类深度超 30 层很常见,尤其 CMS 或电商后台带自定义字段的类目树。
- 不要直接调高
xdebug.max_nesting_level—— 这只是掩盖问题,不是解决 - 每次递归调用前加深度计数:
$depth = $depth ?? 0; if ($depth > 50) return []; - 终止条件必须含明确数值边界,比如
if (!$parentId || $depth >= 50),不能只依赖empty($children) - 注意:PHP 7.4+ 对引用传递更严格,
buildTree($map, $parentId, &$result, $depth + 1)传参时&$result要和函数定义里的&$result完全一致
递归函数里漏 return 就等于断链
这不是警告,是事实。PHP 递归中,只要某一层没return,上层拿到的就是 null,后续 foreach 或 array_merge 全崩。
- 所有分支都得有
return:终止分支、递归分支、异常兜底分支,一个都不能少 - 常见翻车点:
if (empty($list)) { echo 'no data'; }—— 这里缺return [] - 调试时别用
var_dump替代return,它不返回值,只输出 - 如果函数本该返回数组,就别让任何分支返回
null或0,类型不一致会引发 Notice 或逻辑错位
传数组还是传引用?关键看你要不要改原数据
多数递归场景下,你只是读数据、拼结构,不是改原始数组。传值更安全,也更符合函数式习惯。- 默认传值即可:
buildTree($list, $parentId)—— 各层操作的是副本,互不影响 - 只有当你需要在递归中持续收集路径、统计节点数、或实时挂载子项(如引用法建树)时,才显式加
& - 加了
&就要承担副作用:下层$item['children'][] = $child会直接改上层变量,调试时容易搞不清谁动了谁 - PHP 8.1+ 对混合引用/值传参更敏感,
function foo(array $a, &$b)这种混用要格外小心参数顺序和调用一致性
比写对递归更重要的,是知道什么时候不该用递归
无限级分类、目录遍历、多维数组搜索……这些看起来“非递归不可”的场景,其实 80% 都可以用迭代栈替代。- 用
$stack = [$root]+while (!empty($stack))模拟调用栈,完全规避爆栈风险 -
RecursiveArrayIterator+RecursiveIteratorIterator两行代码就能扁平化任意嵌套数组,不用自己写终止条件 - 数据库层能解决的,别硬扛到 PHP:MySQL 8.0+ 的
WITH RECURSIVE直接查出带层级的完整树,PHP 只做格式转换 - 真要递归,优先选
array_column建索引 + 单次遍历方案,别在每层都foreach全量数组查子节点——那是 O(n²),1000 条数据就卡住
递归最难的部分,从来不是语法,而是判断「这个问题到底适不适合递归」。很多所谓“写不出来”,其实是问题本身就不该交给递归。



















