使用multipart/form-data时如何正确返回必填属性验证错误
Laravel商品添加必填属性校验异常问题解决方案
问题描述
在Laravel应用的商品添加功能中,需要校验用户提交所有is_force为true的必填属性,但遇到以下异常情况:
- 当请求头为
multipart/form-data且未提交全部必填属性时,接口仅返回数字“1”,商品未创建; - 使用
application/json头并注释图片、图库验证规则时,可正常返回正确格式的验证错误。
相关代码
Controller代码
public function storeForSeller(AddSellerProductRequest $request): \Illuminate\Http\JsonResponse { $uploaded = UploaderHelper::upload($request->file('image'), config('path.product')); $category = $this->categoryRepository->findById($request->category_id); $result = $this->productPropertyRepository->checkPropertyValidation($request->properties(null)); if(!$result){ return ControllerHelper::errorJsonResponse(trans('admin.invalid_property_items')); } $product = $this->productRepository->create($request->product([ 'image' => $uploaded , 'expire_date' => Carbon::now()->addDays($category->listing_duration), 'user_id' => auth('sanctum')->id() ])); if (empty($product)) { UploaderHelper::delete($uploaded); return ControllerHelper::createError(); } $this->productTagRepository->replaceTags($request->tags(), $product->id, auth('sanctum')->id()); $this->productPropertyRepository->createAll($request->properties($product->id)); $galleries = collect($request->galleries())->map(function ($item) use ($product){ return [ 'title' => $product->title, 'product_id' => $product->id, 'url' => UploaderHelper::upload($item, config('path.product')), 'updater' => auth('sanctum')->id() ]; }); $result = $this->productGalleryRepository->insert($galleries->toArray()); if(!$result){ UploaderHelper::delete($uploaded); $this->productRepository->deleteById($product->id); return ControllerHelper::errorJsonResponse(trans('admin.error_image_upload')); } return ControllerHelper::successJsonData($product); }
Request代码
public function rules(): array { return [ 'id' => ['nullable', 'numeric', Rule::exists('products', 'id')->where('status', ListingStatus::PENDING->value), new Ownership('product', false)], 'category_id' => ['required', Rule::exists('categories', 'id')->where('is_hidden', false)], 'product_status' => ['required', 'in:' . implode(',', ProductStatus::toArray()),], 'title' => ['required', 'max:500'], 'price' => ['required', 'numeric', 'min:0', 'max:1000000'], 'discount' => ['nullable', 'numeric', 'min:0', 'max:100'], 'description' => ['nullable', 'string', 'min:0', 'max:2000'], 'city_id' => 'nullable|integer|exists:' . Region::class . ',id', 'state_id' => 'nullable|integer|exists:' . Region::class . ',id', 'country_id' => 'nullable|integer|exists:' . Region::class . ',id', 'tags' => ['required', 'array', 'max:10'], 'tags.*' => ['required', 'string', 'max:50'], 'image' => [Rule::requiredIf(empty(request('id'))), 'mimes:jpg,png,jpe,jpeg', 'max:500', 'image'], 'galleries' => ['array', Rule::requiredIf(empty(request('id'))), 'max:5'], 'galleries.*' => ['required', 'mimes:jpg,png,jpe,jpeg', 'max:500', 'image'], 'properties' => ['array', Rule::requiredIf(empty(request('id'))), new ForceProperty()], 'properties.*.property_id' => ['required',Rule::exists('properties', 'id')], 'properties.*.property_item_id' => ['required',Rule::exists('property_items', 'id')], ]; } public function product($merge, $except = null): array { return $this->safe()->merge([ 'updater' => auth('sanctum')->id() ])->merge($merge)->except('id', 'tags', 'galleries', 'properties', $except); } public function tags(): array { return $this->get('tags'); } public function galleries(): array { return $this->file('galleries'); } public function properties($product_id): array { return collect($this->get('properties'))->map(function ($value) use ($product_id) { $value['product_id'] = $product_id; $value['updater'] = auth()->id(); return $value; })->toArray(); }
ForceProperty验证规则代码
class ForceProperty implements ValidationRule { /** * Run the validation rule. * * @param \Closure(string): PotentiallyTranslatedString $fail */ public function validate(string $attribute, mixed $value, Closure $fail): void { $forceProperties = CategoryProperty::query()->where('is_force', true)->pluck('id','title')->toArray(); $propertyIds = collect($value)->pluck('property_id')->toArray(); $missingForceProperties = array_diff($forceProperties, $propertyIds); if (!empty($missingForceProperties)) { $missingForcePropertyTitles = array_flip($missingForceProperties); $fail("Please provide all required properties with IDs: " . implode(', ', $missingForcePropertyTitles)); } } }
解决方案
1. 调整文件上传顺序,避免验证前执行资源操作
当前Controller在Request验证通过前就执行了图片上传,若验证失败,不仅浪费存储资源,还可能因文件操作抛出未捕获异常导致响应格式异常。将文件上传逻辑移至Request验证通过之后:
修改Controller代码:
public function storeForSeller(AddSellerProductRequest $request): \Illuminate\Http\JsonResponse { // Request已自动验证,此处开始执行业务逻辑 $category = $this->categoryRepository->findById($request->category_id); $result = $this->productPropertyRepository->checkPropertyValidation($request->properties(null)); if(!$result){ return ControllerHelper::errorJsonResponse(trans('admin.invalid_property_items')); } // 验证通过后再上传图片 $uploaded = UploaderHelper::upload($request->file('image'), config('path.product')); $product = $this->productRepository->create($request->product([ 'image' => $uploaded , 'expire_date' => Carbon::now()->addDays($category->listing_duration), 'user_id' => auth('sanctum')->id() ])); // 后续逻辑保持不变... }
2. 修复ForceProperty规则的业务逻辑错误
当前规则校验的是所有分类下的必填属性,实际业务中必填属性应与当前商品的分类绑定。修改ForceProperty类,关联当前请求的category_id:
class ForceProperty implements ValidationRule { public function validate(string $attribute, mixed $value, Closure $fail): void { // 获取当前请求的分类ID $categoryId = request()->input('category_id'); if (!$categoryId) { $fail('分类ID不能为空'); return; } // 仅获取当前分类下的必填属性 $forceProperties = CategoryProperty::query() ->where('category_id', $categoryId) ->where('is_force', true) ->pluck('id','title') ->toArray(); $propertyIds = collect($value)->pluck('property_id')->toArray(); $missingForceProperties = array_diff(array_keys($forceProperties), $propertyIds); if (!empty($missingForceProperties)) { // 根据缺失的ID获取对应标题 $missingTitles = collect($forceProperties)->only($missingForceProperties)->values()->toArray(); $fail("请填写所有必填属性:" . implode(', ', $missingTitles)); } } }
3. 确保Request验证失败时返回JSON响应
在AddSellerProductRequest中重写failedValidation方法,强制验证失败时返回JSON格式响应,避免multipart/form-data请求下的响应异常:
use Illuminate\Contracts\Validation\Validator; use Illuminate\Http\Exceptions\HttpResponseException; class AddSellerProductRequest extends FormRequest { // 其他方法保持不变... protected function failedValidation(Validator $validator) { throw new HttpResponseException( response()->json([ 'status' => false, 'message' => '验证失败', 'errors' => $validator->errors() ], 422) ); } }
4. 移除冗余的属性校验逻辑
Controller中$this->productPropertyRepository->checkPropertyValidation(...)的校验与Request中的ForceProperty规则重复,可移除该冗余逻辑,统一由Request验证处理,减少代码重复和异常点。
总结
通过调整文件上传顺序、修复必填属性的分类关联逻辑、强制验证失败返回JSON响应,即可解决multipart/form-data请求下仅返回数字“1”的异常问题,确保所有请求头下都能返回一致的验证错误格式。
内容的提问来源于stack exchange,提问作者Reza Heydari
相关产品推荐
相关产品推荐

