Symfony/PHP中错误CSV文件导入的处理方案:避免数据表被误截断
解决CSV导入前误截断数据表的问题
你的核心问题在于截断操作执行得太早——在还没确认CSV文件有效性的情况下就清空了MyTable,一旦文件有问题,数据表就空了,这显然很危险。我们可以通过「先验证CSV有效性,再执行截断+导入」的逻辑来修复,同时结合事务保证数据一致性。
具体步骤
- 添加CSV文件验证逻辑:在截断表之前,先检查CSV的表头、字段数量、数据格式是否符合要求;
- 调整执行顺序:验证通过后再启动事务、截断表、执行导入;
- 异常捕获与回滚:任何环节出错(验证失败、导入异常)都终止流程并回滚事务,避免数据损坏。
修改后的代码
1. 在MyTableRepository中添加CSV验证方法
我们先写一个验证方法,检查表头是否匹配预期,同时确保每一行数据的字段数量正确(你有40个字段,所以每行应该有40个值):
public function validateCsv($file): bool { // 替换成你实际业务中需要的表头名称 $expectedHeaders = ['field_one', 'field_two', /* ... 省略其他38个字段 */, 'field_forty']; if (($handle = fopen($file->getPathname(), "r")) === false) { return false; } // 读取第一行表头做匹配验证 $headers = fgetcsv($handle, 0, ","); if ($headers !== $expectedHeaders) { fclose($handle); return false; } // 抽查前5行数据的字段数量,提前发现格式错误 $rowCheckCount = 0; $maxCheckRows = 5; while (($data = fgetcsv($handle, 0, ",")) !== false && $rowCheckCount < $maxCheckRows) { if (count($data) !== 40) { fclose($handle); return false; } // 可选:添加更细致的字段类型验证,比如数字字段是否为有效数字 // if (!is_numeric($data[0])) return false; $rowCheckCount++; } fclose($handle); return true; }
2. 修改ImportController的逻辑
调整执行顺序,先验证CSV,通过后再执行截断和导入,同时处理异常:
$form = $this->createFormBuilder() ->add('form', FileType::class, [ 'attr' => ['accept' => '.csv', 'class' => 'custom-file-input'], 'label' => 'Import' ]) ->getForm(); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { /** @var UploadedFile $file */ $file = $form->get('form')->getData(); $myTableRepo = $this->getDoctrine()->getManager()->getRepository(MyTable::class); // 第一步:先验证CSV文件有效性,不通过直接终止 if (!$myTableRepo->validateCsv($file)) { $this->addFlash('error', "上传的CSV文件格式错误,请检查表头和数据字段数量!"); return $this->redirectToRoute('import'); } // 第二步:验证通过后,启动事务执行导入流程 $em = $this->getDoctrine()->getManager(); $connection = $em->getConnection(); try { $connection->beginTransaction(); // 现在只有CSV合法才会执行截断操作 $platform = $connection->getDatabasePlatform(); $connection->executeQuery($platform->getTruncateTableSQL('MyTable', true)); // 执行批量导入 $myTableRepo->importMyData($file); $connection->commit(); $this->addFlash('success',"CSV文件已成功导入!"); } catch (\Exception $e) { $connection->rollBack(); $this->addFlash('error', "导入失败:" . $e->getMessage()); } return $this->redirectToRoute('import'); }
3. 优化importMyData方法的异常处理
确保导入过程中如果出现错误(比如数据格式不符合实体要求)会抛出异常,让事务能正确回滚:
public function importMyData($file) { $em = $this->entityManager; if (($handle = fopen($file->getPathname(), "r")) !== false) { $count = 0; $batchSize = 1000; fgetcsv($handle, 0,","); // 跳过表头行 while (($data = fgetcsv($handle, 0,",")) !== false) { // 再次验证当前行的字段数量,防止抽查的前几行没问题但后续行出错 if (count($data) !== 40) { fclose($handle); throw new \RuntimeException("第".($count+2)."行数据字段数量错误!"); } $count++; $entity = new MyTable(); $entity->setFieldOne($data[0]); $entity->setFieldTwo($data[1]); //.... $entity->setFieldForty($data[39]); $em->persist($entity); if (($count % $batchSize) === 0 ) { $em->flush(); $em->clear(); } } fclose($handle); $em->flush(); $em->clear(); } else { throw new \RuntimeException("无法打开上传的CSV文件!"); } }
关键说明
- 验证前置:把CSV验证放在最前面,确保只有合法的文件才会触发后续的截断和导入操作;
- 事务保护:整个截断+导入过程包裹在事务中,任何一步出错都会回滚,避免出现「表被截断但数据没导入成功」的尴尬情况;
- 多层验证:既验证表头,又抽查数据行,还在导入时每行再做一次检查,最大程度避免错误数据进入数据库。
内容的提问来源于stack exchange,提问作者Shika
相关产品推荐
相关产品推荐

