最省心的 Excel 导入导出方案是 yectep/phpspreadsheet-bundle:它自动注册 PhpSpreadsheet 服务与读写器,支持多格式及内存优化;需手动处理响应头与路径编码;大文件须用 RowIterator + batchInsert 避免内存溢出;liuggio/excelbundle 已废弃,不再兼容 PHP 8+/Symfony 6+。

用 yectep/phpspreadsheet-bundle 做导入导出最省心
它直接暴露 phpoffice.spreadsheet 服务,不用每次手动 new PhpSpreadsheet\Spreadsheet 或调 IOFactory。Bundle 内部已自动注册 Reader/Writer,支持 xlsx、xls、csv、ods 等格式,且默认启用内存优化(如 ReadFilter 和流式写入)。
安装后只需注入服务即可使用:
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
// 导入
$reader = $this->container->get('phpoffice.spreadsheet.reader.xlsx');
$spreadsheet = $reader->load($filePath);
// 导出
$writer = $this->container->get('phpoffice.spreadsheet.writer.xlsx');
$writer->save($responseStream);
注意:Bundle 不会自动处理 HTTP 响应头,导出时仍需手动设置 Content-Type 和 Content-Disposition;导入时若文件路径含中文或空格,建议先 realpath() 或 urldecode() 处理。
大文件导入卡死?别硬读全表,用 RowIterator + batchInsert
14000 行 Excel 在内存中加载成对象后,很容易触发 Fatal error: Allowed memory size exhausted。关键不是调高 memory_limit,而是跳过中间对象层,逐行解析并批量写入。
实操要点:
- 用
$worksheet->getRowIterator(2)跳过表头,避免getCell('A1')这类随机访问(性能差) - 每 500 行调一次
$em->flush(),再$em->clear()清空一级缓存,防止 Doctrine 持有全部实体引用 - 禁用 SQL 日志:
$em->getConnection()->getConfiguration()->setSQLLogger(null),否则日志对象本身吃内存 - 不要在循环里调
$repo->findOneBy()查关联数据——提前查好,建索引数组映射,例如:$projectMap = array_column($projects, 'id', 'name')
liuggio/excelbundle 已废弃,别再用
这个 Bundle 依赖已停止维护的 PHPExcel,最后更新是 2017 年,不兼容 PHP 8.0+,且其 excel.reader 服务返回的是旧版对象,和当前主流的 PhpSpreadsheet API 不兼容。你可能会遇到:
Call to undefined method PHPExcel_Worksheet::getHighestDataRow()
或者在 Symfony 6+ 中因容器编译失败报错:Service "excel.reader" has a dependency on a non-existent service "phpexcel"。官方文档早已归档,GitHub 仓库设为 archived。现在唯一推荐的 Composer 包是 yectep/phpspreadsheet-bundle(适配 Symfony 4–7)或直接使用原生 phpoffice/phpspreadsheet(需自行配置服务)。
命令行导入时,InputOption::VALUE_REQUIRED 比 VALUE_OPTIONAL 更安全
在 Command 的 configure() 里,如果文件路径是必填项,就别用 VALUE_OPTIONAL 加默认值。用户漏传 -f 时,$input->getOption('file') 返回 null,后续 file_exists(null) 不报错但逻辑中断,容易静默失败。
改成强制参数更明确:
->addOption('file', 'f', InputOption::VALUE_REQUIRED, 'Absolute or relative path to Excel file')
然后在 execute() 开头加校验:
$file = $input->getOption('file');
if (!is_file($file)) {
$output->error(sprintf('File not found: %s', $file));
return 1;
}
另外,命令行下不走 Web 请求生命周期,$_FILES 为空,所有文件路径必须显式传入,不能依赖上传临时目录。
真正麻烦的从来不是读取 Excel,而是字段映射错误、空值处理、编码不一致、时间格式歧义——这些没法靠 Bundle 自动解决,得在 foreach ($row as $cell) 之后立刻做类型断言和清洗,比如 trim((string)$cell->getValue()) 和 DateTime::createFromFormat('Y-m-d', $dateStr),漏掉一步,入库就是脏数据。


















