rafaelmaza/spout

PHP库,用于以快速和可扩展的方式读取和写入电子表格文件(CSV、XLSX和ODS)

v2.7.2 2017-02-16 12:35 UTC

README

Latest Stable Version Project Status Build Status Scrutinizer Code Quality Code Coverage Total Downloads

Spout是一个PHP库,用于以快速和可扩展的方式读取和写入电子表格文件(CSV、XLSX和ODS)。与其它文件读取器或写入器不同,它能够处理非常大的文件,同时保持内存使用率非常低(低于3MB)。

加入社区并讨论Spout: Gitter

安装

Composer(推荐)

Spout可以直接通过Composer进行安装。

运行以下命令

$ composer require box/spout

手动安装

如果您无法使用Composer,不用担心!您仍然可以手动安装Spout。

开始之前,请确保您的系统满足要求

  1. 发布页面下载源代码
  2. 将下载的内容提取到您的项目中。
  3. 将此代码添加到顶级控制器(index.php)或更合适的位置
require_once '[PATH/TO]/src/Spout/Autoloader/autoload.php'; // don't forget to change the path!

要求

  • PHP版本5.4.0或更高
  • PHP扩展php_zip启用
  • PHP扩展php_xmlreader启用
  • PHP扩展php_simplexml启用

基本用法

读取器

无论文件类型如何,读取文件的用户界面始终相同

use Box\Spout\Reader\ReaderFactory;
use Box\Spout\Common\Type;

$reader = ReaderFactory::create(Type::XLSX); // for XLSX files
//$reader = ReaderFactory::create(Type::CSV); // for CSV files
//$reader = ReaderFactory::create(Type::ODS); // for ODS files

$reader->open($filePath);

foreach ($reader->getSheetIterator() as $sheet) {
    foreach ($sheet->getRowIterator() as $row) {
        // do stuff with the row
    }
}

$reader->close();

如果文件中有多个工作表,则读取器将按顺序读取所有工作表。

写入器

与读取器一样,写入数据到文件的接口只有一个

use Box\Spout\Writer\WriterFactory;
use Box\Spout\Common\Type;

$writer = WriterFactory::create(Type::XLSX); // for XLSX files
//$writer = WriterFactory::create(Type::CSV); // for CSV files
//$writer = WriterFactory::create(Type::ODS); // for ODS files

$writer->openToFile($filePath); // write data to a file or to a PHP stream
//$writer->openToBrowser($fileName); // stream data directly to the browser

$writer->addRow($singleRow); // add a row at a time
$writer->addRows($multipleRows); // add multiple rows at a time

$writer->close();

对于XLSX和ODS文件,每个工作表中的行数限制为1,048,576。默认情况下,一旦达到此限制,写入器将自动创建一个新的工作表并继续写入数据。

高级用法

如果您想了解如何使用Spout执行一些常见的高级任务,请查看Wiki。它包含了一些代码片段,可以直接使用。

配置CSV读取器和写入器

可以配置CSV读取器和写入器,指定字段分隔符以及字段封装符

use Box\Spout\Reader\ReaderFactory;
use Box\Spout\Common\Type;

$reader = ReaderFactory::create(Type::CSV);
$reader->setFieldDelimiter('|');
$reader->setFieldEnclosure('@');
$reader->setEndOfLineCharacter("\r");

此外,如果您需要读取非UTF-8文件,您可以指定文件的编码方式

$reader->setEncoding('UTF-16LE');

默认情况下,写入器生成的CSV文件以UTF-8编码,带有BOM。但是,可以不包含BOM

use Box\Spout\Writer\WriterFactory;
use Box\Spout\Common\Type;

$writer = WriterFactory::create(Type::CSV);
$writer->setShouldAddBOM(false);

配置XLSX和ODS读取器和写入器

行样式

可以对行应用一些格式化选项。Spout支持字体、背景、边框以及对齐样式。

use Box\Spout\Common\Type;
use Box\Spout\Writer\WriterFactory;
use Box\Spout\Writer\Style\StyleBuilder;
use Box\Spout\Writer\Style\Color;

$style = (new StyleBuilder())
           ->setFontBold()
           ->setFontSize(15)
           ->setFontColor(Color::BLUE)
           ->setShouldWrapText()
           ->setBackgroundColor(Color::YELLOW)
           ->build();

