首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 單檔案上傳類別完整實作:驗證、重命名、目錄建立、錯誤處理

     編輯:PHP 綜合知識

類別功能清單

  • 支援單檔案上傳 (圖片/文件/任意類型)
  • 完整驗證:上傳錯誤碼、MIME 類型白名單、檔案大小限制
  • 自動建立多層目標目錄
  • 時間戳 + 原檔名重命名,避免衝突
  • 錯誤訊息集中管理 (靜態屬性)
  • 回傳最終儲存檔名,便於資料庫記錄

完整類別原始碼

<?php
/**
 * 單檔案上傳處理類別
 * 使用方式:
 * $upload = new Upload();
 * $fileName = $upload->uploadFile($_FILES['myfile'], 2 * 1024 * 1024, 'uploads/');
 * if ($fileName === false) { echo Upload::$error; }
 */
class Upload {
    // 公開靜態錯誤訊息
    public static string $error = '';

    // 允許的 MIME 類型 (可擴充)
    private array $allowedMimes = [
        'image/jpeg', 'image/jpg', 'image/png', 'image/gif',
        'image/webp', 'application/pdf',
        'application/msword',
        'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    ];

    /**
     * 執行上傳
     * @param array $file  $_FILES['field'] 陣列
     * @param int $maxSize 最大允許位元組數
     * @param string $destDir 目標目錄 (相對或絕對路徑)
     * @return string|false 成功回傳儲存檔名,失敗回傳 false
     */
    public function uploadFile(array $file, int $maxSize, string $destDir): string|false {
        // 1. 重置錯誤
        self::$error = '';

        // 2. 基本結構驗證
        if (!isset($file['error'], $file['tmp_name'], $file['name'], $file['type'], $file['size'])) {
            self::$error = '無效的上傳檔案結構';
            return false;
        }

        // 3. 上傳錯誤碼檢查
        if ($file['error'] !== UPLOAD_ERR_OK) {
            self::$error = $this->getUploadErrorMessage($file['error']);
            return false;
        }

        // 4. MIME 類型驗證
        if (!in_array($file['type'], $this->allowedMimes, true)) {
            self::$error = '不允許的檔案類型: ' . $file['type'];
            return false;
        }

        // 5. 檔案大小驗證
        if ($file['size'] > $maxSize) {
            self::$error = '檔案大小超過限制 (' . $this->formatBytes($maxSize) . ')';
            return false;
        }

        // 6. 確保目標目錄存在
        if (!$this->ensureDir($destDir)) {
            self::$error = '無法建立目標目錄: ' . $destDir;
            return false;
        }

        // 7. 產生唯一檔名
        $destName = $this->generateUniqueName($file['name']);
        $destPath = rtrim($destDir, '/\\') . DIRECTORY_SEPARATOR . $destName;

        // 8. 移動檔案
        if (!move_uploaded_file($file['tmp_name'], $destPath)) {
            self::$error = '檔案移動失敗 (權限不足或磁碟空間不足)';
            return false;
        }

        // 9. 可選:設定檔案權限
        @chmod($destPath, 0644);

        return $destName;
    }

    // --- 私有輔助方法 ---

    /** 取得上傳錯誤碼對應訊息 */
    private function getUploadErrorMessage(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               => '未知上傳錯誤 (代碼: ' . $code . ')',
        };
    }

    /** 確保目錄存在,遞迴建立 */
    private function ensureDir(string $dir): bool {
        if (is_dir($dir)) return true;
        $parent = dirname($dir);
        if ($parent !== $dir && !$this->ensureDir($parent)) return false;
        return @mkdir($dir, 0755, true);
    }

    /** 產生唯一檔名:時間戳(微秒) + 隨機字串 + 原副檔名 */
    private function generateUniqueName(string $originalName): string {
        $ext = pathinfo($originalName, PATHINFO_EXTENSION);
        $ext = $ext ? '.' . $ext : '';
        $base = pathinfo($originalName, PATHINFO_FILENAME);
        // 移除危險字元
        $base = preg_replace('/[^a-zA-Z0-9_\-\.]/', '_', $base);
        $base = mb_substr($base, 0, 50); // 限制長度
        return sprintf('%s_%s_%s%s',
            date('YmdHis'),
            substr(md5(microtime(true)), 0, 6),
            $base,
            $ext
        );
    }

