ファイルアップロードの概念
テクノロジ系 / file-io (read / write / CSV / paths / upload概念)
ファイルアップロードの概念
Lesson 14 / Chapter 6
なぜこのチャプターは「概念だけ」か
- アップロードは ブラウザ → HTTP
multipart/form-data→ PHP の$_FILESという Web 前提の機能 - 本コースの CLI 採点ランナーでは再現できない (
move_uploaded_fileが「本物のアップロード由来か」を内部チェックして弾く) - そこで本チャプターは 「全体像と落とし穴」 を言葉と絵で覚え、実機で試したい人だけ
php -Sで体感してもらう構成
→ ドリルはなし。スライドを読み終わったら file-io トピック完了。
HTML フォーム側に必要なもの
<!DOCTYPE html> <form action="/upload.php" method="post" enctype="multipart/form-data"> <input type="file" name="avatar"> <button type="submit">アップロード</button> </form>
| 要素 | 何のため |
|---|---|
method="post" | ファイルは大きい / URL に乗せられないので 必ず POST |
enctype="multipart/form-data" | バイナリも送れる multipart 形式 を指示 (これを忘れると $_FILES が空になる No.1 原因) |
<input type="file" name="avatar"> | ファイル選択 UI。name 属性が PHP 側の $_FILES['avatar'] になる |
→ 「enctype 付け忘れ」が初学者の事故 No.1。テンプレ化して覚える。
PHP 側に届く $_FILES の構造
<?php // /upload.php print_r($_FILES); // 出力 (avatar に hello.png をアップロードした場合): // Array ( // [avatar] => Array ( // [name] => hello.png ← 元のファイル名 // [type] => image/png ← MIME タイプ (ブラウザ申告: 信用しない) // [tmp_name] => /tmp/phpAbCdEf ← サーバー側で一時保存されたパス // [error] => 0 ← UPLOAD_ERR_OK = 0 // [size] => 12345 ← バイト数 // ) // )
$_FILES['<name>']は 連想配列 (name/type/tmp_name/error/size)tmp_name= PHP が 自動で一時ディレクトリに保存したパス。リクエスト終了時に消える- 受け取った側の役目は
tmp_nameを恒久保存先に移す こと
move_uploaded_file で恒久保存先に移す
<?php
// /upload.php
if (($_FILES['avatar']['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
http_response_code(400);
exit("アップロード失敗");
}
$tmp = $_FILES['avatar']['tmp_name'];
$dest = __DIR__ . '/uploads/' . basename($_FILES['avatar']['name']);
if (move_uploaded_file($tmp, $dest)) {
echo "OK: $dest に保存";
} else {
http_response_code(500);
exit("保存失敗");
}move_uploaded_file($tmp, $dest)= アップロード由来の一時ファイルを 保存先に移すrenameではなくmove_uploaded_fileを使う のが鉄則 (アップロード由来かを内部検証してくれる = 任意ファイル移動の悪用を弾く)errorが 0 (=UPLOAD_ERR_OK) かを 先に確認
セキュリティ最低限 4 つ
| 守ること | 何をする |
|---|---|
| 拡張子ホワイトリスト | pathinfo($name, PATHINFO_EXTENSION) を取り、許可リスト (['jpg','png','gif'] 等) に 入っていれば通す |
| サイズ上限 | $_FILES[..]['size'] を見て、上限超過なら 413 / 400 で拒否 |
| 保存ディレクトリの分離 | 公開ディレクトリ (public/) に PHP 実行可能な状態で置かない。uploads/ を .htaccess などで PHP 実行禁止に |
| ファイル名のサニタイズ | ユーザー名そのままを使わない。uniqid() などで サーバー側生成 にする (../../etc/passwd 攻撃の防止) |
→ 「ブラックリスト方式」(.php を弾く) は破られやすい。「ホワイトリスト方式」(.jpg だけ通す) を defult に。
$_FILES['x']['error'] のエラーコード
| 定数 | 値 | 意味 |
|---|---|---|
UPLOAD_ERR_OK | 0 | 成功 |
UPLOAD_ERR_INI_SIZE | 1 | php.ini の upload_max_filesize 超過 |
UPLOAD_ERR_FORM_SIZE | 2 | HTML form の MAX_FILE_SIZE 超過 |
UPLOAD_ERR_PARTIAL | 3 | 一部しか届かなかった |
UPLOAD_ERR_NO_FILE | 4 | ファイル未選択 |
UPLOAD_ERR_NO_TMP_DIR | 6 | 一時ディレクトリ無し |
UPLOAD_ERR_CANT_WRITE | 7 | ディスクに書けない |
UPLOAD_ERR_EXTENSION | 8 | 拡張モジュールが拒否 |
→ 「0 かどうか」だけで判断しない。ユーザーに 「何が原因か」 を 1 行返せると親切。
本物の動作確認 (任意)
mkdir -p /tmp/upload-demo && cd /tmp/upload-demo mkdir uploads
form.html:
<form action="/upload.php" method="post" enctype="multipart/form-data"> <input type="file" name="avatar"> <button type="submit">送る</button> </form>
upload.php:
<?php
if (($_FILES['avatar']['error'] ?? 4) !== 0) exit("err: ".$_FILES['avatar']['error']);
$dest = __DIR__ . '/uploads/' . basename($_FILES['avatar']['name']);
move_uploaded_file($_FILES['avatar']['tmp_name'], $dest);
echo "saved to: $dest";php -S localhost:8000 # ブラウザで http://localhost:8000/form.html を開いて 画像を 1 枚送信
このチャプターでできるようになること
✅ HTML form で enctype="multipart/form-data" が必要だと言える
✅ $_FILES['<name>'] の構造 (name / tmp_name / size / type / error) を読める
✅ move_uploaded_file で 一時ファイルを保存先に移す流れを 図で説明できる
✅ セキュリティの最低限 (拡張子ホワイトリスト / サイズ上限 / 保存ディレクトリ分離 / ファイル名サニタイズ) を言える
✅ error コードを見て 「何が原因か」 をユーザーに返す重要性を理解した
→ file-io トピック完了。お疲れさまでした。