預設 PHP Session 使用「會話 Cookie」(session.cookie_lifetime=0),瀏覽器關閉即失效,但伺服器端 Session 檔案不會立即刪除,需等待垃圾回收 (GC)。本文說明 Session 生命週期機制,並提供兩種「瀏覽器關閉即時銷毀」的實作方案。
| 設定 | 預設值 | 說明 |
|---|---|---|
| session.gc_maxlifetime | 1440 (秒) | 伺服器端 Session 資料最大存活時間,超過此時間被視為「垃圾」 |
| session.cookie_lifetime | 0 | 瀏覽器端 Cookie 存活秒數,0 = 瀏覽器關閉即失效 (會話 Cookie) |
| session.gc_probability/divisor | 1/100 | 每 100 次請求有 1 次機率觸發 GC |
關鍵觀念:瀏覽器關閉 → 會話 Cookie 消失 → 下次請求送出新 Session ID → 伺服器找不到舊 Session → 視為新會話。但伺服器端舊 Session 檔案仍存在直到 GC 清理。
適合大多數一般應用,無需前端配合:
<?php
// 在 session_start() 之前設定
ini_set('session.gc_maxlifetime', 300); // 伺服器端 5 分鐘無活動即可被清理
ini_set('session.cookie_lifetime', 300); // 瀏覽器端 Cookie 也設 5 分鐘
ini_set('session.gc_probability', 1);
ini_set('session.gc_divisor', 10); // 提高 GC 機率到 10%
// 或在 php.ini 設定
// session.gc_maxlifetime = 300
// session.cookie_lifetime = 300
// session.gc_probability = 1
// session.gc_divisor = 10
session_start();
// ... 你的程式碼
?>
瀏覽器關閉/跳轉時發送同步 AJAX 請求銷毀 Session:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Session 自動銷毀示範</title>
</head>
<body>
<h1>測試頁面</h1>
<p>關閉分頁/瀏覽器將觸發 Session 銷毀</p>
<script>
// 使用 navigator.sendBeacon (現代、可靠、非阻塞)
window.addEventListener('unload', function() {
navigator.sendBeacon('/destroy_session.php');
});
// 兼容舊瀏覽器 (同步 AJAX,會阻塞關閉)
/*
window.addEventListener('unload', function() {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/destroy_session.php', false); // false = 同步
xhr.send();
});
*/
</script>
</body>
</html>
<?php
// 僅接受 POST 請求
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// 1. 啟動現有 Session
session_start();
// 2. 清空所有 Session 變數
$_SESSION = [];
// 3. 刪除 Session Cookie (瀏覽器端)
if (ini_get('session.use_cookies')) {
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000,
$params['path'], $params['domain'],
$params['secure'], $params['httponly']
);
}
// 4. 銷毀伺服器端 Session 資料
session_destroy();
// 可選:回應 204 No Content
http_response_code(204);
exit;
}
http_response_code(405); // Method Not Allowed
?>
unload 事件在某些情況不觸發 (崩潰、強制關閉、行動裝置切換 App)sendBeacon 不保證伺服器處理完成<?php
// 使用資料庫儲存 Session,可精確控制每筆 Session 生命週期
// 建表
/*
CREATE TABLE sessions (
id VARCHAR(128) PRIMARY KEY,
data TEXT NOT NULL,
last_activity INT UNSIGNED NOT NULL,
user_id INT UNSIGNED DEFAULT NULL,
INDEX idx_last_activity (last_activity),
INDEX idx_user_id (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
*/
class DatabaseSessionHandler implements SessionHandlerInterface {
private $pdo;
public function __construct(PDO $pdo) { $this->pdo = $pdo; }
public function open($savePath, $sessionName) { return true; }
public function close() { return true; }
public function read($id) {
$stmt = $this->pdo->prepare("SELECT data FROM sessions WHERE id = ? AND last_activity > ?");
$stmt->execute([$id, time() - 1440]);
return $stmt->fetchColumn() ?: '';
}
public function write($id, $data) {
$stmt = $this->pdo->prepare("
INSERT INTO sessions (id, data, last_activity) VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE data = VALUES(data), last_activity = VALUES(last_activity)
");
return $stmt->execute([$id, $data, time()]);
}
public function destroy($id) {
$stmt = $this->pdo->prepare("DELETE FROM sessions WHERE id = ?");
return $stmt->execute([$id]);
}
public function gc($maxLifetime) {
$stmt = $this->pdo->prepare("DELETE FROM sessions WHERE last_activity < ?");
return $stmt->execute([time() - $maxLifetime]);
}
}
// 註冊處理器
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
session_set_save_handler(new DatabaseSessionHandler($pdo), true);
session_start();
?>
| 誤區 | 事實 |
|---|---|
| session_destroy() 會立即刪除 Cookie | 不會,需手動 setcookie 覆蓋過期時間 |
| unset($_SESSION) 等同 session_destroy() | unset 只清空陣列,session_destroy 才刪除伺服器端儲存檔 |
| 關閉瀏覽器 Session 立即失效 | 伺服器端資料仍存在直到 GC,瀏覽器重開若帶舊 Cookie 可能復活 (極少見) |
| session.gc_maxlifetime 精確控制存活 | GC 是機率觸發,非精確計時器 |