用途:处理 Symfony 控制器已接收到的上传文件——文件存放位置、如何取回、数据库保存什么。接收上传(#[MapUploadedFile]、DTO 内文件、边界约束)由 symfony-yoandev-http 覆盖,本技能从控制器持有有效 UploadedFile 开始。
核心决策:公开(public/uploads/,由 Web 服务器或 CDN 直接提供,URL 稳定可公开缓存,适合封面、头像、公开目录图)还是受保护(绝不放在 public/ 下,使用私有桶或文档根之外,由控制器校验权限后流式返回,适合发票、合同、导出、属于单个用户的文件)。放 public/ 会绕过 access_control、#[IsGranted] 和所有 Voter;保留原始文件名会让 URL 可被猜测,且 move() 会保留客户端扩展名。任何不能确定放左侧的都放右侧,公开转受保护要改所有 URL,反向几乎零成本。
存储:本套件部署为 Docker 容器,本地磁盘在下次部署后丢失且各副本不可见,因此需持久化的文件默认用对象存储,本地磁盘仅作开发便利,Flysystem 可统一两条路径。命令: composer require league/flysystem-bundle composer require league/flysystem-async-aws-s3 async-aws/s3 composer require --dev league/flysystem-memory
实体只保存 storage key 与展示元数据(originalName、mimeType、sizeInBytes),不存字节、不存绝对路径。删除实体时不自动删文件:服务先 remove 并 flush(),最后删文件;遗漏的孤儿文件由定时命令比对键与数据库清扫。不用 Doctrine 生命周期回调。
验证:扩展名与 Content-Type 均由客户端控制,用 #[Assert\File(extensions: ['pdf'])] 做嗅探校验。#[Assert\Image] 默认接受 SVG,可能造成存储型 XSS,应写明 extensions。PHP 的 upload_max_filesize 与 post_max_size 是两个不同限制,后者超限会导致请求为空、只返回空 422 或 CSRF 错误。
限制:不覆盖图片处理(缩放、缩略图、格式转换),需另选库;Voter 写法见 security 技能;清扫命令机制见 console,调度见 async。版本事实核对于 league/flysystem 3.35、flysystem-bundle 3.7、Symfony 8.1;#[IsSignatureValid] 需 Symfony 7.4,UriSigner 需 6.4,其余在 6.4 至 8.x 可用。