首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP Session 類完整實作:資料庫儲存、生命週期管理、垃圾回收

     編輯:PHP 綜合知識

Session 機制回顧

HTTP 無狀態,Session 在伺服器端儲存用戶會話資料,透過 Cookie 傳遞 Session ID 識別用戶。PHP 內建檔案儲存,但分散式環境需共享儲存(資料庫、Redis、Memcached)。本文實作完整的資料庫 Session 類。

完整 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());

核心機制解析

1. 存儲流程

  1. PHP 請求開始 → `session_start()`
  2. 讀取:`read($sid)` → 反序列化 → 填充 `$_SESSION`
  3. 腳本執行期間操作 `$_SESSION`
  4. 請求結束 → `write($sid, serialize($_SESSION))` → 儲存資料庫

2. 垃圾回收 (GC)

  • 機率觸發:`session.gc_probability / session.gc_divisor`(預設 1/100)
  • 手動觸發:`session_gc()` (PHP 7.1+)
  • 本類 `gc()` 刪除 `expiry < time()` 的記錄

3. 並發與鎖

檔案 Session 有鎖機制防止並發寫入衝突。資料庫版本可透過行鎖實現:


// 讀取時加鎖(需 InnoDB)
$sql = "SELECT `data` FROM `{$this->table}` WHERE `session_id` = '{$sid}' AND `expiry` > " . time() . " FOR UPDATE";

// 寫入時更新
// 釋放鎖:事務提交或連線關閉

進階擴展:Redis Session(高效能)


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_mode1嚴格模式:拒絕未初始化的 SID
session.cookie_httponly1防 JavaScript 讀取 Cookie
session.cookie_secure1 (HTTPS)僅 HTTPS 傳輸 Cookie
session.use_only_cookies1不接受 URL 參數傳遞 SID
session.gc_maxlifetime1440 (24h)根據業務調整
session.sid_length32SID 長度(PHP 7.1+)
session.sid_bits_per_character5字元編碼位數(PHP 7.1+)

常見問題排查

  • Session 丟失:檢查 `session.save_path` 權限、磁碟空間、資料庫連線
  • 並發讀取舊資料:啟用行鎖 `SELECT ... FOR UPDATE`、縮短事務時間
  • GC 不生效:確認 `session.gc_probability` > 0、表索引正確
  • 跨子域共享:設定 `session.cookie_domain = .example.com`
  • 負載均衡 Session 不共享:改用資料庫/Redis 共享儲存

此類實現了完整的 Session 生命週期管理,可直接用於單機或分散式環境,配合 Redis 版本可支撐高併發場景。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved