在PHP中自定义session存储需要实现SessionHandlerInterface接口,以下是完整实现方案:
<?php
class MySQLSessionHandler implements SessionHandlerInterface {
private $pdo;
private $table = 'sessions';
private $gcLifetime = 1440; // 24分钟
// 构造函数接收PDO连接
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
// 打开session时调用
public function open($savePath, $sessionName): bool {
return true; // 使用已有PDO连接,无需额外操作
}
// 关闭session时调用
public function close(): bool {
return true;
}
// 读取session数据
public function read($sessionId): string|false {
$stmt = $this->pdo->prepare(
"SELECT session_data FROM {$this->table}
WHERE session_id = ? AND expires_at > NOW()"
);
$stmt->execute([$sessionId]);
return $stmt->fetchColumn() ?: '';
}
// 写入session数据
public function write($sessionId, $sessionData): bool {
$stmt = $this->pdo->prepare(
"REPLACE INTO {$this->table}
(session_id, session_data, expires_at)
VALUES (?, ?, DATE_ADD(NOW(), INTERVAL ? SECOND))"
);
return $stmt->execute([
$sessionId,
$sessionData,
$this->gcLifetime
]);
}
// 销毁session
public function destroy($sessionId): bool {
$stmt = $this->pdo->prepare(
"DELETE FROM {$this->table} WHERE session_id = ?"
);
return $stmt->execute([$sessionId]);
}
// 垃圾回收
public function gc($maxLifetime): int|false {
$stmt = $this->pdo->prepare(
"DELETE FROM {$this->table} WHERE expires_at < NOW()"
);
return $stmt->execute() ? $stmt->rowCount() : false;
}
}
// 使用示例:
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$handler = new MySQLSessionHandler($pdo);
session_set_save_handler($handler, true);
session_start();我将从最底层逐行解析这段代码,彻底讲清楚PHP Session存储到MySQL的原理:
类声明部分
class MySQLSessionHandler implements SessionHandlerInterface {class MySQLSessionHandler:定义自定义Session处理器类implements SessionHandlerInterface:必须实现6个标准方法(open/close/read/write/destroy/gc)
类属性
private $pdo; // 存储数据库连接对象 private $table = 'sessions'; // 指定存储表名 private $gcLifetime = 1440; // 垃圾回收生存期(秒)
构造函数
public function __construct(PDO $pdo) {
$this->pdo = $pdo; // 注入已建立的PDO连接
}参数
PDO $pdo:类型提示确保只接受PDO对象赋值给私有属性供其他方法使用
open方法
public function open($savePath, $sessionName): bool {
return true; // 使用现有连接,无需操作
}$savePath:php.ini中session.save_path配置$sessionName:session名称(默认为PHPSESSID)返回true表示成功
close方法
public function close(): bool {
return true; // 保持连接不关闭
}read方法(核心)
public function read($sessionId): string|false {
$stmt = $this->pdo->prepare(
"SELECT session_data FROM {$this->table}
WHERE session_id = ? AND expires_at > NOW()"
);
$stmt->execute([$sessionId]);
return $stmt->fetchColumn() ?: '';
}$sessionId:客户端传来的Session IDSQL查询条件:
匹配session_id字段
检查expires_at是否过期
?: '':无结果返回空字符串(PHP标准要求)
write方法(核心)
public function write($sessionId, $sessionData): bool {
$stmt = $this->pdo->prepare(
"REPLACE INTO {$this->table}
(session_id, session_data, expires_at)
VALUES (?, ?, DATE_ADD(NOW(), INTERVAL ? SECOND))"
);
return $stmt->execute([
$sessionId,
$sessionData,
$this->gcLifetime
]);
}REPLACE INTO:自动处理重复记录(更新或插入)DATE_ADD(NOW(), INTERVAL ? SECOND):计算过期时间点参数绑定防止SQL注入
destroy方法
public function destroy($sessionId): bool {
$stmt = $this->pdo->prepare(
"DELETE FROM {$this->table} WHERE session_id = ?"
);
return $stmt->execute([$sessionId]);
}显式删除指定session记录
gc方法(垃圾回收)
public function gc($maxLifetime): int|false {
$stmt = $this->pdo->prepare(
"DELETE FROM {$this->table} WHERE expires_at < NOW()"
);
return $stmt->execute() ? $stmt->rowCount() : false;
}清理所有过期session
返回删除的记录数
使用示例
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$handler = new MySQLSessionHandler($pdo);
session_set_save_handler($handler, true);
session_start();session_set_save_handler:注册自定义处理器第二个参数
true表示在脚本结束时自动调用write/close
完整工作流程:
session_start()触发open→read
脚本执行期间$_SESSION操作暂存内存
脚本结束时自动write保存修改
概率触发gc清理过期session
session_destroy()调用destroy方法
关键设计要点:
使用REPLACE而非INSERT+UPDATE简化逻辑
通过expires_at字段实现自动过期
所有SQL使用预处理防止注入
保持PDO连接复用提升性能
数据库准备:需创建sessions表,包含session_id(主键)、session_data、expires_at字段
实现原理:通过REPLACE语句实现自动更新过期时间,采用DATE_ADD计算过期时间点
安全特性:自动验证session有效期,防止读取过期session
性能优化:使用PDO预处理语句防止SQL注入,连接复用减少开销
使用步骤:
创建数据库表结构
实例化处理器并注入PDO连接
通过session_set_save_handler()注册处理器
正常使用session_start()开启会话
扩展建议:
可增加加密功能保护session数据
支持Redis等其他存储介质
添加日志记录功能用于调试