最常见问题是父子关系未断开导致无限循环,需用$visited数组校验已遍历ID或查库时排除自身及祖先路径;PHP7中推荐具名函数+引用传参而非匿名递归。

递归函数里怎么避免无限循环
最常见问题是父子关系没断开,比如 $category['parent_id'] 指向自己、或存在环形引用(A→B→C→A)。必须在进入递归前校验当前节点是否已遍历过,或确保 parent_id 严格小于当前 id(适用于自增主键且无手动改 parent 的场景)。
实操建议:
- 用一个
$visited数组记录已处理的id,每次递归前if (in_array($id, $visited)) return; - 更稳妥的做法是查库时就排除自身及祖先路径,比如加
WHERE id != ? AND parent_id != ? - 数据库字段设计上,
parent_id为NULL或0才代表顶级,非空非零才继续递归
PHP7 中递归获取子分类 ID 的典型写法
假设数据已从数据库查出并组织成扁平数组,键为 id,值为关联数组(含 id、parent_id),不依赖第三方库,纯函数式递归:
function getChildrenIds($categories, $parentId, &$result = []) {
foreach ($categories as $cat) {
if ($cat['parent_id'] == $parentId) {
$result[] = $cat['id'];
getChildrenIds($categories, $cat['id'], $result);
}
}
return $result;
}
// 调用:获取 ID 为 5 的分类下所有子分类 ID
$allIds = getChildrenIds($categories, 5);
注意:&$result 必须传引用,否则每次递归都新建数组,最终返回空;PHP7 默认禁用“静默引用传递”,漏写 & 会导致结果丢失。
立即学习“PHP免费学习笔记(深入)”;
用 PDO 查询时要不要一次性查全再 PHP 递归
取决于数据量和调用频次。100 条以内分类,查一次全量再 PHP 遍历更快、更省连接数;超过 500 条,或分类树极深(>10 层),建议改用闭包表(Closure Table)或路径枚举(path 字段如 "0/1/5/23"),用 SQL 直接 WHERE path LIKE '0/1/5/%' 查出全部子节点。
容易踩的坑:
- 用
mysqli_query在递归里反复查库 → 连接耗尽或超时 - 没设最大递归深度,遇到异常数据直接
Fatal error: Maximum function nesting level - MySQL 的
GROUP_CONCAT长度默认 1024,拼接 ID 列表时被截断,需先SET SESSION group_concat_max_len = 1000000
为什么 array_reduce + 自调用匿名函数在 PHP7 里不推荐
PHP7 支持匿名函数递归,但写法绕、可读性差,且每次调用都创建新 Closure 实例,性能不如具名函数。下面这段看似简洁,实际调试困难、无法复用、也不易加日志:
$getIds = function ($cats, $pid) use (&$getIds) {
$ids = [];
foreach ($cats as $c) {
if ($c['parent_id'] == $pid) {
$ids[] = $c['id'];
$ids = array_merge($ids, $getIds($cats, $c['id']));
}
}
return $ids;
};
真实项目里,优先选具名函数 + 明确参数 + 引用收集,出问题能快速打点、加 var_dump、也方便单元测试。
真正麻烦的不是写递归,而是分类数据本身有脏数据、迁移遗漏、前端误操作留下的孤立节点——上线前务必用 SELECT id FROM category WHERE parent_id NOT IN (SELECT id FROM category) AND parent_id != 0 扫一遍。



















