Skip to content

$_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'] === 1

Session 的生命周期

阶段说明
创建调用 session_start() 时创建新会话
读取通过 $_SESSION 读取会话变量
写入$_SESSION 赋值,脚本结束时自动持久化
过期默认 1440 秒(24 分钟)无操作后过期
销毁调用 session_destroy() 主动销毁

与 session_start() 的关系

php
<?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
<?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
<?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
<?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
<?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 配置

ini
; 会话名称(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 / None

PHP 7.3+ SameSite 支持

PHP 7.3 开始原生支持 session.cookie_samesite 配置。推荐设置为 LaxStrict,可以防止 CSRF 攻击。设置为 None 时,必须同时设置 session.cookie_secure = 1

Session 锁定问题

PHP 默认的文件存储方式会使用文件锁来保证并发安全。这意味着同一会话的并发请求会被串行化:

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
<?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 中存储大量数据(如文件内容、大型数组)。

最佳实践

  1. 尽早 session_start():在脚本开头调用,避免输出之后再调用
  2. 登录后重新生成 ID:使用 session_regenerate_id(true) 防止会话固定
  3. 限制 Session 数据量:只存储必要的用户标识和少量状态数据
  4. 尽早 session_write_close():不需要写入时尽早释放锁
  5. 安全 Cookie 配置:启用 httponly、secure、samesite
  6. 设置合理的过期时间:根据应用场景配置 gc_maxlifetime
  7. 不要在 Session 中存储敏感明文:如密码、信用卡号
  8. 使用 Flash 消息模式:一次性消息读取后立即删除
php
<?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 文件的最佳实践。

参考链接