You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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
  • Banners
  • Settings

但你写的Laravel递归控制器代码运行后,却无限重复输出Dashboard:

Dashboard---Dashboard------Dashboard---------Dashboard------------Dashboard---------------Dashboard------------------Dashboard---------------------Dashboard------------------------Dashboard---------------------------Dashboard------------------------------Dashboard---------------------------------Dashboard------------------------------------Dashboard

问题根源

你的递归函数犯了两个核心错误:

  1. 没有按父ID过滤子节点:每次递归都遍历所有菜单记录,不管当前节点的parentid是否匹配,导致每次循环都会重新处理根节点(比如dashboard),进而无限重复输出。
  2. 缺少递归终止条件:因为每次都遍历全部数据,递归会一直执行直到触发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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 06:14:25