浅谈PHP的session机制

1.前言

PHP的源生session默认的文件存储机制,我想大家用起来肯定遇到不少坑,laravel框架的作者也不怎么建议使用源生的session机制,而代替它的方法也很简单,自己实现了一个文件session存储机制,比起源生的session灵活性很高,也能很好的控制原来session的诡异生命周期。今天,我想介绍下session的原理,并且自己实现一个文件session机制。

2.什么是session

session翻译过来就是会话,我们的客户端浏览器,每次发送请求给Web服务器就可以看作是一个会话。

3.session诞生的技术背景

HTTP协议是无状态的协议,我们web服务器需要记忆不同的HTTP请求,那么势必会引入一个离线存储的概念。HTTP的RFC文档中,并没有提及记忆的概念,于是浏览器厂商加入了cookie的概念,用于维护和web服务器的会话状态。

4.一次session的生命周期

浏览器第一次请求web服务器,同一个域名下是没有cookie的存储,而web服务器上的PHP程序开启会话机制,如:session_start()之后,在响应头中会自动添加会话唯一标识的cookie信息。浏览器拿到cookie响应头后,就会自动存储在客户端,所以,我们知道浏览器每次请求Web服务器,会自动的把cookie信息跟着请求一起发送给Web服务器,这样,Web服务器就能识别发起HTTP协议的浏览器了。这里有一个问题:cookie过期了可以自动销毁,因为浏览器默认是存储在内存中的。而后端的session,默认是文件机制,所以过期后,并不会自动删除。(这一点很坑)

5.为什么要自己重写session?

1.源生的session生命周期难以维护,同一个会话的所有key共享过期时间

2.现在的前端除了浏览器以外,还有移动端、物联网等待。它们没有浏览器的cookie机制,所以,有必要重新去定义一个独立于cookie的session机制。

6.代码实现

<?php
declare(strict_types=1);

interface SessionInterface
{
    public function get($key);

    public function set($key, $value, $expires);

    public function del($key);

    public function setSessionId($session_id);

    public function getSessionId();
}

trait CacheTrait
{
    public function isExpires($expires_at)
    {
        return (time() - $expires_at) >= 0;
    }

}

class FileSessionException extends \Exception
{

}

class FileSession implements SessionInterface
{
    use CacheTrait;

    const EXPIRES = 65535;

    private $configs = [
        'saveDir' => '.' . DIRECTORY_SEPARATOR,
        'fileExpires' => self::EXPIRES
    ];

    private $data;
    private $session_id;

    private function generateUniqueName()
    {
        return uniqid('session');
    }

    private function getSessionPath($session_id)
    {
        return $this->configs['saveDir'] . $session_id;
    }

    private function getAllSession()
    {
        return unserialize($this->data);
    }

    private function loadSessionFromFile($fileName)
    {
        if (is_file($file = $this->getSessionPath($fileName))) {
            if ($handle = fopen($file, 'r')) {
                $caches = fread($handle, filesize($file));
                fclose($handle);
                return $caches;
            }
        }

        throw new FileSessionException('加载文件失败,文件可能不存在');
    }

    public function __construct(array $config = [])
    {
        $this->configs = array_merge($this->configs, $config);
        $this->session_id = $this->configs['session_id'] ?: $this->generateUniqueName();
        $this->data = isset($this->configs['session_id']) ? $this->loadSessionFromFile($this->configs['session_id']) : '';

    }

    public function persistent()
    {
        if ($handle = fopen($this->getSessionPath($this->getSessionId()), 'w')) {
            $cnt = fwrite($handle, $this->data);
            fclose($handle);
            if (false === $cnt) {
                goto ERROR;
            }
            return [$cnt, $this->session_id];
        }
        ERROR:
        throw new FileSessionException('缓存文件写入失败');
    }

    public function get($key)
    {
        if ($cache = $this->getAllSession()) {
            if (isset($cache[$key])) {
                if ($this->isExpires($cache[$key]['expires_at'])) {
                    $this->del($cache[$key]);
                    throw new FileSessionException(sprintf('缓存%s,已失效', $key));
                }
                return $cache[$key]['value'];
            }
            throw new FileSessionException(sprintf('缓存%s,不存在', $key));
        }

        throw new FileSessionException('获取缓存失败');
    }

    public function set($key, $value, $expires = self::EXPIRES)
    {
        $oldCache = $this->getAllSession();
        $oldCache[$key] = ['value' => $value, 'expires_at' => $expires + time()];
        $this->data = serialize($oldCache);
    }

    public function del($key)
    {
        if (is_file($this->getSessionPath($this->session_id))) {
            $oldCache = $this->getAllSession();
            if (isset($oldCache[$key])) {
                unset($oldCache[$key]);
            }
        }

        throw new FileSessionException('缓存文件不存在,或者未找到指定key');
    }

    public function setSessionId($session_id)
    {
        $this->session_id = $session_id;
    }

    public function getSessionId()
    {
        return $this->session_id;
    }
}

try {

    $file = new FileSession(array_merge([], ['session_id' => $_COOKIE['session_id']]));

    $file->set('cnt', 1);
    $file->set('nick_name', 'jack');

    var_dump($file->get('nick_name'));

    list(, $session_id) = $file->persistent();
    setcookie('session_id', $session_id, time() + FileSession::EXPIRES);

    $file->setSessionId($session_id);

    var_dump($file->get('cnt'));

} catch (FileSessionException $exception) {
    var_dump($exception->getMessage());
    $file->persistent();
}

如无特殊说明,文章均为本站原创,转载请注明出处。如发现有什么不对的地方,希望得到您的指点。

发表评论

电子邮件地址不会被公开。 必填项已用*标注