
本文介绍如何在 laravel 中不使用循环的前提下,将 `with()` 预加载的关联模型(如 `role`)的字段(如 `role_name`、`role_category`)直接“展开”到主模型(如 `user`)的数组/json 输出中,实现字段扁平化。
在 Laravel 开发中,我们常通过 ->with('relation') 预加载关联数据以避免 N+1 查询。但默认情况下,关联模型会以嵌套对象(如 "role": { ... })形式存在,而实际 API 或前端渲染往往需要扁平化结构——即把 role.role_name 直接提升为 role_name 字段,与用户自身字段同级。手动 foreach 拼接不仅冗余,还违背 Eloquent 的声明式设计哲学。
幸运的是,Laravel 提供了更优雅、可复用且符合框架约定的解决方案:访问器(Accessors) + 序列化追加(Appends)。
✅ 推荐方案:使用 Eloquent 访问器 + $appends
在 App\Models\User 模型中,定义访问器来动态暴露关联字段,并通过 $appends 声明其为“可序列化属性”:
// app/Models/User.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
// 声明需自动包含在 toArray() / toJson() 中的访问器
protected $appends = [
'role_name',
'role_category',
];
// 定义访问器:返回预加载的 role 关联中的字段
public function getRoleNameAttribute()
{
return $this->relationLoaded('role')
? $this->role->role_name
: null;
}
public function getRoleCategoryAttribute()
{
return $this->relationLoaded('role')
? $this->role->role_category
: null;
}
}⚠️ 注意:$this->relationLoaded('role') 是关键防护——它确保仅在 ->with('role') 已执行时才读取关联属性,避免未加载时触发懒加载或报错。
随后,你的原始查询无需任何改动,只需确保调用了 ->with('role'):
$users = User::where('active', 1)
->whereHas('role', function ($query) use ($column, $role) {
$query->where("role_{$column}", $role);
})
->with('role:id,role_name,role_category') // 只加载必要字段,提升性能
->orderBy('users.id')
->get([
'id',
'email',
'phone_number',
'id_role',
'firstname',
'lastname',
'verified',
'active',
'id_configuration',
'external_service_id',
]);
// 直接转数组,role_name 和 role_category 已自动扁平化
return response()->json($users);输出即为你期望的格式:
[{
"id": 968,
"email": "user@example.com",
"phone_number": "123",
"id_role": 4,
"firstname": "Name",
"lastname": "TEST User",
"verified": 0,
"active": 1,
"id_configuration": 1,
"external_service_id": 123,
"role_name": "Admin",
"role_category": "Company"
}]? 替代方案:运行时动态追加(按需使用)
若该扁平化行为并非全局需求(例如仅在某个 API 接口需要),可避免污染模型 $appends,改用 append() 方法动态添加:
return $users->map(function ($user) {
return $user->append(['role_name', 'role_category'])->toArray();
});或在单个模型上:
$user->append(['role_name', 'role_category'])->toArray();
? 注意事项与最佳实践
- 性能意识:始终搭配 ->with('relation:col1,col2') 指定字段,避免加载整张关联表。
- 空值安全:访问器中务必检查 relationLoaded(),防止未预加载时意外触发查询或抛出 Trying to get property on null。
- 命名规范:访问器名(如 getRoleNameAttribute)对应属性名 role_name(驼峰转蛇形),Laravel 自动识别。
- 不推荐 flatten() 或 map() 处理:Collection 的 flatten() 用于降维嵌套数组,不适用于对象属性提取;map() 虽可行但丧失模型层语义,且无法复用。
通过访问器与 $appends 的组合,你既保持了代码的可维护性与复用性,又实现了零循环、高性能、符合 Laravel 约定的扁平化输出——这才是真正的“Laravel 方式”。










