Drupal 8覆盖会话管理

3
我想要覆盖Drupal核心的会话管理,使用我的自定义方案来将会话保存到Redis而不是数据库中。
通过谷歌搜索发现并没有太多相关内容,除了这个: https://www.drupal.org/project/session_proxy 唯一的问题是它不兼容Drupal 8,而我只想要将会话保存到Redis中,不需要其他处理程序。
在Symfony中,我创建了一个会话处理程序服务,但在Drupal 8中似乎更加棘手。
你有关于我该如何继续的建议吗?
谢谢。
2个回答

3

以下是我认为最简单的方法来解决这个问题,而不依赖于第三方模块或任何其他插件,就是覆盖Drupal的核心SessionHandler类。

首先,在我的模块中,我创建了一个ServiceProvider类,它指示容器重新定义核心SessionHandler类定义为我的类。我不需要数据库连接服务,所以我确保只向构造函数传递请求堆栈。

<?php

namespace Drupal\my_module;

use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderBase;
use Symfony\Component\DependencyInjection\Reference;

class OoAuthServiceProvider extends ServiceProviderBase
{
    /**
     * {@inheritdoc}
     */
    public function alter(ContainerBuilder $container)
    {
        $container->getDefinition('session_handler.storage')
            ->setClass('Drupal\my_module\SessionHandler')
            ->setArguments([
                new Reference('request_stack')
            ]);
    }
}

我随后创建了自己的Redis SessionHandler:

<?php

namespace Drupal\my_module;

use Drupal\Component\Utility\Crypt;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Utility\Error;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;

/**
 * Default session handler.
 */
class SessionHandler extends AbstractProxy implements \SessionHandlerInterface {

    use DependencySerializationTrait;

    /**
     * The request stack.
     *
     * @var RequestStack
     */
    protected $requestStack;

    /**
     * @var \Redis
     */
    protected $redis;

    /**
     * SessionHandler constructor.
     *
     * @param RequestStack $requestStack
     */
    public function __construct(RequestStack $requestStack)
    {
        $this->requestStack = $requestStack;
        // TODO: Store redis connection details in config.
        $this->redis = (new PhpRedis())->getClient('redis-host', 6379);
    }

    /**
     * {@inheritdoc}
     */
    public function open($savePath, $name)
    {
        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function read($sid)
    {
        $data = '';

        if (!empty($sid)) {
            $query = $this->redis->get(Crypt::hashBase64($sid));
            $data = unserialize($query);
        }

        return (string) $data['session'];
    }

    /**
     * {@inheritdoc}
     */
    public function write($sid, $value)
    {
        // The exception handler is not active at this point, so we need to do it
        // manually.

        var_dump(['Value', $value]);
        try {
            $request = $this->requestStack->getCurrentRequest();
            $fields = [
                'uid' => $request->getSession()->get('uid', 0),
                'hostname' => $request->getClientIP(),
                'session' => $value,
                'timestamp' => REQUEST_TIME,
            ];

            $this->redis->set(
              Crypt::hashBase64($sid),
              serialize($fields),
              (int) ini_get("session.gc_maxlifetime")
            );

            return true;
        }
        catch (\Exception $exception) {
            require_once DRUPAL_ROOT . '/core/includes/errors.inc';
            // If we are displaying errors, then do so with no possibility of a
            // further uncaught exception being thrown.
            if (error_displayable()) {
                print '<h1>Uncaught exception thrown in session handler.</h1>';
                print '<p>' . Error::renderExceptionSafe($exception) . '</p><hr />';
            }

            return true;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function close()
    {
        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function destroy($sid)
    {
        // Delete session data.
        $this->redis->delete(Crypt::hashBase64($sid));

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function gc($lifetime)
    {
        // Redundant method when using Redis. You no longer have to check the session
        // timestamp as the session.gc_maxlifetime is set as TTL on write.
        return true;
    }

}

我自己实现的SessionHandler中使用的PhpRedis只是一个用于连接到Redis的小型工具类。

<?php

namespace Drupal\my_module;

/**
 * Class PhpRedis
 * @package Drupal\oo_auth
 */
class PhpRedis implements ClientInterface
{
  /**
   * {@inheritdoc}
   */
    public function getClient($host = null, $port = null, $base = null, $password = null)
    {
        $client = new \Redis();
        $client->connect($host, $port);

        if (isset($password)) {
            $client->auth($password);
        }

        if (isset($base)) {
            $client->select($base);
        }

        // Do not allow PhpRedis serialize itself data, we are going to do it
        // oneself. This will ensure less memory footprint on Redis size when
        // we will attempt to store small values.
        $client->setOption(\Redis::OPT_SERIALIZER, \Redis::SERIALIZER_NONE);

        return $client;
    }

  /**
   * {@inheritdoc}
   */
    public function getName() {
        return 'PhpRedis';
    }
}

<?php

namespace Drupal\my_module;

/**
 * Interface ClientInterface
 * @package Drupal\oo_auth
 */
interface ClientInterface
{
    /**
     * Get the connected client instance.
     *
     * @param null $host
     * @param null $port
     * @param null $base
     *
     * @return mixed
     */
    public function getClient($host = NULL, $port = NULL, $base = NULL);

    /**
    * Get underlying library name used.
    *
    * This can be useful for contribution code that may work with only some of
    * the provided clients.
    *
    * @return string
    */
    public function getName();
}

目前我找不到任何建议性的文档,可以为您提供一个使用Redis(实际上可以与任何数据存储一起使用)作为Drupal安装的会话存储的示例。有关如何通过其他第三方模块使其运行的文章是可以的,但我不想要额外的内容。


嘿Kal,你有这个模块的压缩包吗?我想看看能否将其应用在我的Drupal项目中。 - André Perazzi
嘿,安德烈,很抱歉我没有了解你所说的内容,因为这是我之前工作公司的事情。但是,如果你需要帮助,请随时给我发送电子邮件,我会尽力提供帮助:justlikephp@gmail.com :) - Kal

2

现有Redis模块的Alpha版本。如果当前的限制不是最大问题,您可以使用此模块并按照文档进行配置。 https://www.drupal.org/project/redis

请参阅文档以获取有关配置设置的完整详细信息,但作为入门者,在安装模块后,您将向settings.php添加类似于以下内容的选项。

$settings['cache']['default'] = 'cache.backend.redis';
$settings['redis.connection']['host'] = '<<redis_host>>';
$settings['redis.connection']['port'] = '<<redis_port>>';

其中'redis_host'和'redis_port'根据您的Redis实例进行设置。


1
我知道它说“缓存”,但它是否也会将会话存储在Redis中? - Kal
我在使用这个Redis模块时遇到了同样的问题。我成功地将其用于缓存,并且它运行得非常好。然而,尝试将会话也放入Redis中仍然没有成功。 - André Perazzi
目前的D8 Redis模块版本不支持sessions,但已经在开发一个子模块来解决这个问题。请查看此讨论帖https://www.drupal.org/project/redis/issues/2876099。 - Scott Anderson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接