如何在Maatwebsite\Excel的ValidationException异常中获取验证失败的工作表名称?
我明白你的困扰——当使用WithMultipleSheets处理多工作表导入时,默认的ValidationException返回的Failure对象里确实没有包含工作表名称,这给定位错误带来了不小的麻烦。下面我给你两种实用的解决方案,你可以根据自己的需求选择:
方案一:在错误消息中直接嵌入工作表名称(简单直接)
这种方法不需要修改异常处理逻辑,只需要在子工作表的导入类(也就是你的ImportSubjectWiseSheet)中记录当前工作表名称,然后在自定义验证消息中把它加进去,前端直接就能看到错误所属的工作表。
步骤1:修改子工作表导入类,添加工作表名称属性和事件监听
让ImportSubjectWiseSheet实现WithEvents,在BeforeSheet事件中保存当前工作表的标题:
use Maatwebsite\Excel\Concerns\WithEvents; use Maatwebsite\Excel\Events\BeforeSheet; class ImportSubjectWiseSheet implements ToModel, WithValidation, WithEvents { protected $data; protected $sheetTitle; // 新增属性存储工作表名称 public function __construct($data){ $this->data = $data; } // 注册事件监听,捕获当前工作表标题 public function registerEvents(): array { return [ BeforeSheet::class => function(BeforeSheet $event) { $this->sheetTitle = $event->getSheet()->getTitle(); }, ]; } // 你的验证规则,根据实际需求调整 public function rules(): array { return [ 'marks' => 'required|numeric|max:100', 'student_id' => 'required|exists:students,id' ]; } // 自定义验证消息,嵌入工作表名称 public function customValidationMessages() { return [ 'marks.required' => "工作表【{$this->sheetTitle}】第:row行:成绩字段不能为空", 'marks.numeric' => "工作表【{$this->sheetTitle}】第:row行:成绩必须是数字", 'student_id.required' => "工作表【{$this->sheetTitle}】第:row行:学生ID不能为空", 'student_id.exists' => "工作表【{$this->sheetTitle}】第:row行:学生ID不存在" ]; } // 你的ToModel实现方法... }
步骤2:直接使用错误消息
这样当你捕获ValidationException后,每个failure->errors()里的消息已经包含了对应的工作表名称,直接在前端展示这些错误即可,不需要额外处理。
方案二:给Failure对象附加工作表名称(更灵活)
如果你希望在Failure对象中直接获取工作表名称(而不是只在错误消息里),可以通过父导入类(ImportMark)给每个子工作表实例传递当前工作表名称,然后自定义验证失败的处理逻辑。
步骤1:修改父导入类ImportMark,传递工作表名称
在BeforeSheet事件中,根据当前工作表的索引,给对应的ImportSubjectWiseSheet实例设置工作表名称:
class ImportMark implements WithMultipleSheets, WithEvents{ protected $data; protected $no_of_sheets; protected $totalRows; public $sheetNames; public $sheetData; public function __construct($data){ $this->data = $data; // 初始化子工作表实例(这里假设你有4个工作表,可根据实际调整) $this->sheetData = array_fill(0, 4, new ImportSubjectWiseSheet($this->data)); } public function sheets(): array{ return $this->sheetData; } public function registerEvents(): array { return [ BeforeImport::class => function (BeforeImport $event) { $this->totalRows = $event->getReader()->getTotalRows(); }, BeforeSheet::class => function(BeforeSheet $event) { $sheetTitle = $event->getSheet()->getTitle(); $this->sheetNames[] = $sheetTitle; // 获取当前工作表的索引 $sheetIndex = $event->getSheet()->getIndex(); // 给对应的子工作表实例设置名称 if(isset($this->sheetData[$sheetIndex])){ $this->sheetData[$sheetIndex]->setSheetTitle($sheetTitle); } } ]; } public function getSheetNames() { return $this->sheetNames; } }
步骤2:修改子工作表类,添加setter和自定义验证处理
在ImportSubjectWiseSheet中添加setter方法,并自定义验证失败的逻辑,把工作表名称附加到Failure对象的自定义属性中:
use Maatwebsite\Excel\Validators\Validator; use Maatwebsite\Excel\Validators\ValidationException; class ImportSubjectWiseSheet implements ToModel, WithValidation { protected $data; protected $sheetTitle; public function __construct($data){ $this->data = $data; } public function setSheetTitle($title){ $this->sheetTitle = $title; } public function rules(): array { return [ // 你的验证规则 'marks' => 'required|numeric|max:100', ]; } // 重写验证方法,自定义异常抛出逻辑 public function validate(Validator $validator) { if ($validator->fails()) { // 获取默认的Failure对象数组 $failures = $validator->failures(); // 给每个Failure对象添加工作表名称(通过反射修改protected属性) foreach ($failures as $failure) { $reflection = new \ReflectionClass($failure); $property = $reflection->getProperty('customAttributes'); $property->setAccessible(true); $customAttrs = $property->getValue($failure); $customAttrs['sheet_title'] = $this->sheetTitle; $property->setValue($failure, $customAttrs); } // 重新抛出包含自定义属性的异常 throw new ValidationException($validator, $failures); } } // 你的ToModel实现方法... }
步骤3:在控制器中获取工作表名称
现在你在捕获异常后,就可以通过$failure->customAttributes['sheet_title']获取对应的工作表名称了:
catch (\Maatwebsite\Excel\Validators\ValidationException $e) { $failures = $e->failures(); foreach ($failures as $failure) { $row = $failure->row(); $sheetName = $failure->customAttributes['sheet_title']; // 这里获取工作表名称 $errors = $failure->errors(); $values = $failure->values(); // 处理错误信息... } return redirect()->back()->withInput()->with('xls_errors', $failures); }
两种方案里,方案一更简单易维护,适合大多数场景;方案二更灵活,如果你需要对错误信息做更复杂的处理可以选择它。
备注:内容来源于stack exchange,提问作者Nishan Hitang

