我有一个任务是用PHP实现我的自定义会话处理程序,其中来自每个会话的数据保存在JSON文本文件中的指定目录中.

以下是代码(它是文档中的代码和其中的用户注释的组合):

class CustomSessionHandler implements SessionHandlerInterface
{
    //this field contains folder, where all sessions files will be stored
    private string $savePath;

    //according to docs
    public function close(): bool
    {
        return true;
    }

    //delete file of session with specified ID
    public function destroy(string $id): bool
    {
        $sessionPath = $this->savePath . DIRECTORY_SEPARATOR . "sess_" . $id . ".json";
        if (file_exists($sessionPath)) {
            unlink($sessionPath);
        }
        return true;
    }

    //according to docs
    public function gc(int $max_lifetime): int|false
    {
        foreach (glob($this->savePath . DIRECTORY_SEPARATOR . "sess_*" . ".json") as $file) {
            if (filemtime($file) + $max_lifetime < time() && file_exists($file)) {
                unlink($file);
            }
        }
        return true;
    }

    //generally, 'if' doesn't make any sense because save directory in my
    //study project will always exist
    public function open(string $path, string $name): bool
    {
        $this->savePath = $path;
        if (!is_dir($path)) {
            mkdir($path);
        }
        return true;
    }

    //I think the problem is here...
    public function read(string $id): string
    {
        $sessionPath = $this->savePath . DIRECTORY_SEPARATOR . "sess_" . $id . ".json";
        if (!file_exists($sessionPath)) {
            file_put_contents($sessionPath, "");
            chmod($sessionPath, 0777);
        }
        return file_get_contents($sessionPath);
    }

    //create JSON string from serialized session string
    public function write(string $id, string $data): bool
    {
        $rawData = [];
        try {
            $rawData = self::unserialize_php($data);
        } catch (Exception $e) {
            echo $e->getMessage();
        }
        $jsonData = json_encode($rawData);
        return !(file_put_contents($this->savePath . DIRECTORY_SEPARATOR . 'sess_' . $id . ".json", $jsonData) === false);
    }

    /**
     * @throws Exception
     */
    //this code is from comment section from docs and it works fine
    private static function unserialize_php($sessionData): array
    {
        $returnData = [];
        $offset = 0;
        while ($offset < strlen($sessionData)) {
            if (!str_contains(substr($sessionData, $offset), "|")) {
                throw new Exception("invalid data, remaining: " . substr($sessionData, $offset));
            }
            $pos = strpos($sessionData, "|", $offset);
            $num = $pos - $offset;
            $varName = substr($sessionData, $offset, $num);
            $offset += $num + 1;
            $data = unserialize(substr($sessionData, $offset));
            $returnData[$varName] = $data;
            $offset += strlen(serialize($data));
        }
        return $returnData;
    }
}

我创建了一个身份验证系统.在负责判断用户凭据的名为login.php的文件的开头,有以下代码:

$handler = new CustomSessionHandler();
session_set_save_handler($handler);
session_start([
    'save_path' => '/var/www/phpcourse.loc/hometask_3/sessions',
]);

if (isset($_SESSION['logged_in']) && $_SESSION['logged_in'] === true) {
    //welcome.php is the page, where user just see 'Hi, {username}' and can logout
    header('Location: welcome.php');
    exit;
}

问题是会话文件是在不应该发生的时候创建和删除的.

正因为如此,我无法登录,我有一个警告:

Warning:Session_Start():无法解码会话对象.会话已在/var/www/phpcourse.loc/hometask_3/login.php中的第12行被销毁

接下来,如果用户凭据正确,则设置$_SESSION变量:

if (password_verify($password, $hashed_password)) {
    $handler = new CustomSessionHandler();
    session_set_save_handler($handler);
    session_start([
        'save_path' => '/var/www/phpcourse.loc/hometask_3/sessions',
    ]);

    $_SESSION['logged_in'] = true;
    $_SESSION['id'] = $id;
    $_SESSION['username'] = $username;

    header('Location: welcome.php');
}

如果我调试这段代码并完成每个步骤,将会发生以下情况:

  1. 在第一次下载登录页面时,创建的会话文件完全为空

  2. 按下登录按钮后,在从CustomSessionHandler类的Read()函数返回值之后,会话文件被销毁.它发生在上述代码中的第一个SESSION_START()中.

  3. 当代码到达第二个SESSION_START()时,当所有凭据都正确并且下一个页面(欢迎.php)打开时,将再次创建会话文件,并且所有变量都将以JSON格式写入其中.

但是,当我以与文件login.php相同的方式启动脚本开头的Session时,在Read()之后,文件再次被销毁.我又回到了登录页面.

怎么一回事?

Upate:我的CustomSessionHandler似乎有问题,因为如果我只在每个脚本的开头加上简单的session_start(),一切都是正常的.

