採点されません。匿名です。 間違えることは学習の一部です。読むだけでもかまいません。

CSV を読み書きする

テクノロジ系 / file-io (read / write / CSV / paths / upload概念)

CSV を読み書きする

Lesson 14 / Chapter 4

CSV って結局なに?

id,name,age
1,太郎,20
2,花子,25
3,次郎,30
  • Comma-Separated Values = カンマで区切ったテキスト
  • 表 (Excel / DB) を 1 行 = 1 レコード / カンマで列を分ける 形式で並べたもの
  • PHP では fgetcsv / fputcsv行 = 配列 として扱う

→ Excel と PHP の橋渡し / 簡易データ交換に頻出。

読み込み: fgetcsv の基本

<?php
$fp = fopen(__DIR__ . '/tests/data/users.csv', 'r');

// 第 2〜5 引数: length=0 (制限なし) / separator="," / enclosure="\"" / escape=""
while (($row = fgetcsv($fp, 0, ',', '"', '')) !== false) {
    // $row は数値添字の配列: [0] = id, [1] = name, [2] = age
    print_r($row);
}

fclose($fp);

// 出力 (1 行目はヘッダ):
// Array ( [0] => id    [1] => name  [2] => age )
// Array ( [0] => 1     [1] => 太郎  [2] => 20  )
// Array ( [0] => 2     [1] => 花子  [2] => 25  )
  • fgetcsv1 行を配列で返す (列を自動分割)
  • 値は 常に文字列 (age も "20")。数値が必要なら (int) でキャスト
  • EOF で false を返す
  • PHP 8.4 以降 は 第 5 引数 $escape を明示しないと Deprecated 警告が出る。空文字 '' を渡すのが安全 (RFC 4180 準拠)

ヘッダ行を分離する常套手段

<?php
$fp = fopen(__DIR__ . '/tests/data/users.csv', 'r');

// 1 行目 (ヘッダ) を先に読み捨てる
$header = fgetcsv($fp, 0, ',', '"', '');   // ['id', 'name', 'age']

// 2 行目以降をループで処理
while (($row = fgetcsv($fp, 0, ',', '"', '')) !== false) {
    [$id, $name, $age] = $row;   // 配列の分配代入
    echo "{$id}: {$name} ({$age}歳)\n";
}

fclose($fp);
  • ヘッダを ループ前に 1 回だけ読む = データ行と混ざらない
  • [$id, $name, $age] = $row;配列の分配代入 (PHP 7.1+)。インデックス順に変数に入る

→ 「ヘッダは別扱い、本体はループ」が CSV 処理の定形。

書き込み: fputcsv の基本

<?php
$tmp = tempnam(sys_get_temp_dir(), 'dojo_csv_');
$fp = fopen($tmp, 'w');

// ヘッダ。第 3〜5 引数 (separator/enclosure/escape) を明示 (PHP 8.4+ で警告回避)
fputcsv($fp, ['id', 'name', 'age'], ',', '"', '');
// データ行
fputcsv($fp, [1, '太郎', 20], ',', '"', '');
fputcsv($fp, [2, '花子', 25], ',', '"', '');

fclose($fp);

echo file_get_contents($tmp);
// id,name,age
// 1,太郎,20
// 2,花子,25

unlink($tmp);
  • fputcsv($fp, $row)配列を 1 行の CSV にして書く
  • 区切り (,) / 囲み (") / エスケープは 自動で処理 (空白や " を含む値は "..." で囲ってくれる)
  • 末尾の改行も自動で付く

CSV の罠 (本コース外だけ把握だけしておく)

