Laravel多对多关联:如何按中间表module_id分组预加载数据
在Laravel多对多关联中按中间表字段分组并获取指定结构(仓库模式兼容)
我在Laravel中实现了Role与Group的多对多关联,中间表role_groups包含额外字段module_id。希望通过预加载关联数据并按module_id分组,得到键为module_id,值为对应group_id数组的结构。目前直接查询中间表能得到近似结果,但项目采用仓库模式,无法直接操作中间表模型,需要通过预加载实现。
当前相关代码
直接查询中间表的代码(不符合仓库模式)
$selectedGroups = RoleGroup::select('role_id', 'module_id', 'group_id')->where('role_id', $role->id)->get()->groupBy('module_id');
迁移文件
Schema::create('role_groups', function (Blueprint $table) { $table->id(); $table->unsignedBigInteger('role_id'); $table->unsignedBigInteger('module_id'); $table->unsignedBigInteger('group_id'); $table->timestamps(); $table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade'); $table->foreign('group_id')->references('id')->on('groups')->onDelete('cascade'); });
Role模型
public function groups() { return $this->belongsToMany(Group::class, 'role_groups')->withPivot('module_id')->withTimestamps(); }
Group模型
public function roles() { return $this->belongsToMany(Role::class, 'role_groups')->withPivot('module_id')->withTimestamps(); }
期望结果结构
键为module_id,值为对应的group_id数组
{ "1": [ 1 ], "2": [ 1 ], "3": [ 1 ], "4": [ 1 ], "5": [ 1, 2, 3, 4 ] }
当前直接查询得到的结果
{ "1": [ { "role_id": 1, "module_id": 1, "group_id": 1 }, { "role_id": 1, "module_id": 1, "group_id": 3 } ], "2": [ { "role_id": 1, "module_id": 2, "group_id": 1 }, { "role_id": 1, "module_id": 2, "group_id": 3 } ], "3": [ { "role_id": 1, "module_id": 3, "group_id": 1 } ], "5": [ { "role_id": 1, "module_id": 5, "group_id": 1 } ] }
符合仓库模式的解决方案
方法1:预加载关联后通过集合转换结构
在Role仓库中,预加载groups关联,再利用Laravel集合的groupBy和map方法转换为期望结构:
// RoleRepository.php public function getGroupIdsGroupedByModule(int $roleId) { $role = $this->model->with('groups')->findOrFail($roleId); return $role->groups->groupBy(function ($group) { return $group->pivot->module_id; })->map(function ($groupCollection) { return $groupCollection->pluck('id')->toArray(); }); }
方法2:模型添加访问器实现复用
如果需要在多个业务场景中使用该结构,可以在Role模型中添加访问器,仓库中直接调用:
// Role.php public function getGroupsByModuleAttribute() { return $this->groups->groupBy(fn($group) => $group->pivot->module_id) ->map(fn($groups) => $groups->pluck('id')->toArray()); } // RoleRepository.php public function getRoleWithGroupIdsByModule(int $roleId) { return $this->model->with('groups')->findOrFail($roleId)->groups_by_module; }
方法3:通过关联查询减少数据冗余
若Group表字段较多,预加载完整Group数据会造成冗余,可通过Role与中间表的hasMany关联查询必要字段再处理:
// Role.php(添加与中间表的关联) public function roleGroups() { return $this->hasMany(RoleGroup::class); } // RoleRepository.php public function getGroupIdsGroupedByModule(int $roleId) { $role = $this->model->with(['roleGroups' => function ($query) { $query->select('role_id', 'module_id', 'group_id'); }])->findOrFail($roleId); return $role->roleGroups->groupBy('module_id') ->map(fn($items) => $items->pluck('group_id')->toArray()); }
以上方法均遵循仓库模式,通过模型关联而非直接实例化中间表模型查询,满足项目架构要求。
内容的提问来源于stack exchange,提问作者Wcan
相关产品推荐
相关产品推荐

