ThinkPHP6.0文件上传成功需满足三条件:表单必须为POST且enctype="multipart/form-data";request()->file()才能获取有效对象;推荐用Filesystem::putFile()替代move()以自动校验、重命名并防路径穿越。

ThinkPHP6.0 文件上传不是“调个方法就行”,关键在于三件事:表单是否合规、request()->file() 是否能取到有效对象、后续保存逻辑是否绕过常见陷阱。直接用 move() 而不校验或捕获异常,90% 的上传失败都卡在这一步。
表单和请求必须满足两个硬条件
缺一不可,否则 request()->file('xxx') 永远返回 null 或抛出异常:
- HTTP 方法必须是
POST(GET请求下$_FILES根本不会被 PHP 解析) -
<form>必须显式声明enctype="multipart/form-data";漏写或写成application/x-www-form-urlencoded会导致整个文件域数据丢失
如果这两个条件不满足,request()->file('img') 不会报错,而是静默返回 null——很多开发者卡在这里反复调试却没检查表单源码。
request()->file() 返回 null 或抛异常的真正含义
它不是“没选文件”,而是“请求层面已无法识别上传内容”。常见对应关系:
立即学习“PHP免费学习笔记(深入)”;
-
null→ 不是 POST 请求,或未设置enctype,或字段名拼写错误(比如前端是name="avatar",后端却写file('image')) -
think\Exception→ 文件超限(upload File size exceeds the maximum value)、临时目录不可写、PHP 配置upload_max_filesize或post_max_size过小
别依赖 is_null($file) 就判定“用户没传”,先确保请求结构正确。建议在控制器开头加一行日志:Log::info('Request method: ' . $this->request->method() . ', enctype: ' . $this->request->header('content-type'));
用 Filesystem::putFile() 代替 move() 更安全
move() 是底层操作,不自动重命名、不校验 MIME 类型、不处理路径穿越风险;而 Filesystem::disk('public')->putFile() 封装了这些:
- 自动为文件生成唯一哈希名,避免同名覆盖
- 默认启用真实类型校验(比如防止把
.php改后缀伪装成.jpg) - 支持链式调用
validate(),例如->validate(['size' => 2048000, 'ext' => 'png,jpg']),失败时抛ValidateException - 保存路径基于磁盘配置,天然适配软链接(如
php think storage:link后可通过/storage/xxx.jpg访问)
注意:putFile() 的第二个参数是目录名(如 'avatars'),不是完整路径;它最终落在 public/storage/avatars/xxx.png,不是 public/uploads/ —— 别手动建 uploads 目录还硬塞进去。
多文件上传要改前端 name 属性,后端代码几乎不变
前端必须用数组式命名:<input type="file" name="photos[]" multiple>,否则 request()->file('photos') 只拿到第一个文件。
后端处理时,request()->file('photos') 返回的是 array(不是单个 File 对象),遍历即可:
$files = request()->file('photos');
foreach ($files as $file) {
try {
$path = \think\facade\Filesystem::disk('public')->putFile('gallery', $file);
// 记录 $path
} catch (\think\exception\ValidateException $e) {
// 单个文件失败,不影响其余
}
}
别对数组整体调 validate(),那会校验整个数组对象而非每个文件——这是初学者高频误点。
最易被忽略的是:上传成功后返回的 $path 是相对路径(如 gallery/abc123.png),要拼上磁盘配置里的 url 才能生成可访问的 URL;而这个 url 默认是 /storage,不是 /public 或根目录——漏掉这步,前端拿到路径也打不开图。



















