thinkphp5的select()查不出树形结构是因为关系型数据库只存储父子边而非层级视图,需用buildtree()等方法将扁平数据一次性组装成树,避免n+1查询;根节点pid应统一设为0,注意引用赋值与排序,超2000条建议缓存。

为什么直接用 select() 查不出树形结构
ThinkPHP5 的模型查询默认返回扁平数组,哪怕数据库里用 pid 字段做了父子关联,Db::name('category')->select() 也只是一维列表。这不是 TP5 的 bug,而是关系型数据库本身不存储“层级视图”——它存的是边(父子对),不是树。你得自己把边组装成树。
常见错误是试图用循环嵌套查子集:foreach ($list as $item) { $item['children'] = Db::where('pid', $item['id'])->select(); }。这会产生 N+1 查询,100 个分类就发 101 次 SQL,性能崩得很快。
- 必须一次性查出全部数据,再用 PHP 组织层级
- 确保数据已按
sort或id排序(影响渲染顺序) -
pid字段值应统一:根节点建议设为0,不要用NULL(TP5 的where('pid', null)行为不稳定)
用 buildTree() 手动递归构造数组树
这是最可控、兼容性最强的方式,不依赖扩展或额外包。核心逻辑是:先建索引映射(id → item),再遍历一次,把每个节点挂到其父节点的 children 数组下。
public function getTreeList()
{
$list = Db::name('category')->order('sort ASC, id ASC')->select();
$map = [];
foreach ($list as $item) {
$map[$item['id']] = $item;
}
$tree = [];
foreach ($list as $item) {
if ($item['pid'] == 0) {
$tree[] = &$map[$item['id']];
} else {
if (isset($map[$item['pid']])) {
$map[$item['pid']]['children'][] = &$map[$item['id']];
}
}
}
return $tree;
}
注意两个 &:第一个让 $map[$item['id']] 成为引用,第二个确保子节点写入的是原数组元素而非副本。漏掉任一个,children 就是空的。
- 返回结果中,根节点无
children键时会报Undefined index,建议在模板里用isset($node['children'])判断 - 如果分类表有软删除字段(如
delete_time),记得加->whereNull('delete_time') - 这个函数适合中小型分类(≤2000 条),超量时考虑缓存
模板里用递归 {:getSubCate($list)} 渲染 HTML
TP5 模板不支持原生递归标签,所以得封装一个函数(放在 application/common.php):
function getSubCate($list, $pid = 0, $level = 0)
{
$html = '';
foreach ($list as $v) {
if ($v['pid'] == $pid) {
$prefix = str_repeat(' ', $level);
$html .= "<tr>
<td>{$prefix}{$v['name']}</td>
<td>{$v['id']}</td>
</tr>";
if (!empty($v['children'])) {
$html .= getSubCate($v['children'], $v['id'], $level + 1);
}
}
}
return $html;
}
在模板中调用:{:getSubCate($tree)}。这里关键点是:函数接收的是已组织好的树(带 children 的嵌套数组),不是原始扁平列表;$level 控制缩进,避免用 CSS margin-left 算层级。
- 别在模板里做查询或数据库操作,所有数据必须在控制器里准备好
- 如果需要生成 select 下拉选项,把
换成—或├─更直观 - 输出前用
htmlspecialchars()过滤$v['name'],防 XSS
想省事?用 thinkphp-helper 的 Collection::tree()
如果你项目已引入 topthink/think-helper(TP5.1+ 默认有),可以用集合的 tree() 方法快速构造:
use think\Collection;
$list = Db::name('category')->select();
$tree = Collection::make($list)->tree('id', 'pid');
它内部也是单次遍历建树,但封装了引用和键名适配(自动识别 pid 和 id)。不过要注意:
- 返回的是
Collection对象,不是数组,模板里用$tree->toArray()转一下 - 如果字段名不是标准
id/pid,得传第三个参数:tree('cate_id', 'parent_id') - 它不处理排序,仍需在
select()时加order()
真正容易被忽略的是「根节点判定条件」:该方法只认 pid === 0 或 pid === null,若你用 -1 表示根,它会把所有节点当根处理,整个树变平。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











