diff --git a/src/RedisConfiguration.php b/src/RedisConfiguration.php index 23ad50731d26a6f0bdd0170bfd1c3b655ca7ed49..567076241560637d199271dc27315e537a8b2047 100644 --- a/src/RedisConfiguration.php +++ b/src/RedisConfiguration.php @@ -4,18 +4,17 @@ declare(strict_types=1); namespace Distantmagic\Resonance; -use SensitiveParameter; +use Ds\Map; readonly class RedisConfiguration { - public function __construct( - #[SensitiveParameter] - public string $host, - #[SensitiveParameter] - public string $password, - #[SensitiveParameter] - public int $port, - #[SensitiveParameter] - public string $prefix, - ) {} + /** + * @var Map<string,RedisConnectionPoolConfiguration> + */ + public Map $connectionPoolConfiguration; + + public function __construct() + { + $this->connectionPoolConfiguration = new Map(); + } } diff --git a/src/RedisConnectionPoolConfiguration.php b/src/RedisConnectionPoolConfiguration.php new file mode 100644 index 0000000000000000000000000000000000000000..9986d37ec34fe6b7c9d6568f7f75a267bfd78a10 --- /dev/null +++ b/src/RedisConnectionPoolConfiguration.php @@ -0,0 +1,25 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +use SensitiveParameter; + +readonly class RedisConnectionPoolConfiguration +{ + public function __construct( + #[SensitiveParameter] + public int $dbIndex, + #[SensitiveParameter] + public string $host, + #[SensitiveParameter] + public string $password, + #[SensitiveParameter] + public int $port, + #[SensitiveParameter] + public string $prefix, + #[SensitiveParameter] + public int $timeout, + ) {} +} diff --git a/src/RedisConnectionPoolRepository.php b/src/RedisConnectionPoolRepository.php new file mode 100644 index 0000000000000000000000000000000000000000..7148f47b7ccb0c41e0978c9e118dff1d77c65001 --- /dev/null +++ b/src/RedisConnectionPoolRepository.php @@ -0,0 +1,44 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +use Ds\Map; +use OutOfBoundsException; +use Redis; +use Swoole\Database\RedisPool; + +readonly class RedisConnectionPoolRepository +{ + /** + * @var Map<string,RedisPool> + */ + public Map $redisConnectionPool; + + public function __construct() + { + $this->redisConnectionPool = new Map(); + } + + public function getConnection(string $name): Redis + { + if (!$this->redisConnectionPool->hasKey($name)) { + throw new OutOfBoundsException(sprintf( + 'Redis connection pool is not configured: "%s". Available connection pools: "%s"', + $name, + $this->redisConnectionPool->keys()->join('", "'), + )); + } + + /** + * @var Redis + */ + return $this->redisConnectionPool->get($name)->get(); + } + + public function putConnection(string $name, Redis $redis): void + { + $this->redisConnectionPool->get($name)->put($redis); + } +} diff --git a/src/Session.php b/src/Session.php index a59db8332354c94e3df1f128473a57b032d30e8d..3c4d0db2af7721ca5c74264e69d0fff07c29c983 100644 --- a/src/Session.php +++ b/src/Session.php @@ -6,7 +6,6 @@ namespace Distantmagic\Resonance; use Ds\Map; use Redis; -use Swoole\Database\RedisPool; use Swoole\Event; readonly class Session @@ -16,11 +15,21 @@ readonly class Session public function __construct( RedisConfiguration $redisConfiguration, - private RedisPool $redisPool, + private RedisConnectionPoolRepository $redisConnectionPoolRepository, + private SessionConfiguration $sessionConfiguration, public string $id, ) { - $this->redis = $this->redisPool->get(); - $this->redis->setOption(Redis::OPT_PREFIX, $redisConfiguration->prefix.'session:'); + $redisPrefix = $redisConfiguration + ->connectionPoolConfiguration + ->get($this->sessionConfiguration->redisConnectionPool) + ->prefix + ; + + $this->redis = $this + ->redisConnectionPoolRepository + ->getConnection($this->sessionConfiguration->redisConnectionPool) + ; + $this->redis->setOption(Redis::OPT_PREFIX, $redisPrefix.'session:'); $this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_IGBINARY); $this->data = new Map($this->restoreSessionData()); @@ -29,7 +38,10 @@ readonly class Session public function __destruct() { Event::defer(function () { - $this->redisPool->put($this->redis); + $this->redisConnectionPoolRepository->putConnection( + $this->sessionConfiguration->redisConnectionPool, + $this->redis, + ); }); } diff --git a/src/SessionConfiguration.php b/src/SessionConfiguration.php new file mode 100644 index 0000000000000000000000000000000000000000..7c5438215577068326ca4d13721e139d6858e74b --- /dev/null +++ b/src/SessionConfiguration.php @@ -0,0 +1,14 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +readonly class SessionConfiguration +{ + public function __construct( + public string $cookieName, + public int $cookieLifespan, + public string $redisConnectionPool, + ) {} +} diff --git a/src/SessionManager.php b/src/SessionManager.php index 2a8908a092b82a54dc7f6fef7f93c2b120c7cd1f..5e956e30cdad75bef791424d39955314f6a151a8 100644 --- a/src/SessionManager.php +++ b/src/SessionManager.php @@ -6,7 +6,6 @@ namespace Distantmagic\Resonance; use Distantmagic\Resonance\Attribute\Singleton; use Distantmagic\Resonance\Event\HttpResponseReady; -use Swoole\Database\RedisPool; use Swoole\Http\Request; use Swoole\Http\Response; use WeakMap; @@ -25,7 +24,8 @@ final readonly class SessionManager extends EventListener public function __construct( private EventListenerAggregate $eventListenerAggregate, private RedisConfiguration $redisConfiguration, - private RedisPool $redisPool, + private RedisConnectionPoolRepository $redisConnectionPoolRepository, + private SessionConfiguration $sessionConfiguration, ) { /** * False positive, $this IS an EventListenerInterface @@ -69,14 +69,17 @@ final readonly class SessionManager extends EventListener return $this->sessions->offsetGet($request); } - if (!is_array($request->cookie) || !isset($request->cookie[DM_SESSSION_COOKIE_NAME])) { + if ( + !is_array($request->cookie) + || !isset($request->cookie[$this->sessionConfiguration->cookieName]) + ) { return null; } /** * @var string */ - $sessionId = $request->cookie[DM_SESSSION_COOKIE_NAME]; + $sessionId = $request->cookie[$this->sessionConfiguration->cookieName]; if (!uuid_is_valid($sessionId)) { return null; @@ -88,9 +91,9 @@ final readonly class SessionManager extends EventListener public function setSessionCookie(Response $response, Session $session): void { $response->cookie( - expires: time() + 60 * 60 * 24, + expires: time() + $this->sessionConfiguration->cookieLifespan, httponly: true, - name: DM_SESSSION_COOKIE_NAME, + name: $this->sessionConfiguration->cookieName, samesite: 'strict', secure: true, value: $session->id, @@ -115,7 +118,8 @@ final readonly class SessionManager extends EventListener { $session = new Session( $this->redisConfiguration, - $this->redisPool, + $this->redisConnectionPoolRepository, + $this->sessionConfiguration, $sessionId, ); diff --git a/src/SingletonProvider/ConfigurationProvider/RedisConfigurationProvider.php b/src/SingletonProvider/ConfigurationProvider/RedisConfigurationProvider.php index 0db6b8b9362030da2df94b2372abb638cb14b77f..0261d1275745ab96826ca319285fcdb6b65284ff 100644 --- a/src/SingletonProvider/ConfigurationProvider/RedisConfigurationProvider.php +++ b/src/SingletonProvider/ConfigurationProvider/RedisConfigurationProvider.php @@ -6,17 +6,23 @@ namespace Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use Distantmagic\Resonance\Attribute\Singleton; use Distantmagic\Resonance\RedisConfiguration; +use Distantmagic\Resonance\RedisConnectionPoolConfiguration; use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use Nette\Schema\Expect; use Nette\Schema\Schema; /** - * @template-extends ConfigurationProvider<RedisConfiguration, object{ - * host: string, - * password: string, - * port: int, - * prefix: string, - * }> + * @template-extends ConfigurationProvider< + * RedisConfiguration, + * array<string, object{ + * db_index: int, + * host: string, + * password: string, + * port: int, + * prefix: string, + * timeout: int, + * }> + * > */ #[Singleton(provides: RedisConfiguration::class)] final readonly class RedisConfigurationProvider extends ConfigurationProvider @@ -28,21 +34,38 @@ final readonly class RedisConfigurationProvider extends ConfigurationProvider protected function getSchema(): Schema { - return Expect::structure([ + $keySchema = Expect::string()->min(1)->required(); + + $valueSchema = Expect::structure([ + 'db_index' => Expect::int()->min(0)->required(), 'host' => Expect::string()->min(1)->required(), 'password' => Expect::string()->required(), 'port' => Expect::int()->min(1)->max(65535)->required(), 'prefix' => Expect::string()->min(1)->required(), + 'timeout' => Expect::int()->min(0)->required(), ]); + + return Expect::arrayOf($valueSchema, $keySchema); } protected function provideConfiguration($validatedData): RedisConfiguration { - return new RedisConfiguration( - host: $validatedData->host, - password: $validatedData->password, - port: $validatedData->port, - prefix: $validatedData->prefix, - ); + $databaseconfiguration = new RedisConfiguration(); + + foreach ($validatedData as $name => $connectionPoolConfiguration) { + $databaseconfiguration->connectionPoolConfiguration->put( + $name, + new RedisConnectionPoolConfiguration( + dbIndex: $connectionPoolConfiguration->db_index, + host: $connectionPoolConfiguration->host, + password: $connectionPoolConfiguration->password, + port: $connectionPoolConfiguration->port, + prefix: $connectionPoolConfiguration->prefix, + timeout: $connectionPoolConfiguration->timeout, + ), + ); + } + + return $databaseconfiguration; } } diff --git a/src/SingletonProvider/ConfigurationProvider/SessionConfigurationProvider.php b/src/SingletonProvider/ConfigurationProvider/SessionConfigurationProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..1e682f00e4e86b5e90c11a7e602dd025646b7121 --- /dev/null +++ b/src/SingletonProvider/ConfigurationProvider/SessionConfigurationProvider.php @@ -0,0 +1,63 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; + +use Distantmagic\Resonance\Attribute\Singleton; +use Distantmagic\Resonance\ConfigurationFile; +use Distantmagic\Resonance\RedisConfiguration; +use Distantmagic\Resonance\SessionConfiguration; +use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; +use Nette\Schema\Expect; +use Nette\Schema\Processor; +use Nette\Schema\Schema; + +/** + * @template-extends ConfigurationProvider<SessionConfiguration, object{ + * cookie_lifespan: int, + * cookie_name: string, + * redis_connection_pool: string, + * }> + */ +#[Singleton(provides: SessionConfiguration::class)] +final readonly class SessionConfigurationProvider extends ConfigurationProvider +{ + public function __construct( + private ConfigurationFile $configurationFile, + private Processor $processor, + private RedisConfiguration $redisConfiguration, + ) { + parent::__construct($configurationFile, $processor); + } + + protected function getConfigurationKey(): string + { + return 'session'; + } + + protected function getSchema(): Schema + { + $redisConnectionPools = $this + ->redisConfiguration + ->connectionPoolConfiguration + ->keys() + ->toArray() + ; + + return Expect::structure([ + 'cookie_lifespan' => Expect::int()->min(1)->required(), + 'cookie_name' => Expect::string()->min(1)->required(), + 'redis_connection_pool' => Expect::anyOf(...$redisConnectionPools), + ]); + } + + protected function provideConfiguration($validatedData): SessionConfiguration + { + return new SessionConfiguration( + cookieLifespan: $validatedData->cookie_lifespan, + cookieName: $validatedData->cookie_name, + redisConnectionPool: $validatedData->redis_connection_pool, + ); + } +} diff --git a/src/SingletonProvider/RedisConnectionPoolRepositoryProvider.php b/src/SingletonProvider/RedisConnectionPoolRepositoryProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..48c4a74527920fa698ac271d2f2033c85435a915 --- /dev/null +++ b/src/SingletonProvider/RedisConnectionPoolRepositoryProvider.php @@ -0,0 +1,48 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance\SingletonProvider; + +use Distantmagic\Resonance\Attribute\Singleton; +use Distantmagic\Resonance\EventDispatcherInterface; +use Distantmagic\Resonance\PHPProjectFiles; +use Distantmagic\Resonance\RedisConfiguration; +use Distantmagic\Resonance\RedisConnectionPoolRepository; +use Distantmagic\Resonance\SingletonContainer; +use Distantmagic\Resonance\SingletonProvider; +use Swoole\Database\RedisConfig; +use Swoole\Database\RedisPool; + +/** + * @template-extends SingletonProvider<RedisConnectionPoolRepository> + */ +#[Singleton(provides: RedisConnectionPoolRepository::class)] +final readonly class RedisConnectionPoolRepositoryProvider extends SingletonProvider +{ + public function __construct( + private RedisConfiguration $databaseConfiguration, + private EventDispatcherInterface $eventDispatcher, + ) {} + + public function provide(SingletonContainer $singletons, PHPProjectFiles $phpProjectFiles): RedisConnectionPoolRepository + { + $redisConnectionPoolRepository = new RedisConnectionPoolRepository(); + + foreach ($this->databaseConfiguration->connectionPoolConfiguration as $name => $connectionPoolConfiguration) { + $redisConfig = (new RedisConfig()) + ->withHost($connectionPoolConfiguration->host) + ->withPort($connectionPoolConfiguration->port) + ->withAuth($connectionPoolConfiguration->password) + ->withDbIndex($connectionPoolConfiguration->dbIndex) + ->withTimeout($connectionPoolConfiguration->timeout) + ; + $redisConnectionPoolRepository->redisConnectionPool->put( + $name, + new RedisPool($redisConfig), + ); + } + + return $redisConnectionPoolRepository; + } +} diff --git a/src/SingletonProvider/SwooleRedisConfigProvider.php b/src/SingletonProvider/SwooleRedisConfigProvider.php deleted file mode 100644 index cc0c03cf46f69c31aaa22139f58ef9e1b57625ba..0000000000000000000000000000000000000000 --- a/src/SingletonProvider/SwooleRedisConfigProvider.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace Distantmagic\Resonance\SingletonProvider; - -use Distantmagic\Resonance\Attribute\Singleton; -use Distantmagic\Resonance\PHPProjectFiles; -use Distantmagic\Resonance\RedisConfiguration; -use Distantmagic\Resonance\SingletonContainer; -use Distantmagic\Resonance\SingletonProvider; -use Swoole\Database\RedisConfig; - -/** - * @template-extends SingletonProvider<RedisConfig> - */ -#[Singleton(provides: RedisConfig::class)] -final readonly class SwooleRedisConfigProvider extends SingletonProvider -{ - public function __construct(private RedisConfiguration $redisConfiguration) {} - - public function provide(SingletonContainer $singletons, PHPProjectFiles $phpProjectFiles): RedisConfig - { - $redisConfig = new RedisConfig(); - - return $redisConfig - ->withHost($this->redisConfiguration->host) - ->withPort($this->redisConfiguration->port) - ->withAuth($this->redisConfiguration->password) - ->withDbIndex(0) - ->withTimeout(1) - ; - } -} diff --git a/src/SingletonProvider/SwooleRedisPoolProvider.php b/src/SingletonProvider/SwooleRedisPoolProvider.php deleted file mode 100644 index 846fc944d0627e6d48b103e9e917bb78a84fce10..0000000000000000000000000000000000000000 --- a/src/SingletonProvider/SwooleRedisPoolProvider.php +++ /dev/null @@ -1,26 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace Distantmagic\Resonance\SingletonProvider; - -use Distantmagic\Resonance\Attribute\Singleton; -use Distantmagic\Resonance\PHPProjectFiles; -use Distantmagic\Resonance\SingletonContainer; -use Distantmagic\Resonance\SingletonProvider; -use Swoole\Database\RedisConfig; -use Swoole\Database\RedisPool; - -/** - * @template-extends SingletonProvider<RedisPool> - */ -#[Singleton(provides: RedisPool::class)] -final readonly class SwooleRedisPoolProvider extends SingletonProvider -{ - public function __construct(private RedisConfig $redisConfig) {} - - public function provide(SingletonContainer $singletons, PHPProjectFiles $phpProjectFiles): RedisPool - { - return new RedisPool($this->redisConfig); - } -}