PHPでCSVファイル出力
SplFileObjectを使ってCSVファイルを出力してみる。
使用環境
・ Amazon Linux 2
・ PHP 8.2.9 ※default_charset = UTF-8 環境
※以下はUTF-8環境を前提に書いているが、そうでない場合は話が変わるので注意。
基本サンプル
<?php
// 出力する内容を配列で用意する
$vs = [
'あいうえお',
'カンマ「,」',
'ダブルクォーテーション「"」',
"改行「\n」",
"バックスラッシュ「\\」"
];
// インスタンス生成
$fp = new SplFileObject( './sample.csv', 'w' );
// 出力する(例として100行同じ内容を出力)
for ( $i = 0; $i < 100; ++$i ) {
$fp->fputcsv( $vs, escape:'' ); // 行末をCRLFにしたい場合は、eol:"\r\n"も指定
}
PHPSplFileObjectを使う場合、最低限であればこれだけで終わる。
出力される文字コードはUTF-8(BOMなし)。
UTF-8(BOMあり)で出力する
BOMありで出力する場合、先頭にBOMの情報を付加して出力すればOK。
<?php
// 出力する内容を配列で用意する
$vs = [
'あいうえお',
'カンマ「,」',
'ダブルクォーテーション「"」',
"改行「\n」",
"バックスラッシュ「\\」"
];
// インスタンス生成
$fp = new SplFileObject( './sample.csv', 'w' );
// 出力する(例として100行同じ内容を出力)
for ( $i = 0; $i < 100; ++$i ) {
// BOMを付加する
if ( $i === 0 ) {
$vs[0] = "\xEF\xBB\xBF" . $vs[0];
}
$fp->fputcsv( $vs, escape: '' ); // 行末をCRLFにしたい場合は、eol : "\r\n"も指定
}
PHPSJISで出力する
SJISのファイルを出力したい場合は、出力する配列の内容をSJISにすれば良い。
<?php
// 文字コードをSJISに変換する
function convSjis( $v ) {
return mb_convert_encoding( $v, 'SJIS-win', 'UTF-8' );
}
// 出力する内容を配列で用意する
$vs = [
'あいうえお',
'カンマ「,」',
'ダブルクォーテーション「"」',
"改行「\n」",
"バックスラッシュ「\\」"
];
$vs = array_map( 'convSjis', $vs );
// インスタンス生成
$fp = new SplFileObject( './sample.csv', 'w' );
// 出力する(例として100行同じ内容を出力)
for ( $i = 0; $i < 100; ++$i ) {
$fp->fputcsv( $vs, escape: '' ); // 行末をCRLFにしたい場合は、eol : "\r\n"も指定
}
PHPfputcsv()の注意点
いくつかのルールが固定されるので、これに沿わない場合は fputcsv() は使えない。
- 囲み文字は必要な時に必ず付く(常につけたり、なしには出来ない)
- 囲み文字(デフォルトはダブルクォーテーション)のエスケープは二重出力
これは、SplFileObjectを使わずに fopen() + fputcsv() で出力しても同様。
このあたりを調整するには、以下のように自前で配列を文字列に変換して出力する必要がある。
囲み文字やエスケープを自前で調整
常に囲み文字を付ける(ブランクの場合も囲み文字あり)のサンプル。
<?php
// 値に囲み文字を付ける&囲み文字をエスケープ
function convVs( $v ) {
return '"' . preg_replace( '/"/u', '""', $v ) . '"';
}
// 出力する内容を配列で用意する
$vs = [
'あいうえお',
'カンマ「,」',
null,
'ダブルクォーテーション「"」',
"改行「\n」",
"バックスラッシュ「\\」"
];
// インスタンス生成
$fp = new SplFileObject( './sample.csv', 'w' );
// 出力する(例として100行同じ内容を出力)
for ( $i = 0; $i < 100; ++$i ) {
$str = implode( ',', array_map( 'convVs', $vs ) ) . "\n";
$fp->fwrite( $str );
}
PHPBOMありやSJISで出力する場合も前述の内容でそのまま使える。
SplFileObjectを使わない場合
SplFileObjectを使わない場合、fopen() + fputcsv() (又は fwrite() )で同じように出力出来る。
<?php
// 出力する内容を配列で用意する
$vs = [
'あいうえお',
'カンマ「,」',
'ダブルクォーテーション「"」',
"改行「\n」",
"バックスラッシュ「\\」"
];
// ファイルを開く
$fp = fopen( './sample.csv', 'w' );
// 出力する(例として100行同じ内容を出力)
for ( $i = 0; $i < 100; ++$i ) {
fputcsv( $fp, $vs, escape: '' );
}
// ファイルを閉じる
fclose( $fp );
PHPSplFileObject::fputcsv() と fputcsv()、SplFileObject::fwrite() と fwrite() は同じように使えるので、どちらを使っても似た実装になる。
多量に出力する場合
1行ずつ出力するとその分File I/Oに時間がかかるので、多量に出力する場合は複数行をまとめて出力した方が速い。
<?php
// 値に囲み文字を付ける&囲み文字をエスケープ
function convVs( $v ) {
return '"' . preg_replace( '/"/u', '""', $v ) . '"';
}
// 出力する内容を配列で用意する
$vs = [
'あいうえお',
'カンマ「,」',
null,
'ダブルクォーテーション「"」',
"改行「\n」",
"バックスラッシュ「\\」"
];
// インスタンス生成
$fp = new SplFileObject( './sample.csv', 'w' );
// 出力内容のストック初期化
$stock = '';
// 出力する(例として500,000行同じ内容を出力)
for ( $i = 0; $i < 500000; ++$i ) {
// 行毎に出力せず一度ストックに格納する
$stock .= implode( ',', array_map( 'convVs', $vs ) ) . "\n";
if ( $i > 0 && $i % 1000 === 0 ) {
$fp->fwrite( $stock );
$stock = '';
}
}
// 出力出来ていない分を出力する
if ( $stock !== '' ) {
$fp->fwrite( $stock );
}
PHP複数行まとめて出力する場合は fputcsv() は使えないので、囲み文字やエスケープは自前で調整する必要がある。
また、ある程度出力内容をためるので、その分メモリを使う。
何件ずつ出力するかは、使えるメモリ量を踏まえて適宜判断が必要。