导读:本期聚焦于小伙伴创作的《ThinkPHP框架中如何将无限级分类的一维数组转换为多维数组?》,敬请观看详情。处理商品类目或部门架构时,数据库往往只存了带有pid指向父级的一维记录,前端树形组件却要求嵌套子级的格式。直接写递归查库会造成大量重复查询,拖慢接口响应。利用ThinkPHP模型取出的普通数组,在内存里通过引用映射一次遍历即可完成扁平数据到多维树的重组。核心是为每条记录建立以主键为键的临时索引,再把非根节点挂到对应父节点的children里。下面说明具体实现与注意事项,帮你避开引用污染和排序丢失等常见问题。

在ThinkPHP项目里,后台菜单、商品分类、地区数据等通常都以无限级分类方式存进同一张表,字段至少包含id、pid和name。从模型里取出的结果一般是一个线性的一维数组,而很多Vue或React的树形组件需要多维嵌套结构。掌握在框架内高效转换的方法,能减少数据库查询并提升接口性能。

ThinkPHP框架中如何将无限级分类的一维数组转换为多维数组?

一、为什么不能直接用递归查库

初学者最容易想到的办法是写一个函数,根据pid去数据库里查子级,再对每个子级继续查。这种做法在ThinkPHP里虽然能跑通,但分类层级一深,查询次数就会呈指数级上涨。比如一个三级类目有一百条记录,可能就要发起几十次SQL,明显拖累接口速度。

更合理的思路是先把整张表或某个根节点下的全部记录一次性查出来,得到一维数组,然后在PHP内存中完成树形化。ThinkPHP的Db类或模型all方法都能轻松拿到这种集合,之后用引用处理即可,全程零额外查询。

二、基于引用的一次遍历算法

核心原理是:先以记录的主键id为键,把一维数组重新索引成关联数组;同时让每个元素带上一个空的children字段。然后再次遍历,如果当前记录的pid大于0,就通过引用把它挂到父级记录的children中。由于使用的是引用,挂载后原数组结构同步更新,最后筛出pid为0的顶级节点就是完整多维树。

这种写法时间复杂度是O(n),仅需两次循环,且不依赖数据库。下面给出在ThinkPHP控制器中可直接使用的示例代码,假设数据表为category:

<?php
// 在ThinkPHP控制器方法中
use appcommonmodelCategory;

// 1. 一次性取出所有分类(一维数组)
$list = Category::field('id,pid,name,sort')->select()->toArray();

// 2. 转换为多维数组的函数
function listToTree($data) {
    $tree = [];
    $refer = [];
    // 第一次遍历:建立引用索引
    foreach ($data as &$item) {
        $item['children'] = [];
        $refer[$item['id']] = &$item;
    }
    unset($item); // 断开foreach的引用,防止后续污染

    // 第二次遍历:挂载子节点
    foreach ($data as &$item) {
        if ($item['pid'] == 0) {
            $tree[] = &$item;
        } else {
            if (isset($refer[$item['pid']])) {
                $refer[$item['pid']]['children'][] = &$item;
            }
        }
    }
    unset($item);
    return $tree;
}

$tree = listToTree($list);
// 此时 $tree 就是多维嵌套数组

代码中的关键点说明

第一处foreach结束后必须写unset($item),否则变量$item还保留着对数组最后一个元素的引用,在第二次遍历赋值时可能意外修改数据。这是PHP引用最常见的坑,在ThinkPHP开发中也经常因此出现树形结果错乱。

另外,如果原表有sort字段,可以在挂载完成后用usort对children排序,保证前端展示顺序正确。引用方式不会改变原$data数组的顶层顺序,只调整了children里的挂靠关系。

三、使用模型关联或集合类的替代方案

ThinkPHP提供了模型关联和Collection对象,也可以间接实现类似效果。例如给Category模型定义hasMany自关联,再用with方法预载入。但预载入本质上仍按层级生成多次查询或合并处理,对于真正无限级且深度不确定的情况,还是上面的一次性取出加引用转换最稳妥。

如果你使用了ThinkPHP的Collection,可以把toArray后的数组交给上面函数;也可以封装成工具类静态方法,在多个控制器里复用。下面示例展示如何封装:

<?php
namespace appcommonutil;

class TreeUtil {
    public static function toTree($data) {
        $tree = [];
        $refer = [];
        foreach ($data as &$v) {
            $v['children'] = [];
            $refer[$v['id']] = &$v;
        }
        unset($v);
        foreach ($data as &$v) {
            if ($v['pid'] == 0) {
                $tree[] = &$v;
            } elseif (isset($refer[$v['pid']])) {
                $refer[$v['pid']]['children'][] = &$v;
            }
        }
        unset($v);
        return $tree;
    }
}
// 调用:appcommonutilTreeUtil::toTree($list);

两种方案对比

方案查询次数适用场景
递归查库随层级指数增长数据极少且层级固定
一维转多维引用法仅1次无限级、接口性能敏感

从表格能看出,引用法在绝大多数后台管理系统中都是首选。只要注意释放引用,就不会产生副作用。

四、常见错误与排查

有些开发者把一维数组转树后,发现某些子分类丢失。通常原因是pid字段值存在但对应父id在结果集中被条件过滤掉了,比如只查了pid=0的再递归,却忘了父级也可能不在本次查询范围。因此转换前必须保证传入的$data包含完整链路。

还有人直接在模板里用volist嵌套无限层,导致代码难以维护。更好的做法是控制器里转好多维数组,模板只用一层递归组件渲染。这样业务逻辑和视图分离,也符合ThinkPHP的MVC设计理念。

只要遵循先索引、再挂载、及时unset引用的步骤,在ThinkPHP中把无限级分类的一维数组转换为多维数组就是一件简单且高效的事。

ThinkPHP无限级分类数组转换修改时间:2026-08-09 08:21:29

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。