HTML 表單上傳多檔案的關鍵在於 input name="file[]" 的陣列語法,PHP 端會收到結構化的 $_FILES['file'] 陣列。本文提供從前端表單到後端處理的完整流程,包含 檔案類型驗證、大小限制、隨機重命名防衝突、錯誤碼處理、資料夾權限檢查。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>多檔案上傳</title>
</head>
<body>
<form action="upload.php" method="post" enctype="multipart/form-data">
<!-- 關鍵:name="file[]" 讓瀏覽器送出陣列 -->
<input type="file" name="file[]" multiple><br>
<input type="file" name="file[]" multiple><br>
<input type="file" name="file[]" multiple><br>
<button type="submit">上傳</button>
</form>
</body>
</html>
技巧:單一 <input name="file[]" multiple> 即可選取多檔,多個 input 只是 UI 習慣。
<?php
header("Content-Type: text/html; charset=utf-8");
// 1. 設定上傳限制
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];
const UPLOAD_DIR = __DIR__ . '/uploads/files';
// 2. 確保上傳目錄存在且可寫
if (!is_dir(UPLOAD_DIR)) {
if (!mkdir(UPLOAD_DIR, 0755, true)) {
die('無法建立上傳目錄');
}
}
if (!is_writable(UPLOAD_DIR)) {
die('上傳目錄不可寫入');
}
// 3. 檢查是否有檔案上傳
if (!isset($_FILES['file']) || empty($_FILES['file']['name'][0])) {
die('請選擇檔案');
}
$success = 0;
$errors = [];
// 4. 迴圈處理每個檔案
$count = count($_FILES['file']['name']);
for ($i = 0; $i < $count; $i++) {
// 4.1 取得單一檔案資訊
$name = $_FILES['file']['name'][$i];
$type = $_FILES['file']['type'][$i];
$tmpName = $_FILES['file']['tmp_name'][$i];
$error = $_FILES['file']['error'][$i];
$size = $_FILES['file']['size'][$i];
// 4.2 錯誤碼檢查
if ($error !== UPLOAD_ERR_OK) {
$errors[] = "{$name}: " . uploadErrorMessage($error);
continue;
}
// 4.3 檔案大小檢查
if ($size > MAX_FILE_SIZE) {
$errors[] = "{$name}: 檔案過大(限制 " . (MAX_FILE_SIZE/1024/1024) . "MB)";
continue;
}
// 4.4 MIME 類型檢查(注意:瀏覽器提供,不可全信,建議搭配 finfo)
if (!in_array($type, ALLOWED_TYPES, true)) {
// 二次驗證:使用 finfo 檢測真實類型
$finfo = new finfo(FILEINFO_MIME_TYPE);
$realType = $finfo->file($tmpName);
if (!in_array($realType, ALLOWED_TYPES, true)) {
$errors[] = "{$name}: 禁止的檔案類型(偵測為 {$realType})";
continue;
}
}
// 4.5 產生唯一檔名:時間戳 + 隨機數 + 原副檔名
$ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
$newName = sprintf('%s_%s.%s', time(), mt_rand(10000, 99999), $ext);
$destPath = UPLOAD_DIR . DIRECTORY_SEPARATOR . $newName;
// 4.6 移動檔案
if (move_uploaded_file($tmpName, $destPath)) {
$success++;
// 可選:記錄資料庫、產生縮圖、掃毒...
} else {
$errors[] = "{$name}: 移動失敗(權限或磁碟空間)";
}
}
// 5. 輸出結果
echo "上傳完成:成功 {$success} 筆,失敗 " . count($errors) . " 筆
";
if ($errors) {
echo "";
foreach ($errors as $e) echo "- " . htmlspecialchars($e) . "
";
echo "
";
}
echo "返回繼續上傳";
// 錯誤碼對照表
function uploadErrorMessage(int $code): string {
return match ($code) {
UPLOAD_ERR_INI_SIZE => '超過 php.ini upload_max_filesize',
UPLOAD_ERR_FORM_SIZE => '超過表單 MAX_FILE_SIZE',
UPLOAD_ERR_PARTIAL => '檔案上傳不完整',
UPLOAD_ERR_NO_FILE => '沒有檔案被上傳',
UPLOAD_ERR_NO_TMP_DIR => '缺少暫存資料夾',
UPLOAD_ERR_CANT_WRITE => '寫入磁碟失敗',
UPLOAD_ERR_EXTENSION => '被 PHP 擴充功能阻止',
default => '未知錯誤',
};
}
| 鍵名 | 說明 | 範例值 |
|---|---|---|
$_FILES['file']['name'][$i] | 原始檔名 | photo.jpg |
$_FILES['file']['type'][$i] | 瀏覽器回報 MIME | image/jpeg |
$_FILES['file']['tmp_name'][$i] | 暫存檔完整路徑 | /tmp/phpA1B2C3 |
$_FILES['file']['error'][$i] | 錯誤碼(0=成功) | 0 |
$_FILES['file']['size'][$i] | 檔案大小(位元組) | 102400 |
pathinfo($name, PATHINFO_EXTENSION) ∈ ['jpg','png','gif','pdf']。getimagesize() 驗證真為圖片;PDF 可用 finfo 確認 header %PDF。uploads/ 應置於 public/ 外層,或 Nginx 設定 location ~ \.php$ { deny all; }。shell.php.jpg 並嘗試解析執行。post_max_size、upload_max_filesize、表單隱藏欄位 MAX_FILE_SIZE 三重保護。多檔案上傳的核心在於理解 $_FILES 的陣列結構,並對每個檔案獨立執行「驗證 → 重命名 → 移動 → 記錄」流程。建議將上述邏輯封裝為 FileUploader 類別,支援 驗證器鏈、儲存策略(本地/S3/OSS)、事件鉤子(上傳後縮圖/掃毒/入庫),即可滿足企業級檔案管理系統需求。