Session 是有狀態 Web 應用的核心。本文涵蓋原生 PHP Session 儲存優化、分散式方案、ThinkPHP 多版本 Session 封裝用法、私有命名空間、資料庫驅動與自定義驅動開發。
; php.ini
; 格式:session.save_path = "N;MODE;/path"
; N = 目錄層級 (1-2 建議),MODE = 目錄權限 (八進制)
session.save_path = "2;600;/var/lib/php/sessions"
效果:建立 36×36=1296 個子目錄 (0-9, a-z),大幅降低單目錄 inode 壓力。
pdo = $pdo;
$this->table = $table;
}
public function open($path, $name): bool { return true; }
public function close(): bool { return true; }
public function read($id): string|false {
$stmt = $this->pdo->prepare("SELECT data FROM {$this->table} WHERE id = ? AND expiry > ?");
$stmt->execute([$id, time()]);
return $stmt->fetchColumn() ?: '';
}
public function write($id, $data): bool {
$expiry = time() + (int)ini_get('session.gc_maxlifetime');
$stmt = $this->pdo->prepare(
"INSERT INTO {$this->table} (id, data, expiry) VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE data = VALUES(data), expiry = VALUES(expiry)"
);
return $stmt->execute([$id, $data, $expiry]);
}
public function destroy($id): bool {
$stmt = $this->pdo->prepare("DELETE FROM {$this->table} WHERE id = ?");
return $stmt->execute([$id]);
}
public function gc($maxLifetime): int|false {
$stmt = $this->pdo->prepare("DELETE FROM {$this->table} WHERE expiry < ?");
$stmt->execute([time()]);
return $stmt->rowCount();
}
}
// 註冊 (必須在 session_start 前)
$handler = new DbSessionHandler($pdo);
session_set_save_handler($handler, true);
session_start();
; php.ini 全域設定
session.save_handler = redis
session.save_path = "tcp://127.0.0.1:6379?auth=password&database=0&prefix=session:"
; 或多節點
session.save_path = "tcp://host1:6379,tcp://host2:6379?weight=2&timeout=2.5"
; php.ini
session.save_handler = memcached
session.save_path = "127.0.0.1:11211"
; 多節點
session.save_path = "host1:11211,host2:11211"
// 僅在當前模組/應用有效,避免衝突
Session::set('token', 'abc', 'api'); // 命名空間 'api'
$token = Session::get('token', '', 'api');
Session::has('token', 'api');
Session::delete('token', 'api');
return [
'type' => 'file', // file, redis, memcache, db
'path' => '', // 檔案儲存路徑 (空用系統預設)
'prefix' => 'think', // Session 鍵前綴
'expire' => 3600, // 生命週期 (秒)
'auto_start' => true, // 自動啟動
'name' => 'PHPSESSID', // Cookie 名稱
'domain' => '', // Cookie 域名
'secure' => false, // HTTPS only
'httponly' => true, // 防 XSS
'same_site' => 'Lax', // CSRF 防護
'var_session_id' => '', // URL 參數傳遞 ID 變數名
// Redis 專用
'host' => '127.0.0.1',
'port' => 6379,
'password' => '',
'select' => 0,
'timeout' => 0,
'expire' => 3600,
// DB 專用
'table' => 'think_session',
];
CREATE TABLE `think_session` (
`id` CHAR(40) NOT NULL PRIMARY KEY,
`session_id` VARCHAR(255) NOT NULL,
`session_expires` INT UNSIGNED NOT NULL,
`session_data` BLOB,
INDEX `expires` (`session_expires`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
配置 'type' => 'db' 即可啟用。
handler = new \Redis();
$this->handler->connect($config['host'], $config['port']);
}
public function open($savePath, $sessionName): bool { return true; }
public function close(): bool { return true; }
public function read($sessId): string {
return $this->handler->get("session:$sessId") ?: '';
}
public function write($sessId, $data): bool {
return $this->handler->setex("session:$sessId", $this->config['expire'], $data);
}
public function destroy($sessId): bool {
return $this->handler->del("session:$sessId");
}
public function gc($maxLifetime): int { return 0; } // Redis 自動過期
}
配置 'type' => '\\app\\session\\driver\\CustomDriver' 即可使用。
post('username');
$password = $request->post('password');
$user = User::where('username', $username)->find();
if ($user && password_verify($password, $user->password)) {
// 寫入 Session
Session::set('user.id', $user->id);
Session::set('user.name', $user->username);
Session::set('user.role', $user->role);
// 重生 ID 防固定攻擊
Session::regenerate();
return redirect('dashboard');
}
return $this->error('帳號或密碼錯誤');
}
// 中介層驗證 (Middleware)
public function handle(Request $request, Closure $next) {
if (!Session::has('user.id')) {
return redirect('login')->with('msg', '請先登入');
}
return $next($request);
}
// 登出
public function logout() {
Session::clear();
Session::destroy();
return redirect('login');
}
session.cookie_httponly = 1session.cookie_secure = 1 (HTTPS 環境)session.cookie_samesite = 'Lax'session.use_strict_mode = 1Session::regenerate()Session 管理的演進路徑:檔案 → 分散檔案 → 資料庫 → Redis/Memcached → 自定義驅動。ThinkPHP 提供優雅的門面介面、命名空間隔離、多驅動支援,滿足從單體到微服務的各階段需求。核心原則:儲存介質可替換、業務邏輯不變、安全配置不可少。