Symfony 文件下载需构造含正确响应头的 Response,必须设置 Content-Type、Content-Disposition(filename 需编码)、Content-Length;大文件用 StreamedResponse 流式传输并校验路径安全。
在 Symfony 中实现文件下载,核心是构造一个带有正确响应头的
对象,让浏览器识别为“附件”并触发保存对话框。关键不在内容本身,而在头部设置是否精准、内容是否完整、路径是否可读。
设置标准下载响应头
必须显式声明三类头部:内容类型、内容处置方式、内容长度。缺一不可,否则可能下载为空或被浏览器直接打开。
Content-Type
推荐用
(通用二进制流),避免浏览器按 MIME 类型尝试渲染;也可根据文件类型设为
、
等
Content-Disposition
必须设为
,其中
值需经
或
处理中文名,防止乱码或截断
Content-Length
要准确传入文件字节数,可用
获取;若文件过大或动态生成,可省略,但会失去进度提示
安全读取并写入文件内容
不建议直接用
加载大文件到内存,易导致内存溢出。应优先使用流式响应:
小文件(如 <2MB):用
+
简洁可靠
大文件(如图片、PDF、压缩包):用
配合
+
,边读边发,内存占用恒定
务必校验文件路径合法性:检查
、
,且路径不能穿透应用目录(防范
路径遍历)
使用 StreamedResponse 处理大文件
这是生产环境推荐方式,尤其适用于用户上传的原始文件或导出报表等场景:
创建
实例,传入匿名函数作为回调
在回调中打开文件句柄,设置
清空输出缓冲,再调用
响应自动关闭连接,无需手动
或
示例:
return new StreamedResponse(function () use ($filePath) {if (file_exists($filePath) && is_readable($filePath)) {ob_end_clean();$handle = fopen($filePath, 'rb');fpassthru($handle);fclose($handle);}});
常见问题与规避方法
下载失败、0 字节、乱码、被拦截,多由细节疏漏导致:
响应前已有输出(如空格、BOM、
)→ 触发 headers already sent 错误 → 使用
检查,确保控制器无意外输出
中文文件名未编码 → 浏览器解析失败 → 改用
格式
Apache/Nginx 缓存了响应 → 下载旧版本 → 在响应头中添加
反向代理(如 Cloudflare)拦截二进制流 → 检查代理配置,临时禁用或设置
Responseapplication/octet-streamimage/jpegapplication/pdfattachment; filename="xxx"filenamerawurlencode()mb_convert_encoding()filesize($path)file_get_contents()file_get_contents()setContent()streamedResponse()fopen()fpassthru()file_exists()is_readable()../StreamedResponseob_end_clean()fpassthru()exitdievar_dumpheaders_sent()Content-Disposition: attachment; filename*=UTF-8''{encoded}Cache-Control: no-cache, no-store, must-revalidateCache-Control: private