必须主动隔离请求上下文,FrankenPHP不会自动重置全局状态;Worker模式下$_SESSION、static属性、$GLOBALS、未关闭PDO连接等均跨请求残留,需显式清理,否则导致数据污染。

必须主动隔离请求上下文,FrankenPHP 不会帮你重置全局状态。 Worker 模式下进程常驻,$_SESSION、static 属性、$GLOBALS、未关闭的 PDO 连接等,只要没显式清理,就会被下一个请求复用——这不是 bug,是设计前提。
检查并重置所有静态属性和全局变量
很多老项目把用户 ID、权限标识塞进 static $current_user 或 $GLOBALS['auth'],Worker 下这些值不会自动清空。
- 全局搜索
static $和$GLOBALS[,确认是否真需要跨请求保持;否则改用函数参数或依赖注入传入 - 入口脚本中,在
frankenphp_handle_request()回调执行前,手动重置关键变量:$GLOBALS['user_id'] = null;、MyAuth::reset(); - 避免在 preload 阶段或入口文件顶部写
define('ENV', 'prod')以外的运行时定义——define()是常量,但自定义常量一旦定义就不可变,可能干扰后续请求逻辑 - 用
var_dump(get_defined_vars())在请求末尾快速扫一遍,看是否有意外挂载的变量残留
每次请求后显式清理框架级状态
不同框架有各自的“重置接口”,不调用就等于把上一个用户的 request、response、cache 实例留给了下一个用户。
- Laravel:在
frankenphp_handle_request()回调末尾加$app->resetResolvedInstances();和$app->forgetInstance('request'); - ThinkPHP 6:调用
think\Container::clear();,并确保Db::close();或Cache::clear();被执行 - Symfony:监听
kernel.terminate事件,在其中调用$container->get('cache.app')->invalidateTags(['user']);等按需清理 - 禁用
session_start()自动调用——若未配置 session handler,$_SESSION会变成全局变量残留;应统一走SessionInterface::start()并确保 handler 支持并发写入
数据库连接必须按请求生命周期管理
传统 FPM 下 new PDO() 后不 close 也问题不大,Worker 下连接对象长期存活,但底层 socket 可能已断、事务未 rollback、字符集错乱。
立即学习“PHP免费学习笔记(深入)”;
- 禁用
PDO::ATTR_PERSISTENT => true(除非你明确实现了连接池健康检查) - 每次请求结束时显式关闭:
$pdo = null;或$pdo->close();(MySQLi)或DB::disconnect();(Laravel) - 避免在循环外创建单例 DB 实例;如必须复用,应在请求开始时
beginTransaction(),结束时commit()或rollback() - 用
netstat -an | grep :3306 | wc -l监控连接数,缓慢上涨就是泄漏信号
别信 chdir() 和相对路径
某个请求里执行了 chdir('/tmp'),之后所有请求的 require 'config.php' 都会去 /tmp/config.php 找——getcwd() 在线程内永久生效,重启 worker 也不会自动还原。
- 所有
require/include必须用绝对路径:require __DIR__ . '/config/database.php'; - 日志、缓存、上传目录等写入操作,禁止裸用
'logs/app.log',改用__DIR__ . '/../storage/logs/app.log' - 入口第一行加
chdir(__DIR__);强制锚定工作目录,比依赖环境更可靠 - 不要在中间件或控制器里调用
chdir()——它没有“作用域”,只有“线程全局”效果
最易被忽略的是:你以为清掉了 $_SESSION,但框架内部的 Request 对象仍持有上个用户的 attributes、cookies 或 headers;你以为关了 PDO,但 ORM 的查询构建器缓存(如 Laravel 的 Builder::$globalScopes)还在。状态隔离不是“清空几个变量”,而是对整个请求生命周期做显式建模和裁剪。



















