topic: file-io (read / write / CSV / paths / upload概念) / ch04

ch04 — CSV を読み書きする

学習目標

  • fgetcsv() で CSV を 行 × 列の配列 として読める
  • fputcsv() で 配列を CSV の 1 行 として書ける
  • CSV のヘッダ行 (id,name,age) を切り離して、データ行だけを処理できる
  • 書き込みは 一時ファイル で完結させ、読み返して検算できる

所要時間

スライド = 5 分 / ドリル = 約 25 分

ドリル

# 内容
01-read-csv/ tests/data/users.csv を読んで "id: name (age歳)" 形式で出力
02-write-csv/ 配列を fputcsv で 一時 CSV に書き、読み返して fgetcsv で出力

採点

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

確認のコツ

  • fgetcsv数値添字の配列 を返す (連想配列ではない)。[0] = id, [1] = name, [2] = age のように 列の順番 で取り出す
  • ヘッダ行は 最初の 1 回だけ別扱い にして、本体ループに混ぜない
  • 書き込みは fputcsv($fp, ['1', '太郎', '20']) のように 配列を 1 行ずつ 渡す

解説スライド

教材スライド(Marp)を1枚ずつ表示します。矢印キー(←→)・スワイプ・ナビボタンで移動できます。

  1. <!-- LLM_CONTEXT: Lesson 14 / Chapter 4 目的: CSV を fgetcsv / fputcsv で読み書きする。ヘッダ行の扱いと配列の添字に慣れる 扱わない: パス分解 (ch05) / アップロード (ch06) 読み上げ時間目安: 5 分 -->

    CSV を読み書きする

    Lesson 14 / Chapter 4

  2. CSV って結局なに?

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

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

  3. 読み込み: 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  )
    ▶ 3v4l で実行
    • fgetcsv1 行を配列で返す (列を自動分割)
    • 値は 常に文字列 (age も "20")。数値が必要なら (int) でキャスト
    • EOF で false を返す
    • PHP 8.4 以降 は 第 5 引数 $escape を明示しないと Deprecated 警告が出る。空文字 '' を渡すのが安全 (RFC 4180 準拠)
  4. ヘッダ行を分離する常套手段

    <?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);
    ▶ 3v4l で実行
    • ヘッダを ループ前に 1 回だけ読む = データ行と混ざらない
    • [$id, $name, $age] = $row;配列の分配代入 (PHP 7.1+)。インデックス順に変数に入る

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

  5. 書き込み: 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);
    ▶ 3v4l で実行
    • fputcsv($fp, $row)配列を 1 行の CSV にして書く
    • 区切り (,) / 囲み (") / エスケープは 自動で処理 (空白や " を含む値は "..." で囲ってくれる)
    • 末尾の改行も自動で付く
  6. CSV の罠 (本コース外だけ把握だけしておく)

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

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

  7. 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 | 太郎
    ▶ 3v4l で実行

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

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

    fgetcsv で CSV を 行配列 として読める ✅ ヘッダ行を 分離して データ行をループ処理できる ✅ fputcsv で 配列を CSV の 1 行 として書ける ✅ 配列の 分配代入 ([$a, $b] = $row) で列を変数に取り出せる ✅ 書き込みは 一時ファイル で完結させ、読み返して検算する流れができる

    → ドリルへ

1 / 8
スライドを全部一気に読む(縦表示)

<!-- LLM_CONTEXT: Lesson 14 / Chapter 4 目的: CSV を fgetcsv / fputcsv で読み書きする。ヘッダ行の扱いと配列の添字に慣れる 扱わない: パス分解 (ch05) / アップロード (ch06) 読み上げ時間目安: 5 分 -->

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  )
▶ 3v4l で実行
  • 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);
▶ 3v4l で実行
  • ヘッダを ループ前に 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);
▶ 3v4l で実行
  • fputcsv($fp, $row)配列を 1 行の CSV にして書く
  • 区切り (,) / 囲み (") / エスケープは 自動で処理 (空白や " を含む値は "..." で囲ってくれる)
  • 末尾の改行も自動で付く

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

何が起きる
Shift_JIS の CSV (Excel 由来) UTF-8 前提の fgetcsv で文字化け
BOM (バイトオーダーマーク) 付き UTF-8 1 列目の先頭に \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 | 太郎
▶ 3v4l で実行

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


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

fgetcsv で CSV を 行配列 として読める ✅ ヘッダ行を 分離して データ行をループ処理できる ✅ fputcsv で 配列を CSV の 1 行 として書ける ✅ 配列の 分配代入 ([$a, $b] = $row) で列を変数に取り出せる ✅ 書き込みは 一時ファイル で完結させ、読み返して検算する流れができる

→ ドリルへ

演習問題の詳細

この章の演習問題の内容を読めます。実際に手元で解くには教材リポジトリを clone してください。

ドリル 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/14-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);
▶ 3v4l で実行
  • 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],
];
▶ 3v4l で実行

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

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

採点

php scripts/grade.php topics/14-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);
▶ 3v4l で実行
  • 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 されていない

演習問題(2問)

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

    README.md starter.php answer.php

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

    README.md starter.php answer.php

サイト内で問題文・雛形・解答例を確認できます。実際に手元で解くには教材リポジトリを clone してください。