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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 09:36:24