如何将PHP会话数据保存到数据库而不是文件系统中? [英] How do I save PHP session data to a database instead of in the file system?

查看:53
本文介绍了如何将PHP会话数据保存到数据库而不是文件系统中?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有两个网站,一个是TLS,一个不是,这两个都是针对同一客户端的,但是我需要这些网站彼此共享(并且彼此之间)供用户使用的通用数据订单帐户

I have two websites, One is TLS and one is not, both are for the same client but I need the websites to share with each other (and only each other) common data for users, orders, accounts etc.

通常使用$_SESSION数据完成此操作,但是我显然无法在其他站点上使用它们,而且我发现我可以将会话数据存储在数据库(MySQL)中,而不是文件系统中.

This would normally be done with $_SESSION data but I obviously these can't work across other sites, and I have found that I can store session data in a database (MySQL) rather than in the file system.

我已经挖了一下,发现此实用指南以及较旧但 有用的指南.我还发现了本指南,其中包含更多内容最新的MySQL.

I have dug around and found This useful guide as well as this older but useful guide. I also found this guide which has slightly more up to date MySQL.

我已经编写了一个接口类,但是它只能部分起作用,它将会话数据存储在数据库中,但不会检索它.我还使用了PHP手册中的建议的方法

I have written an interface class but it only partly works, it stores the session data in the database, but it doesn't retrieve it. I have also used the suggested method from the PHP manual.

我的MySQL (复制自上述前两个链接):

My MySQL (as copied from first couple of the above links):

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连接使用我自己定制的接口,并且它本身可以完美地工作.

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.

$sessionDBconnectionUrl包含会话数据库连接详细信息,因为我将会话保留在与主要网站内容不同的数据库上.

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

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

My interface class (as based on all the above links)

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

问题:

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

The test pages I run save the data to the database ok but they do not seem to retrieve the data,

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

I have error logging enabled and no PHP errors are reported. No critical MySQL errors are reported.

为什么不起作用?

推荐答案

在数小时的调试过程中,我发现在众多Google搜索中找到的参考文章以及大量的Stack Overflow答案(例如此处此处此处均提供无效或过时的信息.

I have found over the course of several hours debugging that the referenced articles found on numerous Google searches as well as a significant subset of Stack Overflow answers such as here, here and here all provide invalid or outdated information.

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

Things that can cause [critical] issues with saving session data to a database:

  • 尽管所有示例在线状态都可以填充" session_set_save_handler,但是没有一个示例说明您也必须设置register_shutdown_function('session_write_close')(

  • While all the examples online state that you can "fill" the session_set_save_handler, none of them state that you must also set the register_shutdown_function('session_write_close') too (reference).

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

Several (older) guides refer to an outdated SQL Database structure, and should not be used. The database structure that you need for saving session data to the database is: id/access/data. That's it. no need for various extra timestamp columns as I've seen on a few "guides" and examples.

  • 一些较旧的指南也已经过时了MySQL语法,例如DELETE * FROM ...

[在我的问题中提出的]类必须 实现 SessionHandlerInterface.我已经看到了指南(上面已引用),这些指南给出了sessionHandler的实现,这是不合适的接口.也许以前版本的PHP的方法稍有不同(可能小于5.4).

The class [made in my question] must implement the SessionHandlerInterface . I have seen guides (referenced above) that give the implementation of sessionHandler which is not a suitable interface. Perhaps previous versions of PHP had a slightly different method (probably <5.4).

会话类方法必须返回PHP手册中列出的值.同样,可能是从5.4之前的PHP继承的,但是我阅读的两个指南指出class->open返回要读取的行,而

The session class methods must return the values set out by the PHP manual. Again, probably inherited from pre-5.4 PHP but two guides I read stated that class->open returns the row to be read, whereas the PHP manual states that it needs to return true or false only.

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

This is the cause of my Original Issue: I was using custom session names (actually id's as session names and session id's are the same thing!) as per this very good StackOverflow post and this was generating a session name that was 128 characters long. As the session name is the sole key that is needed to be cracked to compromise a session and take over with a session hijacking then a longer name/id is a very good thing.

  • 但是,这引起了一个问题,因为 MySQL默默地将会话ID切成32个字符而不是128个字符,因此它永远无法找到会话数据在数据库中.这是一个完全沉默的问题(可能是由于我的数据库连接类未引发此类警告).但是,这是要提防的.如果从数据库检索会话有任何问题,请首先检查 full 会话ID是否可以存储在提供的字段中.
  • But, this caused an issue because MySQL was silently slicing the session id down to just 32 characters instead of 128, so it was never able to find the session data in the database. This was a completely silent issue (maybe due to my database connection class not throwing warnings of such things). But this is the one to watch out for. If you have any issues with retrieving sessions from a database first check is that the full session id can be stored in the field provided.

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

So with all that out of the way there are some extra details to add as well:

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

The PHP manual page (linked above) shows an unsuitable pile of lines for a class object:

$handler = new MySessionHandler();
session_set_save_handler($handler, true);
session_start();

如果将其放在类构造函数中,效果也一样:

Whereas it works just as well if you put this in the class constructor:

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 means that to then start a session on your output page all you need is:

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

//Bang session has been setup and started and works


完整的Session通讯类如下,以供参考,该类适用于PHP 5.6(可能是7,但尚未在7上进行测试)


For reference the complete Session communication class is as follows, this works with PHP 5.6 (and probably 7 but not tested on 7 yet)

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

}

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

Usage: As shown just above the class code text.

这篇关于如何将PHP会话数据保存到数据库而不是文件系统中?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