upload() 方法。$_FILES['file'])與多檔($_FILES['files'] 陣列)。<?php
class UploadHandler {
public $allowed_types = ['jpg','jpeg','png','gif','pdf','zip'];
public $max_size = 2 * 1024 * 1024; // 2MB
public $save_dir = 'uploads/';
public $overwrite = false;
public function __construct($config = []) {
foreach ($config as $k => $v) {
if (property_exists($this, $k)) $this->$k = $v;
}
if (!is_dir($this->save_dir)) {
mkdir($this->save_dir, 0755, true);
}
}
// 統一入口:自動判斷單檔/多檔
public function upload($field_name) {
if (!isset($_FILES[$field_name])) {
return ['error' => '未接收到上傳欄位'];
}
$file = $_FILES[$field_name];
// 多檔判斷:name 為陣列
if (is_array($file['name'])) {
return $this->uploadMultiple($file);
} else {
return [$this->processSingle($file)];
}
}
private function uploadMultiple($file) {
$results = [];
$count = count($file['name']);
for ($i = 0; $i < $count; $i++) {
if ($file['error'][$i] === UPLOAD_ERR_NO_FILE) continue;
$single = [
'name' => $file['name'][$i],
'type' => $file['type'][$i],
'tmp_name' => $file['tmp_name'][$i],
'error' => $file['error'][$i],
'size' => $file['size'][$i]
];
$results[] = $this->processSingle($single);
}
return $results;
}
private function processSingle($file) {
$info = ['original_name' => $file['name']];
// 錯誤代碼檢查
if ($file['error'] !== UPLOAD_ERR_OK) {
$info['error'] = $this->errorMessage($file['error']);
return $info;
}
// 類型檢查(副檔名)
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($ext, $this->allowed_types)) {
$info['error'] = '檔案類型不允許';
return $info;
}
// 大小檢查
if ($file['size'] > $this->max_size) {
$info['error'] = '檔案超過最大限制';
return $info;
}
// MIME 二次驗證(建議)
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
$allowed_mimes = [
'jpg'=>'image/jpeg','jpeg'=>'image/jpeg','png'=>'image/png',
'gif'=>'image/gif','pdf'=>'application/pdf','zip'=>'application/zip'
];
if (!isset($allowed_mimes[$ext]) || $allowed_mimes[$ext] !== $mime) {
$info['error'] = '檔案內容類型不符';
return $info;
}
// 產生唯一檔名
$new_name = date('YmdHis') . '_' . bin2hex(random_bytes(8)) . '.' . $ext;
$dest = $this->save_dir . $new_name;
if (!$this->overwrite && file_exists($dest)) {
$info['error'] = '檔案已存在';
return $info;
}
if (!move_uploaded_file($file['tmp_name'], $dest)) {
$info['error'] = '移動檔案失敗';
return $info;
}
$info['saved_name'] = $new_name;
$info['saved_path'] = $dest;
$info['size'] = $file['size'];
$info['mime'] = $mime;
$info['error'] = '';
return $info;
}
private function errorMessage($code) {
static $msgs = [
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 擴充阻擋',
];
return $msgs[$code] ?? '未知錯誤';
}
}
?>
<?php
require 'UploadHandler.php';
$uploader = new UploadHandler([
'allowed_types' => ['jpg','png','gif'],
'max_size' => 5 * 1024 * 1024,
'save_dir' => 'data/uploads/'
]);
// 前端
$results = $uploader->upload('photos');
foreach ($results as $r) {
if ($r['error']) {
echo "失敗:{$r['original_name']} - {$r['error']}<br>";
} else {
echo "成功:{$r['original_name']} → {$r['saved_name']} ({$r['size']} bytes)<br>";
}
}
?>
第 2 部分將介紹:圖片自動縮圖(GD/Imagick)、上傳進度條(Session upload progress)、雲端儲存對接(S3/OSS)、資料庫記錄與軟刪除機制。