PHP 读取 Excel,CSV 文件的库有很多, 但用的比较多的有: https://github.com/PHPOffice/PHPExcel , https://github.com/PHPOffice/PhpSpreadsheet , 现在 PHPExcel 已经不再维护了, 最新的一次提交还是在 2017 年 12 月 25 号, 建议直接使用 PhpSpreadsheet, 而且这两个项目都是同一个组织维护的, 本文介绍 PhpSpreadsheet 的使用.
介绍 PhpSpreadsheet
PhpSpreadsheet 这个库是纯 PHP 写的, 提供了非常丰富的类和方法, 而且支持很多文件格式:
环境要求
PHP>= 5.6
开启 php_zip 扩展
开启 php_xml 扩展
开启 php_gd2 扩展
开始使用??
我们写一个简单的 demo, 来学习 PhpSpreadsheet 的使用, 大概就是一个简单的文件上传页面, 上传我们要读取的 Excel 文件, PHP 接收到文件, 调用 PhpSpreadsheet 读取 Excel 里面的内容.
0. 配置环境
略..., 自己配置
我当前的 PHP 版本是 7.2.13
1. 新建一个项目
mkdir demo cd demo
2. 安装
使用 https://getcomposer.org/ 安装:
Composer require phpoffice/phpspreadsheet
默认安装的是最新的稳定版本 (1.5), 如果想要安装 dev 版本, 可以执行下面的命令:
Composer require phpoffice/phpspreadsheet:develop
上面步骤执行完毕后, 目录结构是这样的:
3. 新建一个简单的 html 文件, 用来上传 Excel 文件
VIM index.HTML
index.HTML 里面的内容很简单, 如下:
这里要注意下: form 表单的 enctype 一定要是 multipart/form-data
这只是一个简单的 demo, 一个 form 表单就可以了, 运行后就是下面这样了 :)
4. PhpSpreadsheet 如何使用?
在处理前端传过来的 Excel 文件之前, 先来介绍下 PhpSpredsheet 如何使用.
4.1 读取文件
PhpSpreadsheet 中读取文件有很多种, 对于不同格式的文件有不同的读取方法, 比如: xlsx 格式, 使用 \ PhpOffice\PhpSpreadsheet\Reader\Xlsx(),CSV 格式, 使用 \ PhpOffice\PhpSpreadsheet\Reader\CSV(), 乍一看这么多类就感觉有点复杂, 其实这些类都实现了 \ PhpOffice\PhpSpreadsheet\Reader\IReader 和 \ PhpOffice\PhpSpreadsheet\Writer\IWriter 接口, 指定了要加载的文件类型. 我们可以直接使用 \ PhpOffice\PhpSpreadsheet\IOFactory 这个工厂类:
$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load('demo.xlsx');
如果想在读写文件的时候设置一些属性, 比如读写属性, 可以这样设置:
- $reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReaderForFile("demo.xlsx");
- $reader->setReadDataOnly(true);
- $reader->load("demo.xlsx");
使用这个工厂类的好处就是你不需要关心文件上传的格式, 它能自动帮识别, 其实这个工厂类就是对你上传的文件做一些识别, 如果识别出来是 xls 格式, 就返回 xls 的 reader, 如果是 CSV, 就返回 CSV 的 reader, 通过分析代码我们可以看到这个 IOFactory 可以生产出如下的 reader 和 writer:
- abstract class IOFactory
- {
- private static $readers = [
- 'Xlsx' => Reader\Xlsx::class,
- 'Xls' => Reader\Xls::class,
- 'Xml' => Reader\xml::class,
- 'Ods' => Reader\Ods::class,
- 'Slk' => Reader\Slk::class,
- 'Gnumeric' => Reader\Gnumeric::class,
- 'Html' => Reader\HTML::class,
- 'Csv' => Reader\CSV::class,
- ];
- private static $writers = [
- 'Xls' => Writer\Xls::class,
- 'Xlsx' => Writer\Xlsx::class,
- 'Ods' => Writer\Ods::class,
- 'Csv' => Writer\CSV::class,
- 'Html' => Writer\HTML::class,
- 'Tcpdf' => Writer\PDF\Tcpdf::class,
- 'Dompdf' => Writer\PDF\Dompdf::class,
- 'Mpdf' => Writer\PDF\Mpdf::class,
- ];
- ...
可以看到支持的类型还是蛮多的, 但是很多都不常用.
在 IOFactory 工厂中还可以指定读写的文件类型, 返回对应的 reader, 这样就免去了识别文件类型的步骤, 如下:
- $reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader("Xlsx"); // 指定为 xlsx 格式
- $spreadsheet = $reader->load("demo.xlsx");
4.2 从源码比较两种读写方式
首先, 来看下 IOFactory 这个工厂类, 我们在不指定 reader 类型时直接 load, 代码内部是要做一个识别格式的操作:
// 源码解析 // 不指定 reader, 直接获取上传的文件创建 $reader = \PhpOffice\PhpSpreadsheet\IOFactory::load($_FILES['file']['tmp_name']); // IOFactory::load() public static function load($pFilename) { // 这步棋室就是创建 reader, 免去了你手动创建 $reader = self::createReaderForFile($pFilename); return $reader->load($pFilename); } // IOFactory::createReaderForFile() // 这步就是返回一个 reader, 具体返回什么 reader, 是根据文件名来的 public static function createReaderForFile($filename) { // 判断文件是否存在并且可读, 会抛出 InvalidArgumentException File::assertFile($filename); // 根据文件后缀猜测类型 $guessedReader = self::getReaderTypeFromExtension($filename); if ($guessedReader !== null) { $reader = self::createReader($guessedReader); // Let's see if we are lucky if (isset($reader) && $reader->canRead($filename)) { return $reader; } } // 如果没有检测到类型, 就会遍历默认的 reader 数组, 直到找到可以使用的那个 reader foreach (self::$readers as $type => $class) { if ($type !== $guessedReader) { $reader = self::createReader($type); if ($reader->canRead($filename)) { return $reader; } } } throw new Reader\Exception('Unable to identify a reader for this file'); }
从上面的代码, 可以看到在 load 前是做了文件检测和类型判断的操作, 然后再返回对应的 reader, 接下来, 再来看看当我们指定了类型后, 做了哪些操作的:
// 指定 reader $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx(); $spreadsheet = $reader->load($_FILES['file']['tmp_name']);
上面的就比较简单了, 直接创建 reader, 然后就 load 了, 只是做了一些实例化的操作. 这两种方法相比, 第二种方法性能更好一点, 当然前提是要知道文件格式.
5. 读取 Excel 文件内容
让我们接着继续上面的 index.HTML, 我们需要编写一个 PHP 文件来处理请求:
require 'vendor/autoload.php'; $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx(); try { $spreadsheet = $reader->load($_FILES['file']['tmp_name']); } catch (\PhpOffice\PhpSpreadsheet\Reader\Exception $e) { die($e->getMessage()); } $sheet = $spreadsheet->getActiveSheet(); $res = array(); foreach ($sheet->getRowIterator(2) as $row) { $tmp = array(); foreach ($row->getCellIterator() as $cell) { $tmp[] = $cell->getFormattedValue(); } $res[$row->getRowIndex()] = $tmp; } echo json_encode($res);
我们先引入 autoload, 接着创建了一个 Xlsx 的 reader, 然后 load 我们上传的文件, 因为在 Excel 中, 内容都是按 sheet 区分的, 每一个 sheet 中都由行和列组成, 我们获取到当前使用的 sheet, 通过 sheet 获取到行的迭代对象, 再针对每一行得到每一列对象, 在 PhpSpreadsheet 中, cell 是一个最小的单元, 对应着第几行第几列, 数据都是存在 cell 中, 得到 cell 对象我们就能获取到数据.
当我们上传如下内容后:
返回结果如下:
因为我们在读取时, 是从第二行开始的, 所以第一行的内容就不显示了.
这里说一下, 在 Excel 中第三列是一个时间, PhpSpreadsheet 对时间的处理有点特殊. 在 PhpSpreadsheet 中 date 和 time 在存储时都是作为数字类型, 当要区分数字是时间格式时, 需要用到 format mask, 默认情况下, format mask 是开启了的, 但如果设置 setReadDataOnly 等于 true 的话, 就不能使用 format mask, 从而就区分不了数字和时间格式, PhpSpreatsheet 将会全部作为数字处理.
此时, 我们开启只读模式看一下,
$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx(); $reader->setReadDataOnly(true);
输出结果如下:
第三列就变成了奇怪的数字, 当初这个问题还困扰了我半天.
5. PhpSpreadsheet 读取文件时的一些常用方法
如果一个 Excel 中有多个 sheet, 只想操作其中的某几个 sheet, 可以设置 setLoadSheetsOnly
$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx(); // 参数支持字符串或一个数组 $reader->setLoadSheetsOnly(['sheet1','sheet3']);
读取指定行和列的数据
class MyReadFilter implements \PhpOffice\PhpSpreadsheet\Reader\IReadFilter { public function readCell($column, $row, $worksheetName = '') { // 只读取 A1:E7 的数据 if ($row>= 1 && $row <= 7) { if (in_array($column,range('A','E'))) { return true; } } return false; } } $myFilter = new MyReadFilter(); $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx(); $reader->setReadFilter($filterSubset); $spreadsheet = $reader->load('demo.xlsx');
上面的例子不够通用, 可以修改下使之更为通用:
class MyReadFilter implements \PhpOffice\PhpSpreadsheet\Reader\IReadFilter { private $startRow = 0; private $endRow = 0; private $columns = []; public function __construct($startRow, $endRow, $columns) { $this->startRow = $startRow; $this->endRow = $endRow; $this->columns = $columns; } public function readCell($column, $row, $worksheetName = '') { if ($row>= $this->startRow && $row <= $this->endRow) { if (in_array($column,$this->columns)) { return true; } } return false; } } $myFilter = new MyReadFilter(9,15,['A', 'B', 'D']);
列出 Excel 中所有 sheet 的名字
$reader->listWorksheetNames('demo.xlsx');
列出一个 sheet 的信息, 包括多少列, 多少行
$reader->listWorksheetInfo('demo.xlsx');
PhpSpreadsheet 的学习与使用就到这, 真的很强大, 几乎满足了日常的所有需求, 是读取 Excel,CSV 文件的利器.
来源: http://www.bubuko.com/infodetail-2905472.html