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が使える
本領はフラグ設定にあります。setFlags で READ_CSV を立てると、foreach するだけで1行ずつ CSV パース済みの配列が返ってきます。SKIP_EMPTY と READ_AHEAD、DROP_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 すると連想配列になります。列数がずれるとエラーになるので、件数チェックを挟むと安全です。