文件上傳是 Web 表單的高頻功能,正確判斷用戶是否選擇了文件、文件是否合法,關係到業務流程順暢與服務器安全。本文梳理 PHP 後端判斷邏輯、前端預檢方案、多文件場景處理,並指出常見誤區。
PHP 通過 $_FILES 超全局陣列接收上傳信息。關鍵在於檢查 ['error'] 狀態碼與 ['tmp_name'] 是否存在。
<?php
/**
* 判斷單文件上傳是否有效
* @param string $fieldName 表單 input name 屬性值
* @return array ['success'=bool, 'message'=string, 'file'=array|null]
*/
function validateSingleUpload(string $fieldName): array
{
// 1. 檢查字段是否存在
if (!isset($_FILES[$fieldName])) {
return ['success' => false, 'message' => '表單缺少文件字段'];
}
$file = $_FILES[$fieldName];
// 2. 檢查上傳錯誤碼(最關鍵!)
// UPLOAD_ERR_OK (0) 表示成功
if ($file['error'] !== UPLOAD_ERR_OK) {
$errors = [
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 擴展阻止',
];
return ['success' => false, 'message' => $errors[$file['error']] ?? '未知上傳錯誤'];
}
// 3. 檢查臨時文件是否真實存在(防偽造)
if (!is_uploaded_file($file['tmp_name'])) {
return ['success' => false, 'message' => '非法上傳文件'];
}
// 4. 可選:檢查文件大小、MIME、擴展名等業務規則
// if ($file['size'] > 2 * 1024 * 1024) return ['success' => false, 'message' => '文件超過 2MB'];
return ['success' => true, 'message' => '驗證通過', 'file' => $file];
}
// 使用示例
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$result = validateSingleUpload('file');
if ($result['success']) {
// 移動到永久存儲
$dest = __DIR__ . '/uploads/' . basename($result['file']['name']);
move_uploaded_file($result['file']['tmp_name'], $dest);
echo "上傳成功:$dest";
} else {
echo "失敗:" . $result['message'];
}
}
?>
if (!empty($_FILES['file']['tmp_name'])):不可靠。即使用戶未選文件,PHP 仍會創建 tmp_name 鍵(值為空字符串),且 error=UPLOAD_ERR_NO_FILE (4)。必須檢查 error 碼。if ($_FILES['file']['size'] > 0):同理,無文件時 size 為 0,但無法區分「用戶未選」與「文件為空」。$_FILES['file']['type']:瀏覽器根據擴展名猜測,極易偽造。務必使用 finfo_file() 或 mime_content_type() 解析實際內容。<!-- HTML -->
<form id="uploadForm" enctype="multipart/form-data">
<input type="file" id="fileInput" name="file" accept="image/*,.pdf" required>
<button type="submit" id="submitBtn">上傳</button>
</form>
<script>
// 原生 JS 驗證
document.getElementById('uploadForm').addEventListener('submit', function(e) {
const fileInput = document.getElementById('fileInput');
if (fileInput.files.length === 0) {
e.preventDefault();
alert('請選擇文件');
return;
}
// 可選:客戶端大小/類型預檢
const file = fileInput.files[0];
if (file.size > 2 * 1024 * 1024) {
e.preventDefault();
alert('文件不得超過 2MB');
}
});
// 或使用 jQuery(若項目已引入)
$('#uploadForm').on('submit', function() {
return $('#fileInput').val().length > 0 || (alert('請選擇文件'), false);
});
</script>
HTML:<input type="file" name="files[]" multiple>
<?php
function validateMultipleUpload(string $fieldName): array
{
if (!isset($_FILES[$fieldName])) {
return ['success' => false, 'message' => '缺少字段'];
}
$files = $_FILES[$fieldName];
$validFiles = [];
$errors = [];
// $_FILES 結構為:['name'=>[...], 'tmp_name'=>[...], 'error'=>[...], ...]
// 需重組為逐文件結構
$count = count($files['name']);
for ($i = 0; $i < $count; $i++) {
$file = [
'name' => $files['name'][$i],
'tmp_name' => $files['tmp_name'][$i],
'error' => $files['error'][$i],
'size' => $files['size'][$i],
'type' => $files['type'][$i],
];
if ($file['error'] === UPLOAD_ERR_OK && is_uploaded_file($file['tmp_name'])) {
$validFiles[] = $file;
} else {
$errors[] = "文件 {$file['name']}: " . ($file['error'] === UPLOAD_ERR_NO_FILE ? '未選擇' : "錯誤碼 {$file['error']}");
}
}
return [
'success' => !empty($validFiles),
'valid_files' => $validFiles,
'errors' => $errors,
];
}
// 使用
$result = validateMultipleUpload('files');
if ($result['success']) {
foreach ($result['valid_files'] as $file) {
move_uploaded_file($file['tmp_name'], __DIR__ . '/uploads/' . $file['name']);
}
echo "成功上傳 " . count($result['valid_files']) . " 個文件";
}
if (!empty($result['errors'])) {
echo "部分失敗:" . implode('; ', $result['errors']);
}
?>
<script>
$('#uploadForm').on('submit', function() {
const files = $('input[name="files[]"]')[0].files;
if (files.length === 0) {
alert('至少選擇一個文件');
return false;
}
let hasValid = false;
for (let f of files) {
if (f.size > 0) { hasValid = true; break; }
}
if (!hasValid) {
alert('所選文件均為空');
return false;
}
return true; // 允許提交
});
</script>
| 層面 | 關鍵點 |
|---|---|
| 後端必檢 | $_FILES['xxx']['error'] === UPLOAD_ERR_OK + is_uploaded_file() |
| 後端建議檢 | 文件大小、MIME 類型、擴展名白名單、文件名安全化 |
| 前端預檢 | required 屬性 + JS 檢查 files.length / size |
| 多文件 | 重組 $_FILES 結構,逐個驗證,收集錯誤信息 |
| 存儲安全 | 隨機文件名、存放於 Web 目錄外、執行權限禁止、病毒掃描 |