正确做法是单次查询全部分类并用引用传递递归组装树结构,避免N+1查询;视图层只遍历预构树,URL生成走路由而非拼接segment。

多级分类数据必须一次查全,不能递归查
CI 没有内置树形查询能力,get_where() 或 join() 都无法动态处理任意深度的父子关系。常见错误是:在循环里对每个 parent_id 单独查一次子项,结果 N+1 查询直接拖垮性能。
- 正确做法是用单次查询拉取全部分类:
$this->db->where('status', 1)->order_by('sort_order')->get('categories')->result_array() - 表结构必须含
id、parent_id(顶级设为NULL,别用0)、name、slug - 避免在 Model 中写
get_children($id)这类方法——它看似清晰,实则埋下 N+1 隐患
PHP 递归组装树时,引用传递和 NULL 判断不能错
从平铺数组生成嵌套结构,核心是用 parent_id 建立索引映射,再从 parent_id IS NULL 的节点开始递归。这里两个细节极易出错:
- 递归函数参数必须传引用:
private function build_tree(&$items, $parent_id = null),否则子级数据无法写回原数组 -
if ($parent_id === null)要严格匹配数据库值;若存的是0,就得写=== 0,混用==会把0和空字符串、false全判成真 - 建议加深度限制,比如递归层数超过
5就return [],防恶意构造超深分类导致栈溢出
视图层渲染嵌套菜单,避免模板内写递归逻辑
CI 视图不支持函数定义,硬在 .php 模板里写递归函数不仅难维护,还容易触发 PHP 作用域错误。更可靠的方式是提前在 Controller 或 Helper 中完成结构化,只把最终树传给视图。
- Controller 中调用:
$data['menu_tree'] = $this->category_model->get_menu_tree(); - View 中只做扁平遍历:
foreach ($menu_tree as $item):,子项统一用$item['children']访问 - 如果必须复用渲染逻辑,抽成独立
_menu_item.php子视图,用$this->load->view('_menu_item', ['item' => $item])传参调用
URL 生成必须走路由,别拼接 segment
多级分类常对应多级 URL,比如 /products/electronics/smartphones。很多人直接用 $this->uri->segment(1) + segment(2) 拼地址,但一旦启用路由重写(如 $route['shop'] = 'products/index';),这些 segment 就完全不可信。
- 每个分类项的
url字段应在 Model 层生成,调用base_url($item['slug'])或更稳妥的site_url($item['slug']) - 如果分类层级影响控制器行为(如三级分类需进不同方法),应在路由文件中预定义规则:
$route['products/(:any)/(:any)'] = 'products/category/$1/$2'; - Active 状态判断别依赖 URI 段数,而应比对当前请求的完整路由路径与分类
slug


















