PHP 檔案上傳 (File Upload)

PHP 可以處理使用者透過 HTML 表單上傳的檔案。上傳的檔案會暫存在伺服器上,PHP 腳本可以取得檔案資訊並決定如何處理它(如儲存到指定目錄、驗證類型等)。

上傳表單

要允許使用者上傳檔案,表單必須使用 POST 方法,並設定 enctype="multipart/form-data" 屬性:

<form action="upload.php" method="POST" enctype="multipart/form-data">
    <input type="file" name="myfile">
    <button type="submit">上傳</button>
</form>
表單必須設定 enctype="multipart/form-data" 才能上傳檔案。

處理上傳

上傳的檔案資訊存放在 $_FILES 超全域變數中。每個檔案欄位包含 5 個屬性:name(原始檔名)、type(MIME 類型)、tmp_name(暫存路徑)、error(錯誤碼)、size(檔案大小)。使用 move_uploaded_file() 將檔案從暫存位置移至目標位置:

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (isset($_FILES['myfile'])) {
        $file = $_FILES['myfile'];
        
        // 檔案資訊
        $name = $file['name'];        // 原始檔名
        $type = $file['type'];        // MIME 類型
        $tmpName = $file['tmp_name']; // 暫存路徑
        $error = $file['error'];      // 錯誤碼
        $size = $file['size'];        // 檔案大小(bytes)
        
        if ($error === UPLOAD_ERR_OK) {
            $destination = 'uploads/' . $name;
            move_uploaded_file($tmpName, $destination);
            echo "上傳成功!";
        }
    }
}
?>

錯誤處理

$_FILES['欄位名']['error'] 包含上傳狀態碼,UPLOAD_ERR_OK(值為 0)表示成功,其他值表示不同類型的錯誤:

<?php
$errorMessages = [
    UPLOAD_ERR_INI_SIZE   => '檔案超過 php.ini 的限制',
    UPLOAD_ERR_FORM_SIZE  => '檔案超過表單的限制',
    UPLOAD_ERR_PARTIAL    => '檔案只有部分被上傳',
    UPLOAD_ERR_NO_FILE    => '沒有檔案被上傳',
    UPLOAD_ERR_NO_TMP_DIR => '找不到暫存目錄',
    UPLOAD_ERR_CANT_WRITE => '無法寫入磁碟',
    UPLOAD_ERR_EXTENSION  => '上傳被擴展阻止',
];

if ($file['error'] !== UPLOAD_ERR_OK) {
    $message = $errorMessages[$file['error']] ?? '未知錯誤';
    die("上傳失敗:$message");
}
?>

安全驗證

處理檔案上傳時,安全性非常重要。永遠不要信任使用者提供的資料(包括檔名和 MIME 類型),應該進行多重驗證:

<?php
function uploadFile(array $file, string $uploadDir): array {
    $errors = [];
    
    // 1. 檢查錯誤
    if ($file['error'] !== UPLOAD_ERR_OK) {
        $errors[] = '上傳失敗';
        return ['success' => false, 'errors' => $errors];
    }
    
    // 2. 檢查檔案大小(5MB)
    $maxSize = 5 * 1024 * 1024;
    if ($file['size'] > $maxSize) {
        $errors[] = '檔案太大(最大 5MB)';
    }
    
    // 3. 檢查副檔名
    $allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'pdf'];
    $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
    if (!in_array($ext, $allowedExtensions)) {
        $errors[] = '不允許的檔案類型';
    }
    
    // 4. 檢查 MIME 類型
    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $mimeType = $finfo->file($file['tmp_name']);
    $allowedMimes = [
        'image/jpeg', 'image/png', 'image/gif', 'application/pdf'
    ];
    if (!in_array($mimeType, $allowedMimes)) {
        $errors[] = '檔案類型不符';
    }
    
    if (!empty($errors)) {
        return ['success' => false, 'errors' => $errors];
    }
    
    // 5. 產生安全的檔名
    $newName = bin2hex(random_bytes(16)) . '.' . $ext;
    $destination = rtrim($uploadDir, '/') . '/' . $newName;
    
    // 6. 移動檔案
    if (!move_uploaded_file($file['tmp_name'], $destination)) {
        return ['success' => false, 'errors' => ['無法儲存檔案']];
    }
    
    return [
        'success' => true,
        'filename' => $newName,
        'path' => $destination
    ];
}

// 使用
$result = uploadFile($_FILES['myfile'], 'uploads/');
if ($result['success']) {
    echo "上傳成功:" . $result['filename'];
} else {
    foreach ($result['errors'] as $error) {
        echo "錯誤:$error<br>";
    }
}
?>

多檔案上傳

<form method="POST" enctype="multipart/form-data">
    <input type="file" name="files[]" multiple>
    <button type="submit">上傳</button>
</form>
<?php
if (isset($_FILES['files'])) {
    $files = $_FILES['files'];
    $count = count($files['name']);
    
    for ($i = 0; $i < $count; $i++) {
        $file = [
            'name' => $files['name'][$i],
            'type' => $files['type'][$i],
            'tmp_name' => $files['tmp_name'][$i],
            'error' => $files['error'][$i],
            'size' => $files['size'][$i],
        ];
        
        // 處理每個檔案
        $result = uploadFile($file, 'uploads/');
    }
}
?>

php.ini 設定

這些 php.ini 設定會影響檔案上傳行為,需要根據實際需求調整:

; 最大上傳檔案大小
upload_max_filesize = 10M

; POST 資料最大大小
post_max_size = 12M

; 最大檔案上傳數量
max_file_uploads = 20

; 上傳暫存目錄
upload_tmp_dir = /tmp