PHP层级树数组遍历及_children追加员工数据方案咨询
问题根源
现有buildHierarchyDepartment方法递归时直接为_children字段赋值子部门数组,会覆盖字段内提前存入的员工数据;而Tabulator组件本身支持_children字段下混合存放部门、员工两类节点,不存在格式兼容问题。
实现方案
方案1:修改原有构建逻辑,构建阶段直接合并员工(性能最优)
不需要分两次遍历树,在原有部门构建逻辑里直接合并员工数据,从根源避免_children被覆盖的问题,适合可以动原有构建方法的场景。
- 先做一次预处理,把所有员工按所属部门ID分组,避免遍历树时重复查找员工:
// 预处理员工数据,按所属部门ID分组 $employeeMap = []; foreach ($employeeList as $emp) { $emp['_type'] = 'employee'; // 加类型标识,后续Tabulator渲染时可区分部门/员工 $employeeMap[$emp['department_id']][] = $emp; }
- 修改原有部门构建方法,统一合并子部门和员工数据到
_children:
private function buildHierarchyDepartment(array $elements, array $employeeMap, $parentId = 0) { $branch = []; foreach ($elements as $element) { if ($element['parent_id'] == $parentId) { $element['_type'] = 'department'; // 标记节点类型为部门 // 递归获取当前部门下的所有子部门 $childDepts = static::buildHierarchyDepartment($elements, $employeeMap, $element['id']); // 取当前部门下的员工,无员工则取空数组 $currentEmps = $employeeMap[$element['id']] ?? []; // 合并员工和子部门到_children,调整参数顺序可改变展示顺序:员工放前面则先显示员工,子部门放前面则先显示子部门 $element['_children'] = array_merge($currentEmps, $childDepts); $branch[] = $element; } } return $branch; }
方案2:保留原有构建逻辑,树构建完成后单独追加员工(改动最小)
如果不想修改已经调试好的部门构建方法,可以先按原有逻辑生成纯部门树,再单独写递归逻辑往每个节点追加员工。
- 同方案1,先把员工按部门ID分组为
$employeeMap - 新增员工追加方法,引用传递直接修改原树结构:
/** * 往已构建完成的部门树中追加员工数据 * @param array $deptTree 构建好的纯部门层级树,引用传递 * @param array $employeeMap 按部门ID分组的员工数组 */ private function appendEmployeesToDeptTree(array &$deptTree, array $employeeMap) { foreach ($deptTree as &$node) { // 处理当前部门节点:存在下属员工则追加到_children if (isset($employeeMap[$node['id']])) { // 无_children字段则先初始化 if (!isset($node['_children'])) { $node['_children'] = []; } // 合并员工到子节点列表,调整参数顺序可修改展示优先级 $node['_children'] = array_merge($employeeMap[$node['id']], $node['_children']); } // 存在子节点则递归处理下一层 if (!empty($node['_children'])) { static::appendEmployeesToDeptTree($node['_children'], $employeeMap); } } unset($node); // 解除引用避免后续逻辑出现数据污染 }
- 调用示例:
// 按原有逻辑生成纯部门树 $deptTree = $this->buildHierarchyDepartment($allDepts); // 追加员工数据 $this->appendEmployeesToDeptTree($deptTree, $employeeMap);
注意事项
- 给节点加
_type字段区分部门/员工是Tabulator的通用实践,后续自定义行样式、绑定点击事件、做字段格式化时可以直接用这个字段判断节点类型,避免逻辑混淆 - 两种方案都是递归实现,当前业务节点最深仅5层,完全不会出现PHP栈溢出问题,不需要额外改写成非递归的广度/深度优先遍历,优先保证代码可读性即可
- 提前把员工按部门ID分组是必要的性能优化,不要在树遍历的循环里每次全量匹配员工数据,会把时间复杂度从O(n+m)拉高到O(n*m),数据量大时性能差异明显
内容的提问来源于stack exchange,提问作者Franz Justin Buenaventura
相关产品推荐
相关产品推荐

