简介:PHPExcel-1.8.zip是一份面向PHP开发者的Excel处理组件完整源码包,源于开源项目PHPExcel 1.8版本,提供读写XLS、XLSX等常见格式的类库,适用于数据导入导出、报表生成、批量格式化、公式运算等Web开发场景,既适合刚接触PHPExcel的初学者学习核心API,也能作为现有项目的集成参考。压缩包共581个文件,约4.98MB,以340个php核心类文件为主体,同时包含81个xlsx示例工作簿、37个md说明文档、31个png示意图,以及config、functions、txt、xls、doc、csv等配置与辅助文件,覆盖源码、样例、文档和测试资源,目录结构清晰。已有305人浏览学习。借助其中的类库与示例,开发者可快速掌握创建工作簿、读取与写入单元格、设置字体边框样式、使用SUM/AVERAGE等公式以及生成图表的方法;包内提供的示例代码和配置说明能帮助理解从简单导出到复杂报表的实现思路,便于直接引用或二次开发。
1. PHPExcel 1.8:这包还能用,但得按 2014 年的规矩用
PHPExcel 1.8 是 PHP 读写 Excel 的老牌库,PHPExcel-1.8.zip 这个文件名到今天还在下载站和搜索框里反复出现,不是因为新项目选它,而是大量遗留系统跑在 PHP 5.6 / 7.0 上,导报表、读导入模板的代码全是PHPExcel_IOFactory::load()那一套。1.8 是这个系列最后的正式版本,官方早已归档并把新开发迁到了 PhpSpreadsheet,所以"能跑"和"该不该继续用"是两回事。这篇文章给两种情况都备好答案:接手老项目的人需要知道这个 zip 解开后怎么加载、读写时哪些参数必须调、报错长什么样;准备动手替换的人需要一份能对上号的迁移对照。适合维护导出功能、批量导入任务的 PHP 工程师,也适合刚被安排去修一个报 read zip archive 错误的报表接口的人。
2. 解开 PHPExcel-1.8.zip:目录结构、PHP 版本与最小加载
先把这个压缩包当成一个普通 PHP 依赖来看。它和 Composer 拉下来的包不一样,没有vendor/autoload.php,也没有命名空间,所有类都是全局类名,加载顺序错了就是Class not found。解开之后真正有用的只有Classes/目录,官方示例和文档在Examples/、Documentation/里,线上环境不需要引入。
2.1 先确认 PHP 版本,PHPExcel 1.8 只吃特定几口饭
PHPExcel 1.8 官方要求 PHP 5.2.0 以上,但那是 2014 年前后的说法,放到现在的运行时环境里有明显边界。
| PHP 版本 | PHPExcel 1.8 实际表现 | 建议 |
|---|---|---|
| 5.3 ~ 5.6 | 官方支持区间,读写正常 | 老系统可以继续用 |
| 7.0 ~ 7.3 | 基本可用,部分场景有 notice | 临时过渡可用 |
| 7.4 | 大量 deprecated 告警,功能仍在 | 能跑但日志很难看 |
| 8.0 及以上 | 花括号字符串偏移语法被移除,直接 fatal | 别用,换 PhpSpreadsheet |
PHPExcel 1.8 源码里有$str{0}这种取字符串下标的写法,PHP 7.4 开始废弃、PHP 8 直接移除,所以"装好了却打不开"在 PHP 8 环境是必然结果。另外要注意,这里说的 1.8 是 PHPExcel 的版本号,和 JDK 1.8 没有任何关系,别被版本号相同的巧合绕进去;真正要查的是php -v和php -m | grep zip,后者决定你能不能读 xlsx——xlsx 本质是 zip,读写都依赖 ZipArchive 扩展,扩展没装会直接抛异常。
2.2 解压与校验:别让 error read zip archive 卡住第一关
下载回来的 PHPExcel-1.8.zip 第一件事不是解压,是校验完整性:
unzip -t PHPExcel-1.8.zip-t是 test 模式,只校验不落地。如果输出里有error read zip archive或者could not find EOCD,说明压缩包在传输中被截断了——EOCD 是 zip 末尾的中心目录记录,找不到它基本等于文件没下完。这种包强行解压会缺文件,缺的往往是Classes/PHPExcel/子目录里的类文件,运行时报错还特别隐蔽。用 7-Zip 打开看一眼目录树,确认Classes/PHPExcel.php在,再解压不迟。
GitHub 上 PHPOffice/PHPExcel 仓库已经归档,页面上的 Download ZIP 和流传的这个包内容一致:zip 里没有.git,只有源码快照,所以升级只能靠换新包,没有版本历史可以翻。解压时注意路径别带中文和空格,PHP 的 require 拼接路径时这些字符容易出幺蛾子。
2.3 最小加载代码:三行让 IOFactory 跑起来
<?php // 假设解压后目录是 /data/lib/PHPExcel-1.8/ $base = __DIR__ . '/PHPExcel-1.8/Classes/'; require_once $base . 'PHPExcel.php'; require_once $base . 'PHPExcel/Autoloader.php'; PHPExcel_Autoloader::Register(); // 冒烟测试:能 new 出对象,说明加载链路通了 $book = new PHPExcel(); echo get_class($book), PHP_EOL; // PHPExcel先加载Classes/PHPExcel.php拿到核心类,再加载Autoloader.php并注册,后续PHPExcel_*开头的类会按需自动加载,不用手写一堆 require。PHPExcel_Autoloader内部用的是spl_autoload_register,它把PHPExcel_前缀映射到Classes/PHPExcel/目录,这是 1.8 推荐的加载方式,比把所有类文件一次性 require 进来省内存。
如果项目本身用了 Composer,更省事的做法是:
composer require phpoffice/phpexcel:1.8.*require vendor/autoload.php之后同样能用PHPExcel_IOFactory,Composer 的 autoload 机制兼容这种全局类名。装完先跑php -l检查语法,再跑上面这段冒烟测试。常见误区是只 require 了PHPExcel.php就调PHPExcel_IOFactory,结果报Class 'PHPExcel_IOFactory' not found——IOFactory 不在核心类里,必须走 autoload 或者单独 require。
提示:把
Classes/目录放在 Web 可访问的目录之外,避免别人直接通过 URL 拿到你的类文件。
3. 用 PHPExcel 1.8 读 Excel:IOFactory、迭代器与日期序列号
读取是这套库用得最多的场景。IOFactory 是统一入口,它负责根据文件类型挑对应的 Reader 类,Excel2007 的 Reader 内部用 ZipArchive 把 xlsx 解包,然后解析xl/worksheets/sheet1.xml这些 XML 部件。理解这层关系,后面排错会快很多。
3.1 load 与 createReader 的几个必调参数
<?php $reader = PHPExcel_IOFactory::createReader('Excel2007'); $reader->setReadDataOnly(true); // 只取数据,丢样式,内存大降 $reader->setPreCalculateFormulas(false); // 先拿公式字符串,不计算结果 $reader->setLoadSheetsOnly(['Sheet1']); // 只加载指定工作表 $book = $reader->load('/data/upload/report.xlsx');直接PHPExcel_IOFactory::load($file)也能用,它会自己探测文件类型,但少了中间的参数设置机会。createReader('Excel2007')是显式指定,适合你知道上游文件格式固定是 xlsx 的场景。三个参数按需取舍,别无脑全开。
| 参数 | 作用 | 什么场景开 |
|---|---|---|
setReadDataOnly(true) | 跳过样式、列宽、合并单元格等对象 | 只做数据统计、入库时必开 |
setPreCalculateFormulas(false) | 读入时不跑公式引擎 | 大文件加速;需要结果时再getCalculatedValue() |
setLoadSheetsOnly(['Sheet1']) | 只解析指定 sheet | 文件有多个 sheet 但只用其中一个 |
setReadFilter($filter) | 按行/列范围过滤读取 | 几十万行的大表,见第 5 章 |
setReadDataOnly(true)对大文件的收益最明显,样式对象比数据本身占内存大得多。代价是后面拿不到单元格的字体、背景色这些信息,做格式导出需求的不要开。
3.2 遍历工作表:行迭代器、列号换算与空单元格
<?php $sheet = $book->getSheet(0); // 按索引取第一个工作表 foreach ($sheet->getRowIterator() as $row) { $rowIndex = $row->getRowIndex(); $cellIterator = $row->getCellIterator(); $cellIterator->setIterateOnlyExistingCells(true); // 跳过空单元格 foreach ($cellIterator as $cell) { $addr = $cell->getCoordinate(); // 形如 'B3' $value = $cell->getValue(); echo $rowIndex, ' ', $addr, ' => ', $value, PHP_EOL; } }getRowIterator()是 PHPExcel 1.8 提供的迭代器接口,比手写双重 for 循环省内存,因为它不会把整个工作表一次性物化。setIterateOnlyExistingCells(true)是关键,默认迭代器会扫描整行范围内的每个格子,大表里大量空单元格会白跑 CPU。
需要按列名定位时,用两个配套的换算方法:
<?php $colIndex = PHPExcel_Cell::columnIndexFromString('C'); // 结果为 3 $colName = PHPExcel_Cell::stringFromColumnIndex(3); // 结果为 'C'手写循环时一般这样用:getHighestRow()拿最大行号,getHighestColumn()拿最大列字母,再用columnIndexFromString转成数字做边界。注意getHighestRow()返回的是有内容的行,不是文件的"总行数",Excel 里有人误按过空格也会算进去,判断数据边界时最好配合getCell()->getValue() !== null再过滤一次。
3.3 日期序列号和格式化值的两个坑
从 Excel 单元格拿日期,getValue()返回的不会是2016-01-01这样的字符串,而是一个浮点数——Excel 的日期序列号。直接拿这个数入库,十有八九要在报表上赔笑脸。
<?php $cell = $sheet->getCell('B2'); $rawValue = $cell->getValue(); // float(42370) $formatted = $cell->getFormattedValue(); // '2016-01-01',按单元格格式输出 $timestamp = PHPExcel_Shared_Date::ExcelToPHP($rawValue); // PHP 时间戳 echo date('Y-m-d H:i:s', $timestamp), PHP_EOL;两个坑要记住。第一,getValue()拿序列号,getFormattedValue()拿展示文本,后者依赖单元格的数字格式,如果源文件里格式是yyyy-mm-dd,拿到的才是2016-01-01;如果源文件格式不规范,getFormattedValue()返回的可能还是序列号。第二,ExcelToPHP()把序列号转成 Unix 时间戳,这个函数内部已经处理了 Excel 1900 日期系统里那个不存在的 1900-02-29 偏移,直接用不用自己减天数;但要注意它第二个参数可以传时区对象,跨时区项目里别省。
提示:判断一个单元格是不是日期,别依赖
getValue()的类型,要拿$cell->getStyle()->getNumberFormat()->getFormatCode()看格式串里有没有y、d这些字符,再做转换分支。
4. 用 PHPExcel 1.8 导出 xlsx:写入参数、Writer 选择与下载响应头
导出比读取更常用。PHPExcel 1.8 的写入流程是:new 一个 PHPExcel 对象,往里填单元格、样式,最后交给 Writer 输出。这套 API 是老项目里最常见的一段代码,很多新手的第一个坑是忘记setActiveSheetIndex,导致数据写到了默认的第二个空 sheet 上。
4.1 一段能直接用的写入代码
<?php $book = new PHPExcel(); $book->getProperties() ->setCreator('IT Ops') ->setTitle(date('Ymd') . ' 报表'); $sheet = $book->setActiveSheetIndex(0); $sheet->setTitle('月度流水'); // 表头 $headers = ['订单号', '金额', '下单时间']; foreach ($headers as $i => $title) { $col = PHPExcel_Cell::stringFromColumnIndex($i); $sheet->setCellValue($col . '1', $title); $style = $sheet->getStyle($col . '1'); $style->getFont()->setBold(true); $style->getAlignment()->setHorizontal( PHPExcel_Style_Alignment::HORIZONTAL_CENTER ); $sheet->getColumnDimension($col)->setWidth($i === 1 ? 14 : 20); } // 数据区:订单号必须按字符串写,否则长数字变科学计数法 $sheet->setCellValueExplicit( 'A2', '202501150001234567', PHPExcel_Cell_DataType::TYPE_STRING ); $sheet->setCellValue('B2', 128.50); $sheet->setCellValue('C2', date('Y-m-d H:i:s')); // 公式和数字格式 $sheet->setCellValue('B3', '=SUM(B2:B100)'); $sheet->getStyle('B2:B100') ->getNumberFormat() ->setFormatCode('#,##0.00'); $sheet->getStyle('A1:C1') ->getBorders() ->getAllBorders() ->setBorderStyle(PHPExcel_Style_Border::BORDER_THIN);setCellValueExplicit强制指定数据类型是最容易被忽略的参数。订单号、身份证号这类超过 15 位的数字,不指定TYPE_STRING会被当 float 处理,导出的 Excel 里显示成1.23457E+15,这基本是导出功能被投诉的头号原因。样式层面的几个常量也值得记住:对齐方式用PHPExcel_Style_Alignment::HORIZONTAL_CENTER,边框用PHPExcel_Style_Border::BORDER_THIN,字重用getFont()->setBold(true),这些都是 1.8 里最常用的样式参数。
4.2 输出到浏览器:Writer 选型与 zip 的关系
<?php header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); header('Content-Disposition: attachment; filename="report.xlsx"'); header('Cache-Control: max-age=0'); $writer = PHPExcel_IOFactory::createWriter($book, 'Excel2007'); $writer->save('php://output');save('php://output')直接把文件流打到响应体,不需要先生成临时文件。Content-Type 必须用 OOXML 的 MIME,用application/octet-stream虽然能下载但文件名后缀经常被浏览器改掉。三个 header 之前不能有任何输出,包括 BOM 和空行,否则header()直接失败,这个问题在 Windows 下用 UTF-8 编码保存 PHP 文件时尤其常见。
| Writer 类型 | 输出格式 | 行数上限 | 依赖扩展 | 使用场景 |
|---|---|---|---|---|
Excel2007 | .xlsx | 1048576 行 | php_zip | 默认选择,文件小,兼容新 Excel |
Excel5 | .xls | 65536 行 | php_xml | 老 ERP、银行系统只收 xls |
CSV | .csv | 无硬性限制 | 无 | 数据交换,但丢样式和公式 |
HTML | .html | 无 | 无 | 快速预览,Excel 能打开但不算真表格 |
xlsx 本身就是 zip 容器,里面是[Content_Types].xml、xl/workbook.xml这些 XML 部件,所以不要再把生成的 xlsx 压一层 zip 发给用户,双重后缀反而会让对方解压失败。反过来,如果读取 xlsx 时报 read zip archive 类错误,先怀疑这个 xlsx 是不是被改过扩展名的 HTML 文件——接口返回错误页面但保存成了 .xlsx,这种事在对接第三方系统时很常见。
4.3 大报表的内存参数:单元格缓存与数据写入量
PHPExcel 1.8 默认把每个单元格对象都放内存,几万行的报表轻松吃掉几百 MB,这是它最出名的问题。导出前可以改缓存策略:
<?php PHPExcel_Settings::setCacheStorageMethod( PHPExcel_CachedObjectStorageFactory::cache_to_phpTemp, ['memoryCacheSize' => '64MB'] ); // 之后再创建 PHPExcel 对象 $book = new PHPExcel();cache_to_phpTemp表示单元格对象在内存里攒够 64MB 就溢写到临时文件,用磁盘换内存。cache_in_memory_serialized是序列化后驻内存,速度比 phpTemp 快但省得有限;cache_to_discISAM是纯磁盘缓存,最慢但最省内存,适合大批量后台导出。这个设置必须在new PHPExcel()之前调用,而且对读取和写入都生效。
5. PHPExcel 1.8 报错对照表,以及分块读大表的 ReadFilter 技巧
接手老项目时,你面对的不是怎么用,而是报错怎么解。把最常见的几类问题整理成对照表,排错时先对着看,能省大半时间。
| 报错或症状 | 根因 | 处理方向 |
|---|---|---|
Class 'ZipArchive' not found | php_zip 扩展未启用 | 安装扩展并重启 PHP-FPM |
加载 xlsx 报read zip archive/could not find EOCD | 文件不是合法 zip,多为 HTML 伪装的假 xlsx | 用file命令看文件头,MAGIC 不是PK就找上游 |
PHPExcel_Settings::setCacheStorageMethod无效 | 设置放在了创建对象之后 | 移到new PHPExcel()之前 |
PHP 8 下直接Fatal error | 花括号字符串偏移语法被移除 | 迁移 PhpSpreadsheet 或退回 PHP 7.4 |
长数字列显示1.23457E+15 | 数字被识别为 float | 用setCellValueExplicit指定TYPE_STRING |
| 导出文件打不开,提示格式错误 | save('php://output')前有 BOM 或空白 | 检查 PHP 文件编码,去掉输出前的一切字符 |
这里要特别说明迁移的边界:PHPExcel 1.8 和 PhpSpreadsheet 的类名、方法名基本一一对应,PHPExcel_IOFactory对应PhpSpreadsheet\IOFactory,PHPExcel_Cell::columnIndexFromString对应Coordinate::columnIndexFromString,但返回值类型更严格了,很多老代码在 PHP 8 下跑不动不是库的错,是返回值 null 判断没写。换库时先用静态分析工具扫一遍PHPExcel_前缀,再按类逐个替换,别指望一次性全量迁移。
读大表时最值得留下的技巧是 ReadFilter 分块读取。PHPExcel 1.8 没有现成的"只读第 N 到 M 行"的参数,但setReadFilter给了钩子,自己实现一个行区间过滤器:
<?php class ChunkReadFilter implements PHPExcel_Reader_IReadFilter { private $startRow = 0; private $endRow = 0; public function setRows($start, $end) { $this->startRow = $start; $this->endRow = $end; } public function readCell($column, $row, $worksheetName = '') { // 表头行永远读,其它行只读指定区间 return ($row == 1) || ($row >= $this->startRow && $row < $this->endRow); } } $filter = new ChunkReadFilter(); $reader = PHPExcel_IOFactory::createReader('Excel2007'); $reader->setReadDataOnly(true); $reader->setReadFilter($filter); // 假设有 20 万行,每 5000 行一个分块 for ($start = 2; $start <= 200000; $start += 5000) { $filter->setRows($start, $start + 5000); $book = $reader->load($bigFile); $sheet = $book->getSheet(0); // 在这里处理当前分块的数据,比如拼 SQL 批量入库 $book->disconnectWorksheets(); // 断掉内部引用,释放内存 unset($book); }disconnectWorksheets()是 1.8 提供的内存释放手段,配合unset()才能让 PHP 的引用计数立刻回收,否则分块循环里内存只增不减。这个方案也有代价:每个分块都会重新解压整个 zip,分块越小重复读取的开销越大,所以 5000 行只是一个起点,不是标准答案。上线前用memory_get_peak_usage(true)观察峰值,从 5000 往 20000 试着调,找到内存曲线开始快速爬升的那个分块大小,就是你这台机器上的最优值。
本文还有配套的精品资源,点击获取