8000 bug #26227 Add support for URL-like DSNs for the PdoSessionHandler (s… · mssimi/symfony@297ae74 · GitHub
[go: up one dir, main page]

Skip to content

Commit 297ae74

Browse files
committed
bug symfony#26227 Add support for URL-like DSNs for the PdoSessionHandler (stof)
This PR was merged into the 3.4 branch. Discussion ---------- Add support for URL-like DSNs for the PdoSessionHandler | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | symfony#25186 | License | MIT | Doc PR | This allows migrating away from the deprecated DbalSessionHandler when DBAL was used for its ability to be configured through a URL (which is what is provided on Heroku and some other PaaS). I know that this is technically a new feature (and so may target master instead), but we currently have no way to configure a database session storage on Heroku in 4.0 (and in 3.4, it requires using a deprecated class). I decided to add support for the URL-like configuration directly rather than adding support for passing a DBAL connection, to minimize the code changes. I also left out the support for OCI in this feature, as the PDO DSN for the Oracle driver is totally crazy (it has nothing in common with other drivers). If someone wants to use a Oracle DB, they should pass the PDO DSN directly instead of a URL. Differences with the URL handling in Doctrine DBAL: - schemeless URLs are not supported (DBAL allows configuring the driver separately in case you don't have it in the URL) - the query string is ignored (DBAL allows to use the query string to configure any supported DBAL params, which are driver-specific. Just use a DSN directly if you need them. PaaS are unlikely to provide such params anyway and they are the main motivation for this PR) Commits ------- 14c35ad Add support for URL-like DSNs for the PdoSessionHandler
2 parents 4be63f9 + 14c35ad commit 297ae74

File tree

2 files changed

+134
-1
lines changed

2 files changed

+134
-1
lines changed

src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ class PdoSessionHandler extends AbstractSessionHandler
164164
* * db_connection_options: An array of driver-specific connection options [default: array()]
165165
* * lock_mode: The strategy for locking, see constants [default: LOCK_TRANSACTIONAL]
166166
*
167-
* @param \PDO|string|null $pdoOrDsn A \PDO instance or DSN string or null
167+
* @param \PDO|string|null $pdoOrDsn A \PDO instance or DSN string or URL string or null
168168
* @param array $options An associative array of options
169169
*
170170
* @throws \InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION
@@ -178,6 +178,8 @@ public function __construct($pdoOrDsn = null, array $options = array())
178178

179179
$this->pdo = $pdoOrDsn;
180180
$this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
181+
} elseif (is_string($pdoOrDsn) && false !== strpos($pdoOrDsn, '://')) {
182+
$this->dsn = $this->buildDsnFromUrl($pdoOrDsn);
181183
} else {
182184
$this->dsn = $pdoOrDsn;
183185
}
@@ -431,6 +433,102 @@ private function connect($dsn)
431433
$this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
432434
}
433435

