symfony单例在frankenphp worker中会“串请求”,因进程常驻导致容器不重建,服务实例长期存活;用户态、日志上下文、doctrine缓存等动态数据未重置即被复用。

FrankenPHP Worker 模式下,Symfony 的单例类会跨请求污染上下文,这不是配置错误,是常驻进程 + 请求作用域错配的必然结果。
为什么 Symfony 单例在 FrankenPHP Worker 里会“串请求”
Symfony 的 ContainerInterface 本身是单例,但它的服务(比如 logger、cache.pool、自定义的 UserContext)默认也是单例作用域。Worker 进程不重启,容器不重建,这些服务实例就一直活着。一旦你在某个请求中往 $this->userContext->setUserId(123) 写了值,下次请求进来,只要没重置,读出来的还是 123——不是并发竞争,是同一个对象被反复复用。
常见错误现象包括:
- 登录态错乱:用户 A 登录后,用户 B 刷新页面看到 A 的用户名
- 日志上下文残留:
withContext(['request_id' => 'req-1'])后,下一个请求没设就自动带上req-1 - Doctrine 查询缓存命中异常:上个请求查过的实体,下个请求
find()直接返回旧数据且不触发 SQL
怎么改 Symfony 服务的作用域:从 singleton 改成 request 或 prototype
别碰容器单例本身,改具体服务的生命周期。在 config/services.yaml 里显式声明作用域:
services:
App\Context\UserContext:
class: App\Context\UserContext
# 关键:改成 prototype,每次 get() 都新建实例
scope: 'prototype'
# 或者更安全的 request 作用域(需启用 HttpKernel)
# scope: 'request'
注意点:
-
scope: 'request'依赖RequestStack,在 FrankenPHP Worker 的纯 CLI/HTTP handler 环境中可能不可用(没有传统 Request 生命周期),优先选prototype - 不能只改类定义,还要检查构造函数注入是否带了单例依赖——比如
UserContext依赖了LoggerInterface,而logger是单例,那它仍可能把上下文写进共享状态 - 第三方 Bundle 提供的服务(如
security.token_storage)默认是container作用域,需单独覆盖:security.token_storage: '@security.token_storage.inner'并确保 inner 是 request 级
避免手动存取全局状态:别用 static 属性或 $container->set()
很多老项目会在 Controller 或 Service 里写 self::$currentUserId = $id 或 $container->set('current_user', $user),这在 Worker 下等于给所有后续请求埋雷。
正确做法是:
- 用 FrankenPHP 提供的协程上下文:
Coroutine::getContext()['user_id'] = $id(仅限协程环境,即onRequest回调中) - 在非协程回调(如传统
onMessage)中,显式把用户 ID 当参数传给业务方法,不存任何静态状态 - 如果必须缓存连接级数据(如 WebSocket 用户登录态),用
WeakMap绑定到$connection对象,而不是用static::$connections[$id]
错误示范:static::$token = $token; → 下一个请求直接读到上个用户的 token。
Worker 启动时清空 Symfony 缓存并禁用 dev-only 服务
FrankenPHP Worker 进程启动一次后长期运行,若启动时加载了开发环境配置,WebProfilerBundle、DebugBundle 这些不仅拖慢性能,还自带静态状态(比如 VarDumper 的 Cloner 实例),容易引发内存缓慢增长。
部署时必须做到:
- Worker 启动命令明确指定环境:
frankenphp-worker --env=prod --no-debug - 构建阶段执行
php bin/console cache:clear --env=prod --no-debug,确保var/cache/prod是干净的 - 检查
config/bundles.php,确认WebProfilerBundle只在dev和test中启用 - 禁用
debug.dump_destination等调试配置项,它们可能在后台创建静态资源句柄
最容易被忽略的是:即使 APP_ENV=prod,如果 .env 文件里写了 APP_DEBUG=true,某些 Symfony 版本仍会悄悄启用调试组件——务必删掉或覆盖为 false。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











