$_SESSION — 会话变量
概述
$_SESSION 是 PHP 中用于存储会话数据的超全局变量。与 $_GET 和 $_POST 不同,$_SESSION 的数据在多个页面请求之间持久存在,直到会话过期或被显式销毁。$_SESSION 是 PHP 会话机制的核心数据存储,配合 session_start() 函数使用,是实现用户登录状态管理、购物车、表单多步提交等功能的基石。
前置知识
在阅读本节之前,你需要了解:
- HTTP 是无状态协议,请求之间相互独立
- Cookie 的基本概念(Session 通常基于 Cookie)
- PHP 中
session_start()函数的作用 - 数据序列化与反序列化的基本概念
基础概念
Session 的工作原理
第一次请求(用户登录)
┌──────────┐ ┌──────────┐
│ 浏览器 │ ── POST 登录 ──→ │ PHP 服务器 │
│ │ ← Set-Cookie: │ │
│ │ PHPSESSID=abc │ │
└──────────┘ └──────────┘
$_SESSION['user_id'] = 1
(数据存储在服务端)
后续请求(已登录状态)
┌──────────┐ ┌──────────┐
│ 浏览器 │ ── Cookie: ────→ │ PHP 服务器 │
│ │ PHPSESSID=abc │ │
│ │ ← 页面内容 │ 通过 PHPSESSID 找到会话数据
└──────────┘ └──────────┘
$_SESSION['user_id'] === 1Session 的生命周期
| 阶段 | 说明 |
|---|---|
| 创建 | 调用 session_start() 时创建新会话 |
| 读取 | 通过 $_SESSION 读取会话变量 |
| 写入 | 对 $_SESSION 赋值,脚本结束时自动持久化 |
| 过期 | 默认 1440 秒(24 分钟)无操作后过期 |
| 销毁 | 调用 session_destroy() 主动销毁 |
与 session_start() 的关系
<?php
declare(strict_types=1);
// 必须在使用 $_SESSION 之前调用 session_start()
session_start();
// 现在 $_SESSION 可用了
$_SESSION['last_visit'] = time();
$_SESSION['page_count'] = ($_SESSION['page_count'] ?? 0) + 1;
echo "这是你第 {$_SESSION['page_count']} 次访问";必须先调用 session_start()
在访问 $_SESSION 之前,必须先调用 session_start()。否则 $_SESSION 不会被初始化,对其操作会触发警告。PHP 7.4+ 建议在脚本最开头使用 session_start()。
语法与代码
基本 Session 读写操作
<?php
declare(strict_types=1);
session_start();
// 写入会话变量
$_SESSION['username'] = 'alice';
$_SESSION['user_id'] = 12345;
$_SESSION['is_admin'] = false;
$_SESSION['login_time'] = time();
$_SESSION['preferences'] = [
'theme' => 'dark',
'lang' => 'zh-CN',
'PerPage'=> 20,
];
// 读取会话变量
$username = $_SESSION['username'] ?? 'Guest';
$userId = $_SESSION['user_id'] ?? 0;
$theme = $_SESSION['preferences']['theme'] ?? 'light';
$currentPage = $_SESSION['page_count'] ?? 1;
echo "欢迎, {$username}! 你的主题是 {$theme}。";删除会话变量
<?php
declare(strict_types=1);
session_start();
// 删除单个会话变量
unset($_SESSION['temp_data']);
// 删除多个会话变量
unset($_SESSION['step1_data'], $_SESSION['step2_data']);
// 注意:不要使用 $_SESSION = [] 来清空
// 这可能破坏会话的其他内部数据
// 正确:保留会话 ID,只清空用户数据
$_SESSION = [];
// 完全销毁会话(包括服务端文件和客户端 Cookie)
$_SESSION = [];
if (ini_get('session.use_cookies')) {
$params = session_get_cookie_params();
setcookie(
session_name(),
'',
time() - 42000,
$params['path'],
$params['domain'],
$params['secure'],
$params['httponly']
);
}
session_destroy();Flash 消息实现
<?php
declare(strict_types=1);
/**
* Flash 消息管理器
* 消息在下一次请求后自动清除
*/
class FlashMessage
{
private const KEY = 'flash_messages';
public static function set(string $type, string $message): void
{
$_SESSION[self::KEY][$type][] = $message;
}
public static function get(string $type): array
{
$messages = $_SESSION[self::KEY][$type] ?? [];
// 读取后立即删除(一次性消息)
unset($_SESSION[self::KEY][$type]);
return $messages;
}
public static function has(string $type): bool
{
return !empty($_SESSION[self::KEY][$type]);
}
public static function all(): array
{
$messages = $_SESSION[self::KEY] ?? [];
// 读取后清除所有 Flash 消息
unset($_SESSION[self::KEY]);
return $messages;
}
}
// 使用示例
session_start();
// 设置 Flash 消息
FlashMessage::set('success', '用户创建成功!');
FlashMessage::set('error', '邮箱格式不正确');
// 在下一个请求中获取
foreach (FlashMessage::all() as $type => $messages) {
foreach ($messages as $msg) {
echo "[{$type}] {$msg}" . PHP_EOL;
}
}表单多步提交(向导)
<?php
declare(strict_types=1);
/**
* 多步表单处理器
* 使用 $_SESSION 在步骤间保存数据
*/
class MultiStepForm
{
private const STEP_KEY = 'form_wizard';
public function saveStep(int $step, array $data): void
{
$_SESSION[self::STEP_KEY][$step] = $data;
$_SESSION[self::STEP_KEY]['current_step'] = $step + 1;
}
public function getStepData(int $step): ?array
{
return $_SESSION[self::STEP_KEY][$step] ?? null;
}
public function getCurrentStep(): int
{
return $_SESSION[self::STEP_KEY]['current_step'] ?? 1;
}
public function getAllData(): array
{
$steps = $_SESSION[self::STEP_KEY] ?? [];
unset($steps['current_step']);
return $steps;
}
public function clear(): void
{
unset($_SESSION[self::STEP_KEY]);
}
}
// 使用示例
session_start();
$wizard = new MultiStepForm();
$currentStep = $wizard->getCurrentStep();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// 保存当前步骤数据
$stepData = [
'username' => trim($_POST['username'] ?? ''),
'email' => trim($_POST['email'] ?? ''),
];
$wizard->saveStep($currentStep, $stepData);
// 获取所有步骤数据
$allData = $wizard->getAllData();
echo '所有步骤数据: ' . print_r($allData, true);
}详细说明
Session 存储方式
PHP 支持多种 Session 存储后端,通过 php.ini 中的 session.save_handler 配置:
| 存储方式 | 配置值 | 说明 |
|---|---|---|
| 文件 | files(默认) | 存储在 session.save_path 指定的目录 |
| Redis | 通过扩展 | 高性能分布式缓存 |
| Memcached | 通过扩展 | 分布式内存缓存 |
| 数据库 | 自定义 | 通过自定义 SessionHandler 实现 |
| 自定义 | user | 实现 SessionHandlerInterface |
常用 Session 配置
; 会话名称(Cookie 名称)
session.name = PHPSESSID
; 默认会话过期时间(秒)
session.cookie_lifetime = 0 ; 0 = 浏览器关闭时过期
; 服务器端垃圾回收时间(秒)
session.gc_maxlifetime = 1440 ; 24 分钟
; Cookie 路径
session.cookie_path = /
; Cookie 域名
session.cookie_domain =
; 仅 HTTPS 传输 Cookie
session.cookie_secure = 0 ; 1 = 仅 HTTPS
; 禁止 JavaScript 访问 Cookie
session.cookie_httponly = 1 ; 防止 XSS 窃取
; Cookie SameSite 属性(PHP 7.3+)
session.cookie_samesite = Lax ; Strict / Lax / NonePHP 7.3+ SameSite 支持
PHP 7.3 开始原生支持 session.cookie_samesite 配置。推荐设置为 Lax 或 Strict,可以防止 CSRF 攻击。设置为 None 时,必须同时设置 session.cookie_secure = 1。
Session 锁定问题
PHP 默认的文件存储方式会使用文件锁来保证并发安全。这意味着同一会话的并发请求会被串行化:
<?php
declare(strict_types=1);
// 默认行为:session_start() 会锁定会话文件
// 如果页面有多个 AJAX 请求使用同一会话,它们会被串行执行
// 解决方案:尽早关闭 session 写入
session_start();
// 读取需要的会话数据
$userId = $_SESSION['user_id'] ?? null;
// 尽早关闭 session 写入(释放锁)
session_write_close();
// 现在可以并行处理其他逻辑
sleep(2); // 模拟长时间操作
echo "用户 ID: {$userId}";注意事项
1. Session 固定攻击
攻击者获取一个有效的 Session ID 后,诱导用户使用该 ID 登录,从而劫持会话。防御方法是登录后重新生成 Session ID:
<?php
declare(strict_types=1);
session_start();
// 登录成功后,重新生成 Session ID
session_regenerate_id(true); // true = 删除旧会话文件2. Session 劫持
如果 Session ID 被窃取(如通过 XSS 或网络嗅探),攻击者可以冒充用户。防御措施:
- 设置
session.cookie_httponly = 1(防止 JS 读取) - 设置
session.cookie_secure = 1(仅 HTTPS 传输) - 设置
session.cookie_samesite = Strict - 登录后调用
session_regenerate_id()
3. 并发请求阻塞
同一用户的多个并发请求(如 AJAX 请求)会因为 Session 文件锁而阻塞。解决方案是尽早调用 session_write_close()。
4. 内存消耗
$_SESSION 中的所有数据都会被序列化存储,不要在 Session 中存储大量数据(如文件内容、大型数组)。
最佳实践
- 尽早 session_start():在脚本开头调用,避免输出之后再调用
- 登录后重新生成 ID:使用
session_regenerate_id(true)防止会话固定 - 限制 Session 数据量:只存储必要的用户标识和少量状态数据
- 尽早 session_write_close():不需要写入时尽早释放锁
- 安全 Cookie 配置:启用 httponly、secure、samesite
- 设置合理的过期时间:根据应用场景配置
gc_maxlifetime - 不要在 Session 中存储敏感明文:如密码、信用卡号
- 使用 Flash 消息模式:一次性消息读取后立即删除
<?php
declare(strict_types=1);
// 最佳实践模板:安全的 Session 使用
session_start([
'cookie_lifetime' => 7200, // 2 小时
'cookie_path' => '/',
'cookie_domain' => 'example.com',
'cookie_secure' => true, // 仅 HTTPS
'cookie_httponly' => true, // 禁止 JS 访问
'cookie_samesite' => 'Lax',
]);
// 用户登录
function login(int $userId, string $username): void
{
$_SESSION['user_id'] = $userId;
$_SESSION['username'] = $username;
$_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
$_SESSION['user_agent'] = substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 255);
$_SESSION['last_activity'] = time();
// 重新生成 Session ID
session_regenerate_id(true);
}
// 检查登录状态
function isLoggedIn(): bool
{
return isset($_SESSION['user_id']);
}
// 登出
function logout(): void
{
$_SESSION = [];
$params = session_get_cookie_params();
setcookie(
session_name(),
'',
time() - 42000,
$params['path'],
$params['domain'],
$params['secure'],
$params['httponly']
);
session_destroy();
}下一节
下一节将详细介绍 $_ENV 超全局变量,了解 PHP 环境变量的获取方式以及 .env 文件的最佳实践。