Worker模式下Symfony全局状态会“串”是因为进程常驻导致$container、$em、EventDispatcher等复用,若未在onKernelRequest/onKernelTerminate中主动重置,上一请求的临时状态(如未销毁$_SESSION、闭包捕获大对象、缓存未清)将污染下一请求。

Worker 模式下 Symfony 全局状态为何会“串”
FrankenPHP 的 Worker 模式让 PHP 进程常驻内存,框架初始化只做一次。这对性能是好事,但 Symfony 的 $container、$kernel、EventDispatcher、甚至 Doctrine 的 $em 都会在多个请求间复用——如果没主动清理,上一个请求写入的临时状态(比如 $_SESSION 未销毁、监听器闭包捕获了大对象、缓存未重置)就会污染下一个请求。
这不是 FrankenPHP 的 bug,而是常驻模型下的必然行为:没有进程隔离,就得靠应用层自己划清边界。
Symfony 请求生命周期钩子必须用 onKernelRequest + onKernelTerminate
不能依赖传统 FPM 下“每次请求从头来”的侥幸。必须在每个请求开始和结束时显式重置关键状态。重点不是“清空”,而是“按需隔离”:
-
onKernelRequest:重置Security::setToken(null)、清空自定义上下文存储(如RequestStack::pop()后再push($request))、重置自定义全局计数器 -
onKernelTerminate:调用$em->clear()(非close())、手动 unset 闭包监听器持有的大对象引用、调用gc_collect_cycles()(尤其在大量事件触发后) - 避免在
onKernelFinish或控制器里做清理——它可能不被执行(如异常提前退出),而onKernelTerminate是保证执行的最后机会
Doctrine EntityManager 和 EventDispatcher 是两大污染源
这两个组件最容易因复用导致内存泄漏或状态残留:
立即学习“PHP免费学习笔记(深入)”;
-
$em:一级缓存默认跨请求存活。必须在onKernelTerminate中调用$em->clear();若用了二级缓存,确认其 driver 在 prod 环境已禁用或使用无共享后端(如 Redis) -
EventDispatcher:闭包监听器(function () use ($bigData) { })会强持有$bigData,即使调用removeListener也无法释放。改用类方法监听器([$this, 'onEvent']),或在移除后显式$listener = null - 检查
EventDispatcher::$optimized缓存是否残留——Symfony 6.2+ 默认启用,但它不会随removeListener自动失效,需手动调用$dispatcher->reset()(慎用,仅限必要场景)
别信 .env 或 $_SERVER 的“静态性”,环境变量也要按请求重载
FrankenPHP 启动时读取一次 .env,但某些场景需要 per-request 级别的环境覆盖(比如多租户路由解析出不同 APP_ENV)。此时不能依赖 $_ENV['APP_ENV'] 的初始值:
- 在
onKernelRequest中解析租户标识,动态设置putenv('APP_ENV=' . $tenantEnv),再触发Dotenv::overload()加载对应.env.tenant - 避免直接修改
$_SERVER或$_ENV数组——它们是只读快照,改了也不影响 Symfony 内部的ParameterBag - 真正要覆盖参数,走容器编译期逻辑(如自定义
Kernel::configureContainer()),或运行时用$container->setParameter()(注意线程安全)
最易被忽略的是:Worker 模式下,var_dump() 或日志里看到的 $_SERVER 值,可能是上一个请求留下的旧快照,不是当前请求的真实环境上下文。



















