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

Laravel 10父模型关联orderBy无效,求更优实现方案

菜单与子菜单排序问题解决指导

我已经实现了从数据库两张表构建菜单和子菜单的功能,但父菜单并未按weight字段排序,而是按id排序,子菜单排序正常。作为Laravel和Eloquent新手,希望得到优化指导。

表结构

menus
+----+-----------+--------+
| id | menu_name | weight |
+----+-----------+--------+
|  1 | Menu1     |      1 |
|  2 | Menu2     |      0 |
+----+-----------+--------+

        sub_menus
+----+-------+--------+---------+
| id | name  | weight | menu_id |
+----+-------+--------+---------+
|  1 | Menu1 |      0 |       2 |
|  2 | Menu2 |      1 |       1 |
+----+-------+--------+---------+

模型代码

class Menu extends Model
{
    use HasFactory;

    public function submenu()
    {
        return $this->hasMany(SubMenu::class)->orderBy('weight', 'ASC');
    }
}
class SubMenu extends Model
{
    use HasFactory;

    public function menu()
    {
        return $this->belongsTo(Menu::class)->orderBy('weight', 'ASC');
    }
}

原控制器代码

class MenuController extends Controller
{
    public function index(){
        $menu = Menu::all()->load('submenu');
        return $menu;
    }
}

输出问题

输出结构符合预期,但父菜单未按weight排序:

[
{
"id": 1,
"menu_name": "Users",
"weight": 1,
"submenu": [
{
     "id": 1,
     "name": "Add user",
     "weight": 0,
     "menu_id": 1
},
{
     "id": 2,
     "name": "Delete User",
     "weight": 1,
     "menu_id": 1
}
]
},
{
 "id": 2,
 "menu_name": "Schedule",
 "weight": 0,
 "submenu": [
{
        "id": 4,
        "name": "View Schedule",
        "weight": 0,
        "menu_id": 2
},
{
        "id": 3,
        "name": "Create Schedule",
        "weight": 1,
        "menu_id": 2
}
]
}
]

我的临时实现(非最佳实践)

Controller

class MenuController extends Controller
{
    public function index(){
        //get menu items
        $menus = Menu::orderBy('weight', 'ASC')->get()->toArray();
        //create array
        $data = [];
           foreach($menus as $menu){
               $data[]=[
                 'id'=>$menu['id'],
                 'menu_name'=>$menu['menu_name'],
                 'weight'=>$menu['weight'],
                 'sub_menu' => $this->getSubMenu($menu['id']),   
                ];
            }
            
        return view('menus',compact('data'));
    }

    public function getSubMenu($data){
        $data = SubMenu::where(fn($query) => $query->where('menu_id', '=', $data))->orderBy('weight', 'ASC')->get();
        return $data;
    }
}

View

<ul>
@foreach($data as $item)
    <li>
        <a href="{{ $item['weight']; }}">{{ $item['menu_name']; }}</a>
        <ul>
           @foreach ($item['sub_menu'] as $subitem)
           <li><a href="{{ $subitem['weight']; }}">{{ $subitem['name']; }}</a></li>
           @endforeach 
        </ul>
    </li>
@endforeach
</ul>

优化方案

核心问题

原代码中Menu::all()会直接按主键id查询所有菜单,后续的load('submenu')只是加载关联,但不会改变父菜单的排序;临时实现的循环查询会导致N+1性能问题(每个父菜单都执行一次子菜单查询)。

最佳实践代码

修正后的MenuController

class MenuController extends Controller
{
    public function index()
    {
        // 按weight排序父菜单,同时预加载子菜单(避免N+1查询)
        $menus = Menu::orderBy('weight', 'ASC')->with('submenu')->get();
        return view('menus', compact('menus'));
    }
}

简化后的View

<ul>
@foreach($menus as $menu)
    <li>
        <a href="{{ $menu->weight }}">{{ $menu->menu_name }}</a>
        <ul>
           @foreach ($menu->submenu as $subitem)
           <li><a href="{{ $subitem->weight }}">{{ $subitem->name }}</a></li>
           @endforeach 
        </ul>
    </li>
@endforeach
</ul>

优化说明

  1. 父菜单排序:直接在查询父菜单时添加orderBy('weight', 'ASC'),确保父菜单按权重排序
  2. 预加载关联:使用with('submenu')替代load('submenu'),一次性加载所有父菜单对应的子菜单,避免循环中的多次查询,提升性能
  3. 简化代码结构:无需手动转换数组和构建数据,直接利用Eloquent模型的属性访问,代码更简洁易维护
  4. 复用关联排序:子菜单的排序规则已经在Menu模型的submenu方法中定义,预加载时会自动应用该排序

内容的提问来源于stack exchange,提问作者XxnumbxX

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 17:34:58