在默认情况下,PHP会把全部会话数据保存在服务器上的文本文件里。这些文件通常使保存在系统的临时目录里(比如UNIX和Mac OS X系统中就是/tmp目录),其文件名匹配会话ID。然而,PHP还提供一种机制可以让我们以其他方式来管理会话,比如在数据库中存储这些会话数据。
这样做的原因是提高系统的安全性。在共享主机服务器上,如果没有进行特别的设置,所有网站站点都会使用同一个临时目录,这意味着数十个程序都在同一个位置对文件进行读写操作。知道了这些,我们就可以编写一个脚本从这个目录里读取会话文件夹中所有的文件内容,这样的话,我们就可以在其他站点上访问到存储的用户数据了。(其实我们也可以改变会话的目录,安全起见,我们可以在每次调用session_start()之前调用函数session_save_path(),同时我们应确保新目录存在并且具有适当的权限)
另外,把会话数据保存在数据库里可以更方便地搜索Web站点会话更多的信息,我们可以查询活动会话的数量,还可以对会话数据进行备份。
第三个原因,如果站点运行于多个服务器上,这种情况下,同一个用户在一个会话过程有可能会对不同服务器上的多个页面发送请求。但是会话数据如果保存在某一个服务器上的文件里,就不能被其他服务器上的页面使用,所以我们要使用数据库保存会话数据库。
1、创建会话表
字段类型 | 保存的数据 |
CHAR(32) | 会话ID |
TEXT | 会话数据 |
TIMESTAMP | 会话数据的最后一次访问时间 |
CREATE TABLE session_tb(
id CHAR(32) NOT NULL,
data TEXT,
last_accessed TIMESTAMP NOT NULL,
PRIMARY KEY (id)
);
确认会话表的结构:
2、定义会话函数
在创建了数据表之后,把会话数据保存到数据库需要两个步骤(从PHP的角度来说):
第一步,定义与数据库交互的函数;
第二部,让PHP来使用这些函数。
在第二部中,需要通过调用函数session_set_save_handler()来完成,该函数需要6个参数,每个参数都是一个函数名,如下表所示:
次序 | 函数被调用的时机 |
1 | 启动会话 |
2 | 关闭会话 |
3 | 读取会话数据 |
4 | 写入会话数据 |
5 | 销毁会话数据 |
6 | 删除旧的会话数据(即执行垃圾收集程序) |
除了“读取”函数之外,其他函数都必需返回一个布尔值;而“读取”函数必需返回一个字符串,哪怕是一个空字符串。
注:
每次会话启动时,“打开”和“读取”函数将会立即被调用。当“读取”函数被调用的时候,可能会发生垃圾回收的过程。
当脚本执行结束时,“写入”函数将会被调用,然后调用“关闭”函数,除非会话被销毁了,这种情况下,不会调用“写入”函数。但是,在“关闭”函数之后,调用“销毁”函数。
建立会话处理函数:
<?php
//定义用于数据库连接的全局变量
$con = null;
//定义“打开”函数
//这个函数不接收任何参数
//函数会打开数据库连接
//同时这个函数应返回true
function open_session() {
global $con;
$con = mysqli_connect('localhost', 'root', '', 'dnf_db');
return true;
}
//定义“关闭”函数
//不接收参数
//函数用于关闭数据库
//返回关闭状态
function close_session() {
global $con;
return mysqli_close($con);
}
//定义“读取”函数
//接收参数:the session ID
//函数用于取数据
//返回session data 字符串
function read_session($id) {
global $con;
$sql = sprintf('SELECT data FROM session_tb WHERE id="%s"',mysqli_real_escape_string($con, $id));
$result = mysqli_query($con, $sql);
if (mysqli_num_rows($result) == 1) {
list($data) = mysqli_fetch_array($result, MYSQLI_NUM);
return $data;
}
else {
return "";
}
}
//定义“写入”函数
//接收两个参数:session ID and session data
function write_session($id, $data) {
global $con;
$sql = sprintf('REPLACE INTO session_tb (id, data) VALUES ("%s", "%s")', mysqli_real_escape_string($con, $id), mysqli_real_escape_string($con, $data));
$result = mysqli_query($con, $sql);
return true;
}
//定义“销毁”函数
//接收一个参数:the session ID
function destroy_session($id) {
global $con;
$sql = sprintf('DELETE FROM session_tb WHERE id = "%s"',mysqli_real_escape_string($con, $id));
$result = mysqli_query($con, $sql);
//清空session数组
$_SESSION = array();
return true;
}
//定义“清除”函数
//接收一个参数 : a value in seconds
function gc_session($expire) {
global $con;
$sql = sprintf('DELETE FROM session_tb WHERE date_add(last_accessed, INTERVAL %d SECOND) < NOW()', (int) $expire);
$result = mysqli_query($con, $sql);
return true;
}
//使用这些函数
session_set_save_handler('open_session', 'close_session', 'read_session', 'write_session', 'destroy_session', 'gc_session');
session_start();
说明:函数session_set_save_handler()并不会启动会话,要调用session_start()用于启动会话。(函数的调用次序不能改变,如果PHP设置里session.auto_start被打开了,就不能使用session_set_save_handler())。
编写一个页面使用新会话处理程序:
<?php
require('session.php');
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DB Sesssion Test</title>
</head>
<body>
<?php
if (empty($_SESSION)) {
$_SESSION['blah'] = 'KBen';
$_SESSION['this'] = '533517.45';
$_SESSION['that'] = 'blue';
echo "<p>已经存储SESSION数据</p>";
}
else {
echo "<p>SESSION数据存在:<pre>".print_r($_SESSION, 1)."</pre></p>";
}
//退出
if (isset($_GET['logout'])) {
session_destroy();
echo "<p>销毁SESSION</p>";
}
else {
echo "<a href='testSession.php?logout=true'>Log Out</a>";
}
echo "<p>SESSION数据存在:<pre>".print_r($_SESSION, 1)."</pre></p>";
echo "</body>
</html>";
session_write_close();
?>
当页面第一次加载时,新数据会保存在会话里。
数据也确实存到了数据表中:
当页面第二次被加载时,就可以使用已经存在的数据了。
点击“注销”:
看下数据表:
总结一下:在PHP中修改会话的处理方式,可以调用session_set_save_handler()函数。该函数接收6个参数,且次序不能乱。