Laravel 双向数据转换:模型级自动字段映射与格式化

11次阅读

Laravel 双向数据转换:模型级自动字段映射与格式化

本文介绍如何在 laravel 中通过模型访问器accessors)、修改器(mutators)结合自定义字段映射机制,实现数据库字段与前端字段的全自动双向转换,避免控制器中重复调用 transform(),提升大型遗留系统的可维护性。

在处理遗留数据库时,前后端字段命名不一致是常见痛点——例如数据库用 first_name,而前端期望 firstName;或需对数值做格式化(如金额存为分,返回为元)。若每次请求都在 Controller 或 Service 层手动转换,不仅冗余,还易出错、难维护。laravel 原生提供的 访问器(accessors)与修改器(Mutators) 是解决此问题的核心机制,再辅以轻量级字段映射逻辑,即可实现真正“模型层自治”的双向转换。

✅ 推荐方案:访问器 + 修改器 + 自定义 $maps 映射

Laravel 模型天然支持 get{Attribute}Attribute() 和 set{Attribute}Attribute() 方法,它们分别在读取和赋值属性时自动触发。结合一个简单的 $maps 配置数组,即可统一管理字段别名映射:

 'firstName',         'last_name'   => 'lastName',         'birth_date'  => 'birthDate',         'salary_cents'=> 'salary', // 存储为分,对外显示为元     ];      // 【访问器】将数据库值转为前端所需格式     public function getFirstNameAttribute($value)     {         return $value ? ucfirst(strtolower($value)) : null;     }      public function getSalaryAttribute($value)     {         return $value ? $value / 100 : 0.0;     }      public function getBirthDateAttribute($value)     {         return $value ? CarbonCarbon::parse($value)->format('Y-m-d') : null;     }      // 【修改器】将前端传入值转为数据库存储格式     public function setFirstNameAttribute($value)     {         $this->attributes['first_name'] = trim($value);     }      public function setSalaryAttribute($value)     {         $this->attributes['salary_cents'] = (int) round((float) $value * 100);     }      public function setBirthDateAttribute($value)     {         $this->attributes['birth_date'] = $value              ? CarbonCarbon::parse($value)->format('Y-m-d')              : null;     } }

? 注意:$maps 本身不被 Laravel 自动识别,它仅作为开发者约定的配置项。真正的字段别名能力由 Eloquent 的 casts、appends、hidden 及访问器/修改器协同完成。

? 使用示例:零侵入式 API 开发

启用上述模型后,Controller 完全无需手动转换:

// 创建(自动转换输入 → 数据库格式) public function store(Request $request) {     $person = Person::create($request->only([         'firstName', 'lastName', 'birthDate', 'salary'     ]));      return response()->json($person, 201); // 自动转换数据库值 → 前端格式 }  // 查询(自动转换数据库值 → 响应格式) public function show($id) {     $person = Person::findOrFail($id);     return response()->json($person); // 返回已格式化的 firstName、salary 等 }

此时 $person->firstName 会触发 getFirstNameAttribute(),而 $person->firstName = ‘john’ 会触发 setFirstNameAttribute() —— 转换完全透明、不可绕过。

⚠️ 注意事项与进阶建议

  • 不要滥用 $casts 替代逻辑转换:$casts = [‘salary_cents’ => ‘Integer’] 仅做类型转换,无法实现单位换算或字符串标准化,务必用修改器/访问器。
  • 保持 fillable 与前端字段一致:若前端提交 firstName,请确保模型中 protected $fillable = [‘firstName’, ‘lastName’];,Eloquent 会自动委托给对应修改器。
  • 序列化控制:使用 protected $appends = [‘fullName’]; 配合 getFullNameAttribute() 可添加计算字段;用 protected $hidden = [‘first_name’, ‘last_name’]; 隐藏原始字段,避免混淆。
  • 批量更新安全提示:Person::where(…)->update([…]) 不触发修改器!该方法直接执行 SQL。如需强一致性,请始终使用模型实例操作(如 save() 或 fill()->save())。
  • 扩展性增强(可选):可封装为 Trait(如 HasFieldMapping),在多个模型中复用 $maps 解析与通用访问器逻辑,进一步解耦。

通过这种模型驱动的设计,你将告别散落在各处的 transform() 调用,让数据契约清晰固化在领域层——既符合 Laravel 的设计哲学,也显著提升了遗留系统在快速迭代中的健壮性与可读性。

text=ZqhQzanResources