Laravel验证器验证时将输入数据属性设为null的问题咨询
使用Laravel Validator验证包含数组与模型实例的混合数据时,验证器会将输入数据中的模型属性修改为null。
说明:本人清楚FormRequest在控制器中的使用方式、Laravel的注入机制及FormRequest的核心用途,疑问点在于验证器为何要修改传入的待验证数据?
可直接在php artisan tinker中运行以下代码复现问题:
$rules = [ 'users' => [ 'required', 'array', 'min:1', ], 'users.*' => [ 'required', ], 'users.*.name' => [ 'required', 'string', 'max:255', ], 'users.*.age' => [ 'required', 'integer', ], 'users.*.best_friend' => [ 'required', ], ]; $data = [ 'users' => [ (new \App\Models\User)->forceFill([ 'name' => 'USER #1', 'age' => 30, 'best_friend' => (new \App\Models\User)->forceFill(['name' => 'User X']) ]), ], ]; echo 'BEFORE: ' . data_get($data, 'users.0.name'); // 输出 USER #1 $validator = Validator::make($data, $rules); echo 'AFTER: ' . data_get($data, 'users.0.name'); // 输出 NULL dd($data);
验证通过后,$data中的users.*.name、users.*.age、users.*.best_friend等字段会被设为null。调试发现问题源于/vendor/laravel/framework/src/Illuminate/Validation/ValidationData.php:42处的data_set方法(该方法按引用修改数据)。
问题原因
Laravel验证器的ValidationData类在解析验证规则时,会遍历所有规则字段并通过data_set方法标准化数据结构。由于Eloquent模型实现了ArrayAccess接口,data_set会将模型视为数组进行操作。
当处理users.*.name这类嵌套通配符规则时,验证器会尝试遍历数组中的每个模型实例,在提取并验证属性值的过程中,data_set方法会错误地将模型原本存在的属性覆盖为null——这是因为验证器的设计初衷是处理HTTP请求这类纯数组/标量输入,未考虑混合模型实例的场景,导致对象类型数据的处理逻辑出现异常。
修复方案
方案1:将模型实例转换为数组后验证
验证前把模型实例转换成纯数组,避免验证器直接操作模型对象:
$data = [ 'users' => [ (new \App\Models\User)->forceFill([ 'name' => 'USER #1', 'age' => 30, 'best_friend' => (new \App\Models\User)->forceFill(['name' => 'User X']) ])->toArray(), ], ];
如果需要保留原始模型实例,可先复制模型再转换:
$model = (new \App\Models\User)->forceFill([...]); $data = [ 'users' => [$model->toArray()], ]; // 验证后原始$model不受影响
方案2:传入数据副本进行验证
创建原始数据的副本,让验证器操作副本而非原始数据,避免修改原数据:
// 使用深拷贝复制数据(需确保模型支持拷贝) $dataCopy = unserialize(serialize($data)); $validator = Validator::make($dataCopy, $rules); // 原始$data不会被修改
方案3:使用验证器的validated()方法获取结果
验证通过后,使用$validator->validated()获取验证后的纯数组数据,该方法不会修改原始输入:
if ($validator->passes()) { $validatedData = $validator->validated(); // 使用$validatedData进行后续操作,原始$data保持不变 }
是否属于框架Bug或可改进点
这不属于严格意义上的框架Bug,而是验证器未覆盖的边界使用场景。Laravel验证器的核心设计目标是处理HTTP请求的输入数据(通常为数组或标量类型),而非混合模型实例的复杂数据结构。
不过这可以作为框架的可改进点:建议在ValidationData类中增加数据类型判断,当处理实现ArrayAccess的对象时,跳过不必要的data_set赋值操作,或者在赋值前检查值是否存在,避免覆盖已有属性。
内容的提问来源于stack exchange,提问作者mowses

