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 | +----+-------+--------+---------+
模型代码
Menu Model
class Menu extends Model { use HasFactory; public function submenu() { return $this->hasMany(SubMenu::class)->orderBy('weight', 'ASC'); } }
SubMenu Model
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>
优化说明
- 父菜单排序:直接在查询父菜单时添加
orderBy('weight', 'ASC'),确保父菜单按权重排序 - 预加载关联:使用
with('submenu')替代load('submenu'),一次性加载所有父菜单对应的子菜单,避免循环中的多次查询,提升性能 - 简化代码结构:无需手动转换数组和构建数据,直接利用Eloquent模型的属性访问,代码更简洁易维护
- 复用关联排序:子菜单的排序规则已经在
Menu模型的submenu方法中定义,预加载时会自动应用该排序
内容的提问来源于stack exchange,提问作者XxnumbxX
相关产品推荐
相关产品推荐

