session_regenerate_id()在PHP7与PHP5行为差异致数据库连接过早关闭
问题背景
我有一个运行在PHP 5.4环境下的应用,通过session_set_save_handler()函数将会话数据存储到数据库。自定义会话处理器中的close可调用方法如下:
public function close() { return $this->dbConnection->close(); }
我原本理解close()会在PHP完成会话写入后被调用,因此数据库连接也会在此时关闭。我见过其他示例中不会在close()可调用方法内关闭数据库连接,但当前应用采用了该实现。
在登录脚本中,我调用session_regenerate_id(TRUE)来更改会话ID。在PHP 5.4中此操作正常,调用后不会立即关闭数据库连接,但升级到PHP 7.4后,调用该函数会自动立即关闭数据库连接,导致应用在此阶段失败。脚本后续在验证用户登录凭证后还有写入会话的代码,因此需要保持数据库连接活跃。
请问预期(或正确)的行为是什么?是PHP 5.4(及更早版本)的实现错误,session_regenerate_id(TRUE)本应关闭会话却未执行,还是我遗漏了某些要点?
问题解答
PHP 7.4的行为才是符合规范的预期行为,PHP 5.4及更早版本确实存在实现上的偏差。
原因是当你调用session_regenerate_id(TRUE)时,第二个参数为TRUE意味着要删除旧的会话数据。按照PHP会话处理的规范,这个操作会触发完整的会话"关闭-销毁"流程:
- 调用自定义处理器的
write()方法写入当前会话数据 - 调用
destroy()方法删除旧会话记录 - 调用
close()方法关闭会话相关资源
在PHP 5.4里,这个流程没有严格执行,调用session_regenerate_id(TRUE)后没有立刻触发close(),所以数据库连接还能保留。但升级到7.4后,PHP修复了这个行为,严格按照规范执行会话关闭逻辑,导致你的数据库连接被提前关闭。
至于为什么很多示例不在close()里关闭数据库连接,核心原因是:close()方法的设计初衷是关闭会话相关的资源,而不是全局的数据库连接。PHP的会话处理器在请求周期内可能会被多次调用(比如开启、 regenerate、关闭会话),如果每次close()都关掉DB连接,后续需要操作会话时就会因为连接已断而失败。
你可以参考以下几种解决思路:
- 不要在自定义会话处理器的
close()方法里关闭数据库连接,把DB连接的关闭逻辑放到请求结束的钩子(比如register_shutdown_function())里执行 - 调整
session_regenerate_id()的调用时机,等所有会话写入操作完成后再调用它 - 维护一个全局的数据库连接实例,确保会话处理器不会随意关闭它
内容的提问来源于stack exchange,提问作者Noel Whitemore

