在PHP开发中,很多业务场景的数据库表会采用父子ID关联的方式存储多层数据,比如商品分类、部门架构等,查询后得到的是所有记录的平铺数组,需要转换成嵌套的树形结构才能方便后续使用,递归就是处理这类问题的常用有效方法。

递归处理的核心思路
递归处理多层查询结果的核心是先找到所有顶层节点,再对每个顶层节点递归查找其下属的子节点,直到没有更多子节点为止。要实现这个逻辑,首先需要有明确的数据结构约定,通常数据库表中需要有id(唯一标识)和parent_id(父节点ID)两个字段,顶层节点的parent_id一般设为0或者null。
基础递归实现示例
以下代码演示了如何将平铺的数据库结果集转换成树形结构,假设已经从数据库查询得到了所有分类数据:
<?php
// 模拟从数据库查询得到的平铺分类结果集
$categoryList = [
['id' => 1, 'name' => '电子产品', 'parent_id' => 0],
['id' => 2, 'name' => '手机', 'parent_id' => 1],
['id' => 3, 'name' => '电脑', 'parent_id' => 1],
['id' => 4, 'name' => '智能手机', 'parent_id' => 2],
['id' => 5, 'name' => '办公用品', 'parent_id' => 0],
['id' => 6, 'name' => '笔记本', 'parent_id' => 5],
];
/**
* 递归构建树形结构
* @param array $list 平铺的结果集
* @param int $parentId 当前查找的父节点ID
* @return array 构建好的树形结构数组
*/
function buildTree(array $list, int $parentId = 0): array {
$tree = [];
foreach ($list as $item) {
// 找到当前父ID对应的子节点
if ($item['parent_id'] == $parentId) {
// 递归查找该节点的子节点
$children = buildTree($list, $item['id']);
// 如果有子节点,就添加到当前节点的children字段
if (!empty($children)) {
$item['children'] = $children;
}
$tree[] = $item;
}
}
return $tree;
}
// 调用函数生成树形结构
$treeResult = buildTree($categoryList);
// 打印结果查看结构
print_r($treeResult);
?>实际数据库查询场景适配
实际开发中我们不会手动写死数据,而是先从数据库查询出结果再处理,以下示例结合PDO查询演示完整流程:
<?php
try {
// 连接数据库,这里替换成你自己的数据库配置
$pdo = new PDO('mysql:host=127.0.0.1;dbname=test;charset=utf8', 'root', '123456');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 查询所有分类数据,按需要排序
$stmt = $pdo->query('SELECT id, name, parent_id FROM category ORDER BY id ASC');
$categoryList = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 复用之前的buildTree函数
function buildTree(array $list, int $parentId = 0): array {
$tree = [];
foreach ($list as $item) {
if ($item['parent_id'] == $parentId) {
$children = buildTree($list, $item['id']);
if (!empty($children)) {
$item['children'] = $children;
}
$tree[] = $item;
}
}
return $tree;
}
$treeResult = buildTree($categoryList);
// 输出JSON格式方便接口返回
echo json_encode($treeResult, JSON_UNESCAPED_UNICODE);
} catch (PDOException $e) {
echo '数据库操作失败:' . $e->getMessage();
}
?>递归使用的注意事项
使用递归处理数据库结果集时需要注意几个问题:
- 如果层级过深可能导致递归栈溢出,一般业务场景下的分类层级不会超过10层,这种情况无需担心,如果层级特别深可以考虑迭代方式实现。
- 每次递归都会遍历整个结果集,如果数据量特别大,可以先把结果集按parent_id分组,减少遍历次数,提升性能。
- 如果数据库中可能出现循环引用(比如子节点的parent_id指向了自身的后代节点),需要在递归时增加已访问节点的判断,避免无限递归。
迭代方式替代方案
如果担心递归的性能或栈溢出问题,也可以用迭代的方式实现树形结构转换,以下是示例代码:
<?php
function buildTreeByIterate(array $list): array {
$tree = [];
$map = [];
// 先把所有节点用id作为键存到map中
foreach ($list as $item) {
$map[$item['id']] = $item;
$map[$item['id']]['children'] = [];
}
// 遍历map构建层级关系
foreach ($map as $id => $item) {
$parentId = $item['parent_id'];
if ($parentId == 0) {
// 顶层节点直接加入tree
$tree[] = &$map[$id];
} else {
// 非顶层节点加入到对应父节点的children中
if (isset($map[$parentId])) {
$map[$parentId]['children'][] = &$map[$id];
}
}
}
return $tree;
}
// 测试迭代函数
$treeResult = buildTreeByIterate($categoryList);
print_r($treeResult);
?>无论是递归还是迭代方式,核心都是理清父子节点的关联关系,根据实际业务场景选择合适的方法即可,递归写法更简洁易懂,迭代方式在大数据量或深层级场景下更稳定。