何が起きる
Shift_JIS の CSV (Excel 由来)UTF-8 前提の fgetcsv で文字化け
BOM (バイトオーダーマーク) 付き UTF-81 列目の先頭に \xEF\xBB\xBF が混ざる
値の中にカンマや改行RFC 4180 では "..." で囲む。fgetcsv / fputcsv は自動対応
値の中に """ に二重化して "..." で囲む。同上
巨大ファイル全行配列に積むとメモリが死ぬ。fgetcsv1 行ずつ 流す

→ 本 lesson では UTF-8 / 単純なデータのみ扱う。文字化けに遭遇したら mb_convert_encoding / iconv を検討。

fputcsv で書いた後は読み返して検算

<?php
$tmp = tempnam(sys_get_temp_dir(), 'dojo_csv_');

// 書く
$fp = fopen($tmp, 'w');
fputcsv($fp, ['id', 'name'], ',', '"', '');
fputcsv($fp, [1, '太郎'], ',', '"', '');
fclose($fp);

// 読み返す (= 書いた内容の検算)
$fp = fopen($tmp, 'r');
while (($row = fgetcsv($fp, 0, ',', '"', '')) !== false) {
    echo implode(' | ', $row) . "\n";
}
fclose($fp);

unlink($tmp);

// 出力:
// id | name
// 1 | 太郎

書き込み系のドリルでは必ず 「書く → 読み返す → 出力する」 を 1 セットでやる。

このチャプターでできるようになること

fgetcsv で CSV を 行配列 として読める

✅ ヘッダ行を 分離して データ行をループ処理できる

fputcsv で 配列を CSV の 1 行 として書ける

✅ 配列の 分配代入 ([$a, $b] = $row) で列を変数に取り出せる

✅ 書き込みは 一時ファイル で完結させ、読み返して検算する流れができる

→ ドリルへ

考えてみよう

既定では正解・不正解の判定はしません。自分のペースで「答えを見る」を。採点してほしい場合だけ「採点してほしい」を押してください。

ドリル 01 — CSV を読んで整形出力

問題

__DIR__ . '/tests/data/users.csv' を読み、各データ行を "id: name (age歳)" の形式で出力してください。

データファイル (tests/data/users.csv):

id,name,age
1,太郎,20
2,花子,25
3,次郎,30

期待される出力:

1: 太郎 (20歳)
2: 花子 (25歳)
3: 次郎 (30歳)

(1 行目のヘッダ id,name,age は出力に含めない)

採点

php scripts/grade.php topics/file-io/ch04-csv/drill/01-read-csv/

ヒント

$fp = fopen(__DIR__ . '/tests/data/users.csv', 'r');
$header = fgetcsv($fp, 0, ',', '"', '');   // 1 行目を読み捨てる
while (($row = fgetcsv($fp, 0, ',', '"', '')) !== false) {
    [$id, $name, $age] = $row;
    echo "{$id}: {$name} ({$age}歳)\n";
}
fclose($fp);
  • fgetcsv1 行を配列で返す (数値添字)。[0]=id, [1]=name, [2]=age
  • ヘッダ行は ループに入る前に 1 回だけ読み捨てる
  • 配列の分配代入 [$a, $b, $c] = $row; で列を変数に取り出せる (PHP 7.1+)
  • 第 2〜5 引数 (length, separator, enclosure, escape) を明示 — PHP 8.4 以降 で警告が出るのを防ぐ

つまづいたら

  • 出力の 1 行目が "id: name (age歳)" になる

→ ヘッダ行を 読み捨てていない。ループに入る前に fgetcsv($fp) を 1 回呼ぶ

  • age"20歳" ではなく "20.0歳" になる

(float) キャストや number_format を使っている。fgetcsv の値は 文字列のまま で OK

ドリル 02 — 配列を CSV に書いて読み返す

問題

次の配列を 一時 CSV ファイル に書き出し、その CSV を 読み返して 各行を " | " で結合して 1 行ずつ出力してください。最後に一時ファイルを unlink で消します。

書き出す配列:

$rows = [
    ['id', 'name', 'score'],
    [1, '太郎', 80],
    [2, '花子', 95],
    [3, '次郎', 60],
];

期待される出力 (書き出した CSV を読み返した結果):

id | name | score
1 | 太郎 | 80
2 | 花子 | 95
3 | 次郎 | 60

採点

php scripts/grade.php topics/file-io/ch04-csv/drill/02-write-csv/

ヒント

$tmp = tempnam(sys_get_temp_dir(), 'dojo_csv_');

// 書く
$fp = fopen($tmp, 'w');
foreach ($rows as $row) {
    fputcsv($fp, $row, ',', '"', '');
}
fclose($fp);

// 読み返す
$fp = fopen($tmp, 'r');
while (($row = fgetcsv($fp, 0, ',', '"', '')) !== false) {
    echo implode(' | ', $row) . "\n";
}
fclose($fp);

unlink($tmp);
  • fputcsv($fp, $row, ',', '"', '')配列を 1 行の CSV にして書く
  • 読み返しは fgetcsv で 行ごとの配列に戻る → implode(' | ', $row) で表示用に結合
  • 書き込みは sys_get_temp_dir() 配下、最後に unlink で消す

つまづいたら

  • 出力が id | name | score ではなく id|name|score (空白なし) になる

implode(' | ', $row) のセパレータが '|' になっている。前後に空白付きの " | " にする

  • Deprecated 警告が出る

fgetcsv / fputcsv の引数を省略している。第 2〜5 引数を明示 (fgetcsv($fp, 0, ',', '"', '') / fputcsv($fp, $row, ',', '"', ''))

  • 1 行多い・少ない

→ 「書いた配列」と「読み返した結果」は本来一致する。fputcsv を呼び忘れた / fclose を忘れて flush されていない