PHP の fwrite と fputcsv — ファイルへの書き込み

ファイルへの書き込みにおいて、fwrite は汎用的なデータ書き込み、fputcsv は CSV 形式に特化した書き込みを担当する。どちらも fopen で取得したファイルハンドルに対して操作を行い、file_put_contents では難しい段階的な書き込みや構造化されたデータ出力を可能にする。

fwrite — バイト列の書き込み

fwrite はファイルハンドルに文字列を書き込む最も基本的な関数だ。戻り値は実際に書き込まれたバイト数で、失敗した場合は false を返す。

$fp = fopen('output.txt', 'w');
$bytes = fwrite($fp, "Hello, PHP!\n");
echo "書き込み: {$bytes}バイト";
fclose($fp);

fwrite は呼び出すたびにファイルポインタの現在位置にデータを書き込み、ポインタが書き込んだ分だけ進む。そのため、連続して呼び出せば順番にデータが追加されていく。

$fp = fopen('report.txt', 'w');
fwrite($fp, "=== レポート ===\n");
fwrite($fp, "日付: 2024-01-15\n");
fwrite($fp, "ステータス: 完了\n");
fclose($fp);

この挙動は file_put_contents にはない利点で、ループの中で少しずつデータを書き出すような処理に適している。

書き込みモードの違い

書き込み時のファイルモードによって挙動が大きく変わる。目的に合ったモードを選ばないと、既存データを意図せず消してしまうことがある。

w モード(上書き)

ファイルを開いた時点で中身がすべて消去される。新規作成やファイル全体の置き換えに使う。

a モード(追記)

既存の内容はそのまま残り、末尾にデータが追加される。ログの書き出しに最適。

追記モードでログを書き込む典型的なパターンを見てみよう。

$fp = fopen('app.log', 'a');
$timestamp = date('Y-m-d H:i:s');
fwrite($fp, "[{$timestamp}] ユーザーがログインしました\n");
fclose($fp);

このスクリプトを実行するたびに、ファイルの末尾に新しい行が追加される。

書き込みバイト数の制限

fwrite の第 3 引数で、書き込むバイト数の上限を指定できる。文字列の一部だけを書き込みたい場合に使う。

$data = "ABCDEFGHIJ";
$fp = fopen('partial.txt', 'w');
fwrite($fp, $data, 5); // "ABCDE" だけ書き込まれる
fclose($fp);

大量のデータを分割して書き込むときにも役立つが、通常のテキスト処理では第 3 引数を省略するケースのほうが多い。

fputcsv — CSV 形式での書き込み

fputcsv は配列を受け取り、CSV の 1 行としてファイルに書き込む関数だ。カンマ区切りの生成やダブルクォートのエスケープを自動で処理してくれるため、手動で CSV 文字列を組み立てるより安全で確実だ。

$fp = fopen('users.csv', 'w');

fputcsv($fp, ['名前', '年齢', '都市']);
fputcsv($fp, ['Alice', 30, '東京']);
fputcsv($fp, ['Bob', 25, '大阪']);
fputcsv($fp, ['Charlie', 35, '福岡']);

fclose($fp);

出力されるファイルの中身は次のようになる。

名前,年齢,都市
Alice,30,東京
Bob,25,大阪
Charlie,35,福岡

フィールド内のカンマや改行の処理

CSV で厄介なのが、フィールド内にカンマや改行、ダブルクォートが含まれるケースだ。fputcsv はこれらを自動的にダブルクォートで囲んでエスケープしてくれる。

$fp = fopen('data.csv', 'w');
fputcsv($fp, ['商品名', '説明']);
fputcsv($fp, ['Tシャツ, Lサイズ', '綿100%で"快適"な着心地']);
fclose($fp);

出力結果では、カンマを含むフィールドとダブルクォートを含むフィールドが適切にエスケープされる。この処理を自前で実装すると抜け漏れが起きやすいため、fputcsv に任せるのが賢明だ。

区切り文字のカスタマイズ

fputcsv の第 3 引数で区切り文字を変更できる。タブ区切り(TSV)で出力したい場合は次のように指定する。

$fp = fopen('data.tsv', 'w');
fputcsv($fp, ['Name', 'Age'], "\t");
fputcsv($fp, ['Alice', 30], "\t");
fclose($fp);

Excel で直接開ける CSV を作成する場合、先頭に BOM(バイトオーダーマーク)を付けると文字化けを防げる。

UTF-8 BOM は 3 バイト(\xEF\xBB\xBF)の識別子で、Excel がエンコーディングを正しく認識するために使われる。

BOM 付き UTF-8 の CSV を出力する例を示す。

$fp = fopen('excel_friendly.csv', 'w');
fwrite($fp, "\xEF\xBB\xBF"); // BOM を書き込む

fputcsv($fp, ['商品名', '価格', '在庫']);
fputcsv($fp, ['りんご', 150, 200]);
fputcsv($fp, ['みかん', 100, 350]);

fclose($fp);

fwrite で BOM を先に書き込んでから fputcsv でデータ行を追加するという合わせ技だ。

データベースの結果を CSV に書き出す

実務でよくあるパターンとして、データベースのクエリ結果を CSV ファイルにエクスポートする処理がある。fputcsv とループを組み合わせるだけで簡潔に実装できる。

$pdo = new PDO('mysql:host=localhost;dbname=shop', 'user', 'pass');
$stmt = $pdo->query('SELECT name, price, stock FROM products');

$fp = fopen('products_export.csv', 'w');
fputcsv($fp, ['商品名', '価格', '在庫']);

while ($row = $stmt->fetch(PDO::FETCH_NUM)) {
    fputcsv($fp, $row);
}

fclose($fp);

PDO::FETCH_NUM で数値インデックスの配列を取得し、そのまま fputcsv に渡している。行数が多くても 1 行ずつ処理するためメモリ効率がよい。

fwrite と fputcsv の使い分け

テキストやバイナリデータを自由な形式で書き出すなら fwrite、CSV 形式のデータを正確に出力するなら fputcsv を選ぶ。特に CSV はエスケープ処理が複雑なので、自力で fwrite を使って CSV を生成するのは避けたほうがよい。fputcsv に任せることで、仕様に準拠した安全な CSV を手間なく作成できる。