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

ファイルアップロードの概念

テクノロジ系 / 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_OK0成功
UPLOAD_ERR_INI_SIZE1php.iniupload_max_filesize 超過
UPLOAD_ERR_FORM_SIZE2HTML form の MAX_FILE_SIZE 超過
UPLOAD_ERR_PARTIAL3一部しか届かなかった
UPLOAD_ERR_NO_FILE4ファイル未選択
UPLOAD_ERR_NO_TMP_DIR6一時ディレクトリ無し
UPLOAD_ERR_CANT_WRITE7ディスクに書けない
UPLOAD_ERR_EXTENSION8拡張モジュールが拒否

「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 トピック完了。お疲れさまでした。