# ch03 — ファイルに書く

Source: https://php-school.pages.dev/topics/file-io/ch03-write-file/
Site: AIと学ぶPHPの学校 (https://php-school.pages.dev)
Lesson: ファイル入出力
Level: L4

Reference (official): https://www.php.net/manual/ja/book.filesystem.php

## 本文

# ch03 — ファイルに書く

## 概要

書き込みも読み込みと対称で、手軽さの **`file_put_contents($path, $data)`** と、制御性の **`fopen('w') + fwrite + fclose`** の 2 系統。前者は「文字列を渡すだけで全部書く」、後者は「ハンドルを開きながら何度も書き足す」。普段使いはほぼ `file_put_contents` で済む。

**モードの違い** が地味に重要。デフォルトは **上書きモード** (既存ファイルの中身を消してから書く)。`file_put_contents(..., FILE_APPEND)` か `fopen($path, 'a')` で **追記モード** になり、末尾に足す動きに変わる。ログや日次レポートなど「過去分を残しつつ書き足す」用途では追記必須。複数プロセスから同時に書く可能性がある場合は **`LOCK_EX`** (排他ロック) を併用する。

教育用ドリルでは **書き込み先を必ず `sys_get_temp_dir()` 配下** にして、終わったら `unlink($tmp)` で消す約束。「リポジトリ内のファイルを書き換えない」「CI で何度回しても残骸が残らない」ためのお作法。実務でも `tempnam(sys_get_temp_dir(), 'prefix_')` で一時ファイルを取って作業し、完了後に消すパターンは頻出。

## 学習目標

- `file_put_contents()` で **文字列をファイルに書ける**
- `fopen('w')` + `fwrite` + `fclose` のパターンで書ける
- **追記モード** (`FILE_APPEND` / `fopen('a')`) と上書きモードの違いを言える
- 書き込み先は必ず **`sys_get_temp_dir()` 配下** にして、後で `unlink` で消す

## 所要時間

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

## ドリル

| # | 内容 |
|---|---|
| `01-write-and-read/` | 一時ファイルに書いて読み返す (ラウンドトリップ) |
| `02-append/` | `FILE_APPEND` で 2 回書いて、追記されていることを読み返しで確認 |

## 採点

```bash
php scripts/grade.php topics/file-io/ch03-write-file/drill/01-write-and-read/
php scripts/grade.php topics/file-io/ch03-write-file/drill/02-append/
```

## 確認のコツ

- 書き込み先は **`tempnam(sys_get_temp_dir(), 'dojo_')`** で OS の一時領域に作る
- 最後に **`unlink($tmp)`** でクリーンアップ (CI が同じ drill を何度回しても汚れない)
- リポジトリ内のファイル (`tests/data/...`) には **書き込まない**

## スライド

---
marp: true
theme: dojo
paginate: true
size: 16:9
---

<!--
LLM_CONTEXT:
  Lesson 14 / Chapter 3
  目的: テキストファイルに書く 2 系統 (file_put_contents / fopen+fwrite+fclose) と追記モード (FILE_APPEND) を覚える
  扱わない: CSV (ch04) / パス分解 (ch05) / アップロード (ch06)
  読み上げ時間目安: 5 分
-->

# ファイルに書く

Lesson 14 / Chapter 3

---

## 書き込みも 2 系統

| 関数 | どんな時 |
|---|---|
| `file_put_contents($path, $str)` | **文字列を 1 発で書く / 追記する** |
| `fopen` + `fwrite` + `fclose` | **段階的に書きたい / 大きいデータを流す** |

```php
<?php
// パターン A: 一発で書く
$tmp = tempnam(sys_get_temp_dir(), 'dojo_');
file_put_contents($tmp, "Hello, File!\n");

// パターン B: fopen → fwrite → fclose
$fp = fopen($tmp, 'w');     // 'w' = 上書きモード
fwrite($fp, "line1\n");
fwrite($fp, "line2\n");
fclose($fp);

unlink($tmp);   // クリーンアップ
```

file-io トピックでは **大半が A で十分**。B は慣れのため一度書く。

---

## 書き込み先は OS の一時ディレクトリへ

```php
// ✅ OK: 一時ディレクトリ
$tmp = tempnam(sys_get_temp_dir(), 'dojo_');
file_put_contents($tmp, "hi\n");
unlink($tmp);

// ❌ NG: drill 内に書く (リポジトリが汚れる)
file_put_contents(__DIR__ . '/output.txt', "hi\n");
```

- **`sys_get_temp_dir()`** = OS の一時領域 (Mac/Linux なら `/tmp`)
- **`tempnam($dir, $prefix)`** = その中に **被らないファイル名** を作って返す (ゼロバイトの空ファイル付き)
- **`unlink($path)`** = ファイルを消す

→ 「採点が何度走っても、リポジトリの状態が変わらない」 のがゴール。

---

## `file_put_contents` の典型形

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

// 上書き (デフォルト)
$bytes = file_put_contents($tmp, "Hello, File!\n");
echo "wrote {$bytes} bytes\n";   // wrote 13 bytes

// 読み返して確認
echo file_get_contents($tmp);    // Hello, File!

unlink($tmp);
```

- 戻り値は **書いたバイト数** (失敗時 `false`)
- ファイルが既にあれば **デフォルトで上書き**
- 第 3 引数で挙動を変えられる (`FILE_APPEND` / `LOCK_EX` など)

---

## 追記モード — `FILE_APPEND`

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

file_put_contents($tmp, "line1\n");                  // 中身: "line1\n"
file_put_contents($tmp, "line2\n", FILE_APPEND);     // 中身: "line1\nline2\n"
file_put_contents($tmp, "line3\n", FILE_APPEND);     // 中身: "line1\nline2\nline3\n"

echo file_get_contents($tmp);
// line1
// line2
// line3

unlink($tmp);
```

- `FILE_APPEND` を渡すと **末尾に追記** (元の中身を保つ)
- 渡さないと **上書き** (前の中身は消える)

→ ログ書き込みは `FILE_APPEND | LOCK_EX` のセットが定番。

---

## `fopen` のモード一覧 (主要なもの)

| モード | 意味 | 既存ファイル |
|---|---|---|
| `'r'` | 読み込み | そのまま |
| `'w'` | 書き込み (上書き) | **中身が消える** (truncate) |
| `'a'` | 追記 | 末尾に追記 |
| `'r+'` | 読み書き | そのまま |
| `'w+'` | 読み書き (上書き) | **中身が消える** |
| `'a+'` | 読み書き (追記) | 末尾に追記 |

```php
$fp = fopen($tmp, 'a');     // 追記モード
fwrite($fp, "extra line\n");
fclose($fp);
```

**「`w` は中身を消す」** が一番の落とし穴。設定ファイルを `'w'` で開いて空にしてしまう事故が定番。

---

## 書いたら必ず読み返して検算

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

// 書く
file_put_contents($tmp, "Hello, File!\n");

// 読む (= 書いた内容を検算)
echo file_get_contents($tmp);

// 後始末
unlink($tmp);
```

- 採点では **「書いたものを読み返した結果」** を `expected.txt` と比べる
- 「書いた」と「保存できた」は **別物**。`fclose` / `file_put_contents` の戻り値で確認する癖を

→ ドリル 01 はこの **「書く → 読む → 消す」のラウンドトリップ** を体験する形。

---

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

✅ `file_put_contents` でファイルに 1 発で書ける
✅ `fopen('w')` + `fwrite` + `fclose` で書ける
✅ `FILE_APPEND` / `fopen('a')` で追記できる
✅ `'w'` モードは **既存の中身を消す** ことを認識している
✅ 書き込み先は **`sys_get_temp_dir()`** 配下にして `unlink` で消す癖がついた

→ ドリルへ
