如何在Laravel的FormRequest中实现CSV逐行验证替代Service类?
背景
我已开发完成招聘媒体管理后台,为实现导入CSV文件的逐行验证,当前采用了Service类的实现方式,具体代码如下:
视图(View)
<div id="main"> <h1 id="page_title">求人票一覧</h1> @if ($errors->get('upload_errors')) @foreach ($errors->get('upload_errors') as $error) <li class="error_text" style="color: #f44336;">{{ $error }}</li> @endforeach @endif </div>
控制器(Controller)
use App\Imports\JobsImport; use Illuminate\Http\Request; use Maatwebsite\Excel\Facades\Excel; class JobsController extends \App\Http\Controllers\Controller { public function __construct( JobService $jobService, ) { $this->jobService = $jobService; } public function import(Request $request) { $csv = $request->file("csv"); $csvArray = Excel::toArray(new JobsImport, $csv); $errors = $this->jobService->validateCsv($csvArray[0]); if (!is_null($errors)) { session()->flash("errors", $errors); return redirect("admin/job"); } ImportCsvJobs::dispatch($csv); return redirect("admin/job"); } }
服务类(JobService)
namespace App\Services\Admin; use Illuminate\Support\Facades\Validator; use Illuminate\Support\MessageBag; use Illuminate\Support\ViewErrorBag; use Exception; class JobService { public function validateCsv(array $csvArray) { $rules = [ "求人ID" => [ "required", "integer" ], "求人名" => [ "required", ], "企業名" => [ "required", ], "園名" => [ "required", ], "担当者名" => [ "required", ], "雇用形態" => [ "required", ], "職種" => [ "required", ], "おすすめポイント1" => [ "required", ], "おすすめポイント2" => [ "required", ], "こんな方におすすめ1" => [ "required", ], "こんな方におすすめ2" => [ "required", ], "求める人物像" => [ "required", ], "応募資格" => [ "required", ], "給与形態" => [ "required", ], "最低給与額" => [ "required", ], "最高給与額" => [ "required", ], "給与・待遇" => [ "required", ], "勤務時間" => [ "required", ], "固定時間勤務" => [ "required", ], "休日" => [ "required", ], "福利厚生" => [ "required", ], "交通費" => [ "required", ], ]; $attributes = [ "求人ID" => "ID", // job posting ID ]; $uploadErrorList = []; foreach ($csvArray as $key => $value) { if (!is_array($value)) continue; $validator = Validator::make($value, $rules, $attributes); if ($validator->fails()) { $errorMessage = array_map(fn ($message) => $key + 1 . "行目: " . $message, $validator->errors()->all()); $uploadErrorList = array_merge($uploadErrorList, $errorMessage); } }; if (empty($errorMessage)) return null; $errors = new ViewErrorBag(); $messages = new MessageBag(["upload_errors" => $uploadErrorList]); $errors->put("default", $messages); return $errors; } }
问题
能否将上述CSV逐行验证逻辑迁移至FormRequest中实现?若可行则无需在Controller中处理验证,代码结构将更优。
解答
当然可以把CSV逐行验证逻辑迁移到FormRequest中,这样能让控制器代码更清爽,完全符合Laravel的验证规范,代码结构也会更合理。具体操作步骤如下:
1. 创建自定义FormRequest
执行Artisan命令生成FormRequest文件:
php artisan make:request ImportJobCsvRequest
修改生成的ImportJobCsvRequest.php文件,实现验证逻辑:
namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Support\Facades\Validator; use Illuminate\Support\MessageBag; use Maatwebsite\Excel\Facades\Excel; use App\Imports\JobsImport; class ImportJobCsvRequest extends FormRequest { /** * 确定用户是否有权限进行此请求 */ public function authorize(): bool { return true; // 可根据实际权限需求调整 } /** * 获取上传文件的基础验证规则 */ public function rules(): array { return [ 'csv' => 'required|file|mimes:csv,txt', ]; } /** * 提前解析CSV文件,方便后续逐行验证 */ protected function prepareForValidation() { if ($this->hasFile('csv')) { $csvArray = Excel::toArray(new JobsImport, $this->file('csv')); $this->merge(['csv_data' => $csvArray[0]]); } } /** * 添加CSV逐行验证的自定义逻辑 */ public function withValidator($validator) { $validator->after(function ($validator) { if ($this->has('csv_data')) { $csvData = $this->input('csv_data'); $rules = [ "求人ID" => ["required", "integer"], "求人名" => ["required"], "企業名" => ["required"], "園名" => ["required"], "担当者名" => ["required"], "雇用形態" => ["required"], "職種" => ["required"], "おすすめポイント1" => ["required"], "おすすめポイント2" => ["required"], "こんな方におすすめ1" => ["required"], "こんな方におすすめ2" => ["required"], "求める人物像" => ["required"], "応募資格" => ["required"], "給与形態" => ["required"], "最低給与額" => ["required"], "最高給与額" => ["required"], "給与・待遇" => ["required"], "勤務時間" => ["required"], "固定時間勤務" => ["required"], "休日" => ["required"], "福利厚生" => ["required"], "交通費" => ["required"], ]; $attributes = [ "求人ID" => "ID", ]; $uploadErrorList = []; foreach ($csvData as $key => $row) { if (!is_array($row)) continue; $rowValidator = Validator::make($row, $rules, [], $attributes); if ($rowValidator->fails()) { $errorMessages = array_map(fn ($msg) => ($key + 1) . "行目: " . $msg, $rowValidator->errors()->all()); $uploadErrorList = array_merge($uploadErrorList, $errorMessages); } } if (!empty($uploadErrorList)) { $messageBag = new MessageBag(['upload_errors' => $uploadErrorList]); $validator->errors()->merge($messageBag); } } }); } }
2. 简化控制器代码
移除JobService依赖,直接使用自定义的FormRequest:
use App\Imports\JobsImport; use App\Http\Requests\ImportJobCsvRequest; use Maatwebsite\Excel\Facades\Excel; class JobsController extends \App\Http\Controllers\Controller { public function import(ImportJobCsvRequest $request) { // 经过FormRequest验证后,此处无需再处理验证逻辑 $csv = $request->file("csv"); ImportCsvJobs::dispatch($csv); return redirect("admin/job"); } }
3. 视图无需修改
原视图的错误展示逻辑可以直接复用,FormRequest验证失败后会自动将错误信息存入session,和之前的逻辑保持一致。
这样调整后,验证逻辑被封装在FormRequest中,控制器只负责处理核心业务流程,代码结构更清晰,符合Laravel的MVC设计原则。
内容的提问来源于stack exchange,提问作者Hayato Kuroda
相关产品推荐
相关产品推荐

