从 TP5.1 升级到 ThinkPHP 8.0 后,不少开发者发现 Session 突然全部丢失:登录跳转失效、购物车数据被清空、表单重复提交校验形同虚设。这通常不是漏改了某个配置项,而是底层机制发生了根本性变化,必须逐层排查才能定位真正的断点。

确认是否启用了原生 Session 驱动
首先检查 config/app.php 中 session 配置块的 type 字段具体使用了什么。TP8.0 默认禁用了 PHP 原生的 session_start(),转而通过自定义 Session 管理器接管。如果配置了 'type' => 'redis' 或 'type' => 'memcache',但对应服务未启动或连接失败,Session 会静默降级为空实现——此时 session_id() 返回空白,$_SESSION 始终是空数组。
一个简单的验证方法:执行 var_dump(session_status() === PHP_SESSION_ACTIVE),如果返回 false,说明 Session 压根没有被激活。这种情况下,问题很可能出在驱动配置上。
检查 Session 初始化时机是否被跳过
打开 app/middleware.php,确认 thinkmiddlewareSessionInit::class 是否位于中间件队列的最前面。TP8.0 要求 Session 中间件必须在路由解析之前执行,如果它被放在 AllowCrossDomain 或 CheckAuth 后面,控制器中首次调用 $this->request->session() 时,Session 其实还没有初始化。
另一个临时验证手段:在 app/common.php 顶部直接加上 session_start();,强制激活原生 Session。如果问题消失,说明就是中间件顺序的问题。不过需要警惕的是,这个操作会绕过 TP8.0 的 Session 生命周期管理,上线前务必删除。
验证 Session 保存路径与权限
先运行 php -r "echo session_sa ve_path();",查看 PHP 实际写入 Session 文件的目录。然后进入该目录,检查是否存在且 Web 服务器用户(例如 www-data 或 nginx)拥有 rwx 权限。
一个很常见的坑:TP5.1 时代手动创建的 runtime/session 目录还在,但 TP8.0 默认改用了系统临时目录(如 /tmp)。如果在 Docker 环境中,/tmp 可能被挂载为只读文件系统,写入就会失败。
如果采用 'type' => 'file',可以在 config/app.php 中显式指定路径,例如 'path' => runtime_path('session'),然后用 mkdir -p runtime/session && chmod -R 777 runtime/session 确保目录可写。这一步虽然基础,但往往是问题根源。
排查 Cookie 域与 Secure 标志冲突
TP8.0 默认将 'secure' => true 和 'httponly' => true 同时打开。如果网站仍运行在 HTTP 环境下,浏览器会直接拒绝保存 Session Cookie,结果就是每次请求都生成一个新会话,之前的登录状态自然无法识别。
打开浏览器开发者工具,切换到 Application → Cookies,查看是否存在名为 PHPSESSID 的 Cookie。如果没有,说明 Cookie 被浏览器拦截了。
临时修复方法:在 config/app.php 的 session 配置中添加一行 'secure' => false,再测试一下。但注意,生产环境必须配合 HTTPS 部署,否则 CSRF 和窃听风险很高。
验证 Session ID 是否被意外重置
在任意控制器方法开头插入这段代码:dump($this->request->session()->getId(), session_id());。如果两个值不一致,说明 TP8.0 的 Session 实例与 PHP 原生的 Session 状态脱钩了。这通常是因为全局代码中使用了 session_destroy() 或 session_unset(),而 TP8.0 的 Session 管理器有自己内部的逻辑,直接调用原生函数会破坏一致性。
解决办法其实不复杂:全局搜索项目中所有的 session_* 函数调用,把 session_destroy() 换成 $this->request->session()->destroy(),session_unset() 换成 $this->request->session()->clear()。统一之后,Session ID 就不会自行变化了。
