以前、Laravel Excelというpackageを使用してExcelやCSVにデータを出力する方法について解説しました。Laravel Excelはinterfaceが充実しており、それに則ってコードを書くと体系的かつ統一的に少ない記述量で実装できるので気に入っていました。しかし、大きなデータを扱う際などはなぜか消費メモリが徐々に増えていく為(今は改善されているかもしれませんが)、 その場合はPHPのファイル関数(fopen(), fputcsv(), fclose()など)を用いていました。

そうすると、今度はBOMの処理やヘッダの取得、読み込んだ行毎の処理など、その場その場で独自に必要な処理を追加していくのでコードが肥大化しがちです。適宜リファクタしていけば良いのですが、これら汎用的な処理はビジネスの肝では無いので他に良いパッケージがあれば導入したいところ。そんな折、Kenji氏からspatieのsimple-excelを使ってみれば?と提案いただき試してみたところ良い感触を得られたので記事にする事にしました。

simple-excel

GitHub – spatie/simple-excel

その名の通り、シンプルにExcelやCSVファイルの読み書きができるパッケージです。裏ではジェネレータを使用しており大きなファイルを扱う際もメモリの消費を抑えてくれるそうです。早速、以下のコマンドを実行してインストールしてみましょう。

composer require spatie/simple-excel

ファイル出力

simple-excelを使用してcsvファイルを出力してみましょう。ファイル出力を行う際は以下の様にSimpleExcelWriterクラスを使用します。

use Spatie\SimpleExcel\SimpleExcelWriter;

$writer = SimpleExcelWriter::create('member_list.csv');

ヘッダを追加する場合はaddHeader()を使用します。

$writer->addHeader(['性', '名', '性別', '年齢']);

1行ずつデータを追加する場合はaddRow()です。

$writer->addRow(['山田', '太郎', 'M', 35]);

addRows()を使用すれば複数行まとめて追加できます。

$writer->addRows([
    ['山田', '花子', 'F', 29],
    ['鈴木', '一郎', 'M', 56],
]);

出力されたcsvファイルを確認してみましょう。以下の様になっていると思います。

因みに、SimpleExcelWriterを使用したcsvファイルの出力はデフォルトでBOM付きとなっている為、問題なくExcelで開けます。BOMを付与したく無い場合は$writerを取得する際にcreate()ではなくcreateWithoutBom()を使用します。

$writer = SimpleExcelWriter::createWithoutBom('member_list.csv');

ファイル読み込み

今度は先ほど出力したファイルを読み込んでみましょう。読み込みにはSimpleExcelReaderを使用します。

use Spatie\SimpleExcel\SimpleExcelReader;

$reader = SimpleExcelReader::create('member_list.csv');

ヘッダを取得する場合にはgetHeaders()を使用します。

$header = $reader->getHeaders();
= [
    "性",
    "名",
    "性別",
    "年齢",
  ]

また、データ部分を取得するにはgetRows()を使用します。getRows()LazyCollectionインスタンスを返します。そちらは裏ではジェネレータを使用して反復処理を行うごとにファイルから1行ずつデータを取得します。取得した行データはヘッダをキーとした連想配列になっています。LazyCollectionは通常のCollectionと同様に扱う事ができるのでeach()やforeachループで処理する事ができます。

$rows = $reader->getRows();
= Illuminate\Support\LazyCollection {#5116
    +source: Closure() {#5115 …4},
  }

// 最初の1行取得
$rows->first();
= [
    "性" => "テスト",
    "名" => "太郎",
    "性別" => "M",
    "年齢" => "35",
  ]

// each()で行毎の処理実行
$rows->each(function ($row) {
  printf("%s %s (%d)\n", $row['性'], $row['名'], $row['年齢']);
});
テスト 太郎 (35)
山田 花子 (29)
鈴木 一郎 (56)

時としてヘッダは日本語だがコードで処理する際は英語で扱いたい、という場合があるかと思います。先ほど、「取得した行データはヘッダをキーとした連想配列になっています。」と述べましたが、useHeaders()を使用して行データのキーを指定する事ができます。また、以下に示す例ではメソッドチェーンとしてコンパクトに記述してみました。

$header = ['last_name', 'first_name', 'gender', 'age'];

$rows = SimpleExcelReader::create('member_list.csv')
    ->useHeaders($header)
    ->getRows();

$rows->first();
= [
    "last_name" => "テスト",
    "first_name" => "太郎",
    "gender" => "M",
    "age" => "35",
  ]

まとめ

やっている事はfopen()でファイルを開いてwhileループで回して処理するのと同じなのですが、Collectionと同様の操作で扱えるので複雑な処理が必要な場合でも可読性を保つ事ができそうですね。

参照

そうだ、Laravel Excelを使ってみよう(1)セットアップ
そうだ、Laravel Excelを使ってみよう(2)ヘッダ, map()
そうだ、Laravel Excelを使ってみよう(3)日付のフォーマット
そうだ、Laravel Excelを使ってみよう(4)保存先の指定
そうだ、Laravel Excelを使ってみよう(5)画像を挿入する
そうだ、Laravel Excelを使ってみよう(6)顔写真付き名簿を作成しよう

By hikaru