ch02 6章中 2章目

ch02 — ファイルを読む

スライドで見る

この章を AI と学ぶ

💬 AI に聞く質問例 3 件
  1. 「ch02 — ファイルを読む」のつまずきやすいポイントを、初学者向けに3つ教えてください

    ChatGPT Claude Gemini

  2. 「ch02 — ファイルを読む」について、自分の手で試せる練習問題を3つ出してください (難易度を上げながら)

    ChatGPT Claude Gemini

  3. 「ch02 — ファイルを読む」を学んだ後、次に学ぶと良いトピックは何ですか?理由も教えてください

    ChatGPT Claude Gemini

概要

PHP には「ファイルを読む」関数が 手軽さ重視制御性重視 の 2 系統ある。手軽さの代表が file_get_contents($path)。パスを渡すだけで ファイル全体を 1 つの文字列として返してくれる。設定ファイルや小さなテキストならこの 1 行で十分。

もう一方の制御性重視が fopen + fgets + fclosefopen('path', 'r') でハンドルを取り、fgets($fp)1 行ずつ 読み、用が済んだら fclose($fp) で閉じる。手間は増えるが、ループしながら少しずつ処理できるので ギガ単位の大きなファイル でもメモリを食い潰さない。「メモリと手軽さのトレードオフ」と覚えると選び分けが楽。

ファイル系関数では パスの組み立て にも注意。スクリプトの隣にあるファイルを指したいとき、ただ 'data.txt' と書くとカレントディレクトリ依存で事故る。__DIR__ . '/data.txt' のように __DIR__ を起点に組む のがイディオム (__DIR__ は「このファイルが置かれているディレクトリの絶対パス」)。ch05 でパス操作を本格的に扱うが、ここでも先取りで使う。

学習目標

  • file_get_contents()ファイル全体 を 1 つの文字列として読める
  • fopen + fgets + fclose の組み合わせで 1 行ずつ 読める
  • __DIR__ . '/tests/data/foo.txt' のように スクリプトからの相対パス を組める
  • 読み込んだテキストの 末尾改行 に気をつけて出力できる

ドリル

# 内容
01-read-all/ tests/data/hello.txt の中身をそのまま出力
02-read-line-by-line/ fgets で 1 行ずつ読み、先頭に 行番号 を付けて出力

確認のコツ

  • 読み込み元は drill 内固定パス (__DIR__ . '/tests/data/...')。リポジトリにコミットされているデータを読み取る形
  • このチャプターでは 書き込みは一切しない (ch03 から)

解説スライド 全 7 枚

右の ボタン キー(またはスワイプ)で次のスライドに進めます。 下の「縦表示」を開くと全スライドを一気に読めます。

  1. ファイルを読む

    Lesson 14 / Chapter 2

  2. 2 つの読み方を持っておく

    関数 どんな時
    file_get_contents($path) 小さい / 中身を文字列でまとめて欲しい とき
    fopen + fgets + fclose 大きい / 1 行ずつ処理したい とき
    <?php
    // パターン A: 一発で全部
    $text = file_get_contents(__DIR__ . '/tests/data/hello.txt');
    
    // パターン B: 1 行ずつ
    $fp = fopen(__DIR__ . '/tests/data/hello.txt', 'r');
    while (($line = fgets($fp)) !== false) {
        echo $line;   // $line には末尾の \n も含まれる
    }
    fclose($fp);

    「設定読み込みは A」「ログ走査は B」 くらいの肌感で使い分けると良い。

  3. file_get_contents の挙動

    $path = __DIR__ . '/tests/data/hello.txt';
    $text = file_get_contents($path);
    
    var_dump($text);
    // string(36) "こんにちは
    // ファイル読み込みのテスト
    // "
    • 成功すると ファイル全体を文字列で返す
    • 失敗すると false を返す (パス間違い・権限なしなど)
    • そのまま echo すれば中身がそのまま出る (末尾改行も含む)

    「末尾に \n が含まれてる前提」 で出力を組み立てる。echo $text . "\n" のように追加で改行を足すと 空行が増える 罠あり。

  4. fopen / fgets / fclose の作法

    <?php
    $fp = fopen(__DIR__ . '/tests/data/hello.txt', 'r');
    if ($fp === false) {
        fwrite(STDERR, "open failed\n");
        exit(1);
    }
    
    while (($line = fgets($fp)) !== false) {
        // $line には末尾の改行 (\n / \r\n) が含まれる
        echo rtrim($line) . "\n";
    }
    
    fclose($fp);
    ステップ 役割
    fopen($path, 'r') ファイルを 読み込みモード で開く
    fgets($fp) 1 行読む (末尾改行付き)。EOF で false
    fclose($fp) ファイルを 閉じる (OS のリソースを返す)

    fclose を忘れても PHP 終了時に解放されるが、「開いたら閉じる」 を癖にする。

  5. なぜ fgets のループは while (($line = fgets($fp)) !== false) なのか

    // ❌ よくある罠: feof で書くと最後の行を 1 個多く読む
    while (!feof($fp)) {
        $line = fgets($fp);   // EOF 直前で fgets が false を返すが、ループは 1 回余分に回る
        echo $line;
    }
    
    // ✅ 安全: fgets の戻り値を直接判定
    while (($line = fgets($fp)) !== false) {
        echo $line;
    }
    • feof直前の読み取りで EOF を踏んだ後 に true になる → 「false が来てから初めて気づく」
    • 戻り値で判定すれば EOF を踏んだ瞬間に抜ける

    → 「fgets の戻り値で判定」を defalut 形として覚える。

  6. パスは __DIR__ から組む

    // ❌ NG: cwd (現在ディレクトリ) に依存する
    $text = file_get_contents('tests/data/hello.txt');
    
    // ✅ OK: スクリプト自身のディレクトリ基準
    $text = file_get_contents(__DIR__ . '/tests/data/hello.txt');
    • 採点ランナー (php scripts/grade.php drill/01-read-all/) は リポジトリルートから起動 される
    • cwd 相対パスだと、起動位置がズレた瞬間 false (= パス間違い) で落ちる
    • __DIR__ を基準 にすれば、どこから呼ばれてもファイルを見つけられる

    → file-io トピックでは 「パスは必ず __DIR__ から組む」 が鉄則。

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

    file_get_contents で 1 発でテキストを読める ✅ fopen / fgets / fclose で 1 行ずつ読める ✅ fgets ループは 戻り値判定 (!== false) で書ける ✅ パスは __DIR__ 基準 で組める ✅ 読み込んだ文字列の 末尾改行 を意識して echo できる

    → ドリルへ

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