$writer = WriterFactory::create(Type::XLSX);
$writer->openToFile($filePath);

$writer->addRowWithStyle($singleRow, $style); // style will only be applied to this row
$writer->addRow($otherSingleRow); // no style will be applied
$writer->addRowsWithStyle($multipleRows, $style); // style will be applied to all given rows

$writer->close();

给行添加边框需要Border对象。

use Box\Spout\Common\Type;
use Box\Spout\Writer\Style\Border;
use Box\Spout\Writer\Style\BorderBuilder;
use Box\Spout\Writer\Style\Color;
use Box\Spout\Writer\Style\StyleBuilder;
use Box\Spout\Writer\WriterFactory;

$border = (new BorderBuilder())
    ->setBorderBottom(Color::GREEN, Border::WIDTH_THIN, Border::STYLE_DASHED)
    ->build();

$style = (new StyleBuilder())
    ->setBorder($border)
    ->build();

$writer = WriterFactory::create(Type::XLSX);
$writer->openToFile($filePath);

$writer->addRowWithStyle(['Border Bottom Green Thin Dashed'], $style);

$writer->close();

Spout将为所有创建的行使用默认样式。可以通过这种方式来覆盖此样式

$defaultStyle = (new StyleBuilder())
                ->setFontName('Arial')
                ->setFontSize(11)
                ->build();

$writer = WriterFactory::create(Type::XLSX);
$writer->setDefaultRowStyle($defaultStyle)
       ->openToFile($filePath);

不幸的是,Spout尚不支持所有可能的格式化选项。但您可以找到最重要的选项

      | Italic        | `StyleBuilder::setFontItalic()`
      | Underline     | `StyleBuilder::setFontUnderline()`
      | Strikethrough | `StyleBuilder::setFontStrikethrough()`
      | Font name     | `StyleBuilder::setFontName('Arial')`
      | Font size     | `StyleBuilder::setFontSize(14)`
      | Font color    | `StyleBuilder::setFontColor(Color::BLUE)`<br>`StyleBuilder::setFontColor(Color::rgb(0, 128, 255))`

对齐 | 换行文本 | StyleBuilder::setShouldWrapText(true|false)

创建新工作表

还可以更改写入器在当前工作表中写入行数达到最大值(1,048,576)时的行为

use Box\Spout\Writer\WriterFactory;
use Box\Spout\Common\Type;

$writer = WriterFactory::create(Type::ODS);
$writer->setShouldCreateNewSheetsAutomatically(true); // default value
$writer->setShouldCreateNewSheetsAutomatically(false); // will stop writing new data when limit is reached

使用自定义临时文件夹

处理XLSX和ODS文件需要创建临时文件。默认情况下,Spout将使用系统默认的临时文件夹(由sys_get_temp_dir()返回)。可以在读取器或写入器上显式设置以覆盖此设置。

use Box\Spout\Writer\WriterFactory;
use Box\Spout\Common\Type;

$writer = WriterFactory::create(Type::XLSX);
$writer->setTempFolder($customTempFolderPath);

字符串存储(XLSX写入器)

XLSX文件支持不同的字符串值存储方式

  • 共享字符串旨在通过将字符串从工作表表示中分离出来并忽略字符串重复(如果字符串被使用了三次,则只存储一个字符串)来优化文件大小
  • 内联字符串不太优化(因为所有重复的字符串都被存储),但处理速度更快

为了保持内存使用量真的很低,Spout在使用共享字符串时不会优化字符串。尽管如此,仍然可以使用此模式。

use Box\Spout\Writer\WriterFactory;
use Box\Spout\Common\Type;

$writer = WriterFactory::create(Type::XLSX);
$writer->setShouldUseInlineStrings(true); // default (and recommended) value
$writer->setShouldUseInlineStrings(false); // will use shared strings
关于Apple Numbers和iOS支持的说明

苹果的产品(Numbers和iOS预览器)不支持内联字符串,而是显示空单元格。因此,如果需要支持这些平台,请务必使用共享字符串!

日期/时间格式

当读取包含日期或时间的电子表格时,Spout默认将值作为DateTime对象返回。可以更改此行为,并返回格式化的日期(例如,“2016-11-29 1:22 AM”)。日期的格式与电子表格中指定的格式相对应。

