Symfony文件上传需用FileType定义表单、控制器中调用moveTo()保存,配置uploads_dir路径并确保写权限,模板中必须设enctype="multipart/form-data"。

用 Symfony 创建文件上传功能,核心在于处理用户提交的 multipart/form-data 请求、验证文件合法性、安全存储到指定位置,并避免内存溢出或路径遍历风险。这一步必须在控制器中显式调用 moveTo(),否则临时文件会被 PHP 自动清理。
定义带文件字段的表单
使用 FormType 类声明文件字段,禁用数据转换器,确保原始 UploadedFile 对象被传递到控制器:
创建 src/Form/DocumentUploadType.php:
```php
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints\File;
class DocumentUploadType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('file', FileType::class, [
'label' => '请选择文件',
'constraints' => [
new File([
'maxSize' => '5M',
'mimeTypes' => [
'application/pdf',
'image/jpeg',
'image/png'
],
'mimeTypesMessage' => '只允许上传 PDF 或图片文件'
])
]
]);
}
}
注意:不能用 TextType 或 HiddenType 替代 FileType,否则无法触发浏览器文件选择控件。
在控制器中接收并保存文件
第一步:注入 ParameterBag 获取 uploads_dir 配置路径;第二步:调用 getClientOriginalName() 获取原始文件名;第三步:用 uniqid() + 原扩展名生成唯一安全文件名;第四步:【必须调用 $file->move($targetDir, $newFilename)】,否则文件仅存在于临时目录且请求结束即销毁。
```php
use App\Form\DocumentUploadType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
#[Route('/upload', name: 'app_upload')]
class UploadController extends AbstractController
{
#[Route('', name: 'app_upload_form', methods: ['GET'])]
public function showForm(): Response
{
$form = $this->createForm(DocumentUploadType::class);
return $this->render('upload/form.html.twig', ['form' => $form->createView()]);
}
#[Route('', name: 'app_upload_handle', methods: ['POST'])]
public function handleUpload(Request $request): Response
{
$form = $this->createForm(DocumentUploadType::class);
$form->handleRequest($request);
if (!$form->isSubmitted() || !$form->isValid()) {
return $this->render('upload/form.html.twig', ['form' => $form->createView()]);
}
/** @var UploadedFile $uploadedFile */
$uploadedFile = $form->get('file')->getData();
// 获取配置的上传根目录(如 %kernel.project_dir%/public/uploads)
$targetDir = $this->getParameter('uploads_dir');
$originalExtension = $uploadedFile->getClientOriginalExtension();
$newFilename = uniqid().'.'.$originalExtension;
try {
$uploadedFile->move($targetDir, $newFilename);
} catch (\Exception $e) {
$this->addFlash('error', '文件保存失败:'.$e->getMessage());
return $this->redirectToRoute('app_upload_form');
}
$this->addFlash('success', '文件已上传:'.$newFilename);
return $this->redirectToRoute('app_upload_form');
}
}
配置上传目录与权限
方法一:在 config/services.yaml 中定义参数,确保路径为绝对路径且 Web 服务器有写权限:
```yaml
parameters:
uploads_dir: '%kernel.project_dir%/public/uploads'
```
方法二:在 .env 中设置环境变量,适合多环境部署:
```env
UPLOADS_DIR=%kernel.project_dir%/public/uploads
```
然后在 services.yaml 中引用:uploads_dir: '%env(UPLOADS_DIR)%'。运行前手动创建该目录并赋权:mkdir -p public/uploads && chmod 755 public/uploads。
模板中渲染文件上传表单
第一步:引入表单样式支持;第二步:确保 form 标签包含 enctype="multipart/form-data";第三步:用 {{ form_widget(form.file) }} 渲染文件输入控件;第四步:添加 CSRF token 隐藏域({{ form_rest(form) }} 会自动包含)。
templates/upload/form.html.twig:
```twig
{% extends 'base.html.twig' %}
{% block body %}
上传文档
{{ form_start(form, {'attr': {'enctype': 'multipart/form-data'}}) }}
{{ form_row(form.file) }}
{{ form_end(form) }}
{% for flash in app.flashes('success') %}
{% endfor %}
{% for flash in app.flashes('error') %}
{% endfor %}
{% endblock %}
```
这一步操作起来很简单,直接把文件拖进去就行。但若漏掉 enctype 属性,表单将无法发送文件,且无任何错误提示——浏览器静默丢弃文件字段。


