436+
/**
437+
* Builds a PDO DSN from a URL-like connection string.
438+
*
439+
* @param string $dsnOrUrl
440+
*
441+
* @return string
442+
*
443+
* @todo implement missing support for oci DSN (which look totally different from other PDO ones)
444+
*/
445+
private function buildDsnFromUrl($dsnOrUrl)
446+
{
447+
// (pdo_)?sqlite3?:///... => (pdo_)?sqlite3?://localhost/... or else the URL will be invalid
448+
$url = preg_replace('#^((?:pdo_)?sqlite3?):///#', '$1://localhost/', $dsnOrUrl);
449+
450+
$params = parse_url($url);
451+
452+
if (false === $params) {
453+
return $dsnOrUrl; // If the URL is not valid, let's assume it might be a DSN already.
454+
}
455+
456+
$params = array_map('rawurldecode', $params);
457+
458+
// Override the default username and password. Values passed through options will still win over these in the constructor.
459+
if (isset($params['user'])) {
460+
$this->username = $params['user'];
461+
}
462+
463+
if (isset($params['pass'])) {
464+
$this->password = $params['pass'];
465+
}
466+
467+
if (!isset($params['scheme'])) {
468+
throw new \InvalidArgumentException('URLs without scheme are not supported to configure the PdoSessionHandler');
469+
}
470+
471+
$driverAliasMap = array(
472+
'mssql' => 'sqlsrv',
473+
'mysql2' => 'mysql', // Amazon RDS, for some weird reason
474+
'postgres' => 'pgsql',
475+
'postgresql' => 'pgsql',
476+
'sqlite3' => 'sqlite',
477+
);
478+
479+
$driver = isset($driverAliasMap[$params['scheme']]) ? $driverAliasMap[$params['scheme']] : $params['scheme'];
480+
481+
// Doctrine DBAL supports passing its internal pdo_* driver names directly too (allowing both dashes and underscores). This allows supporting the same here.
482+
if (0 === strpos($driver, 'pdo_') || 0 === strpos($driver, 'pdo-')) {
483+
$driver = substr($driver, 4);
484+
}
485+
486+
switch ($driver) {
487+
case 'mysql':
488+
case 'pgsql':
489+
$dsn = $driver.':';
490+
491+
if (isset($params['host']) && '' !== $params['host']) {
492+
$dsn .= 'host='.$params['host'].';';
493+
}
494+
495+
if (isset($params['port']) && '' !== $params['port']) {
496+
$dsn .= 'port='.$params['port'].';';
497+
}
498+
499+
if (isset($params['path'])) {
500+
$dbName = substr($params['path'], 1); // Remove the leading slash
501+
$dsn .= 'dbname='.$dbName.';';
502+
}
503+
504+
return $dsn;
505+
506+
case 'sqlite':
507+
return 'sqlite:'.substr($params['path'], 1);
508+
509+
case 'sqlsrv':
510+
$dsn = 'sqlsrv:server=';
511+
512+
if (isset($params['host'])) {
513+
$dsn .= $params['host'];
514+
}
515+
516+
if (isset($params['port']) && '' !== $params['port']) {
517+
$dsn .= ','.$params['port'];
518+
}
519+
520+
if (isset($params['path'])) {
521+
$dbName = substr($params['path'], 1); // Remove the leading slash
522+
$dsn .= ';Database='.$dbName;
523+
}
524+
525+
return $dsn;
526+
527+
default:
528+
throw new \InvalidArgumentException(sprintf('The scheme "%s" is not supported by the PdoSessionHandler URL configuration. Pass a PDO DSN directly.', $params['scheme']));
529+
}
530+
}
531+
434532
/**
435533
* Helper method to begin a transaction.
436534
*

src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,41 @@ public function testGetConnectionConnectsIfNeeded()
324324
$this->assertInstanceOf('\PDO', $method->invoke($storage));
325325
}
326326

327+
/**
328+
* @dataProvider provideUrlDsnPairs
329+
*/
330+
public function testUrlDsn($url, $expectedDsn, $expectedUser = null, $expectedPassword = null)
331+
{
332+
$storage = new PdoSessionHandler($url);
333+
334+
$this->assertAttributeEquals($expectedDsn, 'dsn', $storage);
335+
336+
if (null !== $expectedUser) {
337+
$this->assertAttributeEquals($expectedUser, 'username', $storage);
338+
}
339+
340+
if (null !== $expectedPassword) {
341+
$this->assertAttributeEquals($expectedPassword, 'password', $storage);
342+
}
343+
}
344+
345+
public function provideUrlDsnPairs()
346+
{
347+
yield array('mysql://localhost/test', 'mysql:host=localhost;dbname=test;');
348+
yield array('mysql://localhost:56/test', 'mysql:host=localhost;port=56;dbname=test;');
349+
yield array('mysql2://root:pwd@localhost/test', 'mysql:host=localhost;dbname=test;', 'root', 'pwd');
350+
yield array('postgres://localhost/test', 'pgsql:host=localhost;dbname=test;');
351+
yield array('postgresql://localhost:5634/test', 'pgsql:host=localhost;port=5634;dbname=test;');
352+
yield array('postgres://root:pwd@localhost/test', 'pgsql:host=localhost;dbname=test;', 'root', 'pwd');
353+
yield 'sqlite relative path' => array('sqlite://localhost/tmp/test', 'sqlite:tmp/test');
354+
yield 'sqlite absolute path' => array('sqlite://localhost//tmp/test', 'sqlite:/tmp/test');
355+
yield 'sqlite relative path without host' => array('sqlite:///tmp/test', 'sqlite:tmp/test');
356+
yield 'sqlite absolute path without host' => array('sqlite3:////tmp/test', 'sqlite:/tmp/test');
357+
yield array('sqlite://localhost/:memory:', 'sqlite::memory:');
358+
yield array('mssql://localhost/test', 'sqlsrv:server=localhost;Database=test');
359+
yield array('mssql://localhost:56/test', 'sqlsrv:server=localhost,56;Database=test');
360+
}
361+
327362
private function createStream($content)
328363
{
329364
$stream = tmpfile();

0 commit comments

Comments
 (0)
0