php脚本处理树形数据
2026年6月14日大约 5 分钟
php脚本处理树形数据
在实际开发工作中当遇到需要处理树形数据时,通常会使用递归的方式来实现。
提示
假设是在一个类中实现
转换方法
private function listToTree(array $list, string $idField = 'id', string $pidField = 'parent_id', int $rootPid = 0): array
{
$tree = [];
$map = [];
// 1、先全量索引,用引用保证后续修改能同步
foreach ($list as &$item) {
$item['children'] = [];
$map[$item[$idField]] = &$item;
}
// 解除引用,避免副作用
unset($item);
// 2、循环挂载子节点
foreach ($map as &$item) {
$pid = $item[$pidField];
if ($pid === $rootPid) {
// 顶级节点
$tree[] = &$item;
} elseif (isset($map[$pid])) {
// 找到父节点,塞入children(通过引用同步)
$map[$pid]['children'][] = &$item;
} else {
// 找不到父节点
echo "警告:ID={$item[$idField]} 父ID={$pid} 不存在,丢弃\n";
}
}
unset($item);
return $tree;
}清洗数据方法
private function cleanTree(array $tree): array
{
$result = [];
foreach ($tree as $node) {
$clean = [
'region_name' => $node['region_name'],
'region_area' => $node['region_area'] ?? '',
];
if (!empty($node['children'])) {
$clean['children'] = $this->cleanTree($node['children']);
}
$result[] = $clean;
}
return $result;
}使用示例
下面以地区数据处理为例,该地区数据部分数据过于老旧.仅做参考
//数据量过大,这里只是少量演示数据
$regionData = [
['created_time'=>time(),'id'=>1,'region_name'=>'北京','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'华北'],
['created_time'=>time(),'id'=>2,'region_name'=>'天津','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'华北'],
['created_time'=>time(),'id'=>3,'region_name'=>'河北','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'华北'],
['created_time'=>time(),'id'=>4,'region_name'=>'山西','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'华北'],
['created_time'=>time(),'id'=>5,'region_name'=>'内蒙古','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'华北'],
['created_time'=>time(),'id'=>6,'region_name'=>'辽宁','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'东北'],
['created_time'=>time(),'id'=>7,'region_name'=>'吉林','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'东北'],
['created_time'=>time(),'id'=>8,'region_name'=>'黑龙江','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'东北'],
['created_time'=>time(),'id'=>9,'region_name'=>'上海','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'华东'],
['created_time'=>time(),'id'=>10,'region_name'=>'江苏','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'华东'],
['created_time'=>time(),'id'=>11,'region_name'=>'浙江','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'华东'],
['created_time'=>time(),'id'=>12,'region_name'=>'安徽','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'华东'],
['created_time'=>time(),'id'=>13,'region_name'=>'福建','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'华南'],
['created_time'=>time(),'id'=>14,'region_name'=>'江西','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'华东'],
['created_time'=>time(),'id'=>15,'region_name'=>'山东','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'华东'],
['created_time'=>time(),'id'=>16,'region_name'=>'河南','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'华中'],
['created_time'=>time(),'id'=>17,'region_name'=>'湖北','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'华中'],
['created_time'=>time(),'id'=>18,'region_name'=>'湖南','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'华中'],
['created_time'=>time(),'id'=>19,'region_name'=>'广东','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'华南'],
['created_time'=>time(),'id'=>20,'region_name'=>'广西','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'华南'],
['created_time'=>time(),'id'=>21,'region_name'=>'海南','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'华南'],
['created_time'=>time(),'id'=>22,'region_name'=>'重庆','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'西南'],
['created_time'=>time(),'id'=>23,'region_name'=>'四川','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'西南'],
['created_time'=>time(),'id'=>24,'region_name'=>'贵州','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'西南'],
['created_time'=>time(),'id'=>25,'region_name'=>'云南','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'西南'],
['created_time'=>time(),'id'=>26,'region_name'=>'西藏','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'西南'],
['created_time'=>time(),'id'=>27,'region_name'=>'陕西','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'西北'],
['created_time'=>time(),'id'=>28,'region_name'=>'甘肃','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'西北'],
['created_time'=>time(),'id'=>29,'region_name'=>'青海','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'西北'],
['created_time'=>time(),'id'=>30,'region_name'=>'宁夏','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'西北'],
['created_time'=>time(),'id'=>31,'region_name'=>'新疆','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'西北'],
['created_time'=>time(),'id'=>32,'region_name'=>'台湾','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'港澳台'],
['created_time'=>time(),'id'=>33,'region_name'=>'香港','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'港澳台'],
['created_time'=>time(),'id'=>34,'region_name'=>'澳门','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'港澳台'],
['created_time'=>time(),'id'=>35,'region_name'=>'海外','parent_id'=>0,'sort'=>100,'deep'=>1,'region_area'=>'海外'],
['created_time'=>time(),'id'=>36,'region_name'=>'北京市','parent_id'=>1,'sort'=>100,'deep'=>2,'region_area'=>''],
['created_time'=>time(),'id'=>37,'region_name'=>'东城区','parent_id'=>36,'sort'=>100,'deep'=>3,'region_area'=>''],
['created_time'=>time(),'id'=>38,'region_name'=>'西城区','parent_id'=>36,'sort'=>100,'deep'=>3,'region_area'=>''],
['created_time'=>time(),'id'=>39,'region_name'=>'上海市','parent_id'=>9,'sort'=>100,'deep'=>2,'region_area'=>''],
['created_time'=>time(),'id'=>40,'region_name'=>'天津市','parent_id'=>2,'sort'=>100,'deep'=>2,'region_area'=>''],
['created_time'=>time(),'id'=>41,'region_name'=>'朝阳区','parent_id'=>36,'sort'=>100,'deep'=>3,'region_area'=>''],
['created_time'=>time(),'id'=>42,'region_name'=>'丰台区','parent_id'=>36,'sort'=>100,'deep'=>3,'region_area'=>''],
];
// 3. 执行转换,得到你要的树形数组
$regionTreeArray = $this->listToTree($regionData);
$regionTreeArray = $this->cleanTree($regionTreeArray);
// 4. 打印结果,可直接复制粘贴到Seeder里
// 打印时美化一下
$export = var_export($regionTreeArray, true);
// array ( -> [
$export = str_replace('array (', '[', $export);
// ) -> ]
$export = str_replace(')', ']', $export);
// 去掉数字索引 0 =>, 1 => 等
$export = preg_replace('/\d+ => /', '', $export);
// 去掉多余的逗号
$export = str_replace(',]', ']', $export);
// 去掉空格索引的空格
$export = preg_replace('/$$\s+$$/', '[]', $export);
// 直接输出可复制的PHP代码
echo '$regionTreeArray = ' . $export .';';最终生成代码:$regionTreeArray可以直接复制用于RegionSeeder中
结合laravel在seeder中使用
递归处理方法
提示
这样处理的弊端是系统初始化的时候,当数据量大的时候,会有短暂的卡顿,但是因为是一次性操作,所以影响不大.好处是可以长久持续的维护数据,不需要自己手动指定id和层级
#[DocParams(note:'递归插入地区树',params:['nodes'=>'当前层级节点数组','parentId'=>'父级ID','deep'=>'当前深度'])]
protected function insertTree(array $nodes, int $parentId, int $deep)
{
foreach ($nodes as $node) {
// 插入当前节点
$item = Region::create([
'region_name' => $node['region_name'],
'region_area' => $node['region_area'],
'latitude' => '',
'longitude' => '',
'parent_id' => $parentId,
'deep' => $deep,
'sort' => 100,
'created_time' => time(),
'created_at' => date('Y-m-d H:i:s'),
]);
// 递归插入子节点,父ID为刚插入的主键
if (!empty($node['children'])) {
$this->insertTree($node['children'], $item->id, $deep + 1);
}
}
}示例
//示例数据不在这里展示
$regionTreeArray = [...];
Region::truncate();
$this->command->info('开始填充地区');
$this->insertTree($regionTreeArray, 0, 1);
$this->command->info('✅填充地区完成');总结
只要是树形数据都可以用这个方式来处理.用于系统初始化数据填充
