From de9c61f423b52e022ffa8caa344a83029e7e60a5 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 29 Oct 2019 19:32:01 +0100 Subject: [PATCH] [HttpFoundation][FrameworkBundle] allow configuring the session handler with a DSN --- .../Bundle/FrameworkBundle/CHANGELOG.md | 1 + .../FrameworkExtension.php | 17 +++- .../Resources/config/session.xml | 5 ++ .../Bundle/FrameworkBundle/composer.json | 2 +- .../Component/HttpFoundation/CHANGELOG.md | 1 + .../Storage/Handler/SessionHandlerFactory.php | 84 +++++++++++++++++++ .../Component/Lock/Store/StoreFactory.php | 80 +++++++++++------- 7 files changed, 155 insertions(+), 35 deletions(-) create mode 100644 src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 11c3d000b207b..97e84338674ec 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -18,6 +18,7 @@ CHANGELOG * Added sort option for `translation:update` command. * [BC Break] The `framework.messenger.routing.senders` config key is not deep merged anymore. * Added `secrets:*` commands and `%env(secret:...)%` processor to deal with secrets seamlessly. + * Made `framework.session.handler_id` accept a DSN 4.3.0 ----- diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 375852b4a8f93..aebe9517e2613 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -240,6 +240,9 @@ public function load(array $configs, ContainerBuilder $container) } } + // register cache before session so both can share the connection services + $this->registerCacheConfiguration($config['cache'], $container); + if ($this->isConfigEnabled($container, $config['session'])) { if (!\extension_loaded('session')) { throw new LogicException('Session support cannot be enabled as the session extension is not installed. See https://php.net/session.installation for instructions.'); @@ -326,7 +329,6 @@ public function load(array $configs, ContainerBuilder $container) $this->registerFragmentsConfiguration($config['fragments'], $container, $loader); $this->registerTranslatorConfiguration($config['translator'], $container, $loader, $config['default_locale']); $this->registerProfilerConfiguration($config['profiler'], $container, $loader); - $this->registerCacheConfiguration($config['cache'], $container); $this->registerWorkflowConfiguration($config['workflows'], $container, $loader); $this->registerDebugConfiguration($config['php_errors'], $container, $loader); $this->registerRouterConfiguration($config['router'], $container, $loader); @@ -925,7 +927,18 @@ private function registerSessionConfiguration(array $config, ContainerBuilder $c $container->getDefinition('session.storage.native')->replaceArgument(1, null); $container->getDefinition('session.storage.php_bridge')->replaceArgument(0, null); } else { - $container->setAlias('session.handler', $config['handler_id'])->setPrivate(true); + $container->resolveEnvPlaceholders($config['handler_id'], null, $usedEnvs); + + if ($usedEnvs || preg_match('#^[a-z]++://#', $config['handler_id'])) { + $id = '.cache_connection.'.ContainerBuilder::hash($config['handler_id']); + + $container->getDefinition('session.abstract_handler') + ->replaceArgument(0, $container->hasDefinition($id) ? new Reference($id) : $config['handler_id']); + + $container->setAlias('session.handler', 'session.abstract_handler')->setPrivate(true); + } else { + $container->setAlias('session.handler', $config['handler_id'])->setPrivate(true); + } } $container->setParameter('session.save_path', $config['save_path']); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml index 020e29e7211f6..5aae3b4d9feca 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml @@ -56,6 +56,11 @@ + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 55f7706427cbb..ebba20c08f362 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -22,7 +22,7 @@ "symfony/config": "^4.3.4|^5.0", "symfony/dependency-injection": "^4.4|^5.0", "symfony/error-renderer": "^4.4|^5.0", - "symfony/http-foundation": "^4.3|^5.0", + "symfony/http-foundation": "^4.4|^5.0", "symfony/http-kernel": "^4.4", "symfony/polyfill-mbstring": "~1.0", "symfony/filesystem": "^3.4|^4.0|^5.0", diff --git a/src/Symfony/Component/HttpFoundation/CHANGELOG.md b/src/Symfony/Component/HttpFoundation/CHANGELOG.md index 577f27e6c6af8..10eaa0ac5a0f6 100644 --- a/src/Symfony/Component/HttpFoundation/CHANGELOG.md +++ b/src/Symfony/Component/HttpFoundation/CHANGELOG.md @@ -10,6 +10,7 @@ CHANGELOG * `PdoSessionHandler` now precalculates the expiry timestamp in the lifetime column, make sure to run `CREATE INDEX EXPIRY ON sessions (sess_lifetime)` to update your database to speed up garbage collection of expired sessions. + * added `SessionHandlerFactory` to create session handlers with a DSN 4.3.0 ----- diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php new file mode 100644 index 0000000000000..41dba3a2b387c --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +use Doctrine\DBAL\DriverManager; +use Symfony\Component\Cache\Adapter\AbstractAdapter; +use Symfony\Component\Cache\Traits\RedisClusterProxy; +use Symfony\Component\Cache\Traits\RedisProxy; + +/** + * @author Nicolas Grekas + */ +class SessionHandlerFactory +{ + /** + * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy|\Memcached|\PDO|string $connection Connection or DSN + */ + public static function createHandler($connection): AbstractSessionHandler + { + if (!\is_string($connection) && !\is_object($connection)) { + throw new \TypeError(sprintf('Argument 1 passed to %s() must be a string or a connection object, %s given.', __METHOD__, \gettype($connection))); + } + + switch (true) { + case $connection instanceof \Redis: + case $connection instanceof \RedisArray: + case $connection instanceof \RedisCluster: + case $connection instanceof \Predis\ClientInterface: + case $connection instanceof RedisProxy: + case $connection instanceof RedisClusterProxy: + return new RedisSessionHandler($connection); + + case $connection instanceof \Memcached: + return new MemcachedSessionHandler($connection); + + case $connection instanceof \PDO: + return new PdoSessionHandler($connection); + + case !\is_string($connection): + throw new \InvalidArgumentException(sprintf('Unsupported Connection: %s.', \get_class($connection))); + case 0 === strpos($connection, 'file://'): + return new StrictSessionHandler(new NativeFileSessionHandler(substr($connection, 7))); + + case 0 === strpos($connection, 'redis://'): + case 0 === strpos($connection, 'rediss://'): + case 0 === strpos($connection, 'memcached://'): + if (!class_exists(AbstractAdapter::class)) { + throw new InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require symfony/cache".', $this->dsn)); + } + $connection = AbstractAdapter::createConnection($connection, ['lazy' => true]); + + return 0 === strpos($connection, 'memcached://') ? new MemcachedSessionHandler($connection) : new RedisSessionHandler($connection); + + case 0 === strpos($connection, 'pdo_oci://'): + if (!class_exists(DriverManager::class)) { + throw new \InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require doctrine/dbal".', $connection)); + } + $connection = DriverManager::getConnection(['url' => $connection])->getWrappedConnection(); + // no break; + + case 0 === strpos($connection, 'mssql://'): + case 0 === strpos($connection, 'mysql://'): + case 0 === strpos($connection, 'mysql2://'): + case 0 === strpos($connection, 'pgsql://'): + case 0 === strpos($connection, 'postgres://'): + case 0 === strpos($connection, 'postgresql://'): + case 0 === strpos($connection, 'sqlsrv://'): + case 0 === strpos($connection, 'sqlite://'): + case 0 === strpos($connection, 'sqlite3://'): + return new PdoSessionHandler($connection); + } + + throw new \InvalidArgumentException(sprintf('Unsupported Connection: %s.', $connection)); + } +} diff --git a/src/Symfony/Component/Lock/Store/StoreFactory.php b/src/Symfony/Component/Lock/Store/StoreFactory.php index 255a72a736912..8fea94551c07c 100644 --- a/src/Symfony/Component/Lock/Store/StoreFactory.php +++ b/src/Symfony/Component/Lock/Store/StoreFactory.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Lock\Store; +use Doctrine\DBAL\Connection; use Symfony\Component\Cache\Adapter\AbstractAdapter; use Symfony\Component\Cache\Traits\RedisClusterProxy; use Symfony\Component\Cache\Traits\RedisProxy; @@ -25,59 +26,74 @@ class StoreFactory { /** - * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|\Memcached|\Zookeeper|string $connection Connection or DSN or Store short name + * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy|\Memcached|\PDO|Connection|\Zookeeper|string $connection Connection or DSN or Store short name * * @return PersistingStoreInterface */ public static function createStore($connection) { - if ( - $connection instanceof \Redis || - $connection instanceof \RedisArray || - $connection instanceof \RedisCluster || - $connection instanceof \Predis\ClientInterface || - $connection instanceof RedisProxy || - $connection instanceof RedisClusterProxy - ) { - return new RedisStore($connection); - } - if ($connection instanceof \Memcached) { - return new MemcachedStore($connection); - } - if ($connection instanceof \Zookeeper) { - return new ZookeeperStore($connection); - } - if (!\is_string($connection)) { - throw new InvalidArgumentException(sprintf('Unsupported Connection: %s.', \get_class($connection))); + if (!\is_string($connection) && !\is_object($connection)) { + throw new \TypeError(sprintf('Argument 1 passed to %s() must be a string or a connection object, %s given.', __METHOD__, \gettype($connection))); } switch (true) { + case $connection instanceof \Redis: + case $connection instanceof \RedisArray: + case $connection instanceof \RedisCluster: + case $connection instanceof \Predis\ClientInterface: + case $connection instanceof RedisProxy: + case $connection instanceof RedisClusterProxy: + return new RedisStore($connection); + + case $connection instanceof \Memcached: + return new MemcachedStore($connection); + + case $connection instanceof \PDO: + case $connection instanceof Connection: + return new PdoStore($connection); + + case $connection instanceof \Zookeeper: + return new ZookeeperStore($connection); + + case !\is_string($connection): + throw new InvalidArgumentException(sprintf('Unsupported Connection: %s.', \get_class($connection))); case 'flock' === $connection: return new FlockStore(); + case 0 === strpos($connection, 'flock://'): return new FlockStore(substr($connection, 8)); + case 'semaphore' === $connection: return new SemaphoreStore(); - case 0 === strpos($connection, 'redis://') && class_exists(AbstractAdapter::class): - case 0 === strpos($connection, 'rediss://') && class_exists(AbstractAdapter::class): - return new RedisStore(AbstractAdapter::createConnection($connection, ['lazy' => true])); - case 0 === strpos($connection, 'memcached://') && class_exists(AbstractAdapter::class): - return new MemcachedStore(AbstractAdapter::createConnection($connection, ['lazy' => true])); - case 0 === strpos($connection, 'sqlite:'): + + case 0 === strpos($connection, 'redis://'): + case 0 === strpos($connection, 'rediss://'): + case 0 === strpos($connection, 'memcached://'): + if (!class_exists(AbstractAdapter::class)) { + throw new InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require symfony/cache".', $this->dsn)); + } + $connection = AbstractAdapter::createConnection($connection, ['lazy' => true]); + + return 0 === strpos($connection, 'memcached://') ? new MemcachedStore($connection) : new RedisStore($connection); + + case 0 === strpos($connection, 'mssql://'): case 0 === strpos($connection, 'mysql:'): - case 0 === strpos($connection, 'pgsql:'): - case 0 === strpos($connection, 'oci:'): - case 0 === strpos($connection, 'sqlsrv:'): - case 0 === strpos($connection, 'sqlite3://'): case 0 === strpos($connection, 'mysql2://'): + case 0 === strpos($connection, 'oci:'): + case 0 === strpos($connection, 'oci8://'): + case 0 === strpos($connection, 'pdo_oci://'): + case 0 === strpos($connection, 'pgsql:'): case 0 === strpos($connection, 'postgres://'): case 0 === strpos($connection, 'postgresql://'): - case 0 === strpos($connection, 'mssql://'): + case 0 === strpos($connection, 'sqlsrv:'): + case 0 === strpos($connection, 'sqlite:'): + case 0 === strpos($connection, 'sqlite3://'): return new PdoStore($connection); + case 0 === strpos($connection, 'zookeeper://'): return new ZookeeperStore(ZookeeperStore::createConnection($connection)); - default: - throw new InvalidArgumentException(sprintf('Unsupported Connection: %s.', $connection)); } + + throw new InvalidArgumentException(sprintf('Unsupported Connection: %s.', $connection)); } } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy