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"も指定
}SplFileObjectを使う場合、最低限であればこれだけで終わる。
出力される文字コードは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"も指定
}SJISで出力する
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"も指定
}fputcsv()の注意点
いくつかのルールが固定されるので、これに沿わない場合は 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 );
}BOMありや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 );SplFileObject::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 );
}複数行まとめて出力する場合は fputcsv() は使えないので、囲み文字やエスケープは自前で調整する必要がある。
また、ある程度出力内容をためるので、その分メモリを使う。
何件ずつ出力するかは、使えるメモリ量を踏まえて適宜判断が必要。