应选用 phpoffice/phpspreadsheet(phpexcel 已废弃),它兼容多种格式、支持流式读取,安装命令为 composer require phpoffice/phpspreadsheet;需封装服务类、限定加载范围、正确解析日期与空值,并分批事务导入以避免内存溢出。

Yii2 中读取 Excel 文件该选哪个扩展
直接用 phpoffice/phpspreadsheet,别碰 PHPExcel(已废弃),也别用 Yii1 风格的封装库。Yii2 本身不内置 Excel 支持,但 phpoffice/phpspreadsheet 是当前事实标准,兼容 .xlsx/.xls/.csv,且能流式读取大文件。
安装命令:
composer require phpoffice/phpspreadsheet
常见错误:装了 phpoffice/phpexcel —— 这个包早已停止维护,PHP 7.4+ 会直接报 Class 'PHPExcel_IOFactory' not found。
- 若项目已用 Yii2 的
yii2-export扩展,它底层也是基于phpspreadsheet,可复用其读取逻辑,但不要依赖它的导出类做导入 - 避免在控制器里直接 new \PhpOffice\PhpSpreadsheet\Reader\Xlsx() —— 应封装为服务类,便于 mock 和复用
- 注意 PHP 内存限制:读取 10MB 以上 Excel 时,默认加载全量内存,需改用
setReadDataOnly(true)+setLoadSheetsOnly(['Sheet1'])限定范围
如何安全地解析 Excel 单元格内容(尤其日期、数字、空值)
Excel 里看起来是 “2023-05-01”,实际可能是序列号 45076;表面为空的单元格可能返回 null、空字符串、或 ''(带不可见字符)。直接 $cell->getValue() 极易出错。
正确做法是统一走 getFormattedValue(),并配合类型判断:
$cell = $worksheet->getCell('A2');
$value = $cell->getFormattedValue();
if ($cell->getDataType() === \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_NUMERIC) {
$value = (string) $value; // 防止科学计数法转成 1.23E+10
}
- 日期字段必须用
$reader->setReadDataOnly(false)(否则日期变数字),再通过\PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($value)转换 - 空单元格建议用
trim((string)$value) === ''判断,而非empty()(会把 "0"、"0.0" 当空) - 公式单元格默认不计算,需调用
$reader->setCalculateFormulas(true),但性能差,非必要关掉
批量导入时怎么避免内存溢出和事务失败
一次读完全部行再 insert,1 万行就可能崩。必须边读边处理,且控制事务粒度。
推荐模式:按 500 行分批提交,每批开启独立事务,失败只回滚本批:
$batchSize = 500;
$rows = [];
foreach ($worksheet->getRowIterator(2) as $row) { // 从第2行开始(跳过表头)
$cells = $row->getCellIterator();
$cells->setIterateOnlyExistingCells(false);
$rowData = array_map(fn($cell) => $cell->getFormattedValue(), iterator_to_array($cells));
$rows[] = $rowData;
if (count($rows) >= $batchSize) {
$transaction = Yii::$app->db->beginTransaction();
try {
YourModel::insertAll($rows); // 自定义批量插入方法
$transaction->commit();
$rows = [];
} catch (\Exception $e) {
$transaction->rollBack();
throw $e; // 或记录日志后继续下一批
}
}
}
- 别用 ActiveRecord 的
save()循环插入 —— 每次都触发验证和事件,慢且占内存 -
insertAll()要自己实现,基于Yii::$app->db->createCommand()->batchInsert(),注意字段名与 Excel 列顺序对齐 - Excel 行数超 10 万时,考虑改用
XmlReader模式(Xlsx支持loadIntoExisting()流式解析),但 Yii2 默认不启用,需手动配置
上传 Excel 后怎么校验格式和业务规则
用户传个 .txt 改后缀叫 .xlsx,或列顺序错乱,前端校验毫无意义,所有检查必须在服务端重做。
分三层校验:
- 文件层面:用
finfo_open(FILEINFO_MIME_TYPE)确认真实 MIME 是application/vnd.openxmlformats-officedocument.spreadsheetml.sheet或application/vnd.ms-excel,不单看后缀 - 结构层面:读取第一行,比对预设字段名数组
['username', 'email', 'created_at'],缺失任意一项就拒收 - 数据层面:每行进循环前,先做
array_filter($rowData, 'strlen')看是否全空;关键字段如email用filter_var($value, FILTER_VALIDATE_EMAIL)实时校验
错误提示要具体:“第 87 行邮箱格式错误:‘abc’”,而不是“导入失败”。用户无法定位问题时,只会反复重试并骂娘。
真正麻烦的是混合类型列——比如“金额”列里既有 123.45 又有 ¥123.45,这种得靠正则清洗,没法靠库自动识别。











