如何将会话数据保存到数据库而不是文件系统?

Mar*_*tin 14 php mysql database session

我有两个网站,一个是TLS,一个不是,两个都是针对同一个客户,但我需要网站彼此共享(并且只有彼此)用户,订单,账户等的常用数据.

这通常是通过$_SESSION数据完成的,但显然这些不能在其他站点上工作,我发现我可以将会话数据存储在数据库(MySQL)中而不是文件系统中.

我已经挖过这个有用的指南,以及这个较旧但 有用的指南.我还发现这个指南有更新的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;
Run Code Online (Sandbox Code Playgroud)

请注意: 在我向您展示我的界面类之前,请知道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();
    }

}
Run Code Online (Sandbox Code Playgroud)

我的测试页面(从头开始写!)

<?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;
Run Code Online (Sandbox Code Playgroud)

问题:

我运行的测试页将数据保存到数据库ok,但它们似乎没有检索数据,

我启用了错误日志记录,并且未报告任何PHP错误.没有报告严重的MySQL错误.

为什么不起作用?

Mar*_*tin 25

我发现在几个小时的调试过程中,在许多Google搜索中找到的引用文章以及Stack Overflow的一大部分答案,例如此处,此处此处都提供了无效或过时的信息.

将会话数据保存到数据库可能导致[关键]问题的事情:

  • 虽然所有在线示例都表明您可以"填写" session_set_save_handler,但没有一个说明您还必须设置register_shutdown_function('session_write_close')(参考).

  • 一些(旧的)导游指的是过时的SQL数据库结构,应该不会被使用.将会话数据保存到数据库所需的数据库结构是:id/ access/ data.而已.不需要各种额外的时间戳列,就像我在一些"指南"和示例中看到的那样.

    • 一些较旧的指南也有过时的MySQL语法,如 DELETE * FROM ...
  • 类[我的问题作出]必须实现SessionHandlerInterface.我已经看到了指南,sessionHandler它们的实现不是一个合适的接口.也许早期版本的PHP有一个稍微不同的方法(可能<5.4).

  • 会话类方法必须返回PHP手册中设置的值.同样,可能继承自5.4之前的PHP但我读过的两个指南声明class->open返回要读取的行,而PHP手册指出它需要返回truefalse仅返回 .

  • 这是我的原始问题的原因:我使用自定义会话名称(实际上ID是会话名称和会话ID 是相同的!)根据这个非常好的StackOverflow帖子,这产生了一个长度为128个字符的会话名称.由于会话名称是唯一需要破解以破坏会话并接管会话劫持的密钥,因此更长的名称/ ID是一件非常好的事情.

    • 但是,这导致了一个问题,因为MySQL默默地将会话ID分解为只有32个字符而不是128个,所以它永远无法在数据库中找到会话数据.这是一个完全无声的问题(可能是由于我的数据库连接类没有抛出这类事情的警告).但这是值得关注的.如果从数据库中检索会话有任何问题,请首先检查完整的会话ID是否可以存储在提供的字段中.

因此,除了所有这些之外,还需要添加一些额外的细节:

PHP手册页(上面链接)显示了一个类对象的不合适的行:

$handler = new MySessionHandler();
session_set_save_handler($handler, true);
session_start();
Run Code Online (Sandbox Code Playgroud)

如果你把它放在类构造函数中它也可以正常工作:

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();
    }
...
}
Run Code Online (Sandbox Code Playgroud)

意味着要在输出页面上启动会话,您只需要:

<?php
require "path/to/sessionhandler.class.php"; 
new MySessionHandler();

//Bang session has been setup and started and works
Run Code Online (Sandbox Code Playgroud)

作为参考,完整的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();
    }

}
Run Code Online (Sandbox Code Playgroud)

用法:如上面的类代码文本所示.

  • 会话何时最初添加到数据库?你的`open`函数应该只检查是否存在与数据库的有效连接.现在它似乎正在检查数据库中是否已存在有效会话 - 如果这是一个正在创建的新会话,我怀疑它是否会返回`true`. (2认同)
  • @paul 看原接口实现: public function open($savePath, $sessionName) { $this-&gt;savePath = $savePath; if (!is_dir($this-&gt;savePath)) { mkdir($this-&gt;savePath, 0777); 返回真;} (2认同)