Lesson 14 / Chapter 2


2 つの読み方を持っておく

関数 どんな時
file_get_contents($path) 小さい / 中身を文字列でまとめて欲しい とき
fopen + fgets + fclose 大きい / 1 行ずつ処理したい とき
<?php
// パターン A: 一発で全部
$text = file_get_contents(__DIR__ . '/tests/data/hello.txt');

// パターン B: 1 行ずつ
$fp = fopen(__DIR__ . '/tests/data/hello.txt', 'r');
while (($line = fgets($fp)) !== false) {
    echo $line;   // $line には末尾の \n も含まれる
}
fclose($fp);

「設定読み込みは A」「ログ走査は B」 くらいの肌感で使い分けると良い。


file_get_contents の挙動

$path = __DIR__ . '/tests/data/hello.txt';
$text = file_get_contents($path);

var_dump($text);
// string(36) "こんにちは
// ファイル読み込みのテスト
// "
  • 成功すると ファイル全体を文字列で返す
  • 失敗すると false を返す (パス間違い・権限なしなど)
  • そのまま echo すれば中身がそのまま出る (末尾改行も含む)

「末尾に \n が含まれてる前提」 で出力を組み立てる。echo $text . "\n" のように追加で改行を足すと 空行が増える 罠あり。


fopen / fgets / fclose の作法

<?php
$fp = fopen(__DIR__ . '/tests/data/hello.txt', 'r');
if ($fp === false) {
    fwrite(STDERR, "open failed\n");
    exit(1);
}

while (($line = fgets($fp)) !== false) {
    // $line には末尾の改行 (\n / \r\n) が含まれる
    echo rtrim($line) . "\n";
}

fclose($fp);
ステップ 役割
fopen($path, 'r') ファイルを 読み込みモード で開く
fgets($fp) 1 行読む (末尾改行付き)。EOF で false
fclose($fp) ファイルを 閉じる (OS のリソースを返す)

fclose を忘れても PHP 終了時に解放されるが、「開いたら閉じる」 を癖にする。


なぜ fgets のループは while (($line = fgets($fp)) !== false) なのか

// ❌ よくある罠: feof で書くと最後の行を 1 個多く読む
while (!feof($fp)) {
    $line = fgets($fp);   // EOF 直前で fgets が false を返すが、ループは 1 回余分に回る
    echo $line;
}

// ✅ 安全: fgets の戻り値を直接判定
while (($line = fgets($fp)) !== false) {
    echo $line;
}
  • feof直前の読み取りで EOF を踏んだ後 に true になる → 「false が来てから初めて気づく」
  • 戻り値で判定すれば EOF を踏んだ瞬間に抜ける

