Laravel数组元素验证问题:Pickings数组location唯一性校验报错
问题
需要实现pickings数组中location字段的唯一性校验,请求数据结构如下:
{"id":10,"type":"picking","pickings":[{"id":1,"warehouse_id":10,"type":"mobile","location":"testing","created_at":"2023-07-12T03:29:06.000000Z","updated_at":"2023-07-12T03:29:06.000000Z"},{"id":2,"warehouse_id":10,"type":"fixed","location":"testing 1","created_at":"2023-07-12T03:29:06.000000Z","updated_at":"2023-07-12T03:29:06.000000Z"}]}
在请求类中使用了如下验证规则:
public function rules() { return [ 'pickings.*.location' => [ Rule::unique('warehouse_pickings') ->where('warehouse_id', $this->input('id')) ->ignore($this->input('pickings.*.id'), 'id') ] ]; }
触发了如下错误:
exception: "TypeError" file: "/Users/shahanurmdsharif/development/php/ash/tag/trunk/vendor/laravel/framework/src/Illuminate/Validation/Rules/Unique.php" line: 70 message: "addslashes(): Argument #1 ($string) must be of type string, array given"
需要获取当前验证的pickings元素对应的id值,如何获取当前正在测试的元素索引对应的id?
解决方案
错误原因是$this->input('pickings.*.id')会返回整个pickings数组的所有id组成的数组,而ignore()方法要求传入单个字符串/数值类型的id。要获取当前验证元素的对应id,核心是解析出当前元素在数组中的索引,再通过索引获取对应id,以下是两种可行方案:
方案一:使用闭包手动校验
直接通过闭包解析属性名获取索引,手动执行数据库查询进行唯一性校验:
public function rules() { return [ 'pickings.*.location' => function ($attribute, $value, $fail) { // 从属性字符串中拆分出当前元素的索引,例:从"pickings.0.location"得到索引0 $index = explode('.', $attribute)[1]; // 获取当前pickings元素的id $currentPickingId = $this->input("pickings.$index.id"); $warehouseId = $this->input('id'); // 查询数据库是否存在重复记录 $hasDuplicate = \DB::table('warehouse_pickings') ->where('location', $value) ->where('warehouse_id', $warehouseId) // 如果是编辑场景,排除当前记录 ->when($currentPickingId, fn($query) => $query->where('id', '<>', $currentPickingId)) ->exists(); if ($hasDuplicate) { $fail('该位置已被占用'); } } ]; }
方案二:结合Rule::unique动态设置ignore参数
利用闭包解析索引后,动态给Rule::unique()设置ignore参数:
use Illuminate\Validation\Rule; public function rules() { return [ 'pickings.*.location' => [ function ($attribute, $value, $fail) { $index = explode('.', $attribute)[1]; $currentId = $this->input("pickings.$index.id"); // 构建唯一性规则 $uniqueRule = Rule::unique('warehouse_pickings') ->where('warehouse_id', $this->input('id')); // 如果存在当前id,排除该记录 if ($currentId) { $uniqueRule->ignore($currentId); } // 用临时验证器校验当前字段 $validator = \Validator::make([$attribute => $value], [$attribute => $uniqueRule]); if ($validator->fails()) { $fail($validator->errors()->first($attribute)); } } ] ]; }
核心逻辑总结
- 通过拆分验证属性名(如
pickings.0.location)提取出当前元素的索引 - 利用索引从
pickings数组中获取对应元素的id - 将单个id传入
ignore()方法,避免传入数组导致类型错误
内容的提问来源于stack exchange,提问作者Sharif
相关产品推荐
相关产品推荐

