檔案上傳是 Web 開發核心功能之一。本文循序漸進說明:HTML 表單關鍵屬性、$_FILES 陣列結構、核心參數設定、單檔/多檔上傳實作、安全防護與常見錯誤碼對照。
<form action="upload.php" method="post" enctype="multipart/form-data">
<!-- 限制上傳大小(建議值,瀏覽器端攔截,非絕對安全) -->
<input type="hidden" name="MAX_FILE_SIZE" value="2000000">
<input type="file" name="userfile">
<input type="submit" value="上傳檔案">
</form>
關鍵點:
enctype="multipart/form-data":必須,否則 $_FILES 為空。MAX_FILE_SIZE:僅為瀏覽器建議值,易被繞過,後端必須驗證。$_FILES 陣列結構詳解假設 input name="userfile":
| 鍵名 | 說明 |
|---|---|
$_FILES['userfile']['name'] | 用戶端原始檔名 |
$_FILES['userfile']['type'] | MIME 類型(瀏覽器提供,不可全信) |
$_FILES['userfile']['size'] | 檔案大小(Bytes) |
$_FILES['userfile']['tmp_name'] | 伺服器暫存路徑 |
$_FILES['userfile']['error'] | 錯誤代碼(見下表) |
| 代碼 | 常數 | 原因 |
|---|---|---|
| 0 | UPLOAD_ERR_OK | 上傳成功 |
| 1 | UPLOAD_ERR_INI_SIZE | 超過 php.ini upload_max_filesize |
| 2 | UPLOAD_ERR_FORM_SIZE | 超過表單 MAX_FILE_SIZE |
| 3 | UPLOAD_ERR_PARTIAL | 僅部分上傳 |
| 4 | UPLOAD_ERR_NO_FILE | 未選擇檔案 |
| 6 | UPLOAD_ERR_NO_TMP_DIR | 缺少暫存目錄 |
| 7 | UPLOAD_ERR_CANT_WRITE | 寫入磁碟失敗 |
| 8 | UPLOAD_ERR_EXTENSION | 擴展模組中斷上傳 |
修改 php.ini 或 .user.ini:
| 指令 | 預設值 | 說明 |
|---|---|---|
| file_uploads | On | 啟用 HTTP 上傳 |
| upload_max_filesize | 2M | 單檔最大值 |
| post_max_size | 8M | POST 總資料大小,需 > upload_max_filesize |
| upload_tmp_dir | 系統預設 | 暫存目錄,需可寫入權限 |
| max_file_uploads | 20 | 單次請求最大檔案數 |
| max_input_time | 60 | 解析輸入資料最長秒數(大檔需調大) |
| memory_limit | 128M | 腳本記憶體上限 |
<?php
$targetDir = __DIR__ . '/uploads/';
if (!is_dir($targetDir)) mkdir($targetDir, 0755, true);
$file = $_FILES['userfile'] ?? null;
if (!$file || $file['error'] !== UPLOAD_ERR_OK) {
die('上傳失敗,錯誤代碼:' . ($file['error'] ?? '未知'));
}
// 安全驗證
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];
$maxSize = 2 * 1024 * 1024; // 2MB
if (!in_array($file['type'], $allowedTypes)) die('檔案類型不允許');
if ($file['size'] > $maxSize) die('檔案過大');
// 產生唯一檔名防覆蓋
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
$newName = uniqid('upload_', true) . '.' . $ext;
$dest = $targetDir . $newName;
if (move_uploaded_file($file['tmp_name'], $dest)) {
chmod($dest, 0644);
echo "上傳成功:$newName";
} else {
echo "移動檔案失敗,請檢查目錄權限";
}
?>
HTML:<input type="file" name="files[]" multiple>
<?php
$targetDir = __DIR__ . '/uploads/';
$results = ['success' => [], 'failed' => []];
foreach ($_FILES['files']['tmp_name'] as $idx => $tmpName) {
$error = $_FILES['files']['error'][$idx];
if ($error !== UPLOAD_ERR_OK) {
$results['failed'][] = ['name' => $_FILES['files']['name'][$idx], 'error' => $error];
continue;
}
$name = $_FILES['files']['name'][$idx];
$size = $_FILES['files']['size'][$idx];
$type = $_FILES['files']['type'][$idx];
// 同單檔驗證邏輯...
$ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
$allowedExt = ['jpg','jpeg','png','gif','pdf'];
if (!in_array($ext, $allowedExt) || $size > 2*1024*1024) {
$results['failed'][] = ['name' => $name, 'error' => '驗證失敗'];
continue;
}
$newName = uniqid('multi_', true) . '.' . $ext;
if (move_uploaded_file($tmpName, $targetDir . $newName)) {
$results['success'][] = $newName;
} else {
$results['failed'][] = ['name' => $name, 'error' => '移動失敗'];
}
}
echo json_encode($results, JSON_UNESCAPED_UNICODE);
?>
finfo_file() 偵測真實類型。.htaccess / Nginx 規則禁止腳本執行。max_input_time、max_execution_time。client_max_body_size 與 proxy_request_buffering off。