
本文介绍如何将 Laravel 中多对多关联(如用户-角色)的嵌套集合,转换为每个用户-角色组合独立成行的扁平化结果,支持 Eloquent flatMap 方式和原生 SQL JOIN 两种高效实现方案。
本文介绍如何将 laravel 中多对多关联(如用户-角色)的嵌套集合,转换为每个用户-角色组合独立成行的扁平化结果,支持 eloquent `flatmap` 方式和原生 sql join 两种高效实现方案。
在 Laravel 应用中,当使用 belongsToMany 定义用户与角色的多对多关系后,调用 User::with('roles')->get() 会返回类似以下结构的数据:
[
{
"id": 1,
"name": "Alice",
"roles": [
{"id": 1, "name": "admin"},
{"id": 2, "name": "editor"}
]
}
]
但实际业务中(如导出报表、前端表格渲染或权限明细分析),常需「一用户一行一角色」的扁平格式:
[
{"id": 1, "name": "Alice", "role_id": 1, "role_name": "admin"},
{"id": 1, "name": "Alice", "role_id": 2, "role_name": "editor"}
]
✅ 方案一:Eloquent + Collection flatMap()(推荐用于小到中量数据)
利用 Laravel 集合的 flatMap 方法,在内存中将嵌套关系展开。关键点在于:对每个用户,若拥有多个角色,则克隆用户实例并单独绑定一个角色,形成新对象;若无角色,则保留原用户(可按需调整为空跳过):
use Illuminate\Support\Facades\DB;
$userRoleFlattened = \App\Models\User::with('roles')
->get()
->flatMap(function ($user) {
// 若无角色,可返回空集合跳过(更常见)或保留用户
if ($user->roles->isEmpty()) {
return collect(); // 跳过无角色用户
}
return $user->roles->map(function ($role) use ($user) {
$flattened = $user->toArray();
// 合并角色字段,避免嵌套(可选:重命名 role.* 字段避免冲突)
$roleData = $role->toArray();
unset($roleData['pivot']); // 移除中间表元数据(如有)
return array_merge($flattened, [
'role_id' => $role->id,
'role_name' => $role->name,
// 其他角色字段可依需添加
]);
});
});
⚠️ 注意:此方式在内存中操作,适合结果集较小(如
✅ 方案二:原生 JOIN 查询(高性能首选)
绕过 Eloquent 关系加载,直接通过 SQL JOIN 获取扁平化结果,兼顾性能与可控性:
$userRoleFlattened = DB::table('users')
->join('user_role', 'users.id', '=', 'user_role.user_id')
->join('roles', 'user_role.role_id', '=', 'roles.id')
->select(
'users.id',
'users.name',
'users.email',
// 用户其他字段...
'roles.id as role_id',
'roles.name as role_name',
'roles.slug as role_slug'
// 角色其他字段(注意别名避免冲突)
)
->get();
该查询等价于一条 SQL:
SELECT users.*, roles.id AS role_id, roles.name AS role_name FROM users JOIN user_role ON users.id = user_role.user_id JOIN roles ON user_role.role_id = roles.id;
✅ 优势:
- 数据库层完成关联与扁平化,零内存压力;
- 支持
where,orderBy, 分页(paginate())及索引优化; - 字段精确控制,无冗余
pivot或嵌套结构。
? 总结建议
| 场景 | 推荐方案 | 说明 |
|---|---|---|
| 快速原型 / 小数据量 / 需复用模型访问器 |
flatMap() + Collection |
代码简洁,保持 Eloquent 上下文 |
| API 响应 / 导出 / 大数据列表 / 性能敏感 | 原生 DB::table()->join()
|
更快、更省内存、更易调试 |
| 需要动态条件(如只查启用角色) | JOIN + where 链式调用 |
在 SQL 层过滤,避免 PHP 层遍历 |
无论选择哪种方式,最终都可轻松转化为标准 JSON 响应:
return response()->json($userRoleFlattened);










