この内容は古いバージョンです。最新バージョンを表示するには、戻るボタンを押してください。
バージョン:3
ページ更新者:guest
更新日時:2026-06-11 07:07:02

タイトル: $_FILES
SEOタイトル: PHP $_FILES でのファイルアップロード完全ガイド

この本文には実行可能なタグ等が含まれるため、安全のためHTMLソースとして表示しています。本文データは変更していません。

<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;background:#fffbea;border:1px solid #f4c430;margin:1em 0;width:100%"><tr><td style="background:#f4c430;color:#000;font-weight:bold;padding:8px">この記事の要点</td></tr><tr><td style="padding:10px"><ul><li><strong><code>$_FILES</code></strong> は <code>enctype="multipart/form-data"</code> を付けた form の POST でのみ生成される</li><li><strong>キー</strong>: <code>name</code> 元ファイル名、<code>type</code> MIME、<code>size</code> バイト数、<code>tmp_name</code> 一時パス、<code>error</code> エラーコード</li><li><strong>必ず</strong> <code>is_uploaded_file()</code> + <code>move_uploaded_file()</code> でセキュアに移動</li><li><code>php.ini</code> の <code>upload_max_filesize</code> / <code>post_max_size</code> / <code>max_file_uploads</code> を確認</li><li>Laravel なら <code>$request->file("avatar")->store("avatars")</code> で MIME 検証 / 保存まで一発</li></ul></td></tr></table>
<h2>基本: HTML フォームと PHP 側の対応</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><!-- ★ enctype と method を必ず指定 -->
<form action="/upload.php" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="MAX_FILE_SIZE" value="2097152">  <!-- 2MB ヒント -->
    <input type="file" name="avatar">
    <button type="submit">Upload</button>
</form></code></pre><pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-php"><?php
// $_FILES[&apos;avatar&apos;] の構造
[
    &apos;name&apos;     => &apos;photo.jpg&apos;,            // 元のファイル名(クライアント任意)
    &apos;type&apos;     => &apos;image/jpeg&apos;,           // クライアント申告の MIME(信用不可)
    &apos;tmp_name&apos; => &apos;/tmp/phpAbCdEf&apos;,       // サーバ上の一時パス
    &apos;error&apos;    => UPLOAD_ERR_OK,          // 0 なら成功
    &apos;size&apos;     => 123456,                 // バイト数
    &apos;full_path&apos;=> &apos;photo.jpg&apos;,            // PHP 8.1+ 追加(ディレクトリ含む元パス)
]

// 受信側
$file = $_FILES[&apos;avatar&apos;] ?? null;

if (!$file || $file[&apos;error&apos;] !== UPLOAD_ERR_OK) {
    die(&apos;Upload failed: &apos; . ($file[&apos;error&apos;] ?? &apos;no file&apos;));
}

if (!is_uploaded_file($file[&apos;tmp_name&apos;])) {
    die(&apos;Possibly a forged upload&apos;);  // セキュリティ必須
}

$dest = __DIR__ . &apos;/uploads/&apos; . uniqid(&apos;img_&apos;) . &apos;.jpg&apos;;
if (!move_uploaded_file($file[&apos;tmp_name&apos;], $dest)) {
    die(&apos;Move failed&apos;);
}

echo &apos;Uploaded to &apos; . $dest;</code></pre>
<h2>UPLOAD_ERR_* 定数一覧</h2>

