PHP怎样获取栏目树形结构_PHP构栏目树方法【树形】

11次阅读

最常用方式是查出全部栏目后用php组装树形结构,关键在于提前构建父子映射并用引用挂载子树,避免N+1查询和重复遍历,兼顾性能与可控性。

PHP怎样获取栏目树形结构_PHP构栏目树方法【树形】

PHP递归查询数据库生成栏目树的常见写法

直接从数据库查出全部栏目再用PHP组装树形结构,是最常用也最可控的方式。关键不是“能不能”,而是“怎么组织数据才不掉坑”。

假设表结构为 idnameparent_id,且根节点 parent_id = 0NULL(需统一):

  • 先用一次 select * FROM category ORDER BY parent_id, sort ASC 拿到全部数据,避免N+1查询
  • parent_id 做键,把所有子节点归组到一个 $map 数组里:$map[$row['parent_id']][] = $row
  • 递归函数只负责拼装层级,不查库;入口从 $map[0](或 $map[NULL])开始

注意:如果 parent_id 允许为 NULL,别用 0 当作根判断条件,否则 isset($map[0]) 会误判。

用引用方式一次性构建树,避免重复遍历

递归调用本身没问题,但若每次都在全量数组里 array_filter 找子项,性能会随层级和数量陡增。更稳的做法是提前建好父子映射,再用引用“挂载”子树。

立即学习PHP免费学习笔记(深入)”;

核心逻辑是:遍历原始数组时,对每个节点,把它塞进其父节点的 children 数组中;根节点直接进结果集。这要求原始数据已按 parent_id 排序,或至少保证父节点在子节点前被处理(可先按 id 升序查出,再用 usort 调整顺序)。

  • 初始化空数组 $tree$refs(用于存每个 id 对应的引用)
  • 循环每条记录:$refs[$row['id']] = &$row,然后 if ($row['parent_id'] && isset($refs[$row['parent_id']])) { $refs[$row['parent_id']]['children'][] = &$row; }
  • 最后遍历原始数组,把 parent_id 为空的节点推入 $tree

这种方式没有递归调用开销,也不依赖函数深度,适合几百个节点以内的栏目结构。

mysql 8.0+ 用 WITH RECURSIVE 直接查出树形结果

如果数据库是 MySQL 8.0+ 或 postgresql,能用 WITH RECURSIVE 一次性查出带层级和路径的扁平结果,PHP 层只需简单分组,甚至不用递归。

例如 MySQL 查询:

WITH RECURSIVE tree AS (   SELECT id, name, parent_id, 0 AS level, CAST(id AS CHAR) AS path   FROM category WHERE parent_id = 0   UNION ALL   SELECT c.id, c.name, c.parent_id, t.level + 1, CONCAT(t.path, '-', c.id)   FROM category c   INNER JOIN tree t ON c.parent_id = t.id ) SELECT * FROM tree ORDER BY path;

返回结果已按树序排列,PHP 只需按 level 缩进或用维护当前父级即可生成嵌套数组。缺点是无法在低版本 MySQL 使用,且复杂查询可能影响缓存效率。

容易被忽略的边界情况

真实业务里,栏目树常踩的不是语法坑,而是数据逻辑坑:

  • parent_id 指向了不存在的 id(孤儿节点),会导致某层“消失”或无限递归(若没加深度限制)
  • 存在循环引用,比如 A → B → C → A,纯 PHP 递归会爆栈,必须加 $visited 集合或层级计数器截断
  • 前端需要展开/折叠状态,但后端返回的是完整树,建议加 has_children 字段(通过 EXISTS 子查询或预聚合),而不是让前端
  • 多语言或站点隔离场景下,category 表常带 site_idlang 字段,漏加 WHERE 条件会导致树错乱

树形结构看着简单,真正稳定运行靠的是对数据一致性的预判,而不是递归写得有多漂亮。

text=ZqhQzanResources