SplFileObjectでCSVをスマートに読む

SplFileObject とは、ファイルをオブジェクトとして扱えるクラスです。READ_CSV フラグを立てると、foreach で1行ずつパース済みの配列として CSV を読めます。

fopen + fgetcsvからの卒業

CSV を読むとき、fopen して fgetcsv でループ、という書き方が定番ですが、SplFileObject を使うともう少しすっきり書けます。オブジェクトとして扱えて、foreach でそのまま回せるのが気持ち良いところです。fclose を自分で呼ばなくて良いのも地味に助かります。

$file = new SplFileObject('/path/to/data.csv', 'r');

while (!$file->eof()) {
    $row = $file->fgetcsv();
    // $row は各カラムの配列
}

READ_CSVフラグでforeachが使える

本領はフラグ設定にあります。setFlagsREAD_CSV を立てると、foreach するだけで1行ずつ CSV パース済みの配列が返ってきます。SKIP_EMPTYREAD_AHEADDROP_NEW_LINE を合わせると、空行や末尾の改行の扱いもきれいになります。

$file = new SplFileObject('/path/to/data.csv', 'r');
$file->setFlags(
    SplFileObject::READ_CSV
    | SplFileObject::SKIP_EMPTY
    | SplFileObject::READ_AHEAD
    | SplFileObject::DROP_NEW_LINE
);

foreach ($file as $row) {
    // $row = ['id' 相当, '名前' 相当, ...] の配列
    if ($row === [null] || $row === false) {
        continue; // 保険で空行を弾く
    }
    echo implode(' | ', $row) . "\n";
}

SKIP_EMPTY を付けても、環境によっては空行が [null] として1回混じることがあるので、私はループ内で保険の判定を入れています。ここは意外と嵌りどころですね。

区切り文字とヘッダ行の扱い

タブ区切りや、囲み文字が特殊な CSV には setCsvControl で対応します。また1行目をヘッダとして扱いたいときは、最初の1行をキーとして取り出し、あとの行と array_combine するのが定番です。連想配列で扱えると、後続のコードが読みやすくなります。

$file = new SplFileObject('/path/to/tsv.txt', 'r');
$file->setFlags(SplFileObject::READ_CSV | SplFileObject::DROP_NEW_LINE);
$file->setCsvControl("\t"); // タブ区切り

$header = null;
foreach ($file as $row) {
    if ($row === [null]) continue;
    if ($header === null) {
        $header = $row; // 1行目はヘッダ
        continue;
    }
    $record = array_combine($header, $row);
    // $record['name'] のように名前でアクセスできる
}

array_combine はヘッダと行の要素数が違うとエラーになるので、列数のずれたデータが混じる CSV では件数チェックを挟んでおくと安全だと思います。文字コードが Shift_JIS の場合は、読み込み後に mb_convert_encoding を通す一手間も必要ですね。

まとめ

SplFileObject は CSV を foreach で自然に回せて、fclose も不要なぶん fopen 方式よりコードがすっきりします。READ_CSV 系フラグの組み合わせ、setCsvControl での区切り指定、array_combine でのヘッダ連携、この辺を押さえておけば実務のCSV読み込みはほぼ困らないと思います。空行が混じる挙動と文字コードだけ、最後に気を配っておきたいところですね。

よくある質問

Q. fopen + fgetcsv と何が違いますか?
A. foreach でそのまま回せて、fclose を自分で呼ばなくて済みます。コードがすっきりするのが利点です。

Q. タブ区切りのファイルは読めますか?
A. setCsvControl("\t") で区切り文字を変えられます。囲み文字やエスケープ文字も合わせて指定できます。

Q. 1行目をヘッダとして扱いたいです。
A. 最初の1行をキーとして取り出し、以降の行と array_combine すると連想配列になります。列数がずれるとエラーになるので、件数チェックを挟むと安全です。

類似投稿

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です