<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
<thead><tr style="background:#e3f2fd"><th>値</th><th>定数</th><th>意味</th></tr></thead>
<tbody>
<tr><td>0</td><td><code>UPLOAD_ERR_OK</code></td><td>成功</td></tr>
<tr><td>1</td><td><code>UPLOAD_ERR_INI_SIZE</code></td><td>php.ini の <code>upload_max_filesize</code> 超過</td></tr>
<tr><td>2</td><td><code>UPLOAD_ERR_FORM_SIZE</code></td><td>HTML の <code>MAX_FILE_SIZE</code> 超過</td></tr>
<tr><td>3</td><td><code>UPLOAD_ERR_PARTIAL</code></td><td>部分的にしかアップロードされていない</td></tr>
<tr><td>4</td><td><code>UPLOAD_ERR_NO_FILE</code></td><td>ファイルが選択されていない</td></tr>
<tr><td>6</td><td><code>UPLOAD_ERR_NO_TMP_DIR</code></td><td>一時ディレクトリが無い</td></tr>
<tr><td>7</td><td><code>UPLOAD_ERR_CANT_WRITE</code></td><td>ディスク書込失敗</td></tr>
<tr><td>8</td><td><code>UPLOAD_ERR_EXTENSION</code></td><td>拡張モジュールにより中止</td></tr>
</tbody>
</table>

<h2>php.ini の関連設定</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-ini">; /etc/php/8.3/fpm/php.ini

; 1 ファイルあたり最大サイズ
upload_max_filesize = 10M

; POST 全体の最大サイズ(複数ファイル合計 + フォームフィールド)
; ★ upload_max_filesize 以上にする
post_max_size = 12M

; 同時アップロード可能なファイル数
max_file_uploads = 20

; 一時ディレクトリ(未指定なら sys_get_temp_dir)
upload_tmp_dir = /tmp/phpuploads

; メモリ上限(大きなファイル処理時に注意)
memory_limit = 256M

; 実行時間
max_execution_time = 300
max_input_time = 300</code></pre>
<p>変更後は <code>systemctl restart php8.3-fpm</code>。<strong>Nginx の <code>client_max_body_size</code></strong> も合わせて設定しないと、PHP に到達する前に 413 で弾かれます。</p>

<h2>複数ファイルのアップロード</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><form action="/upload.php" method="POST" enctype="multipart/form-data">
    <input type="file" name="files[]" multiple>
    <button>Upload</button>
</form></code></pre><pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-php"><?php
// $_FILES[&apos;files&apos;] は ↓ のような「列指向」になる
[
    &apos;name&apos;     => [&apos;a.jpg&apos;, &apos;b.png&apos;],
    &apos;type&apos;     => [&apos;image/jpeg&apos;, &apos;image/png&apos;],
    &apos;tmp_name&apos; => [&apos;/tmp/php1&apos;, &apos;/tmp/php2&apos;],
    &apos;error&apos;    => [0, 0],
    &apos;size&apos;     => [12345, 23456],
]

// 「行指向」に変換して扱いやすく
$count = count($_FILES[&apos;files&apos;][&apos;name&apos;]);
for ($i = 0; $i < $count; $i++) {
    if ($_FILES[&apos;files&apos;][&apos;error&apos;][$i] !== UPLOAD_ERR_OK) continue;

    $tmp = $_FILES[&apos;files&apos;][&apos;tmp_name&apos;][$i];
    $name = $_FILES[&apos;files&apos;][&apos;name&apos;][$i];

    $safeName = preg_replace(&apos;/[^A-Za-z0-9._-]/&apos;, &apos;_&apos;, $name);
    $dest = __DIR__ . &apos;/uploads/&apos; . uniqid() . &apos;_&apos; . $safeName;
    move_uploaded_file($tmp, $dest);
}</code></pre>
<h2>セキュリティ: 画像 MIME 検証</h2>

<p>クライアント送信の <code>type</code> や拡張子は<strong>絶対に信用しない</strong>こと。<code>example.php.jpg</code> のようなファイルが PHP として実行される事故を防ぎます:</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-php"><?php
$file = $_FILES[&apos;avatar&apos;];

// 1. サイズ上限を別途チェック(php.ini 任せにしない)
if ($file[&apos;size&apos;] > 5 * 1024 * 1024) {  // 5MB
    die(&apos;File too large&apos;);
}

// 2. 画像であることを実体で確認
$info = @getimagesize($file[&apos;tmp_name&apos;]);
if ($info === false) {
    die(&apos;Not an image&apos;);
}

