8000 [HttpFoundation][FrameworkBundle] allow configuring the session handl… · symfony/symfony@016cfab · GitHub
[go: up one dir, main page]

Skip to content

Commit 016cfab

Browse files
[HttpFoundation][FrameworkBundle] allow configuring the session handler with a DSN
1 parent d082732 commit 016cfab

File tree

8 files changed

+151
-31
lines changed

8 files changed

+151
-31
lines changed

src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ CHANGELOG
1818
* Added sort option for `translation:update` command.
1919
* [BC Break] The `framework.messenger.routing.senders` config key is not deep merged anymore.
2020
* Added `secrets:*` commands and `%env(secret:...)%` processor to deal with secrets seamlessly.
21+
* Made `framework.session.handler_id` accept a DSN
2122

2223
4.3.0
2324
-----

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,9 @@ public function load(array $configs, ContainerBuilder $container)
240240
}
241241
}
242242

243+
// register cache before session so both can share the connection services
244+
$this->registerCacheConfiguration($config['cache'], $container);
245+
243246
if ($this->isConfigEnabled($container, $config['session'])) {
244247
if (!\extension_loaded('session')) {
245248
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)
326329
$this->registerFragmentsConfiguration($config['fragments'], $container, $loader);
327330
$this->registerTranslatorConfiguration($config['translator'], $container, $loader, $config['default_locale']);
328331
$this->registerProfilerConfiguration($config['profiler'], $container, $loader);
329-
$this->registerCacheConfiguration($config['cache'], $container);
330332
$this->registerWorkflowConfiguration($config['workflows'], $container, $loader);
331333
$this->registerDebugConfiguration($config['php_errors'], $container, $loader);
332334
$this->registerRouterConfiguration($config['router'], $container, $loader);
@@ -925,7 +927,18 @@ private function registerSessionConfiguration(array $config, ContainerBuilder $c
925927
$container->getDefinition('session.storage.native')->replaceArgument(1, null);
926928
$container->getDefinition('session.storage.php_bridge')->replaceArgument(0, null);
927929
} else {
928-
$container->setAlias('session.handler', $config['handler_id'])->setPrivate(true);
930+
$container->resolveEnvPlaceholders($config['handler_id'], null, $usedEnvs);
931+
932+
if ($usedEnvs || preg_match('#^[a-z]++://#', $config['handler_id'])) {
933+
$id = '.cache_connection.'.ContainerBuilder::hash($config['handler_id']);
934+
935+
$container->getDefinition('session.abstract_handler')
936+
->replaceArgument(0, $container->hasDefinition($id) ? new Reference($id) : $config['handler_id']);
937+
938+
$container->setAlias('session.handler', 'session.abstract_handler')->setPrivate(true);
939+
} else {
940+
$container->setAlias('session.handler', $config['handler_id'])->setPrivate(true);
941+
}
929942
}
930943

931944
$container->setParameter('session.save_path', $config['save_path']);

src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@
5656
</argument>
5757
</service>
5858

59+
<service id="session.abstract_handler" class="Symfony\Component\HttpFoundation\Session\Storage\Handler\AbstractSessionHandler">
60+
<factory class="Symfony\Component\HttpFoundation\Session\Storage\Handler\SessionHandlerFactory" method="createHandler" />
61+
<argument />
62+
</service>
63+
5964
<service id="session_listener" class="Symfony\Component\HttpKernel\EventListener\SessionListener">
6065
<tag name="kernel.event_subscriber" />
6166
<argument type="service_locator">

src/Symfony/Bundle/FrameworkBundle/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"symfony/config": "^4.3.4|^5.0",
2323
"symfony/dependency-injection": "^4.4|^5.0",
2424
"symfony/error-renderer": "^4.4|^5.0",
25-
"symfony/http-foundation": "^4.3|^5.0",
25+
"symfony/http-foundation": "^4.4|^5.0",
2626
"symfony/http-kernel": "^4.4",
2727
"symfony/polyfill-mbstring": "~1.0",
2828
"symfony/filesystem": "^3.4|^4.0|^5.0",

src/Symfony/Component/HttpFoundation/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ CHANGELOG
1010
* `PdoSessionHandler` now precalculates the expiry timestamp in the lifetime column,
1111
make sure to run `CREATE INDEX EXPIRY ON sessions (sess_lifetime)` to update your database
1212
to speed up garbage collection of expired sessions.
13+
* added `SessionHandlerFactory` to create session handlers with a DSN
1314

1415
4.3.0
1516
-----
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
13+
14+
use Doctrine\DBAL\DriverManager;
15+
use Symfony\Component\Cache\Adapter\AbstractAdapter;
16+
use Symfony\Component\Cache\Traits\RedisClusterProxy;
17+
use Symfony\Component\Cache\Traits\RedisProxy;
18+
19+
/**
20+
* This abstract session handler provides a generic implementation
21+
* of the PHP 7.0 SessionUpdateTimestampHandlerInterface,
22+
* enabling strict and lazy session handling.
23+
*
24+
* @author Nicolas Grekas <p@tchwork.com>
25+
*/
26+
class SessionHandlerFactory
27+
{
28+
/**
29+
* @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|\Memcached|\PDO|string $connection Connection or DSN
30+
*/
31+
public static function createHandler($connection): AbstractSessionHandler
32+
{
33+
if (!\is_string($connection) && !\is_object($connection) 1C72 ) {
34+
throw new \TypeError(sprintf('Argument 1 passed to %s() must be a string or a connection object, %s given.', __METHOD__, \gettype($connection)));
35+
}
36+
37+
switch (true) {
38+
case $connection instanceof \Redis:
39+
case $connection instanceof \RedisArray:
40+
case $connection instanceof \RedisCluster:
41+
case $connection instanceof \Predis\ClientInterface:
42+
case $connection instanceof RedisProxy:
43+
case $connection instanceof RedisClusterProxy:
44+
return new RedisSessionHandler($connection);
45+
46+
case $connection instanceof \Memcached:
47+
return new MemcachedSessionHandler($connection);
48+
49+
case $connection instanceof \PDO:
50+
return new PdoSessionHandler($connection);
51+
52+
case !\is_string($connection):
53+
throw new \InvalidArgumentException(sprintf('Unsupported Connection: %s.', \get_class($connection)));
54+
55+
case 0 === strpos($connection, 'file://'):
56+
return new StrictSessionHandler(new NativeFileSessionHandler(substr($connection, 7)));
57+
58+
case 0 === strpos($connection, 'redis://') && class_exists(AbstractAdapter::class):
59+
case 0 === strpos($connection, 'rediss://') && class_exists(AbstractAdapter::class):
60+
return new RedisSessionHandler(AbstractAdapter::createConnection($connection, ['lazy' => true]));
61+
62+
case 0 === strpos($connection, 'memcached://') && class_exists(AbstractAdapter::class):
63+
return new MemcachedSessionHandler(AbstractAdapter::createConnection($connection, ['lazy' => true]));
64+
65+
case 0 === strpos($connection, 'pdo_oci://'):
66+
if (!class_exists(DriverManager::class)) {
67+
throw new \LogicException(sprintf('Failed to parse the DSN "%s". Try running "composer require doctrine/dbal".', $connection));
68+
}
69+
$connection = DriverManager::getConnection(['url' => $connection])->getWrappedConnection();
70+
// no break;
71+
72+
case 0 === strpos($connection, 'mssql://'):
73+
case 0 === strpos($connection, 'mysql://'):
74+
case 0 === strpos($connection, 'mysql2://'):
75+
case 0 === strpos($connection, 'pgsql://'):
76+
case 0 === strpos($connection, 'postgres://'):
77+
case 0 === strpos($connection, 'postgresql://'):
78+
case 0 === strpos($connection, 'sqlsrv://'):
79+
case 0 === strpos($connection, 'sqlite://'):
80+
case 0 === strpos($connection, 'sqlite3://'):
81+
return new PdoSessionHandler($connection);
82+
}
83+
84+
throw new \InvalidArgumentException(sprintf('Unsupported Connection: %s.', $connection));
85+
}
86+
}

src/Symfony/Component/Lock/Store/PdoStore.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ private function getConnection()
232232
if (null === $this->conn) {
233233
if (strpos($this->dsn, '://')) {
234234
if (!class_exists(DriverManager::class)) {
235-
throw new InvalidArgumentException(sprintf('Failed to parse the DSN "%s". Try running "composer require doctrine/dbal".', $this->dsn));
235+
throw new \LogicException(sprintf('Failed to parse the DSN "%s". Try running "composer require doctrine/dbal".', $this->dsn));
236236
}
237237
$this->conn = DriverManager::getConnection(['url' => $this->dsn]);
238238
} else {

src/Symfony/Component/Lock/Store/StoreFactory.php

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\Lock\Store;
1313

14+
use Doctrine\DBAL\Connection;
1415
use Symfony\Component\Cache\Adapter\AbstractAdapter;
1516
use Symfony\Component\Cache\Traits\RedisClusterProxy;
1617
use Symfony\Component\Cache\Traits\RedisProxy;
@@ -25,59 +26,72 @@
2526
class StoreFactory
2627
{
2728
/**
28-
* @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|\Memcached|\Zookeeper|string $connection Connection or DSN or Store short name
29+
* @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|\Memcached|\PDO|Connection|\Zookeeper|string $connection Connection or DSN or Store short name
2930
*
3031
* @return PersistingStoreInterface
3132
*/
3233
public static function createStore($connection)
3334
{
34-
if (
35-
$connection instanceof \Redis ||
36-
$connection instanceof \RedisArray ||
37-
$connection instanceof \RedisCluster ||
38-
$connection instanceof \Predis\ClientInterface ||
39-
$connection instanceof RedisProxy ||
40-
$connection instanceof RedisClusterProxy
41-
) {
42-
return new RedisStore($connection);
43-
}
44-
if ($connection instanceof \Memcached) {
45-
return new MemcachedStore($connection);
46-
}
47-
if ($connection instanceof \Zookeeper) {
48-
return new ZookeeperStore($connection);
49-
}
50-
if (!\is_string($connection)) {
51-
throw new InvalidArgumentException(sprintf('Unsupported Connection: %s.', \get_class($connection)));
35+
if (!\is_string($connection) && !\is_object($connection)) {
36+
throw new \TypeError(sprintf('Argument 1 passed to %s() must be a string or a connection object, %s given.', __METHOD__, \gettype($connection)));
5237
}
5338

5439
switch (true) {
40+
case $connection instanceof \Redis:
41+
case $connection instanceof \RedisArray:
42+
case $connection instanceof \RedisCluster:
43+
case $connection instanceof \Predis\ClientInterface:
44+
case $connection instanceof RedisProxy:
45+
case $connection instanceof RedisClusterProxy:
46+
return new RedisStore($connection);
47+
48+
case $connection instanceof \Memcached:
49+
return new MemcachedStore($connection);
50+
51+
case $connection instanceof \PDO:
52+
case $connection instanceof Connection:
53+
return new PdoStore($connection);
54+
55+
case $connection instanceof \Zookeeper:
56+
return new ZookeeperStore($connection);
57+
58+
case !\is_string($connection):
59+
throw new InvalidArgumentException(sprintf('Unsupported Connection: %s.', \get_class($connection)));
60+
5561
case 'flock' === $connection:
5662
return new FlockStore();
63+
5764
case 0 === strpos($connection, 'flock://'):
5865
return new FlockStore(substr($connection, 8));
66+
5967
case 'semaphore' === $connection:
6068
return new SemaphoreStore();
69+
6170
case 0 === strpos($connection, 'redis://') && class_exists(AbstractAdapter::class):
6271
case 0 === strpos($connection, 'rediss://') && class_exists(AbstractAdapter::class):
6372
return new RedisStore(AbstractAdapter::createConnection($connection, ['lazy' => true]));
73+
6474
case 0 === strpos($connection, 'memcached://') && class_exists(AbstractAdapter::class):
6575
return new MemcachedStore(AbstractAdapter::createConnection($connection, ['lazy' => true]));
66-
case 0 === strpos($connection, 'sqlite:'):
76+
77+
case 0 === strpos($connection, 'mssql://'):
6778
case 0 === strpos($connection, 'mysql:'):
68-
case 0 === strpos($connection, 'pgsql:'):
69-
case 0 === strpos($connection, 'oci:'):
70-
case 0 === strpos($connection, 'sqlsrv:'):
71-
case 0 === strpos($connection, 'sqlite3://'):
7279
case 0 === strpos($connection, 'mysql2://'):
80+
case 0 === strpos($connection, 'oci:'):
81+
case 0 === strpos($connection, 'oci8://'):
82+
case 0 === strpos($connection, 'pdo_oci://'):
83+
case 0 === strpos($connection, 'pgsql:'):
7384
case 0 === strpos($connection, 'postgres://'):
7485
case 0 === strpos($connection, 'postgresql://'):
75-
case 0 === strpos($connection, 'mssql://'):
86+
case 0 === strpos($connection, 'sqlsrv:'):
87+
case 0 === strpos($connection, 'sqlite:'):
88+
case 0 === strpos($connection, 'sqlite3://'):
7689
return new PdoStore($connection);
90+
7791
case 0 === strpos($connection, 'zookeeper://'):
7892
return new ZookeeperStore(ZookeeperStore::createConnection($connection));
79-
default:
80-
throw new InvalidArgumentException(sprintf('Unsupported Connection: %s.', $connection));
8193
}
94+
95+
throw new InvalidArgumentException(sprintf('Unsupported Connection: %s.', $connection));
8296
}
8397
}

0 commit comments

Comments
 (0)
0