use Box\Spout\Reader\ReaderFactory;
use Box\Spout\Common\Type;

$reader = ReaderFactory::create(Type::XLSX);
$reader->setShouldFormatDates(false); // default value
$reader->setShouldFormatDates(true); // will return formatted dates

操作工作表

在创建XLSX或ODS文件时,可以控制数据将写入哪个工作表。在任何时候,都可以检索或设置当前工作表。

$firstSheet = $writer->getCurrentSheet();
$writer->addRow($rowForSheet1); // writes the row to the first sheet

$newSheet = $writer->addNewSheetAndMakeItCurrent();
$writer->addRow($rowForSheet2); // writes the row to the new sheet

$writer->setCurrentSheet($firstSheet);
$writer->addRow($anotherRowForSheet1); // append the row to the first sheet

还可以检索当前创建的所有工作表。

$sheets = $writer->getSheets();

如果应用程序依赖于工作表的名称,可以通过这种方式访问和自定义它。

// Accessing the sheet name when reading
foreach ($reader->getSheetIterator() as $sheet) {
    $sheetName = $sheet->getName();
}

// Accessing the sheet name when writing
$sheet = $writer->getCurrentSheet();
$sheetName = $sheet->getName();

// Customizing the sheet name when writing
$sheet = $writer->getCurrentSheet();
$sheet->setName('My custom name');

请注意,Excel对工作表名称有一些限制。

  • 名称不能为空。
  • 名称长度不能超过31个字符。
  • 名称不能包含以下字符:\ / ? * : [ 或 ]
  • 名称不能以单个引号开头或结尾。
  • 名称必须是唯一的。

处理这些限制是开发者的责任。Spout不会尝试自动更改工作表的名称,因为可能会依赖于此名称与传入的名称完全相同。

流畅的接口

因为流畅接口很棒,所以可以使用Spout。

use Box\Spout\Writer\WriterFactory;
use Box\Spout\Common\Type;

$writer = WriterFactory::create(Type::XLSX);
$writer->setTempFolder($customTempFolderPath)
       ->setShouldUseInlineStrings(true)
       ->openToFile($filePath)
       ->addRow($headerRow)
       ->addRows($dataRows)
       ->close();

运行测试

master分支上,只包括单元和功能测试。性能测试需要非常大的文件,因此已排除。如果只想检查一切是否按预期工作,执行master分支的测试就足够了。

如果想要运行性能测试,需要检出perf-tests分支。然后可以运行多个测试套件,具体取决于预期的输出。

  • phpunit - 运行整个测试套件(单元 + 功能 + 性能测试)
  • phpunit --exclude-group perf-tests - 只运行单元和功能测试
  • phpunit --group perf-tests - 只运行性能测试

信息提示:性能测试大约需要30分钟才能运行(处理100万行文件不是一件快事)。

性能测试状态:Build Status

常见问题解答

Spout如何处理如此大的数据集,同时使用不到3MB的内存?

在写入数据时,Spout将数据流式传输到文件中,一次一行或几行。这意味着它只保留需要写入的几行。一旦写入,内存就会被释放。

读取也是如此。一次只存储一行。使用特殊技术处理XLSX中的共享字符串,如果需要,将它们存储在几个小的临时文件中,从而允许快速访问。

生成包含X行数据的文件需要多长时间?

以下是关于Spout性能的一些数字。

Spout支持图表或公式吗?

不。这是为了保持内存使用低而做出的妥协。图表和公式需要将数据保存在内存中才能使用。因此,文件越大,消耗的内存就越多,这会阻碍代码的扩展。

支持

需要直接联系我们吗?请发送电子邮件至 oss@box.com,并在主题中包含此项目的名称。

您还可以在聊天室中提问、提交新功能想法或讨论Spout。
Gitter

版权和许可

版权所有 2015 Box, Inc. 保留所有权利。

本软件基于Apache许可证版本2.0(“许可证”);除非遵守许可证规定,否则不得使用此文件。您可以在以下地址获取许可证副本:

https://apache.ac.cn/licenses/LICENSE-2.0

除非适用法律要求或书面同意,否则在许可证下分发的软件按“原样”分发,不提供任何形式的保证或条件,无论是明示的还是暗示的。有关许可证的具体语言和权限限制,请参阅许可证。