必须实现ResetInterface的服务是那些持有请求间可变状态且该状态不能跨请求复用的,典型包括:1.自定义EntityManager子类或包装器;2.自定义SessionStorage;3.带上下文的HttpClient装饰器;4.封装PDO/mysqli的工具类。

只有在服务持有“请求间可变状态”且该状态不能跨请求复用时,才必须实现 ResetInterface。 其他情况——比如纯函数式服务、无内部属性的 DTO、只读配置类——完全不用管这个接口。
哪些服务典型地要重置?看这四类
FrankenPHP 的 worker 模式让 PHP 进程常驻,容器和服务实例不会随请求重建。如果某个服务在请求 A 中改了自己身上的字段(比如缓存数组、计数器、临时连接句柄),而没在请求 B 前清理,B 就会拿到 A 留下的脏数据。
-
Doctrine\ORM\EntityManager子类或包装器:官方EntityManager已实现ResetInterface,但你若加了$this->queryCache = []或缓存了未 flush 的实体状态,就得手动重置 - 自定义
SessionStorage:比如继承NativeSessionStorage后加了$this->sessionKeyMap = []或动态生成的加密盐,不重置会导致 session ID 映射错乱 - 带上下文的
HttpClient装饰器:用withOptions(['base_uri' => $dynamicHost])注入了运行时计算的 host,或封装了 token 自动刷新逻辑,这些状态必须在每次请求前归零 - 封装
PDO/mysqli的工具类:若缓存了$this->lastInsertId、$this->affectedRows或 prepared statement 句柄,不重置会让后续请求读到上一次的值
reset() 方法怎么写才安全?
目标不是“销毁对象”,而是“轻量归零副作用”。它会在每个请求开始前被调用,所以必须快、不能有 I/O、不能 new 新对象、不能关连接。
- 只清空本服务明确管理的字段,比如
$this->buffer、$this->isProcessed、$this->requestId - 避免
unset($this->cache)—— 如果$this->cache是引用其他服务的数组,深层状态可能漏掉;直接赋空更可控:$this->cache = [] - 不要调用
parent::reset(),除非父类文档明确声明支持该接口并说明了行为 - 禁止在
reset()里做file_put_contents、curl_exec、$pdo->prepare()等任何阻塞或资源操作
不实现 ResetInterface 会出什么问题?
现象往往隐蔽,但后果严重:
立即学习“PHP免费学习笔记(深入)”;
- 数据库查询返回上一个请求的
lastInsertId,导致业务逻辑误判主键 - 日志处理器(如
BufferHandler子类)把请求 A 的日志和请求 B 的混在一起写进同一条记录 - HttpClient 装饰器带着过期的
Authorizationheader 发起请求,API 返回 401 - SessionStorage 把用户 A 的 session ID 映射到了用户 B 的会话数据上,引发越权访问
最容易被忽略的是“状态是否真的跨请求污染”——别只看类有没有 private $xxx,得确认这个 $xxx 是不是在请求处理中被写入、且没在下个请求来之前被覆盖或清空。



















