栏目、菜单、地区这类数据在数据库里通常是 parent_id 扁平行,前端展示却需要树形或面包屑。用递归把它们组装好,一次封装长期受益。

实现思路

buildTree 递归遍历列表,把 parent_id 等于当前节点的元素挂到 children 下;findPath 先建 id 索引,再沿 parent_id 逐级上溯收集路径,最后用 array_unshift 保证顺序从根到本节点。

完整代码

<?php
/**
 * 扁平列表转树形结构
 * @param array $items 数据行,每行需含 id、parent_id
 */
function buildTree(array $items, int $parentId = 0): array
{
    $tree = [];
    foreach ($items as $item) {
        if ((int)$item["parent_id"] === $parentId) {
            $children = buildTree($items, (int)$item["id"]);
            if ($children) {
                $item["children"] = $children;
            }
            $tree[] = $item;
        }
    }
    return $tree;
}

/**
 * 查找某节点的面包屑路径名称
 */
function findBreadcrumb(array $items, int $id): array
{
    $map = [];
    foreach ($items as $item) {
        $map[$item["id"]] = $item;
    }

    $path = [];
    $cursor = $map[$id] ?? null;
    while ($cursor) {
        array_unshift($path, $cursor["name"]);
        $cursor = $map[$cursor["parent_id"]] ?? null;
    }
    return $path;
}

// 以 ThinkPHP 为例取全表
// use thinkfacadeDb;
// $list = Db::name("cms_category")->order("sort asc, id asc")->select()->toArray();
// $tree = buildTree($list);
// print_r($tree);
// print_r(findBreadcrumb($list, 88));
?>

边界说明

  • buildTree 未对孤儿数据做防御,若出现 parent_id 不存在的脏数据会被丢弃,正式环境可在入库前校验;
  • 递归在万级节点下效率尚可,超大数据量建议改内存索引两次遍历方案;
  • findBreadcrumb 返回的是名称数组,如需带 id 的可自行调整。

小结

递归转树与上溯路径是 CMS 开发的固定套路,配合排序字段即可直接驱动菜单与面包屑组件。