HTTP 無狀態,Session 在伺服器端儲存用戶會話資料,透過 Cookie 傳遞 Session ID 識別用戶。PHP 內建檔案儲存,但分散式環境需共享儲存(資料庫、Redis、Memcached)。本文實作完整的資料庫 Session 類。
<?php
/**
* Session 類 - 資料庫儲存版
* 實現 SessionHandlerInterface (PHP 5.4+)
*
* 適用環境:PHP 5.2+ / MySQL 5.0+
*
* @author 04ie.com
*/
class Session
{
/** Session 預設有效期(秒) */
public $expiry = 3600;
/** Cookie 作用域域名 */
public $domain = '.example.com';
/** 資料庫連線資源 */
private $conn;
/** 表名 */
private $table = 'sessions';
public function __construct()
{
// 關閉透明 SID 傳遞(安全)
ini_set('session.use_trans_sid', 0);
// 設定 GC 最大壽命
ini_set('session.gc_maxlifetime', $this->expiry);
// 強制使用 Cookie
ini_set('session.use_cookies', 1);
ini_set('session.use_only_cookies', 1);
// Cookie 路徑
ini_set('session.cookie_path', '/');
ini_set('session.cookie_domain', $this->domain);
// HttpOnly 防 XSS
ini_set('session.cookie_httponly', 1);
// Secure 僅 HTTPS(生產環境開啟)
// ini_set('session.cookie_secure', 1);
// 註冊自定義儲存處理器
session_set_save_handler(
array($this, 'open'),
array($this, 'close'),
array($this, 'read'),
array($this, 'write'),
array($this, 'destroy'),
array($this, 'gc')
);
// 註冊關閉函數確保寫入
register_shutdown_function('session_write_close');
session_start();
}
/**
* 開啟 Session - 建立資料庫連線
*/
public function open($savePath, $sessionName)
{
$this->conn = mysql_connect('localhost', 'db_user', 'db_pass');
if (!$this->conn) return false;
mysql_select_db('db_name', $this->conn);
mysql_query('SET NAMES utf8', $this->conn);
return true;
}
/**
* 關閉 Session - 關閉資料庫連線
*/
public function close()
{
return mysql_close($this->conn);
}
/**
* 讀取 Session 資料
* @param string $sid Session ID
* @return string 序列化資料或空字串
*/
public function read($sid)
{
$sid = mysql_real_escape_string($sid, $this->conn);
$sql = "SELECT `data` FROM `{$this->table}` WHERE `session_id` = '{$sid}' AND `expiry` > " . time();
$result = mysql_query($sql, $this->conn);
if ($result && mysql_num_rows($result) > 0) {
$row = mysql_fetch_assoc($result);
return $row['data'];
}
return '';
}
/**
* 寫入 Session 資料
* @param string $sid Session ID
* @param string $data 序列化資料
*/
public function write($sid, $data)
{
$sid = mysql_real_escape_string($sid, $this->conn);
$data = mysql_real_escape_string($data, $this->conn);
$expiry = time() + $this->expiry;
// REPLACE INTO:不存在插入,存在更新
$sql = "REPLACE INTO `{$this->table}` (`session_id`, `expiry`, `data`) VALUES ('{$sid}', {$expiry}, '{$data}')";
return mysql_query($sql, $this->conn);
}
/**
* 銷毀 Session
*/
public function destroy($sid)
{
$sid = mysql_real_escape_string($sid, $this->conn);
$sql = "DELETE FROM `{$this->table}` WHERE `session_id` = '{$sid}'";
return mysql_query($sql, $this->conn);
}
/**
* 垃圾回收 - 清理過期 Session
* @param int $maxlifetime 最大壽命
*/
public function gc($maxlifetime)
{
$sql = "DELETE FROM `{$this->table}` WHERE `expiry` < " . time();
mysql_query($sql, $this->conn);
// 可選:優化表碎片
// mysql_query("OPTIMIZE TABLE `{$this->table}`", $this->conn);
return true;
}
}
CREATE TABLE `sessions` (
`session_id` VARCHAR(128) NOT NULL PRIMARY KEY,
`expiry` INT(11) UNSIGNED NOT NULL,
`data` TEXT NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 建立索引加速 GC
CREATE INDEX `idx_expiry` ON `sessions` (`expiry`);
<?php
require_once 'Session.class.php';
// 應用程式入口最早處實例化
$session = new Session();
// 之後正常使用 $_SESSION
$_SESSION['user_id'] = 123;
$_SESSION['username'] = 'admin';
$_SESSION['permissions'] = array('read', 'write');
// 讀取
echo $_SESSION['user_id'];
// 登出銷毀
session_destroy();
// 或手動
// $session->destroy(session_id());
檔案 Session 有鎖機制防止並發寫入衝突。資料庫版本可透過行鎖實現:
// 讀取時加鎖(需 InnoDB)
$sql = "SELECT `data` FROM `{$this->table}` WHERE `session_id` = '{$sid}' AND `expiry` > " . time() . " FOR UPDATE";
// 寫入時更新
// 釋放鎖:事務提交或連線關閉
class RedisSession extends Session
{
private $redis;
private $prefix = 'session:';
public function open($savePath, $sessionName)
{
$this->redis = new Redis();
return $this->redis->connect('127.0.0.1', 6379);
}
public function read($sid)
{
$data = $this->redis->get($this->prefix . $sid);
return $data ?: '';
}
public function write($sid, $data)
{
return $this->redis->setex($this->prefix . $sid, $this->expiry, $data);
}
public function destroy($sid)
{
return $this->redis->del($this->prefix . $sid);
}
public function gc($maxlifetime)
{
// Redis 自動過期,無需手動 GC
return true;
}
}
| 配置 | 建議值 | 說明 |
|---|---|---|
| session.use_strict_mode | 1 | 嚴格模式:拒絕未初始化的 SID |
| session.cookie_httponly | 1 | 防 JavaScript 讀取 Cookie |
| session.cookie_secure | 1 (HTTPS) | 僅 HTTPS 傳輸 Cookie |
| session.use_only_cookies | 1 | 不接受 URL 參數傳遞 SID |
| session.gc_maxlifetime | 1440 (24h) | 根據業務調整 |
| session.sid_length | 32 | SID 長度(PHP 7.1+) |
| session.sid_bits_per_character | 5 | 字元編碼位數(PHP 7.1+) |
此類實現了完整的 Session 生命週期管理,可直接用於單機或分散式環境,配合 Redis 版本可支撐高併發場景。