[PHP] PHPでExcel処理

2018年6月18日月曜日

Excel PHP PHPSpreadSheet Spout

PHPSpreadSheet

PHPからExcelファイルの読み書きをするには、PHPExcelの後継となっているPHPSpreadSheetを使うのが一般的だろうか。

https://phpspreadsheet.readthedocs.io/en/develop/

PHPSpreadSheetを使って、Excelファイルを読み込んで、CSVファイルに書き出す簡単な例は以下のようになる。

excel_test.php
<?php

require 'vendor/autoload.php';

use PhpOffice\PhpSpreadsheet\Reader\Xlsx as Reader;
use PhpOffice\PhpSpreadsheet\Shared\Date;

$reader = new Reader();
$spreadsheet = $reader->load('test.xlsx');

$sheet = $spreadsheet->getSheetByName('Sheet1');

$highest =  $sheet->getHighestRow();
for ($i = 1; $i <= $highest; $i++) {
  echo $sheet->getCellByColumnAndRow(1, $i)->getValue() . ",";
  echo $sheet->getCellByColumnAndRow(2, $i)->getValue() . ",";
  echo $sheet->getCellByColumnAndRow(3, $i)->getValue() . ",";
  echo (Date::excelToDateTimeObject($sheet->getCellByColumnAndRow(4, $i)->getValue())->format('Y-m-d H:i:s')) . PHP_EOL;
}

上記の例では、データが4カラム存在して、1-3カラム目は文字列データで、4カラム目は日付型になっていることを前提としている。

大量データを扱うと

ところが、上記のプログラムを何十万行もある行数の多いExcel(xlsx)ファイルに適用すると、エラーが発生してしまう。

$ php excel_test.php

Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate
 18874368 bytes) in /samples/php/spreadsheet/vendor/phpoffice/phpspreadsheet/src
/PhpSpreadsheet/Collection/Memory.php on line 66

PHPSpreadSheetはxlsxファイルの内容をすべてメモリ上に展開しようとするのか、ファイルサイズに比べても、かなり多くのメモリを消費してしまう。

https://phpspreadsheet.readthedocs.io/en/develop/topics/memory_saving/
https://phpspreadsheet.readthedocs.io/en/develop/faq/

によると、キャッシュを使う手もあるようだけど、環境のメモリ設定を変えたり、別のキャッシュライブラリを導入するのは少々ハードルが高い。

Spout

そこで、このような用途のライブラリとしては、Spoutがもう一つの候補として出てくる。

http://opensource.box.com/spout/

composerを使って、インストール。
$ cat composer.json
{
    "require": {
        "box/spout": "@stable"
    }
}
$ composer install

Spoutを使って、先ほどと同様の例を作ると、こんな感じ。

spout_test.php
<?php
  
require 'vendor/autoload.php';

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

$reader = ReaderFactory::create(Type::XLSX);
$reader->open('test.xlsx');

$i = 0;
foreach ($reader->getSheetIterator() as $sheet) {
    if ($sheet->getName() == 'Sheet1') {
        foreach ($sheet->getRowIterator() as $row) {
            echo $row[0] . "," . $row[1] . "," . $row[2] . "," . $row[3]->format('Y-m-d H:i:s') . PHP_EOL;
        }
    }
}

$reader->close();

$ php spout_test.php

今度はエラーが起らずに処理が完了した。

PHPSpreadSheetは高機能だけど、メモリ消費が大きい。
Spoutは機能はシンプルだけど、大量のデータ処理に向いている、と言ったところだろうか。