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

PHP 經典文件上傳類:配置驅動、多文件支持、完整錯誤碼體系

     編輯:PHP 綜合知識

類設計亮點

  • 配置驅動:構造函數接收關聯數組,動態設置屬性(setOption)。
  • 多文件上傳:自動識別 $_FILES 陣列結構,批量處理。
  • 錯誤碼體系:整合 PHP 內置錯誤碼 (1-4) 與自定義業務碼 (-1 到 -6),統一輸出 HTML 格式提示。
  • 隨機文件名:YmdHis + rand(0,999) + 原擴展名。
  • 目錄自建與權限檢查:mkdir(0755) + is_writable。

FileUpload 類完整代碼

 $val) {
            if (in_array($key, get_class_vars(get_class($this)))) {
                $this->setOption($key, $val);
            }
        }
    }

    private function setOption($key, $val)
    {
        $key = strtolower($key);
        $this->$key = $val;
    }

    // ========== 主入口 ==========
    public function uploadfile($field)
    {
        if (!$this->CheckPath()) {
            $this->errormsg = $this->getErrorNum();
            return false;
        }

        $name = $_FILES[$field]['name'];
        $tmp  = $_FILES[$field]['tmp_name'];
        $size = $_FILES[$field]['size'];
        $err  = $_FILES[$field]['error'];

        // 多文件
        if (is_array($name)) {
            $errors = [];
            for ($i=0; $igetFile($name[$i], $tmp[$i], $size[$i], $err)) {
                    $errors[] = $this->getErrorNum();
                }
            }
            if ($errors) { $this->errormsg = $errors; return false; }

            $fileNames = [];
            for ($i=0; $igetFile($name[$i], $tmp[$i], $size[$i], $err)) {
                    $this->SetFileName();
                    if (!$this->MoveFile()) {
                        $errors[] = $this->getErrorNum();
                    } else {
                        $fileNames[] = $this->getNewName();
                    }
                }
            }
            $this->newname = $fileNames;
            $this->errormsg = $errors;
            return empty($errors);
        }

        // 單文件
        if ($this->getFile($name, $tmp, $size, $err)) {
            if (!$this->CheckSize()) return false;
            if (!$this->CheckType()) return false;
            $this->SetFileName();
            return $this->MoveFile();
        }
        return false;
    }

    // ========== 核心步驟 ==========
    private function getFile($name, $tmpname, $filesize, $error=0)
    {
        $this->setOption('TmpName', $tmpname);
        $this->setOption('OrgiName', $name);
        $ext = pathinfo($name, PATHINFO_EXTENSION);
        $this->setOption('FileType', strtolower($ext));
        $this->setOption('FileSize', $filesize);
        return true;
    }

    private function CheckPath()
    {
        if (empty($this->filepath)) { $this->setOption('ErrorNum', -5); return false; }
        if (!file_exists($this->filepath) || !is_writable($this->filepath)) {
            if (!@mkdir($this->filepath, 0755, true)) {
                $this->setOption('ErrorNum', -4);
                return false;
            }
        }
        return true;
    }

    private function CheckSize()
    {
        if ($this->filesize > $this->maxsize) {
            $this->setOption('ErrorNum', -2);
            return false;
        }
        return true;
    }

    private function CheckType()
    {
        if (in_array($this->filetype, $this->allowtype)) return true;
        $this->setOption('ErrorNum', -1);
        return false;
    }

    private function SetFileName()
    {
        if ($this->israndname) {
            $this->setOption('NewName', $this->RandName());
        } else {
            $this->setOption('NewName', $this->orginame);
        }
    }

    private function RandName()
    {
        return date("YmdHis") . rand(0,999) . '.' . $this->filetype;
    }

    private function MoveFile()
    {
        if ($this->errornum) return false; // 上一步有錯
        $dest = rtrim($this->filepath, '/') . '/' . $this->newname;
        if (@move_uploaded_file($this->tmpname, $dest)) {
            return true;
        } else {
            $this->setOption('ErrorNum', -3);
            return false;
        }
    }

    // ========== 對外接口 ==========
    public function getNewName() { return $this->newname; }
    public function getErrorMsg() { return $this->errormsg; }

    // ========== 錯誤碼映射 ==========
    private function getErrorNum()
    {
        $base = "上傳文件 {$this->orginame} 出錯: ";
        $map = [
            4  => '沒有文件被上傳',
            3  => '文件只被部分上傳',
            2  => '超過表單 MAX_FILE_SIZE',
            1  => '超過 php.ini upload_max_filesize',
            0  => '上傳成功',
            -1 => '未允許的類型',
            -2 => "文件過大,不能超過 {$this->maxsize} 字節",
            -3 => '上傳失敗',
            -4 => '創建上傳目錄失敗',
            -5 => '未指定上傳路徑',
            -6 => '非法操作 (非 HTTP POST 上傳)',
        ];
        return $base . ($map[$this->errornum] ?? '未知錯誤');
    }
}
?>

使用示例

// 1. 實例化配置
$upload = new FileUpload([
    'filepath'   => './data/uploads/',
    'allowtype'  => ['jpg','jpeg','png','gif','pdf'],
    'maxsize'    => 5 * 1024 * 1024, // 5MB
    'israndname' => true,
]);

// 2. 執行上傳 (表單字段名 'file')
if ($upload->uploadfile('file')) {
    $names = $upload->getNewName();
    is_array($names) ? print_r($names) : echo $names;
} else {
    echo $upload->getErrorMsg();
}

錯誤碼對照表

代碼來源含義
1-4PHP 內置上傳過大、部分上傳、無文件等
-1業務類型不在白名單
-2業務超過類配置 maxsize
-3業務move_uploaded_file 失敗
-4業務mkdir 失敗
-5業務未配置 filepath
-6業務非 is_uploaded_file (偽造上傳)

遺留問題與現代化改造建議

  1. 屬性可見性:全部 private,子類無法擴展,建議改 protected 並提供 getter/setter。
  2. 錯誤處理:返回 false + 狀態碼,現代風格應拋出 UploadException 並攜帶結構化錯誤對象。
  3. MIME 偽裝:僅檢查擴展名,需增加 finfo_file 真實類型驗證。
  4. 並發安全:RandName 同秒衝突概率較大,建議 uniqid('', true) 或 hash('sha256', microtime(true).random_bytes(16))。
  5. 存儲抽象:引入 StorageAdapterInterface,支持本地、S3、OSS 切換。
  6. 分片上傳:大文件需支持分片、斷點續傳、秒傳(MD5 索引)。

總結

這是一個典型的 PHP 5 時代「大而全」上傳類,封裝了從配置到錯誤提示的完整鏈路。閱讀其代碼有助於理解上傳業務的核心環節:路徑準備 → 合法性驗證 → 命名策略 → 移動存儲 → 結果返回。現代項目建議基於 PSR-7/PSR-17 重構,或直接使用成熟組件(如 league/flysystem + symfony/http-foundation)。

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