直接用 move_uploaded_file 不够用,因其无法处理上传失败提示、并发冲突、恶意文件拦截、路径创建、重命名、错误映射等真实需求,且裸写逻辑难以维护和扩展。

为什么直接用 move_uploaded_file 不够用
因为真实业务里,你不能只管“把临时文件挪个地方”。上传失败时用户得知道错在哪,不是只看到白屏或 500;多人并发上传时临时目录可能冲突;用户传个 .php 文件改个后缀就想写 shell,你得拦住;还有路径拼接、重命名、目录自动创建、错误码映射……这些都得收口。裸写逻辑散在控制器里,下次加个病毒扫描或分片支持,就得全局搜 $_FILES 改十几处。
Upload 类必须支持多文件 + 单文件统一入口
前端表单可能同时有 name="avatar"(单文件)和 name="attachments[]"(多文件),而 $_FILES 的结构会完全不同:前者是二维数组,后者是五维——$_FILES['attachments']['name'][0] 才是第一个文件名。硬编码处理极易出错。
- 封装时应统一接收
$files参数,内部自动识别单/多格式,返回标准化的文件信息数组 - 避免在类里硬写
$_FILES['face']这种固定键名,改成uploadOne($file)或uploadAll($files)显式传入 - 对多文件,逐个调用
checkError()并收集错误,而不是遇到第一个失败就中断 - 示例调用:
$uploader->uploadAll($_FILES['documents']),不关心它是不是带[]
校验顺序和内容决定安全性底线
很多封装类先检查扩展名,再读 MIME,最后看大小——这个顺序一错,攻击者就能绕过。比如上传一个 shell.jpg.php,你只截取 pathinfo($name, PATHINFO_EXTENSION) 得到 php,但没用,因为浏览器发的 Content-Type 是 image/jpeg,你又只信这个,就漏了。
- 必须第一步查
$file['error'],非UPLOAD_ERR_OK(即0)直接拒收,不走后续任何逻辑 - 第二步用
finfo_open(FILEINFO_MIME_TYPE)读临时文件真实类型,不是$_FILES['type'](可伪造) - 第三步限制大小,注意
post_max_size和upload_max_filesize是 PHP 层硬限,类里校验只是补救 - 第四步才是扩展名白名单,且必须和 MIME 类型双重匹配,例如
image/png只允许.png,不允许.jpg
路径、命名、存储策略要解耦,别写死
示例代码里常见 "./uploads/{date}/".uniqid().".jpg" ——这在上线后会出问题:日期目录爆满、uniqid() 不防重、没考虑 Windows 路径分隔符、没做 is_writable() 检查。更麻烦的是,一旦要切到 OSS 或 S3,整个类得重写。
- 上传路径应作为构造参数传入,或通过配置驱动,不要拼接字符串硬编码
- 文件名生成逻辑单独抽成方法,如
generateFilename($originalName, $mime),方便替换成 UUID 或哈希 - 存储动作抽象为接口(哪怕暂不实现),比如
store($tmpPath, $finalPath),未来可替换为putObject()调用 - 务必检查目标目录是否存在且可写:
!is_dir($dir) && !mkdir($dir, 0755, true)要连用,true表示递归创建
最易被忽略的一点:move_uploaded_file() 成功后,临时文件会被清掉,但失败时不会自动清理。如果校验中途退出(比如 MIME 不合法),那个 $file['tmp_name'] 还躺在 /tmp 里,积少成多会占满磁盘。封装类里必须确保无论成功失败,都明确处理临时文件生命周期。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











