在ThinkPHP项目中导入.xlsx格式的Excel文件时,有一条核心原则必须牢记:务必显式指定Xlsx Reader类型,切勿依赖自动探测机制。原因在于——.xls与.xlsx的底层结构截然不同,自动识别一旦遭遇扩展名被篡改或缺失的情况,就会直接导致读取失败。与其把结果交给运气,不如从一开始就明确锁定读取器类型,确保导入过程万无一失。

环境准备与依赖安装
首先,请确认项目中已成功安装PhpSpreadsheet(推荐方案,适用于ThinkPHP 6+及新版ThinkPHP 5),或兼容旧版的PHPExcel(若您仍在维护TP3/TP5的遗留项目)。安装PhpSpreadsheet非常简单,执行 composer require phpoffice/phpspreadsheet 即可。若使用PHPExcel,则将库文件放置到 Vendor/PHPExcel 目录下并正确引入。
【必要条件】PHP环境必须开启zip与xml扩展,否则读取Xlsx文件时会直接提示 Class 'ZipArchive' not found 或解析中断,流程根本无法推进。
上传文件并校验扩展名
前端表单需设置 enctype="multipart/form-data",name 属性例如 file。后端获取上传文件后,使用 pathinfo($file->getOriginalName(), PATHINFO_EXTENSION) 安全提取扩展名。这里有一个常见陷阱:切勿放入 .csv 文件——它并非真正的Excel格式,PhpSpreadsheet会直接解析失败。
仅允许 .xls 与 .xlsx 两种格式,其余一律拦截:
if (!in_array(strtolower($ext), ['xls', 'xlsx'])) {
$this->error('仅支持.xls或.xlsx文件');
}
选择Reader并加载文件
方式一:PhpSpreadsheet(推荐,适用于TP6+及新版TP5)
根据文件后缀显式创建对应的Reader:
$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader('Xlsx'); // .xlsx用Xlsx,.xls用Xls
【关键操作】千万不要偷懒使用 IOFactory::load(),它会尝试自动探测格式。一旦对方上传了一个伪装成 .xlsx 的 .xls 文件,就会抛出异常,而且排查起来非常困难——调试半天都找不到根因。
方式二:PHPExcel(适用于TP3/TP5旧项目)
$objReader = PHPExcel_IOFactory::createReader('Excel2007'); // 对应.xlsx
// 或 Excel5 对应.xls
$objPHPExcel = $objReader->load($filePath);
读取工作表并过滤空行
第一步:获取当前活动工作表:
$worksheet = $spreadsheet->getActiveSheet();
第二步:遍历所有行,但务必启用全单元格迭代模式,否则空列会被自动跳过:
$rowIterator = $worksheet->getRowIterator();
第三步:对每一行,重置单元格迭代器以包含空单元格:
$cellIterator->setIterateOnlyExistingCells(false);
第四步:逐单元格取值并收集,然后使用 array_filter($rowData) 判断是否全为空——这是判断空行最可靠的方式。使用 getHighestRow() 会遗漏末尾空行,而 isEmpty() 方法也不够稳定,存在不少隐患。
入库前进行数据清洗
遍历 $data 数组时,跳过首行(表头):
if ($i === 0) continue;
对每个字段执行 trim() 与 null 值转换:
$value = is_null($cell->getValue()) ? '' : trim((string)$cell->getValue());
不要直接使用 toArray()——它会将整张表一次性加载到内存中,面对10万行数据时极易触发OOM(内存溢出)。手动遍历行与单元格才能精确控制内存占用,这才是生产环境下的标准写法。
