PHP でファイルアップロード機能を実装する方法

はじめに

実務にて、お問い合わせ画面でユーザーから送信された画像をサーバーに保存する機能を実装する作業があり、それを解決した情報になります。Web 開発の初心者、初学者の方にもわかりやすいように、ソースコードを編集してサンプルを公開しています、ご参考になれば幸いです。

検証環境

サンプル

  • GitHub / ソースコード
    • index.php:ファイルアップロード処理と入力フォーム
    • uploads:ファイルアップロード先のフォルダ

解説

サンプルのソースコードを基に要点だけ解説します。

入力フォームの作成

1. ファイルデータの情報を送信する為に <form> 要素の method 属性を post に設定して、enctype 属性を multipart/form-data に設定します。

2. ユーザーが選択できるファイルの拡張子を制限する為に accept 属性を設定します。ただし、フォームの改竄、拡張子の偽装により簡単に突破できてしまうのでセキュリティ対策はフォームデータ送信後に記述します。

<form enctype="multipart/form-data" method="post">
  <input type="file" name="upfile" accept=".zip, .rar">
  <input type="submit" value="送信する">
</form>

ファイルアップロード処理の作成

1. 入力フォームから送信されたファイルデータを $_FILES から取得します。必要な値は下記になります。

  • テンポラリファイル名:$_FILES[‘upfile’][‘tmp_name’]
  • エラーコード:$_FILES[‘upfile’][‘error’]
  • ファイルサイズ:$_FILES[‘upfile’][‘size’]
$file = $_FILES['upfile'] ?? null;
$file_tmp_name = $file['tmp_name'] ?? null;
$file_upload_err = $file['error'] ?? null;
$file_size = $file['size'] ?? null;

2. エラーコード チェックを作成します。UPLOAD_ERR_OK 以外の場合、match 式で各エラーコード発生時のエラーメッセージを設定します。match 式が使えない場合は switch 文でも良いです。

$file_upload_msg = match ($file_upload_err) {
  UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE => UPLOAD_ERR_MSG_1,
  UPLOAD_ERR_PARTIAL => UPLOAD_ERR_MSG_4,
  UPLOAD_ERR_NO_FILE => UPLOAD_ERR_MSG_5,
  UPLOAD_ERR_NO_TMP_DIR => UPLOAD_ERR_MSG_6,
  UPLOAD_ERR_CANT_WRITE => UPLOAD_ERR_MSG_7,
  UPLOAD_ERR_EXTENSION => UPLOAD_ERR_MSG_8,
  default => UPLOAD_ERR_MSG_9,
};

3. ファイルサイズ、ファイル拡張子チェックを作成します。

ファイルサイズチェックは、単純なバイト数の比較です。

ファイル拡張子チェックは mime_content_type でファイルの メディアタイプ を検出後、array_search で許可している拡張子かどうかを検索します。

また、$_FILES[‘upfile’][‘type’] はファイル名を変えるだけで簡単に偽装ができてしまうので、これを利用したチェックには注意が必要です。

if ($file_size > MAX_FILE_SIZE) $file_upload_msg = UPLOAD_ERR_MSG_1;
elseif (!$file_extension = array_search(mime_content_type($file_tmp_name), ACCEPT_MIME_TYPES, true)) $file_upload_msg = UPLOAD_ERR_MSG_2;
else {
  // ファイルアップロード処理
}

4. ディレクトリ・トラバーサル 対策の為にアップロードするファイル名を生成します。

CSPRNG 関数の random_bytes で生成した疑似ランダムなバイト列を bin2hex で 16 進数の文字列表現に変換しています。

$file_name = bin2hex(random_bytes(16)) . '.' . $file_extension;

5. move_uploaded_file でアップロードされたファイルを新しい位置に移動します。

move_uploaded_file の前に is_uploaded_file は必要ありません、同様のチェックを行っているからです、余分なセキュリティ対策になってしまうので注意しましょう。

if (move_uploaded_file($file_tmp_name, UPLOAD_DIR . $file_name)) {
  // 処理
} else $file_upload_msg = UPLOAD_ERR_MSG_3;

6. chmod でアップロードファイルのパーミッションを 644 に変更して実行権限を削除します。

chmod(UPLOAD_DIR . $file_name, 0644);

以上です。

おわりに

サンプルのソースコードを再利用する際は、要件定義やコーディング規約にお気を付けください。