提问人:Martin 提问时间:4/21/2016 最后编辑:CommunityMartin 更新时间:6/2/2019 访问量:42615
如何将 PHP 会话数据保存到数据库而不是文件系统中?
How do I save PHP session data to a database instead of in the file system?
问:
我有两个网站,一个是TLS,一个不是,都是针对同一个客户端的,但我需要这些网站相互共享(并且只有彼此)用户、订单、帐户等的通用数据。
这通常是用数据完成的,但我显然这些不能在其他站点上工作,而且我发现我可以将会话数据存储在数据库(MySQL)而不是文件系统中。$_SESSION
我四处挖掘并找到了这个有用的指南以及这个较旧但有用的指南。我还找到了这个指南,它有更多最新的MySQL。
我编写了一个接口类,但它只能部分工作,它将会话数据存储在数据库中,但不会检索它。我还使用了PHP手册中建议的方法。
我的MySQL(从上述前几个链接复制):
CREATE TABLE `sessions` (
`id` varchar(32) COLLATE utf8_unicode_ci NOT NULL,
`access` int(10) NOT NULL,
`data` text COLLATE utf8_unicode_ci NOT NULL,
UNIQUE KEY `id` (`id`)
) ENGINE=InnoDb DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
请注意:在我向您展示我的接口类之前,请知道 Db 连接使用我自己的自定义接口,并且它本身运行良好。
包含会话数据库连接详细信息,因为我将会话保存在与主要网站内容不同的数据库上。
$sessionDBconnectionUrl
我的接口类(基于上述所有链接)
<?php
/***
* Created by PhpStorm.
***/
class HafSessionHandler implements SessionHandler {
private $database = null;
public function __construct($sessionDBconnectionUrl){
if(!empty($sessionDBconnectionUrl) && file_exists($_SERVER['DOCUMENT_ROOT'].$sessionDBconnectionUrl)) {
require_once "class.dataBase.php";
// Instantiate new Database object
$this->database = new Database($sessionDBconnectionUrl);
}
else {
error_log("Session could not initialise class.");
}
}
/**
* Open
*/
public function open($savepath, $id){
$openRow = $this->database->getSelect("SELECT `data` FROM sessions WHERE id = ? LIMIT 1",$id);
if($this->database->selectRowsFoundCounter() == 1){
// Return True
return $openRow['data'];
}
else {
// Return False
return ' ';
}
/**
* Read
*/
public function read($id)
{
// Set query
$readRow = $this->database->getSelect('SELECT `data` FROM sessions WHERE id = ? LIMIT 1', $id,TRUE);
if ($this->database->selectRowsFoundCounter() > 0) {
return $readRow['data'];
} else {
error_log("could not read session id ".$id);
return '';
}
}
/**
* Write
*/
public function write($id, $data)
{
$access = time();
// Set query
$dataReplace[0] = $id;
$dataReplace[1] = $access;
$dataReplace[2] = $data;
if ($this->database->noReturnQuery('REPLACE INTO sessions(id,access,`data`) VALUES (?, ?, ?)', $dataReplace)) {
return TRUE;
} else {
return FALSE;
}
}
/**
* Destroy
*/
public function destroy($id)
{
// Set query
if ($this->database->noReturnQuery('DELETE * FROM sessions WHERE id = ? ', $id)) {
return TRUE;
} else {
return FALSE;
}
}
/**
* Close
*/
public function close(){
// Close the database connection
// If successful
if($this->database->dbiLink->close){
// Return True
return true;
}
// Return False
return false;
}
/**
* Garbage Collection
*/
public function gc($max)
{
// Calculate what is to be deemed old
$old = time() - $max;
// Set query
if ($this->database->noReturnQuery('DELETE * FROM sessions WHERE access < ?', $old)) {
return TRUE;
} else {
return FALSE;
}
}
public function __destruct()
{
$this->close();
}
}
我的测试页面(从头开始编写!
<?php
require "class.sessionHandler.inc.php";
$HSH = new HafSessionHandler("connection.session.dbxlink.php");
session_set_save_handler( $HSH, TRUE );
session_start();
print "<p>Hello this is an index page</p>";
$_SESSION['horses'] = "treesx3";
$_SESSION['tiespan'] = (int)$_SESSION['tiespan']+7;
print "<p>There should be some session data in the database now. <a href='index3.php'>link</a></p>";
var_dump($_SESSION);
exit;
问题:
我运行的测试页面将数据保存到数据库正常,但它们似乎没有检索到数据,
我启用了错误日志记录,并且没有报告PHP错误。未报告严重的MySQL错误。
为什么它不起作用?
答:
在几个小时的调试过程中,我发现在大量 Google 搜索中找到的参考文章以及 Stack Overflow 答案的重要子集(例如 here、here 和 here)都提供了无效或过时的信息。
在将会话数据保存到数据库时可能导致 [严重] 问题的原因:
虽然所有在线示例都声明您可以“填充”,但没有一个声明您还必须设置 too(引用)。
session_set_save_handler
register_shutdown_function('session_write_close')
一些(较旧的)指南引用了过时的 SQL 数据库结构,不应使用。将会话数据保存到数据库所需的数据库结构为://。就是这样。不需要各种额外的时间戳列,正如我在一些“指南”和示例中看到的那样。
id
access
data
- 一些较旧的指南也具有过时的MySQL语法,例如
DELETE * FROM ...
- 一些较旧的指南也具有过时的MySQL语法,例如
[在我的问题中提出的]类必须实现 .我已经看到指南(上面引用)给出了其实现不是一个合适的接口。也许以前版本的 PHP 方法略有不同(可能是 <5.4)。
SessionHandlerInterface
sessionHandler
会话类方法必须返回 PHP 手册中规定的值。同样,可能继承自 5.4 之前的 PHP,但我阅读的两个指南指出返回要读取的行,而 PHP 手册指出它需要返回或仅返回。
class->open
true
false
这是我原始问题的原因:我使用自定义会话名称(实际上 id 作为会话名称和会话 id 是一回事!),就像这篇非常好的 StackOverflow 帖子一样,这生成了一个长度为 128 个字符的会话名称。由于会话名称是破坏会话并接管会话劫持所需的唯一密钥,因此较长的名称/ID 是一件非常好的事情。
- 但是,这导致了一个问题,因为MySQL以静默方式将会话ID切片到只有32个字符而不是128个字符,因此它永远无法在数据库中找到会话数据。这是一个完全无声的问题(可能是由于我的数据库连接类没有抛出此类警告)。但这是需要注意的。如果您在从数据库中检索会话时遇到任何问题,首先检查是否可以将完整的会话 ID 存储在提供的字段中。
因此,除了所有这些之外,还有一些额外的细节需要添加:
PHP 手册页(上面链接)显示了一堆不适合类对象的行:
$handler = new MySessionHandler(); session_set_save_handler($handler, true); session_start();
然而,如果你把它放在类构造函数中,它也能正常工作:
class MySessionHandler implements SessionHandlerInterface {
private $database = null;
public function __construct(){
$this->database = new Database(whatever);
// Set handler to overide SESSION
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();
}
...
}
这意味着,要在输出页面上启动会话,您只需要:
<?php
require "path/to/sessionhandler.class.php";
new MySessionHandler();
//Bang session has been setup and started and works
作为参考,完整的 Session 通信类如下,这适用于 PHP 5.6(可能还有 7 个,但尚未在 7 上测试)
<?php
/***
* Created by PhpStorm.
***/
class MySessionHandler implements SessionHandlerInterface {
private $database = null;
public function __construct($sessionDBconnectionUrl){
/***
* Just setting up my own database connection. Use yours as you need.
***/
require_once "class.database.include.php";
$this->database = new DatabaseObject($sessionDBconnectionUrl);
// Set handler to overide SESSION
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();
}
/**
* Open
*/
public function open($savepath, $id){
// If successful
$this->database->getSelect("SELECT `data` FROM sessions WHERE id = ? LIMIT 1",$id,TRUE);
if($this->database->selectRowsFoundCounter() == 1){
// Return True
return true;
}
// Return False
return false;
}
/**
* Read
*/
public function read($id)
{
// Set query
$readRow = $this->database->getSelect('SELECT `data` FROM sessions WHERE id = ? LIMIT 1', $id,TRUE);
if ($this->database->selectRowsFoundCounter() > 0) {
return $readRow['data'];
} else {
return '';
}
}
/**
* Write
*/
public function write($id, $data)
{
// Create time stamp
$access = time();
// Set query
$dataReplace[0] = $id;
$dataReplace[1] = $access;
$dataReplace[2] = $data;
if ($this->database->noReturnQuery('REPLACE INTO sessions(id,access,`data`) VALUES (?, ?, ?)', $dataReplace)) {
return true;
} else {
return false;
}
}
/**
* Destroy
*/
public function destroy($id)
{
// Set query
if ($this->database->noReturnQuery('DELETE FROM sessions WHERE id = ? LIMIT 1', $id)) {
return true;
} else {
return false;
}
}
/**
* Close
*/
public function close(){
// Close the database connection
if($this->database->dbiLink->close){
// Return True
return true;
}
// Return False
return false;
}
/**
* Garbage Collection
*/
public function gc($max)
{
// Calculate what is to be deemed old
$old = time() - $max;
if ($this->database->noReturnQuery('DELETE FROM sessions WHERE access < ?', $old)) {
return true;
} else {
return false;
}
}
public function __destruct()
{
$this->close();
}
}
用法:如类代码文本上方所示。
评论
open
true
open
评论
http://mywebsite.com/
http://google.com/