
在 laravel 中,若需将请求中的 `locale_code` 转换为数据库关联的 `locale_id` 并参与验证,必须在验证执行前(即 `prepareforvalidation()` 阶段)完成字段注入,而非在控制器中修改 `$request` 实例——因为此时验证早已完成。
Laravel 的 FormRequest 生命周期严格遵循「预处理 → 验证 → 授权 → 控制器执行」顺序。一旦进入控制器方法(如 createInvoice),验证早已完成,$request->validated() 返回的是验证阶段结束时已确定的字段快照;此时对 $this['locale_id'] = ... 的赋值仅修改了请求实例的内部状态,但不会回填到验证结果中,因此 validated() 无法返回该字段。
✅ 正确做法是利用 prepareForValidation() 钩子——它在验证规则执行前被自动调用,允许你安全地修改原始输入数据:
// app/Http/Requests/InvoiceCreateRequest.php
class InvoiceCreateRequest extends ApiRequest
{
protected function prepareForValidation()
{
// 检查 locale_code 是否存在且非空
$localeCode = $this->input('locale_code');
if (!blank($localeCode)) {
$locale = Locale::where(Locale::REFERENCE_COLUMN, $localeCode)
->firstOrFail();
// 使用 merge() 将 locale_id 注入原始输入,供后续验证使用
$this->merge(['locale_id' => $locale->locale_id]);
} else {
// 若未提供 locale_code,显式设为 null(避免验证失败)
$this->merge(['locale_id' => null]);
}
}
public function rules()
{
return [
'locale_id' => 'required|exists:locales,locale_id', // 或 'sometimes|nullable|exists:...'
'billing_first_name' => 'required|string|max:255',
'billing_last_name' => 'required|string|max:255',
];
}
}⚠️ 注意事项:
- prepareForValidation() 中调用 $this->merge() 会直接修改底层 InputBag,确保 validated() 和 all() 均能获取到新字段;
- 避免在 convertLocaleCodeToLocaleId() 这类自定义方法中直接操作 $this['key'] = value,该语法不触发 Laravel 请求对象的数据同步机制;
- 若 locale_code 为可选字段,建议在 rules() 中使用 sometimes|nullable|exists:... 组合,而非仅 sometimes(后者不校验存在性);
- firstOrFail() 已包含异常处理,无需额外 try/catch —— 错误会自动转为 404 Not Found 响应,符合 API 规范。
通过此方式,locale_id 不仅参与验证(如 exists 检查),还会完整出现在 validated() 结果中,控制器可直接安全使用:
public function createInvoice(InvoiceCreateRequest $request)
{
$data = $request->validated(); // ✅ 包含 'locale_id' => 1
// 创建发票逻辑...
}










