我有两个网站,一个是TLS,一个不是,这两个都是针对同一客户端的,但是我需要这些网站彼此(并且只能彼此)共享 用户 , 订单 , 帐户 等的通用数据。
通常可以使用$_SESSION数据完成此操作,但是我显然不能在其他站点上使用它们,而且我发现我可以将会话数据存储在数据库(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为我保持从主要网站内容的独立数据库会话包含会话数据库连接的详细信息。
请注意: 在向我展示我的接口类之前,请知道Db连接使用我自己定制的接口,并且它本身可以完美地工作。
在$sessionDBconnectionUrl为我保持从主要网站内容的独立数据库会话包含会话数据库连接的详细信息。
$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)都提供了无效或过时的信息。
在将会话数据保存到数据库中可能导致[严重]问题的事情:
尽管所有在线示例都说明您可以“填充” session_set_save_handler,但没有一个示例说明您也必须设置它register_shutdown_function('session_write_close')(参考)。
session_set_save_handler
register_shutdown_function('session_write_close')
一些(旧的)导游指的是过时的SQL数据库结构,应该 不会 被使用。将会话数据保存到数据库所需的数据库结构是:id/ access/ data。而已。正如我在一些“指南”和示例中看到的那样,不需要各种额外的时间戳列。
id
access
data
DELETE * FROM ...
SessionHandlerInterface
sessionHandler
会话类方法 必须 返回PHP手册中列出的值。同样,可能是从PHP 5.4之前的版本继承的,但是我阅读的两个指南指出class->open返回要读取的行,而PHP手册指出需要返回true或false仅返回行 。
class->open
true
false
这是导致我的原始问题的原因 :根据这个非常好的StackOverflow帖子,我使用的是自定义会话名称(实际上,会话名称和会话ID 都是相同的! ),并且生成的会话名称长度为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(); } }
用法:如类代码文本上方所示。