→ 「fgets の戻り値で判定」を defalut 形として覚える。


パスは __DIR__ から組む

// ❌ NG: cwd (現在ディレクトリ) に依存する
$text = file_get_contents('tests/data/hello.txt');

// ✅ OK: スクリプト自身のディレクトリ基準
$text = file_get_contents(__DIR__ . '/tests/data/hello.txt');
  • 採点ランナー (php scripts/grade.php drill/01-read-all/) は リポジトリルートから起動 される
  • cwd 相対パスだと、起動位置がズレた瞬間 false (= パス間違い) で落ちる
  • __DIR__ を基準 にすれば、どこから呼ばれてもファイルを見つけられる

→ file-io トピックでは 「パスは必ず __DIR__ から組む」 が鉄則。


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

file_get_contents で 1 発でテキストを読める ✅ fopen / fgets / fclose で 1 行ずつ読める ✅ fgets ループは 戻り値判定 (!== false) で書ける ✅ パスは __DIR__ 基準 で組める ✅ 読み込んだ文字列の 末尾改行 を意識して echo できる

→ ドリルへ

この章で取り組む 演習問題の流れ (2ステップ)
  1. 01 ドリル 01 — ファイル全体を読む README.md starter.php answer.php
  2. 02 ドリル 02 — 1 行ずつ読んで行番号を付ける README.md starter.php answer.php

演習問題の詳細

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

ドリル 01 — ファイル全体を読む

問題

__DIR__ . '/tests/data/hello.txt' の中身を そのまま標準出力に出す PHP を書いてください。

データファイル (tests/data/hello.txt):

こんにちは
ファイル読み込みのテスト

期待される出力:

こんにちは
ファイル読み込みのテスト

(末尾の改行はファイルに含まれている前提。echo で追加の "\n" を足さない)

ヒント

$path = __DIR__ . '/tests/data/hello.txt';
echo file_get_contents($path);
  • file_get_contentsファイル全体を文字列で返す
  • 末尾の改行も含めて返すので、echo だけで OK
  • パスは __DIR__ 基準 で組む (cwd 依存にしない)

つまづいたら

  • Warning: file_get_contents(...): Failed to open stream → パスがズレている。__DIR__ . '/tests/data/hello.txt' の綴り / / の数を見直す
  • 末尾に空行が 1 つ余計に出る → echo file_get_contents($path) . "\n"; のように追加で改行を足していないか確認

🧪 ブラウザで試す

スライドのコードを写経して ▶ 実行、自分で書いて 📝 採点 で答え合わせ。初回のみ PHP ランタイム読込で数秒かかります。

PHP ランタイム未読込

          

        

ドリル 02 — 1 行ずつ読んで行番号を付ける

問題

__DIR__ . '/tests/data/poem.txt'1 行ずつ 読み、各行の先頭に "N: " (N は 1 始まりの行番号) を付けて出力する PHP を書いてください。

データファイル (tests/data/poem.txt):

春はあけぼの
やうやう白くなりゆく山ぎは
すこしあかりて
紫だちたる雲のほそくたなびきたる

期待される出力:

1: 春はあけぼの
2: やうやう白くなりゆく山ぎは
3: すこしあかりて
4: 紫だちたる雲のほそくたなびきたる

ヒント

$fp = fopen(__DIR__ . '/tests/data/poem.txt', 'r');
$n = 1;
while (($line = fgets($fp)) !== false) {
    echo $n . ': ' . rtrim($line, "\r\n") . "\n";
    $n++;
}
fclose($fp);
  • fopen($path, 'r')読み込みモード で開く
  • fgets($fp)1 行返す (EOF で false)
  • fgets の戻り値には 末尾改行 が含まれるので rtrim($line, "\r\n") で剥がし、自分で "\n" を付け直す
  • 開いたら必ず fclose

つまづいたら

  • 最後に空行が 1 つ余分に出る → fgets の戻り値末尾の改行を剥がさず、さらに "\n" を付け足している
  • 1 行ズレる / 行が 1 個多い → while (!feof($fp)) で書くと末尾で 1 回余分に回ることがある。while (($line = fgets($fp)) !== false) の形で書く

🧪 ブラウザで試す

スライドのコードを写経して ▶ 実行、自分で書いて 📝 採点 で答え合わせ。初回のみ PHP ランタイム読込で数秒かかります。

PHP ランタイム未読込

          

        

演習問題(2問)

  1. ドリル 01 — ファイル全体を読む

    README.md starter.php answer.php

  2. ドリル 02 — 1 行ずつ読んで行番号を付ける

    README.md starter.php answer.php

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

完了にすると進捗に反映されます