    /** 格式化位元組為人類可讀 */
    private function formatBytes(int $bytes): string {
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        for ($i = 0; $bytes >= 1024 && $i < count($units) - 1; $i++) {
            $bytes /= 1024;
        }
        return round($bytes, 2) . ' ' . $units[$i];
    }

    /** 設定允許的 MIME 類型 (流暢介面) */
    public function setAllowedMimes(array $mimes): self {
        $this->allowedMimes = $mimes;
        return $this;
    }

    /** 取得目前允許的 MIME 類型 */
    public function getAllowedMimes(): array {
        return $this->allowedMimes;
    }
}

使用範例

1. HTML 表單

<form action="upload.php" method="post" enctype="multipart/form-data">
  <input type="hidden" name="MAX_FILE_SIZE" value="2097152"> <!-- 2MB 客戶端提示 -->
  <input type="file" name="avatar" accept="image/*" required>
  <button type="submit">上傳頭像</button>
</form>

2. PHP 處理 (upload.php)

<?php
require_once 'Upload.php';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $upload = new Upload()
        ->setAllowedMimes(['image/jpeg', 'image/png', 'image/gif', 'image/webp']);

    $fileName = $upload->uploadFile($_FILES['avatar'] ?? [], 2 * 1024 * 1024, 'uploads/avatars/');

    if ($fileName) {
        // 成功:記錄資料庫、回傳 JSON
        echo json_encode([
            'success' => true,
            'file'    => $fileName,
            'url'     => 'uploads/avatars/' . $fileName
        ]);
    } else {
        // 失敗
        http_response_code(400);
        echo json_encode(['success' => false, 'error' => Upload::$error]);
    }
    exit;
}
?>

進階安全性強化 (生產環境必做)

  1. 二次驗證檔案內容:MIME 可偽造,需用 finfo_file() 或 getimagesize() 驗證實際內容。
    $finfo = finfo_open(FILEINFO_MIME_TYPE);
    $realMime = finfo_file($finfo, $file['tmp_name']);
    if (!in_array($realMime, $this->allowedMimes)) { ... }
  2. 掃描惡意腳本:上傳目錄設定 php_flag engine off (Apache) 或 Nginx location ~ \.php$ { deny all; } 禁止執行。
  3. 檔名安全:generateUniqueName 已移除路徑遍歷字元,但仍建議資料庫儲存時分離儲存路徑與檔名。
  4. 限制並發與總量:Redis 計數器限制單用戶上傳頻率/總大小。
  5. 病毒掃描:整合 ClamAV 掃描上傳檔案。
  6. 雲端儲存:大型專案直接上傳至 S3/OSS/GCS,伺服器僅處理驗證與簽名。

常見問題排查

現象原因解決
$_FILES 為空表單缺少 enctype="multipart/form-data"檢查 form 標籤屬性
UPLOAD_ERR_INI_SIZEphp.ini upload_max_filesize/post_max_size 過小調整 php.ini 或 .user.ini,重啟 PHP-FPM
move_uploaded_file 失敗目標目錄權限不足、磁碟滿、open_basedir 限制檢查目錄擁有者、磁碟空間、open_basedir 設定
上傳大檔案中斷Nginx client_max_body_size、PHP max_execution_time、memory_limit調整 Nginx/PHP 相關設定
圖片上傳後無法顯示輸出圖片前有 BOM/空白/錯誤訊息檔案儲存為 UTF-8 無 BOM;圖片輸出腳本純淨

擴充:多檔案上傳支援

public function uploadMultiple(array $files, int $maxSize, string $destDir): array {
    $results = ['success' => [], 'failed' => []];
    foreach ($files['name'] as $i => $name) {
        if ($files['error'][$i] === UPLOAD_ERR_NO_FILE) continue;
        $single = [
            'name'     => $files['name'][$i],
            'type'     => $files['type'][$i],
            'tmp_name' => $files['tmp_name'][$i],
            'error'    => $files['error'][$i],
            'size'     => $files['size'][$i],
        ];
        $saved = $this->uploadFile($single, $maxSize, $destDir);
        if ($saved) {
            $results['success'][] = $saved;
        } else {
            $results['failed'][] = ['name' => $name, 'error' => self::$error];
        }
    }
    return $results;
}

此 Upload 類別結構清晰、錯誤處理完善、易於擴充多檔案/雲端儲存/安全掃描,適合直接整合至現代 PHP 專案 (Laravel, Symfony, Slim 等) 或原生專案使用。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved