PHP Session

Session lifecycle, reading and writing $_SESSION, and defending against session fixation.

Beginner
Key PointsSession data lives on the server; the client only holds a random ID.

Starting a session

session_start() must precede output. It reads the session ID from the request, creating one and sending Set-Cookie if absent.

PHP
<?php
session_start();

$_SESSION["user"] = "Tom";
$_SESSION["cart"] = ["apple" => 2, "pear" => 1];

echo session_status() === PHP_SESSION_ACTIVE ? "会话已建立" : "无会话", "\n";
echo $_SESSION["user"], "\n";
echo count($_SESSION["cart"]), "\n";

unset($_SESSION["cart"]);
echo isset($_SESSION["cart"]) ? "有购物车" : "购物车已清空", "\n";

session_destroy();
echo session_status() === PHP_SESSION_ACTIVE ? "仍活跃" : "已销毁", "\n";
Output
会话已建立
Tom
2
购物车已清空
已销毁

Session vs cookie

AspectCookieSession
Where stored用户浏览器服务器
Size limit约 4KB受内存/存储限制
Tamper-proof否是(数据在服务端)
Lifetime可长期保存默认浏览器关闭即失效
Scalability天然支持多机多机需共享存储
Good for偏好、记住我 token登录状态、购物车

Login state

PHP
<?php
session_start();

// 登录成功后
function login(string $user): void {
    session_regenerate_id(true);      // 换新 ID,防会话固定
    $_SESSION["user"] = $user;
    $_SESSION["login_at"] = 1700000000;
}

function logout(): void {
    $_SESSION = [];
    if (ini_get("session.use_cookies")) {
        $p = session_get_cookie_params();
        setcookie(session_name(), "", time() - 42000, $p["path"], $p["domain"], $p["secure"], $p["httponly"]);
    }
    session_destroy();
}

function currentUser(): ?string {
    return $_SESSION["user"] ?? null;
}

login("Tom");
echo currentUser(), "\n";
echo count($_SESSION), "\n";
Output
Tom
2
session_regenerate_id(true) defeats session fixation: an attacker plants their own ID, the victim logs in, and they share the session. Regenerating on login removes the premise.

Where sessions live

By default each session is a file in the temp directory. With multiple servers the files are not shared, so users lose their login when routed elsewhere.

PHP
<?php
// 用 Redis 存会话(需装 redis 扩展)
ini_set("session.save_handler", "redis");
ini_set("session.save_path", "tcp://127.0.0.1:6379");

// 或者用数据库
// 自己实现 SessionHandlerInterface 再 session_set_save_handler()

echo ini_get("session.save_handler"), "\n";
echo ini_get("session.save_path"), "\n";
Output
redis
tcp://127.0.0.1:6379

Production almost always uses Redis or a database. File-based sessions suit only small single-server apps.

Session security checklist

  • Regenerate on login —— defeats fixation
  • **Set httponly, secure, samesite=Lax** —— against XSS theft and CSRF
  • **session.cookie_lifetime = 0** —— session cookie not persisted
  • **session.gc_maxlifetime** —— idle timeout, default 1440s
  • Clear on logout —— empty the array, destroy, delete the cookie
session_destroy() alone leaves $_SESSION populated for this request; clearing the array alone leaves the cookie. Do all three.

Flash messages

One-shot notices like "saved successfully" live in the session and are removed after reading — the standard PRG companion.

PHP
<?php
session_start();

function flash(string $key, ?string $value = null): ?string {
    if ($value !== null) {
        $_SESSION["_flash"][$key] = $value;
        return null;
    }
    $v = $_SESSION["_flash"][$key] ?? null;
    unset($_SESSION["_flash"][$key]);
    return $v;
}

flash("ok", "保存成功");
echo flash("ok") ?? "(空)", "\n";
echo flash("ok") ?? "(第二次读不到了)", "\n";
Output
保存成功
(第二次读不到了)

Friends & Partners