frankenphp worker模式下单例不会自动重置,必须在frankenphp_handle_request回调末尾显式调用resetfornextrequest()清理静态属性、请求上下文和临时数据,避免跨请求状态污染。

FrankenPHP 里没有 ResetInterface,单例服务的请求状态不会自动重置——你必须自己动手清理,否则下次请求拿到的就是上一个用户的残留数据。
worker 模式下单例对象根本不会“重置”
FrankenPHP 的 worker 模式是常驻进程,app('cache')、MyService::getInstance() 这类单例一旦初始化就一直活着。它不像 FPM 那样每次请求都重建整个容器。你看到的“单例”,其实是跨请求共享的同一个实例。
- 静态属性(如
static $user = null;)不会清空,值会一直保留 - 服务容器里绑定为
singleton的类,其构造函数只执行一次,内部状态全靠你手动重置 - 框架如 Laravel 的
Request对象默认是 request-scoped,但如果被错误地 bind 到 singleton scope,就会污染后续请求
别依赖 __destruct() 或 register_shutdown_function()
这两个机制在 worker 循环中不可靠:__destruct() 只在对象真正被销毁时触发(而单例对象几乎不销毁),register_shutdown_function() 在整个 worker 进程退出前才执行一次,不是每次请求后。
- 实测发现:连续发起 10 次请求,
__destruct()可能一次都没调用 - 把清理逻辑塞进
register_shutdown_function(),等于只在进程死前清一次,毫无意义 - 正确做法是在
frankenphp_handle_request()回调末尾显式调用重置方法,例如MyService::resetForNextRequest()
怎么写一个安全的 resetForNextRequest() 方法
核心原则:只清理请求相关状态,不破坏单例本身结构。重点盯住缓存、用户上下文、临时数据容器。
- 清空静态缓存:
self::$cache = [];、self::$currentUser = null; - 重置请求级属性:
$this->requestId = uniqid();、$this->errors = new Collection(); - 避免直接
unset(static::$xxx),改用赋值为null或空数组,防止未定义 notice - 如果用了 APCu 存请求数据,记得配对
apcu_delete('my_service_user_id_' . $this->id)
最容易被忽略的是框架容器本身的复用陷阱
Laravel 的 app() 容器本身是单例,但它的 resolve 逻辑可能依赖 $_SERVER 或请求对象。如果你在服务提供者里写了 app()->bind(UserService::class, function () { return new UserService($_SERVER['HTTP_AUTHORIZATION'] ?? ''); });,那这个 $_SERVER 值就是第一次请求时的,后面全错。
- 解决方案:把请求上下文作为参数传入服务方法,而不是在构造时捕获全局变量
- 检查所有服务提供者和
bind()/singleton()调用,确认没把$_GET、$_POST、$_SERVER直接塞进闭包或静态变量 - 用
var_dump(spl_object_id(app(MyService::class)))连续请求两次,ID 不变说明确实是同一个实例——那就必须重置,不能指望它自动刷新
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