// 3. 許可する MIME のホワイトリスト
$allowed = [&apos;image/jpeg&apos; => &apos;jpg&apos;, &apos;image/png&apos; => &apos;png&apos;, &apos;image/gif&apos; => &apos;gif&apos;];
$mime = $info[&apos;mime&apos;];
if (!isset($allowed[$mime])) {
    die(&apos;Unsupported image type: &apos; . $mime);
}

// 4. finfo でも二重チェック
$finfo = new finfo(FILEINFO_MIME_TYPE);
$realMime = $finfo->file($file[&apos;tmp_name&apos;]);
if ($realMime !== $mime) {
    die(&apos;MIME mismatch&apos;);
}

// 5. 拡張子は自前で付ける(クライアント名を使わない)
$ext = $allowed[$mime];
$dest = __DIR__ . &apos;/uploads/&apos; . bin2hex(random_bytes(16)) . &apos;.&apos; . $ext;

if (!move_uploaded_file($file[&apos;tmp_name&apos;], $dest)) {
    die(&apos;Move failed&apos;);
}

// 6. 保存先は Web 公開外(または .htaccess で php 実行禁止)</code></pre>
<h2>Laravel での書き方</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-php">use Illuminate\Http\Request;

public function upload(Request $request)
{
    // 1. バリデーション(MIME・サイズも一括)
    $request->validate([
        &apos;avatar&apos; => &apos;required|image|mimes:jpg,jpeg,png|max:5120&apos;,  // 5MB
    ]);

    // 2. ファイルオブジェクト取得
    $file = $request->file(&apos;avatar&apos;);
    // $file は Illuminate\Http\UploadedFile

    // 3. 保存(storage/app/public/avatars 配下)
    $path = $file->store(&apos;avatars&apos;, &apos;public&apos;);
    // または自分でファイル名指定
    $path = $file->storeAs(&apos;avatars&apos;, &apos;user_&apos; . auth()->id() . &apos;.jpg&apos;, &apos;public&apos;);

    // 4. 公開 URL
    $url = Storage::url($path);

    return response()->json([&apos;url&apos; => $url], 201);
}</code></pre>
<h2>よくあるトラブル</h2>

<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
<thead><tr style="background:#fff3e0"><th>症状</th><th>原因</th><th>対処</th></tr></thead>
<tbody>
<tr><td>$_FILES が空</td><td>form の <code>enctype</code> 未指定 / GET 送信</td><td><code>multipart/form-data</code> + POST</td></tr>
<tr><td>413 Request Entity Too Large</td><td>Nginx の <code>client_max_body_size</code></td><td>該当値を増やす</td></tr>
<tr><td>error=1(INI_SIZE)</td><td>upload_max_filesize 超過</td><td>php.ini で増やす</td></tr>
<tr><td>error=3(PARTIAL)</td><td>クライアント側で切断</td><td>ネットワーク or タイムアウト確認</td></tr>
<tr><td>error=6(NO_TMP_DIR)</td><td>upload_tmp_dir 不在</td><td>ディレクトリ作成 / 権限付与</td></tr>
</tbody>
</table>

<h2>FAQ</h2>

<p><strong>Q: HTML の <code>MAX_FILE_SIZE</code> hidden は意味ある?</strong><br />
A: クライアントへのヒントにはなりますが、<strong>サーバ側で信用できない</strong>ため、サーバでもチェック必須です。</p>

<p><strong>Q: アップロードファイルを直接 DB に保存したい</strong><br />
A: BLOB として可能ですが、画像は<strong>ファイルシステム / S3</strong>、メタデータのみ DB が定石。バックアップ容易性のため。</p>

<p><strong>Q: チャンクアップロード(巨大ファイル分割送信)したい</strong><br />
A: PHP 単体機能では無いので、フロント側 (resumable.js / tus-js-client) と組み合わせるか、Laravel なら <code>laravel-chunk-upload</code> パッケージを使います。</p>