7.

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

編集
この記事の要点
  • $_FILESenctype="multipart/form-data" を付けた form の POST でのみ生成される
  • キー: name 元ファイル名、type MIME、size バイト数、tmp_name 一時パス、error エラーコード
  • 必ず is_uploaded_file() + move_uploaded_file() でセキュアに移動
  • php.iniupload_max_filesize / post_max_size / max_file_uploads を確認
  • Laravel なら $request->file("avatar")->store("avatars") で MIME 検証 / 保存まで一発

基本: HTML フォームと PHP 側の対応

<!-- ★ 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>
<?php
// $_FILES['avatar'] の構造
[
    'name'     => 'photo.jpg',            // 元のファイル名(クライアント任意)
    'type'     => 'image/jpeg',           // クライアント申告の MIME(信用不可)
    'tmp_name' => '/tmp/phpAbCdEf',       // サーバ上の一時パス
    'error'    => UPLOAD_ERR_OK,          // 0 なら成功
    'size'     => 123456,                 // バイト数
    'full_path'=> 'photo.jpg',            // PHP 8.1+ 追加(ディレクトリ含む元パス)
]

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

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

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

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

echo 'Uploaded to ' . $dest;

UPLOAD_ERR_* 定数一覧

定数意味
0UPLOAD_ERR_OK成功
1UPLOAD_ERR_INI_SIZEphp.ini の upload_max_filesize 超過
2UPLOAD_ERR_FORM_SIZEHTML の MAX_FILE_SIZE 超過
3UPLOAD_ERR_PARTIAL部分的にしかアップロードされていない
4UPLOAD_ERR_NO_FILEファイルが選択されていない
6UPLOAD_ERR_NO_TMP_DIR一時ディレクトリが無い
7UPLOAD_ERR_CANT_WRITEディスク書込失敗
8UPLOAD_ERR_EXTENSION拡張モジュールにより中止

php.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

変更後は systemctl restart php8.3-fpmNginx の client_max_body_size も合わせて設定しないと、PHP に到達する前に 413 で弾かれます。

複数ファイルのアップロード

<form action="/upload.php" method="POST" enctype="multipart/form-data">
    <input type="file" name="files[]" multiple>
    <button>Upload</button>
</form>
<?php
// $_FILES['files'] は ↓ のような「列指向」になる
[
    'name'     => ['a.jpg', 'b.png'],
    'type'     => ['image/jpeg', 'image/png'],
    'tmp_name' => ['/tmp/php1', '/tmp/php2'],
    'error'    => [0, 0],
    'size'     => [12345, 23456],
]

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

    $tmp = $_FILES['files']['tmp_name'][$i];
    $name = $_FILES['files']['name'][$i];

    $safeName = preg_replace('/[^A-Za-z0-9._-]/', '_', $name);
    $dest = __DIR__ . '/uploads/' . uniqid() . '_' . $safeName;
    move_uploaded_file($tmp, $dest);
}

セキュリティ: 画像 MIME 検証

クライアント送信の type や拡張子は絶対に信用しないこと。example.php.jpg のようなファイルが PHP として実行される事故を防ぎます:

<?php
$file = $_FILES['avatar'];

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

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

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

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

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

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

// 6. 保存先は Web 公開外(または .htaccess で php 実行禁止)

Laravel での書き方

use Illuminate\Http\Request;

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

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

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

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

    return response()->json(['url' => $url], 201);
}

よくあるトラブル

症状原因対処
$_FILES が空form の enctype 未指定 / GET 送信multipart/form-data + POST
413 Request Entity Too LargeNginx の client_max_body_size該当値を増やす
error=1(INI_SIZE)upload_max_filesize 超過php.ini で増やす
error=3(PARTIAL)クライアント側で切断ネットワーク or タイムアウト確認
error=6(NO_TMP_DIR)upload_tmp_dir 不在ディレクトリ作成 / 権限付与

FAQ

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

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

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

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. $_SERVER
  2. $_GET
  3. $_POST
  4. $_ENV (PHP スーパーグローバル変数)
  5. $_COOKIE
  6. $_SERVER
  7. $_FILES

最近更新/作成されたページ