Laravel 5.5递归函数生成菜单异常问题求助
解决Laravel递归生成菜单时的重复输出问题
问题梳理
先明确下你的场景:
你有这样的MySQL菜单表:
id | menuname | parentid ---+-------------------+--------- 1 | dashboard | 0 2 | Content | 0 3 | Home Page Content | 2 4 | Banners | 2 5 | Settings | 0 6 | Block Content | 3 7 | Site Content | 3
期望生成嵌套菜单结构:
- dashboard
- Content
- Home Page Content
- Block Content
- Site Content
- Home Page Content
- Banners
- Settings
但你写的Laravel递归控制器代码运行后,却无限重复输出Dashboard:
Dashboard---Dashboard------Dashboard---------Dashboard------------Dashboard---------------Dashboard------------------Dashboard---------------------Dashboard------------------------Dashboard---------------------------Dashboard------------------------------Dashboard---------------------------------Dashboard------------------------------------Dashboard
问题根源
你的递归函数犯了两个核心错误:
- 没有按父ID过滤子节点:每次递归都遍历所有菜单记录,不管当前节点的
parentid是否匹配,导致每次循环都会重新处理根节点(比如dashboard),进而无限重复输出。 - 缺少递归终止条件:因为每次都遍历全部数据,递归会一直执行直到触发PHP的递归深度限制,所以才会出现一串嵌套的Dashboard。
修复方案
我给你几个不同的修复思路,你可以根据需求选择:
方案1:直接修改递归逻辑(基础版)
修改后的函数会只处理当前父ID对应的子节点,并且在没有子节点时停止递归:
public function index() { $data = []; $permissionRecord = Permission::all(); // 调用递归函数获取菜单结构,而不是直接echo $menuTree = $this->categoryTree($permissionRecord); dd($menuTree); $data['permissionRecord'] = $menuTree; return view('Administrator.permission.permissionAdd', $data); } function categoryTree($permissionRecord, $parent_id = 0, $sub_mark = '') { $menuContent = ''; // 只筛选当前parent_id对应的子菜单 $childItems = $permissionRecord->where('parentid', $parent_id); foreach($childItems as $row) { // 拼接当前菜单节点 $menuContent .= $sub_mark . $row->menuname . "\n"; // 递归处理当前节点的子菜单,传递当前节点ID作为父ID $menuContent .= $this->categoryTree($permissionRecord, $row->id, $sub_mark . '---'); } return $menuContent; }
方案2:预分组数据(高效版)
如果菜单数据量较大,每次用where过滤会有点低效,我们可以先把数据按parentid分组,这样递归时能快速找到子节点:
public function index() { $permissionRecord = Permission::all(); // 按parentid分组,构建子节点映射 $groupedMenus = $permissionRecord->groupBy('parentid'); $menuTree = $this->buildCategoryTree($groupedMenus, 0, ''); dd($menuTree); return view('Administrator.permission.permissionAdd', ['permissionRecord' => $menuTree]); } function buildCategoryTree($groupedMenus, $parent_id = 0, $sub_mark = '') { $menuContent = ''; // 如果当前父ID没有子节点,直接返回空,终止递归 if (!isset($groupedMenus[$parent_id])) { return $menuContent; } foreach($groupedMenus[$parent_id] as $row) { $menuContent .= $sub_mark . $row->menuname . "\n"; // 递归处理子节点 $menuContent .= $this->buildCategoryTree($groupedMenus, $row->id, $sub_mark . '---'); } return $menuContent; }
方案3:生成数组结构(适合视图渲染)
如果需要在Blade视图中渲染成HTML列表,返回数组结构会更灵活:
public function index() { $permissionRecord = Permission::all(); $groupedMenus = $permissionRecord->groupBy('parentid'); $menuArray = $this->buildMenuArray($groupedMenus, 0); dd($menuArray); return view('Administrator.permission.permissionAdd', ['menu' => $menuArray]); } function buildMenuArray($groupedMenus, $parent_id = 0) { $menu = []; if (!isset($groupedMenus[$parent_id])) { return $menu; } foreach($groupedMenus[$parent_id] as $row) { $menuItem = [ 'name' => $row->menuname, 'children' => $this->buildMenuArray($groupedMenus, $row->id) ]; $menu[] = $menuItem; } return $menu; }
然后在Blade视图中可以这样渲染:
<ul> @foreach($menu as $item) <li>{{ $item['name'] }} @if(count($item['children']) > 0) <ul> @foreach($item['children'] as $child) <li>{{ $child['name'] }} @if(count($child['children']) > 0) <ul> @foreach($child['children'] as $grandchild) <li>{{ $grandchild['name'] }}</li> @endforeach </ul> @endif </li> @endforeach </ul> @endif </li> @endforeach </ul>
核心修复要点
- 必须按父ID过滤子节点:每次递归只处理属于当前父节点的子项,避免重复遍历全部数据。
- 增加递归终止条件:当当前父节点没有子节点时,直接返回,停止递归。
- 尽量返回结果而非直接echo:这样更便于把数据传递到视图中使用,符合Laravel的MVC模式。
内容的提问来源于stack exchange,提问作者Niladri Banerjee - Uttarpara
相关产品推荐
相关产品推荐

