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

PHP 文件上傳類庫設計模式解析:配置驅動、鏈式調用與安全防護

     編輯:PHP 相關知識

類結構設計亮點

class UploadX {
    // 公開配置屬性 (支持鏈式/直接賦值)
    public $uploadx_form = 'attach';      // 表單字段名
    public $uploadx_save = 'temp';        // 保存目錄
    public $uploadx_type = 'jpg|gif|png|swf|flv|rar|7z|zip|doc|docx|ppt|pptx|xls|xlsx|txt|pdf|wav|mp3|wma|rm|rmvb|wmv';
    public $uploadx_size = '1024';        // KB
    public $uploadx_name;                 // 自定義文件名 (不含擴展名)

    // 內部狀態
    private $uploadx_info = false;
    public $file = [];                    // 結果數組

    public function __construct() { /* 初始化默認值 */ }

    // 核心方法
    public function file() { /* 執行上傳邏輯,返回 bool,結果存入 $this->file */ }
    private function mkdirs($path, $mode = 0777) { /* 遞歸創建目錄 */ }
}

核心流程解析 (file() 方法)

  1. 字段存在性檢查:驗證 $_FILES[$this->uploadx_form] 是否存在。
  2. 上傳錯誤碼處理:switch ($_FILES[...]['error']) 覆蓋 UPLOAD_ERR_INI_SIZE、UPLOAD_ERR_FORM_SIZE、UPLOAD_ERR_PARTIAL、UPLOAD_ERR_NO_FILE。
  3. 擴展名白名單驗證:pathinfo(..., PATHINFO_EXTENSION) 獲取後綴,stripos($this->uploadx_type, $ext) 匹配 (管道分隔字符串)。
  4. 文件大小驗證:round($_FILES[...]['size']/1024) > $this->uploadx_size (KB 比較)。
  5. 目錄準備:mkdirs() 遞歸創建,支持相對/絕對路徑,自動拼接 $_SERVER['DOCUMENT_ROOT']。
  6. 文件名生成:優先使用 $this->uploadx_name + 原擴展名,否則保留原文件名。
  7. 移動文件:move_uploaded_file(),失敗檢查路徑/權限。
  8. 權限設置:chmod(..., 0777)。
  9. 構造結果數組:包含 file(bool), name, path, size, type, time, info。

使用示例

<?php
require_once './libs/uploadx.php';
$upx = new UploadX();
$upx->uploadx_form = 'swfile';
$upx->uploadx_save = "upload";
$upx->uploadx_size = "1024"; // 1MB
$upx->uploadx_name = time(); // 文件名: timestamp.ext

if ($upx->file()) {
    echo "上傳成功<br/>";
    echo "名稱→{$upx->file['name']}<br/>";
    echo "路徑→{$upx->file['path']}<br/>";
    echo "大小→{$upx->file['size']}<br/>";
    echo "類型→{$upx->file['type']}<br/>";
    echo "時間→{$upx->file['time']}<br/>";
    echo "結果→{$upx->file['info']}<br/>";
} else {
    echo $upx->file['info'];
}
?>

設計模式與原則應用

安全性審計與加固建議

模式/原則體現位置優勢
建造者/配置模式公開屬性 + file() 執行靈活配置,關注點分離,易於擴展驗證規則
模板方法file() 定義驗證流程骨架子類可重寫單個驗證步驟 (如改用 MIME 檢測)
單一職責目錄創建剝離至 mkdirs()復用性強,易測試
失敗快速每步驗證失敗即 return false避免無效操作,錯誤信息精準
結果對象統一 $this->file 數組調用者無需處理異常,統一檢查 file 鍵

現代化重構方向 (PSR-12 / PHP 8.1+)

declare(strict_types=1);

namespace App\Storage;

use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints as Assert;

class FileUploader {
    private string $targetDirectory;
    private array $allowedMimeTypes;
    private int $maxSizeBytes;
    private ?string $customNameGenerator = null;

    public function __construct(
        string $targetDirectory,
        array $allowedMimeTypes = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'],
        int $maxSizeBytes = 2_000_000
    ) {
        $this->targetDirectory = rtrim($targetDirectory, '/');
        $this->allowedMimeTypes = $allowedMimeTypes;
        $this->maxSizeBytes = $maxSizeBytes;
    }

    public function setNameGenerator(callable $generator): self {
        $this->customNameGenerator = $generator;
        return $this;
    }

    public function upload(UploadedFile $file): UploadResult {
        // 1. 原生錯誤碼
        if ($file->getError() !== UPLOAD_ERR_OK) {
            throw new UploadException($file->getErrorMessage(), $file->getError());
        }

        // 2. 真實 MIME 驗證
        $mime = $file->getMimeType();
        if (!in_array($mime, $this->allowedMimeTypes, true)) {
            throw new UploadException("MIME 類型不允許: $mime");
        }

        // 3. 大小驗證
        if ($file->getSize() > $this->maxSizeBytes) {
            throw new UploadException("文件過大: {$file->getSize()} > {$this->maxSizeBytes}");
        }

        // 4. 目錄準備
        if (!is_dir($this->targetDirectory)) {
            mkdir($this->targetDirectory, 0755, true);
        }

        // 5. 生成安全文件名
        $ext = $file->guessExtension() ?? 'bin';
        $name = $this->customNameGenerator 
            ? ($this->customNameGenerator)($file) 
            : bin2hex(random_bytes(16));
        $safeName = "{$name}.{$ext}";
        $targetPath = $this->targetDirectory . '/' . $safeName;

        // 6. 移動 (內部已處理安全檢查)
        $file->move($this->targetDirectory, $safeName);

        // 7. 權限
        chmod($targetPath, 0644);

        return new UploadResult($safeName, $targetPath, $file->getSize(), $mime, time());
    }
}

readonly class UploadResult {
    public function __construct(
        public string $name,
        public string $path,
        public int $size,
        public string $mimeType,
        public int $timestamp
    ) {}
}

總結

UploadX 類展示了經典 PHP 過程式風格的配置驅動上傳封裝,邏輯清晰、功能完整。但面對現代安全威脅 (MIME 偽造、文件名注入、權限過寬) 及 PSR/嚴格類型標準,建議遷移至基於 UploadedFile 物件、強類型、異常驅動、依賴注入友好的現代組件,或直接使用成熟庫 (如 league/flysystem, symfony/http-foundation)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
風險點當前代碼加固方案
擴展名繞過僅檢查擴展名字符串必須 使用 finfo_file() 檢測真實 MIME;雙重驗證。
文件名注入直接使用 $this->uploadx_name . '.' . $ext文件名白名單字符 ([a-zA-Z0-9_-]);或強制 UUID/時間戳命名。
目錄遍歷mkdirs 拼接 DOCUMENT_ROOT規範化路徑 realpath();確保最終路徑在允許根目錄內。
權限過寬chmod 0777 / mkdir 0777目錄 0755,文件 0644;Web 服務器用戶組最小權限。
大小單位混淆配置 1024 實為 KB,代碼 round(bytes/1024)統一使用位元組;常量 MAX_SIZE = 1024 * 1024 (1MB)。
覆蓋風險同名文件直接覆蓋生成唯一名 (UUID/時間戳+隨機數);或檢查存在則重命名。
類型混淆uploadx_type 字符串匹配改為數組 ['jpg','png',...],嚴格 in_array。