setOption)。$_FILES 陣列結構,批量處理。YmdHis + rand(0,999) + 原擴展名。mkdir(0755) + is_writable。 $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-4 | PHP 內置 | 上傳過大、部分上傳、無文件等 |
| -1 | 業務 | 類型不在白名單 |
| -2 | 業務 | 超過類配置 maxsize |
| -3 | 業務 | move_uploaded_file 失敗 |
| -4 | 業務 | mkdir 失敗 |
| -5 | 業務 | 未配置 filepath |
| -6 | 業務 | 非 is_uploaded_file (偽造上傳) |
private,子類無法擴展,建議改 protected 並提供 getter/setter。false + 狀態碼,現代風格應拋出 UploadException 並攜帶結構化錯誤對象。finfo_file 真實類型驗證。RandName 同秒衝突概率較大,建議 uniqid('', true) 或 hash('sha256', microtime(true).random_bytes(16))。StorageAdapterInterface,支持本地、S3、OSS 切換。這是一個典型的 PHP 5 時代「大而全」上傳類,封裝了從配置到錯誤提示的完整鏈路。閱讀其代碼有助於理解上傳業務的核心環節:路徑準備 → 合法性驗證 → 命名策略 → 移動存儲 → 結果返回。現代項目建議基於 PSR-7/PSR-17 重構,或直接使用成熟組件(如 league/flysystem + symfony/http-foundation)。