我有两个网站,一个是TLS,一个不是,这两个都是为同一个客户端,但我需要的网站相互分享(只有彼此)users,orders,accounts等共同的数据.

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

我到处找了找,找到了This useful guide个,还有这个更老的,但是 useful guide.我还找到了this guide,它的MySQL稍微更新一些.

我已经编写了一个接口类,但它只能部分工作,它将会话数据存储在数据库中,但不检索它.我也用过suggested method from the PHP manual.

My 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;

Please Note:个 在我向您展示我的接口类之前,请您知道DB连接使用我自己定制的接口,这本身就可以很好地工作.

$sessionDBconnectionUrl包含会话数据库连接详细信息,因为我将会话保存在独立于主网站内容的数据库中.

My interface class(根据上述所有链接)

<?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();
    }

}

My Test Page(从头开始写!)

<?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;

Issue:

我运行的测试页面可以将数据保存到数据库中,但它们似乎无法检索数据,

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

为什么不动呢?

推荐答案

在几个小时的调试过程中,我发现在众多谷歌搜索中找到的引用文章以及堆栈溢出答案的重要子集(如hereherehere)都提供了无效或过时的信息.

可能导致将会话数据保存到数据库时出现[严重]问题的情况:

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

  • 有几个(较旧的)指南引用了过时的SQL数据库 struct ,应该使用not.将会话数据保存到数据库所需的数据库 struct 是:id/access/data.就这样.没有必要像我在一些"指南"和示例中看到的那样,使用各种额外的时间戳列.

    • 一些较旧的指南也有过时的MySQL语法,如DELETE * FROM ...
  • 这个班(在我的问题中)必须是100SessionHandlerInterface.我看过一些指南(上面提到的),它们给出了sessionHandler的实现,这不是一个合适的接口.也许PHP以前的版本有稍微不同的方法(可能是<;5.4).

  • 会话类方法must返回由PHP手册设定的值.同样,可能继承自5.4PHP之前的版本,但我读到的两个指南指出,class->open返回要读取的行,而它需要返回的PHP manual states只返回truefalse.

  • This is the cause of my Original Issue:我按照this very good StackOverflow post使用了自定义会话名称(实际上是id作为会话名称,会话id是are the same thing!),这将生成一个长度为128个字符的会话名称.由于会话名称是唯一需要破解的密钥,需要破解该密钥才能危害会话并用session hijacking取代它,因此较长的名称/ID是一件非常好的事情.

    • 但是,这导致了一个问题,因为MySQL was silently slicing the session id减少到只有32个字符,而不是128个字符,因此它永远无法在数据库中找到会话数据.这是一个完全silent的问题(可能是因为我的数据库连接类没有抛出此类警告).但这是一个需要注意的问题.如果您在从数据库检索会话时有任何问题,首先判断full会话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();
    }
...
}

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

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

//Bang session has been setup and started and works

作为参考,完整的会话通信类如下所示,它适用于PHP5.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();
    }

}

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

Database相关问答推荐

Sequel Pro / MAMP 在哪里存储本地数据库?

MySQL解释更新

SQL Select 用字符串替换整数

在 postgresql 中将列从字符串更改为字符串数组

Laravel 5 从 URL 获取 ID

SQLite3 的动态类型

如何在 Play 2.0 中 for each 环境设置不同的数据库?

无法启动 MongoDB:Windows 中的系统错误 1067

如何在大型数据库中使用 typeahead.js

在内存中创建 SQLite 数据库

如果表不存在,如何使用 Derby Db 创建表

处理多个表的最佳实践

在数据库中存储业务逻辑

为什么用 varbinary 而不是 varchar

Firebase 排行榜排名

为 django 模型自动创建数据的工具

如何在数据库中获取原始的created_at值(不是转换为 ActiveSupport::TimeWithZone 的对象)

如何解析来自 Google 快讯的数据?

Data Mapper 是不是比 Active Record 更现代的趋势

获取 SQL Server 2008 中新插入行的主键