PhpSpreadsheet 是 TP6/TP7 唯一推荐的 Excel 处理库,需用 getRealPath() 获取有效路径、按后缀显式指定 Reader、流式遍历避免 toArray() 内存爆炸,并手动处理合并单元格值。

PhpSpreadsheet 是当前 ThinkPHP(尤其是 TP6/TP7)中唯一应使用的 Excel 处理库,PHPExcel 已于 2019 年正式归档、停止维护,且与 PHP 7.4+ 及现代 ThinkPHP 版本存在兼容性断裂——直接 require PHPExcel 会触发 Class 'PHPExcel_IOFactory' not found 或 ParseError: syntax error, unexpected token "string"。别走老路。
用 request()->file() 拿到文件后,立刻调 getRealPath()
ThinkPHP 的 think\File 对象返回的临时路径,getPathname() 指向的是上传时的原始临时名(如 /tmp/phpXXXXXX),但该文件可能已被移动或清理;而 getRealPath() 才是实际可读的、已落盘的完整路径。
常见错误现象:IOFactory::load() 报 Could not open ... for reading! File does not exist.,本质就是传了无效路径。
- ✅ 正确写法:
$file = request()->file('excel'); $path = $file->getRealPath(); $spreadsheet = IOFactory::load($path); - ❌ 错误写法:
$path = $file->getPathname(); // 不可靠,尤其在 Swoole 或多进程环境下 - ⚠️ 若仍报错,说明文件未成功上传或权限不足,先检查
$file->isValid()和is_readable($path)
IOFactory::createReader() 必须匹配后缀,否则抛 Invalid reader
.xls(Excel 2003)和 .xlsx(Excel 2007+)底层格式完全不同,PhpSpreadsheet 要求显式指定 Reader 类型,不能靠自动探测——尤其当文件扩展名被篡改或无扩展名时,自动探测极易失败。
- .xls → 用
'Xls':$reader = IOFactory::createReader('Xls'); - .xlsx → 用
'Xlsx':$reader = IOFactory::createReader('Xlsx'); - 不支持 .csv 直接用 Reader 加载;CSV 应用
fopen()+fgetcsv()流式处理 - 若不确定类型,可用
pathinfo($file->getOriginalName(), PATHINFO_EXTENSION)安全提取后缀再映射
别用 toArray(),手动遍历行+单元格控制内存
$worksheet->toArray() 会把整张表(含空白行、合并单元格残留、隐藏列)一次性加载进 PHP 内存。一个 8MB 的 Excel 文件,在 TP 中常导致内存暴涨至 400MB+,触发 Fatal error: Allowed memory size exhausted 或被系统 OOM killer 杀掉进程。
立即学习“PHP免费学习笔记(深入)”;
- ✅ 推荐做法:用
getRowIterator()+getCellIterator()边读边过滤 - 关键开关:
$cellIterator->setIterateOnlyExistingCells(true),跳过空单元格(非空值单元格) - 判断表头行请用
$row->getRowIndex() === 1,别依赖循环索引(因空行跳过后索引不连续) - 合并单元格内容只存在于左上角单元格,右侧/下方为
null;需用$cell->isMergeRangeValueCell()判断是否为主单元格,或提前调$worksheet->mergeCells()解析结构
大文件必须流式加载,避免 load() 全量读取
对 >5MB 的 Excel,即使用了迭代,IOFactory::load($path) 仍会先将整个文件解压并构建完整对象树,内存峰值依然很高。此时应绕过文件系统路径,直接传资源句柄:
- ✅ 流式加载写法:
$reader = IOFactory::createReader('Xlsx'); $spreadsheet = $reader->load(fopen($file->getRealPath(), 'r')); - 这能显著降低初始内存占用,尤其适合 Swoole 或高并发导入场景
- 注意:
fopen()返回的 resource 必须可读,且不能在load()前被fclose() - 此方式不适用于 .xls(
XlsReader 不支持流式),.xls 大文件建议转成 .xlsx 再处理,或改用其他轻量方案(如 CSV)
null,很可能它只是合并区域里的非主单元格——得结合 getMergeCells() 和 isMergeRangeValueCell() 手动补全,否则数据就丢了。



