也许includes有问题?下面是文件,其中调用了SESSION_START().我不会将文件放在用户注销时会话被 destruct 的位置.

login.php

<?php

use hometask_3\DBConnection;
use hometask_3\CustomSessionHandler;

require_once 'classes/DBConnection.php';
require_once 'classes/CustomSessionHandler.php';

$handler = new CustomSessionHandler();
session_set_save_handler($handler);
session_start([
    'save_path' => '/var/www/phpcourse.loc/hometask_3/sessions',
]);

if (isset($_SESSION['logged_in']) && $_SESSION['logged_in'] === true) {
    header('Location: welcome.php');
    exit;
}

//next step is form validation, I just leave code where session
//variables are used
if (password_verify($password, $hashed_password)) {
    $_SESSION['logged_in'] = true;
    $_SESSION['id'] = $id;
    $_SESSION['username'] = $username;
    header('Location: welcome.php');
}

在重新加载登录页面后,再次显示有关会话对象已被 destruct 的警告.

welcome.php

<?php

use hometask_3\CustomSessionHandler;

require_once 'classes/CustomSessionHandler.php';

$handler = new CustomSessionHandler();
session_set_save_handler($handler);
session_start([
    'save_path' => '/var/www/phpcourse.loc/hometask_3/sessions',
]);

if (!isset($_SESSION['logged_in']) || $_SESSION['logged_in'] !== true) {
    header('Location: login.php');
    exit;
}

include 'view/welcomeForm.html.php';

我仍然不能理解,当我在欢迎.php文件中调用SESSION_START()时,为什么调用会话处理程序中的方法Destroy().

推荐答案

SOLVED

正如我所说的,问题出在方法读取上.我完全忘记了PHP序列化方法,并且Read()方法返回了FALSE,因为它不能反序列化会话数据.

就像我说的,问题出在CustomSessionHandler节课的read()方法中.我忘了,此方法应该返回可由会话数据的标准PHP序列化方法反序列化的字符串.

因为我的方法返回一个JSON文本字符串,所以发生了一个错误,会话立即被销毁,所以调用了destroy().

现在,我的Read方法如下所示:

public function read(string $id): string
{
    $sessionPath = $this->savePath . DIRECTORY_SEPARATOR . 
                   "sess_" . $id . ".json";
    if (!file_exists($sessionPath)) {
        file_put_contents($sessionPath, "");
        chmod($sessionPath, 0777);
    }
    $fileData = file_get_contents($sessionPath);
    $arrData = json_decode($fileData);
    if (!$fileData) {
        return '';
    } else {
        return self::serialize_php($arrData);
    }
}

方法serialize_php($data)也被从文档或Github中窃取.它只是手动将数据序列化为PHP所需的格式.

public static function serialize_php(array|stdClass $data): string
{
    $res = '';
    foreach ($data as $key => $value) {
        if (strcmp($key, (string)intval($key)) === 0) {
            //unsupported integer key
            continue;
        }
        if (strcspn($key, '|!') !== strlen($key)) {
            //unsupported characters
            return '';
        }
        $res .= $key . '|' . serialize($value);
    }
    return $res;
}

所以,我不认为这个解决方案是最好的,但现在一切都奏效了.起初我想重新定义会话常量session.serialize_handler,但我不知道如何做到这一点. 当然,我在脚本中删除了一个对Session_Start()的调用.

Php相关问答推荐

如何在Laravel Controller中存储文本区域值?

如果再次调用SESSION_START(),会话.gc-max是否会重新启动?或者它是从第一次创建会话开始计算的?

向在添加到购物车上动态创建的WooCommerce产品添加发货类别

为什么在我的PHP联系人脚本中开机自检后出现未定义变量错误?

将下拉 Select 的值从WooCommerce后端保存并显示到前端

在WooCommerce管理中 for each 运输方法设置添加自定义字段

将一个按钮添加到WooCommerce产品的管理视图中,该按钮链接到一个自定义的WebHook,并在末尾附加产品ID

CKEDITOR-如何在PHP中判断提交后的空数据

如果WooCommerce购物车在 checkout 时被清空,请重定向至store 页面

$this->;db->;update();CodIgnitor中的功能不工作

我不明白为什么我收到未定义的数组键异常错误 - Laravel 9 PHP 8

PHP向API发送curl请求

优化后的标题:如何在WooCommerce我的帐户编辑表单中添加账单字段

PHP:如何将多列sql查询结果转换成数组

用于检测此模式的正则表达式:something;something=something,something=something... 未知次数

是否有适用于 Windows 的真正 64 位版本的 PHP 5.6?

将变量从中间件传递到控制器 Laravel

无法在 Laravel 10 中安装 jenssegers/mongodb

如何在 Laravel 中验证多个输入?

如何将 2021-04-08T22:25:09.335541Z 作为时间戳保存到 Laravel 中的数据库中?