添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

PHP读取excel、csv文件的库有很多,但用的比较多的有: PHPOffice/PHPExcel 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. 新建一个项目

    1
    2
    mkdir demo
    cd demo

    2. 安装

    使用 composer 安装:

    1
    composer require phpoffice/phpspreadsheet

    默认安装的是最新的稳定版本(1.5),如果想要安装dev版本,可以执行下面的命令:

    1
    composer require phpoffice/phpspreadsheet:develop

    上面步骤执行完毕后,目录结构是这样的:

    3. 新建一个简单的html文件,用来上传Excel文件

    1
    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 这个工厂类:

    1
    $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load('demo.xlsx');

    如果想在读写文件的时候设置一些属性,比如读写属性,可以这样设置:

    1
    2
    3
    $reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReaderForFile("demo.xlsx");
    $reader->setReadDataOnly(true);
    $reader->load("demo.xlsx");

    使用这个工厂类的好处就是你不需要关心文件上传的格式,它能自动帮识别,其实这个工厂类就是对你上传的文件做一些识别,如果识别出来是xls格式,就返回xls的reader,如果是csv,就返回csv的reader,通过分析代码我们可以看到这个 IOFactory 可以生产出如下的reader和writer:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    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,这样就免去了识别文件类型的步骤,如下:

    1
    2
    $reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader("Xlsx");  // 指定为xlsx格式
    $spreadsheet = $reader->load("demo.xlsx");
    4.2 从源码比较两种读写方式

    首先,来看下 IOFactory 这个工厂类,我们在不指定reader类型时直接 load ,代码内部是要做一个识别格式的操作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    // 源码解析
    // 不指定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,接下来,再来看看当我们指定了类型后,做了哪些操作的:

    1
    2
    3
    // 指定reader
    $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
    $spreadsheet = $reader->load($_FILES['file']['tmp_name']);

    上面的就比较简单了,直接创建reader,然后就load了,只是做了一些实例化的操作。这两种方法相比,第二种方法性能更好一点,当然前提是要知道文件格式。

    5. 读取Excel文件内容

    让我们接着继续上面的index.html,我们需要编写一个PHP文件来处理请求:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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对象我们就能获取到数据。

    当我们上传如下内容后:

    14299

    返回结果如下:

    14380

    因为我们在读取时,是从第二行开始的,所以第一行的内容就不显示了。

    这里说一下,在Excel中第三列是一个时间,PhpSpreadsheet对时间的处理有点特殊。在PhpSpreadsheet中date和time在存储时都是作为数字类型,当要区分数字是时间格式时,需要用到 format mask ,默认情况下, format mask 是开启了的,但如果设置 setReadDataOnly 等于 true 的话,就不能使用 format mask ,从而就区分不了数字和时间格式,PhpSpreatsheet将会全部作为数字处理。

    此时,我们开启只读模式看一下,

    1
    2
    $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
    $reader->setReadDataOnly(true);

    输出结果如下:

    15149

    第三列就变成了奇怪的数字,当初这个问题还困扰了我半天。

    5. PhpSpreadsheet读取文件时的一些常用方法

  • 如果一个Excel中有多个sheet,只想操作其中的某几个sheet,可以设置 setLoadSheetsOnly
  • 1
    2
    3
    $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
    // 参数支持字符串或一个数组
    $reader->setLoadSheetsOnly(['sheet1','sheet3']);
  • 读取指定行和列的数据
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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');

    上面的例子不够通用,可以修改下使之更为通用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    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的名字
  • 1
    $reader->listWorksheetNames('demo.xlsx');
  • 列出一个sheet的信息,包括多少列、多少行
  • 1
    $reader->listWorksheetInfo('demo.xlsx');

    PhpSpreadsheet的学习与使用就到这,真的很强大,几乎满足了日常的所有需求,是读取Excel、CSV文件的利器。

    欢迎阅读本篇文章,如有兴趣可以关注博主公众号哦:

    Share

    Categories

    Tag Cloud

    2021 AI API Artificial Intelligence Big Sur C# CSS Car DIGITS DNS DSP Digital pathology Electric Vehicle Eloquent Ethics GIL Go Hexo JIT Java JavaScript Javascript Laravel LeNet Linux Lloyds bank Lumen MNIST Matlab MySQL Nginx Object Oriented Objective-C OpenSlide Otsu PHP PHPStorm PSR Perl Prolog PyTorch Python Pytorch Reference Counting Reflection Ruby SICP Server Slim Something Stable Diffusion Swift Tensor Tmux Typora UK UniApp VB.NET VPS VirtualBox Vue Vultr WSI angular array_key_exists array_search audio autoload babel canvas colour space confusion matrix contrast convolution cover-view cryptography curl decrypt deep learning denoise edge detect edge detection efficiency electron emoji encrypt event-loop few-shot classification filter firewall framework gdpr git gulp image processing image thresholding in_array interviews javascript kernel life linux macOS machine learning math matplotlib mean networks neural network node noise normalize notes npm numpy overfitting performance permission postgraduate privacy policy python reading routing sobel std dev summary tools ufw underfitting uniApp 公众号阅读笔记 宝塔面板 小程序 微信 羊毛 软件设计 音乐

    Archives

    Recent Posts

    GDPR Privacy Policy stable diffusion webui install failed Enable PHP8.2 Jit on Ubuntu 宝塔面板关闭强制跳转到bind手机号绑定页面 Biased Reference Counting