表单必须设enctype="multipart/form-data",否则$_FILES为空;FileType字段需设'required'=>true触发验证;move()失败主因是目录权限或路径问题,非代码错误。

直接用 UploadedFile::move() 就能完成上传,但绝大多数失败不是代码写错,而是 PHP 配置、目录权限或前端表单属性漏设导致的。
表单必须带 enctype="multipart/form-data"
这是最常被忽略的前提。没有它,$_FILES 为空,Symfony 拿不到任何上传数据。
- HTML 表单里必须显式声明:
<form enctype="multipart/form-data"> - 用
FormBuilder构建表单时,框架会自动加,但手动写 Twig 模板时容易漏 - 检查浏览器开发者工具 Network 标签页:如果请求 Payload 里看不到
file字段,八成是这个原因
FileType 字段要配 'required' => true 才触发验证
验证器(如 File 约束)默认只在字段“被提交且非空”时运行。如果没传文件,又没设 required,整个验证就跳过了。
- 正确写法:
$builder->add('avatar', FileType::class, ['required' => true]) - 验证规则要放在表单类型里,不是实体类上(实体接收的是路径,不是
UploadedFile) - 允许空上传?那就设
'required' => false,再配合NotBlank+File组合控制逻辑 - 模板中渲染错误要用
{{ form_errors(form.avatar) }},否则看不到提示
move() 失败几乎全是权限或路径问题
UploadedFile->move($dir, $name) 底层调的是 PHP 的 move_uploaded_file(),失败时抛出 “The file could not be uploaded”,90% 和 Symfony 无关。
- 目标目录必须存在:
mkdir -p public/uploads/avatar - Web 服务器用户(如
www-data)要有写权限:chown -R www-data:www-data public/uploads - 别用
$file->getRealPath()—— 那是临时路径,请求一结束就删了 - 生成安全文件名别依赖
guessExtension(),它靠 MIME 推断,不可靠;建议用pathinfo($file->getClientOriginalName(), PATHINFO_EXTENSION)+ 白名单过滤
大文件上传得绕过 PHP 默认限制
PHP 默认 upload_max_filesize=2M,post_max_size=8M,Nginx 还有 client_max_body_size。超了就 413 或白屏,根本进不了控制器。
- 改
php.ini:upload_max_filesize = 20M、post_max_size = 22M(后者要比前者略大),改完重启 PHP-FPM - Nginx 用户必须同步加:
client_max_body_size 20M,放在http、server或location块里都行 - 大文件(>50MB)建议走分片上传:前端切片 + 后端用 Redis 记录已收分片 + 最后合并,
UploadedFile本身不支持流式处理
真正麻烦的从来不是 move 这一行代码,而是 upload_tmp_dir 是否可写、目标目录权限是否对、Nginx 是否截断了请求体——这些地方一卡,连 debug 日志都打不出来。


















