FrankenPHP大JSON响应内存飙升的根本原因是json_encode()一次性加载全部数据,须改用StreamedJsonResponse配合数据库生成器查询实现流式输出,禁用zlib压缩并避免任何中间缓存。

FrankenPHP 生成大 JSON 响应时内存峰值飙高,根本原因不是 Symfony 或 PHP 写法差,而是默认用 json_encode() 一次性加载全部数据到内存——哪怕你只导出 10 万条记录,memory_get_peak_usage(true) 也轻松突破 300MB。必须切换到流式输出模式,否则调高 memory_limit 只是掩耳盗铃。
用 Symfony 的 StreamedJsonResponse 替代 JsonResponse
Symfony 5.4+ 原生支持流式 JSON 输出,核心是绕过 json_encode() 的全量内存构建过程,边查边序列化边发送。它不依赖第三方库,也不需要改 FrankenPHP 配置。
- 确保控制器返回的是
StreamedJsonResponse实例,不是JsonResponse或new Response(json_encode(...)) - 数据库查询必须用生成器(如 Doctrine 的
iterate()),不能先getArrayResult()再循环 —— 后者已把全部结果载入内存 - 不要在流式响应中调用
$response->setContent(),它会强制缓冲整个输出 - 若需添加自定义 header(如
X-Total-Count),必须在setCallback()外提前设置,流式回调里无法再发 header
手动控制 JSON 结构时慎用 ob_flush() 和 flush()
当 StreamedJsonResponse 不够灵活(比如要拼接嵌套数组或动态字段),可手写流式逻辑,但 FrankenPHP 对输出缓冲控制更敏感,容易触发 headers already sent。
- 必须在输出前显式关闭所有输出缓冲:
while (ob_get_level()) { ob_end_clean(); } - 每次
echo后紧跟ob_flush()和flush(),否则数据卡在 PHP 缓冲区不下发 - 避免在循环中重复调用
header();若需分块设置状态码或 content-type,请统一在开头完成 - 禁用
zlib.output_compression(FrankenPHP 下极易与流式输出冲突,导致ERR_HTTP2_PROTOCOL_ERROR)
配合 cerbero/json-parser 解析上游大 JSON 并流式转发
如果你的 Symfony 应用本身是代理角色(比如聚合多个 API 返回的大 JSON),就不能靠 json_decode() 全量解析再重 encode——这时得用拉式解析器边读边转。
立即学习“PHP免费学习笔记(深入)”;
- 安装:
composer require cerbero/json-parser - 用
JsonParser::fromFile($path)或JsonParser::fromStream($resource)创建解析器,不加载全文本 - 在
foreach ($parser as $event)中识别JsonEvent::OBJECT_START、JsonEvent::KEY、JsonEvent::VALUE_STRING等事件,按需构造并 echo 片段 - 注意:不要用
json_encode($event->getValue())回写,直接echo $event->getRaw()更省内存(前提是原始格式可信)
流式输出真正的难点不在代码怎么写,而在于整个链路不能有“缓存断点”:Doctrine 查询不能用 getResult(),模板引擎不能参与 JSON 构建,OPcache 不能对流式脚本做过度优化(建议在 php.ini 中为该路由禁用 JIT)。一旦某个环节偷偷把数据攒起来,前面所有流式努力就白做了。



















