【PHP】Goodby CSVを使って高メモリ効率でCSVをパースする

  • 21
    Like
  • 1
    Comment

Goodby CSVってなに

メモリ効率が非常に良い、CSVをインポート/エクスポートするためのPHPライブラリです。
github : Goodby CSV

個人のアプリケーションなどでの利用ではあまりメモリ効率を考える必要がないかもしれませんが、何百万、何千万行となるような大規模なプロジェクトではCSVの処理に莫大なメモリを使用することになります。

Goodby CSVではCSVのすべての行を一気に読み込むことは無く、一行ずつコールバック関数で処理しています。

また、SJIS-win、EUC-JP、UTF-8などもサポートしているため仕様に柔軟に対応できます。

要件

・PHP 5.3.2 以上
・mbstring

インストール

Composerを利用しますので、プロジェクトフォルダ内にcomposerをインストールします。

curl -s http://getcomposer.org/installer | php

プロジェクトディレクトリのrootにcomposer.jsonを作成して、下記を記載します。

{
    "require": {
        "goodby/csv": "*"
    }
}

インストールします。

php composer.phar install

使い方

configの設定

インポート時

use Goodby\CSV\Import\Standard\LexerConfig;

$config = new LexerConfig();
$config
    ->setDelimiter("\t") // Customize delimiter. Default value is comma(,)
    ->setEnclosure("'")  // Customize enclosure. Default value is double quotation(")
    ->setEscape("\\")    // Customize escape character. Default value is backslash(\)
    ->setToCharset('UTF-8') // Customize target encoding. Default value is null, no converting.
    ->setFromCharset('SJIS-win') // Customize CSV file encoding. Default value is null.
;

エクスポート時

use Goodby\CSV\Export\Standard\ExporterConfig;

$config = new ExporterConfig();
$config
    ->setDelimiter("\t") // Customize delimiter. Default value is comma(,)
    ->setEnclosure("'")  // Customize enclosure. Default value is double quotation(")
    ->setEscape("\\")    // Customize escape character. Default value is backslash(\)
    ->setToCharset('SJIS-win') // Customize file encoding. Default value is null, no converting.
    ->setFromCharset('UTF-8') // Customize source encoding. Default value is null.
    ->setFileMode(CsvFileObject::FILE_MODE_WRITE) // Customize file mode and choose either write or append. Default value is write ('w'). See fopen() php docs
;

区切り文字、括り文字、エスケープ文字、文字コード、ファイルモードの設定などが出来ます。

使用例:PDOによるCSVデータをDBへインポート

インポートするCSV

users.csv
1,tom,tomcruiseikemen@example.com
2,taro,taro@example.com
3,hanako,hanako@eample.com

hogeというDBにusersというテーブルがあるとします。

下記を実行します。

CsvImporter.php
use Goodby\CSV\Import\Standard\Lexer;
use Goodby\CSV\Import\Standard\Interpreter;
use Goodby\CSV\Import\Standard\LexerConfig;

require_once("vendor/autoload.php");

//DBの設定(postgreSQLの場合)
$pdo = new PDO('pgsql:host=localhost;dbname=hoge', 'postgres', '');

//configの設定
$config = new LexerConfig();
$lexer = new Lexer($config);

$interpreter = new Interpreter();

$interpreter->addObserver(function(array $columns) use ($pdo) {
    //データに処理を入れるときはこのあたりに
    $stmt = $pdo->prepare('INSERT INTO users (id, name, email) VALUES (?, ?, ?)');
    $stmt->execute($columns);
});

//読み込むCSVファイル
$lexer->parse('users.csv', $interpreter);

こうなります。

スクリーンショット 2017-09-13 23.45.27.png

使用例:PDOによるDBからデータをCSVにエクスポート

下記を実行します。

CsvExporter.php
use Goodby\CSV\Export\Standard\Exporter;
use Goodby\CSV\Export\Standard\ExporterConfig;
use Goodby\CSV\Export\Standard\CsvFileObject;
use Goodby\CSV\Export\Standard\Collection\PdoCollection;
use Goodby\CSV\Export\Standard\Collection\CallbackCollection;

require_once("vendor/autoload.php");

//DBの設定(postgreSQLの場合)
$pdo = new PDO('pgsql:host=localhost;dbname=hoge', 'postgres', '');

$stmt = $pdo->prepare("SELECT * FROM users");
$stmt->execute();

$collection = new CallbackCollection(new PdoCollection($stmt), function($row) {
    //ここにデータの処理など
    $row['name'] = strtoupper($row['name']);

    return $row;
});

//configの設定 SJISで出力する
$config = new ExporterConfig();
$config->setToCharset('SJIS-win');

//エクスポート
$exporter = new Exporter($config);
$exporter->export('users_converted.csv', $collection);

下記のCSVが出力されます。

users_converted.csv
1,TOM,tomcruiseikemen@example.com
2,TARO,taro@example.com
3,HANAKO,hanako@eample.com

まとめ

使い方が簡単。
行数がかなり多いCSVを扱う時にメモリの効率化が可能。

実際にプロジェクトで使用したりしています。

メモリ使用量の比較などはあとで時間があったらやってみます。

21918contribution

紹介有難うございます!