將檔案上傳邏輯封裝為可重用類別,能有效減少控制器層的樣板程式碼,並統一錯誤處理流程。本文提供一個輕量級 UploadClass,具備目錄自動建立、檔案大小限制、唯一檔名生成、上傳狀態追蹤等核心功能,並修正原始碼中變數作用域與邏輯判斷的潛在問題。
UploadClass.php<?php
declare(strict_types=1);
class UploadClass
{
private string $location; // 儲存目錄
private int $maxSize; // 最大允許位元組數
private string $status; // 狀態:success / failure / too_large / empty
private string $filename; // 最終儲存檔名
public function __construct(array $file, string $location = './temp/', int $maxSize = 1000000)
{
$this->location = rtrim($location, '/') . '/';
$this->maxSize = $maxSize;
$this->status = 'success';
$this->filename = '';
// 確保目錄存在
if (!is_dir($this->location)) {
if (!mkdir($this->location, 0755, true)) {
$this->status = 'dir_create_failed';
return;
}
}
// 檔案為空直接返回
if (empty($file['name']) || $file['error'] !== UPLOAD_ERR_OK) {
$this->status = 'empty';
return;
}
// 檔案大小檢查
if ($file['size'] > $this->maxSize) {
$this->status = 'too_large';
return;
}
// 生成唯一檔名:前綴_時間戳_隨機數.副檔名
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$this->filename = sprintf('%s_%d_%d.%s', 'up', time(), mt_rand(10000, 99999), $ext);
// 移動檔案
$dest = $this->location . $this->filename;
if (!move_uploaded_file($file['tmp_name'], $dest)) {
$this->status = 'move_failed';
$this->filename = '';
}
}
public function getFilename(): string
{
return $this->filename;
}
public function getStatus(): string
{
return $this->status;
}
public function isSuccess(): bool
{
return $this->status === 'success';
}
public function getFullPath(): string
{
return $this->location . $this->filename;
}
}
<?php
require_once 'UploadClass.php';
header('Content-Type: application/json; charset=utf-8');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
exit(json_encode(['error' => 'Method not allowed']));
}
$uploader = new UploadClass($_FILES['file'] ?? [], __DIR__ . '/uploads/', 2 * 1024 * 1024); // 2MB
if ($uploader->isSuccess()) {
echo json_encode([
'success' => true,
'filename' => $uploader->getFilename(),
'path' => $uploader->getFullPath(),
'url' => '/uploads/' . $uploader->getFilename() // 前端可直接訪問的 URL
]);
} else {
$messages = [
'empty' => '未選擇檔案或上傳錯誤',
'too_large' => '檔案超過 2MB 限制',
'move_failed' => '檔案儲存失敗,請檢查目錄權限',
'dir_create_failed' => '無法建立上傳目錄',
];
http_response_code(400);
echo json_encode([
'success' => false,
'error' => $messages[$uploader->getStatus()] ?? '未知錯誤',
'status_code' => $uploader->getStatus()
]);
}
| 改進項目 | 原始碼問題 | 改良方案 |
|---|---|---|
| 變數作用域 | 建構子內使用區域變數 $location、$max_size 而非屬性 | 全面改用 $this->property,確保狀態保存 |
| 錯誤碼處理 | 未檢查 $_FILES['error'] | 建構子首行驗證 UPLOAD_ERR_OK |
| 狀態管理 | 單一 $event 字串,語意不明 | 引入枚舉式狀態常數,提供 isSuccess() 便利方法 |
| 檔名生成 | 直接使用 time() + rand(),極小機率衝突 | 採用 sprintf 格式化,前綴區分業務,隨機範圍擴大至 90000 |
| 回傳格式 | 直接 echo 輸出,難以整合 API | 類別專注邏輯,控制器負責 JSON 回應,關注點分離 |
setAllowedExtensions(array $exts) 白名單驗證setOverwriteBehavior(bool $allow) 控制同名檔案覆蓋策略finfo 檢測真實 MIME 類型,防止副檔名偽裝UploadClass::handleMultiple(array $files, ...)onBeforeMove、onAfterMove 供日誌、水印、縮圖等擴充此類別遵循單一職責原則、依賴注入(目錄與大小可外部配置)、狀態機設計,易於單元測試與框架整合,是中型專案檔案上傳模組的優良起點。