
本文介绍如何用递归生成器(Generator)替代固定深度的嵌套 foreach,兼顾性能、可扩展性与内存效率,并提供可复用的 expand_array() 函数实现。
本文介绍如何用递归生成器(generator)替代固定深度的嵌套 foreach,兼顾性能、可扩展性与内存效率,并提供可复用的 `expand_array()` 函数实现。
在处理具有层级结构的关联数组(如配置、分类树、地理区域等)时,开发者常依赖多层 foreach 循环进行遍历与数据提取。例如原始代码中三层嵌套循环将 $data 转换为 [category, sector, 'Sample A'] 这样的三元组数组。虽然逻辑清晰,但该方案存在三个关键局限:结构刚性(仅适配恰好三层嵌套)、内存膨胀(一次性构建完整 $exportData)、以及扩展成本高(新增层级需手动增加循环)。
更优解是采用递归 + 生成器(Generator) 模式。它天然适配任意深度嵌套,避免中间数组累积,且通过 yield 按需产出结果——每次仅驻留一个元素于内存,时间复杂度仍为 O(n),但空间复杂度从 O(n) 降至 O(d)(d 为最大嵌套深度),对大数据集尤为关键。
以下是一个生产就绪的 expand_array() 实现:
function expand_array(array $input, bool $skip_list_keys = true): \Generator {
$is_list = array_is_list($input);
foreach ($input as $key => $value) {
if (is_array($value)) {
foreach (expand_array($value, $skip_list_keys) as $item) {
if ($is_list && $skip_list_keys) {
yield [$value]; // 列表项不带索引键
} else {
yield array_merge([$key], $item); // 关联数组保留键路径
}
}
} else {
if ($is_list && $skip_list_keys) {
yield [$value];
} else {
yield [$key, $value];
}
}
}
}✅ 使用示例:
立即学习“PHP免费学习笔记(深入)”;
$data = [
'category' => [
'sector' => ['Sample A', 'Sample B', 'Sample C']
],
'area' => [
'location' => ['Location A', 'Location B', 'Location C']
],
];
// 默认跳过数字键(适合纯列表场景)
foreach (expand_array($data) as $row) {
echo json_encode($row) . "\n";
}
// 输出:["category","sector","Sample A"] ...
// 保留所有键(含数字索引,便于调试或需索引时)
foreach (expand_array($data, false) as $row) {
echo json_encode($row) . "\n";
}
// 输出:["category","sector",0,"Sample A"] ...⚠️ 注意事项:
- array_is_list() 是 PHP 8.1+ 原生函数,若使用低版本,可用 array_keys($arr) === range(0, count($arr)-1) 替代;
- 生成器不可重用(遍历后即耗尽),需重新调用函数获取新迭代器;
- 若需最终合并为数组,可用 iterator_to_array(expand_array($data), false),但会丧失内存优势,仅建议小数据量场景;
- 对于超深嵌套(>100 层),注意 PHP 默认 xdebug.max_nesting_level 限制,必要时调整配置。
总结而言,嵌套 foreach 并非错误,而是权衡取舍的结果:当结构稳定、数据量小、可读性优先时,它简洁可靠;但当面对动态结构、海量数据或长期维护需求时,递归生成器提供了更专业、可持续的替代方案——以少量代码复杂度换取显著的灵活性与资源效率提升。











