草庐IT

php - 如何将 PHP session 数据保存到数据库而不是文件系统中?

coder 2023-06-11 原文

我有两个网站,一个是 TLS,一个不是,两者都针对同一个客户端,但我需要这些网站为用户彼此(并且只彼此)共享公共(public)数据, 订单账户

这通常是用 $_SESSION 数据完成的,但我显然不能在其他站点上使用这些数据,而且我发现我可以将 session 数据存储在数据库 (MySQL) 中,而不是文件系统。

我四处寻找并找到了This useful guide以及这个较旧但 useful guide .我还找到了this guide它有稍微更新的 MySQL。

我写了一个接口(interface)类,但它只能部分工作,它将 session 数据存储在数据库中,但不检索它。我也用过 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: Before I show you my interface class please know that the Db connetion uses my own custom made interface and that works perfectly, in itself.

The $sessionDBconnectionUrl contains the Session Database connection details as I am keeping sessions on a seperate Database from the main website contents.

我的界面类(基于以上所有链接)

<?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 , herehere都提供无效或过时的信息。

在将 session 数据保存到数据库时可能会导致 [严重] 问题:

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

  • 一些(较旧的)指南提到过时的 SQL 数据库结构,应该使用。将 session 数据保存到数据库所需的数据库结构是:id/access/data。而已。正如我在一些“指南”和示例中看到的那样,不需要各种额外的时间戳列。

    • 一些较旧的指南也有过时的 MySQL 语法,例如 DELETE * FROM ...
  • [在我的问题中提出的]类必须 implement SessionHandlerInterface 。我已经看到指南(上面引用过)给出了 sessionHandler 的实现,这不是一个合适的接口(interface)。也许以前版本的 PHP 有一个稍微不同的方法(可能 <5.4)。>

  • session 类方法必须返回 PHP 手册中规定的值。同样,可能继承自 5.4 之前的 PHP,但我阅读的两个指南指出 class->open 返回要读取的行,而 PHP manual states它只需要返回 truefalse

  • 这是我原来的问题的原因:我使用的是自定义 session 名称(实际上 id 作为 session 名称和 session id 的是一回事!)根据 this very good StackOverflow post这会生成一个 128 个字符长的 session 名称。由于 session 名称是破解 session 并使用 session hijacking 接管所需的唯一 key 。那么更长的名字/身份证是一件非常好的事情。

    • 但是,这导致了一个问题,因为 MySQL 默默地将 session ID 分割为 32 个字符而不是 128 个字符,因此它永远无法找到 session 数据在数据库中。这是一个完全无声的问题(可能是由于我的数据库连接类没有对此类事情发出警告)。但这是需要注意的。如果您在从数据库中检索 session 时遇到任何问题,请首先检查 full session 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();
    }
...
}

意味着在你的输出页面上开始一个 session 你只需要:

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

}

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

关于php - 如何将 PHP session 数据保存到数据库而不是文件系统中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36753513/

有关php - 如何将 PHP session 数据保存到数据库而不是文件系统中?的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  4. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  5. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  6. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  7. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  8. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  9. ruby - 如何每月在 Heroku 运行一次 Scheduler 插件? - 2

    在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/

  10. ruby-on-rails - 如何从 format.xml 中删除 <hash></hash> - 2

    我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为

随机推荐