Skip to content

Commit

Permalink
Merge pull request #729 from dzuelke/database_url
Browse files Browse the repository at this point in the history
Support for database URLs
  • Loading branch information
beberlei committed Dec 4, 2014
2 parents ea3715a + df8eba3 commit 7d17c01
Show file tree
Hide file tree
Showing 3 changed files with 256 additions and 0 deletions.
90 changes: 90 additions & 0 deletions docs/en/reference/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,103 @@ You can get a DBAL Connection through the
);
$conn = \Doctrine\DBAL\DriverManager::getConnection($connectionParams, $config);
Or, using the simpler URL form:

.. code-block:: php
<?php
$config = new \Doctrine\DBAL\Configuration();
//..
$connectionParams = array(
'url' => 'mysql://user:secret@localhost/mydb',
);
$conn = \Doctrine\DBAL\DriverManager::getConnection($connectionParams, $config);
The ``DriverManager`` returns an instance of
``Doctrine\DBAL\Connection`` which is a wrapper around the
underlying driver connection (which is often a PDO instance).

The following sections describe the available connection parameters
in detail.

Connecting using a URL
~~~~~~~~~~~~~~~~~~~~~~

The easiest way to specify commonly used connection parameters is
using a database URL. The scheme is used to specify a driver, the
user and password in the URL encode user and password for the
connection, followed by the host and port parts (the "authority").
The path after the authority part represents the name of the
database, sans the leading slash. Any query parameters are used as
additional connection parameters.

The scheme names representing the drivers are either the regular
driver names (see below) with any underscores in their name replaced
with a hyphen (to make them legal in URL scheme names), or one of the
following simplified driver names that serve as aliases:

- ``db2``: alias for ``ibm_db2``
- ``mssql``: alias for ``pdo_sqlsrv``
- ``mysql``/``mysql2``: alias for ``pdo_mysql``
- ``pgsql``/``postgres``/``postgresql``: alias for ``pdo_pgsql``
- ``sqlite``/``sqlite3``: alias for ``pdo_sqlite``

For example, to connect to a "foo" MySQL DB using the ``pdo_mysql``
driver on localhost port 4486 with the charset set to UTF-8, you
would use the following URL::

mysql://localhost:4486/foo?charset=UTF-8

This is identical to the following connection string using the
full driver name::

pdo-mysql://localhost:4486/foo?charset=UTF-8

If you wanted to use the ``drizzle_pdo__mysql`` driver instead::

drizzle-pdo-mysql://localhost:4486/foo?charset=UTF-8

In the two last example above, mind the dashes instead of the
underscores in the URL schemes.

For connecting to an SQLite database, the authority portion of the
URL is obviously irrelevant and thus can be omitted. The path part
of the URL is, like for all other drivers, stripped of its leading
slash, resulting in a relative file name for the database::

sqlite:///somedb.sqlite

This would access ``somedb.sqlite`` in the current working directory
and is identical to the following::

sqlite://ignored:ignored@ignored:1234/somedb.sqlite

To specify an absolute file path, e.g. ``/usr/local/var/db.sqlite``,
simply use that as the database name, which results in two leading
slashes for the path part of the URL, and four slashes in total after
the URL scheme name and its following colon::

sqlite:////usr/local/var/db.sqlite

Which is, again, identical to supplying ignored user/pass/authority::

sqlite://notused:inthis@case//usr/local/var/db.sqlite

To connect to an in-memory SQLite instance, use ``:memory::`` as the
database name::

sqlite:///:memory:

.. note::

Any information extracted from the URL overwrites existing values
for the parameter in question, but the rest of the information
is merged together. You could, for example, have a URL without
the ``charset`` setting in the query string, and then add a
``charset`` connection parameter next to ``url``, to provide a
default value in case the URL doesn't contain a charset value.


Driver
~~~~~~

Expand Down
79 changes: 79 additions & 0 deletions lib/Doctrine/DBAL/DriverManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,21 @@ final class DriverManager
'sqlsrv' => 'Doctrine\DBAL\Driver\SQLSrv\Driver',
);

/**
* List of URL schemes from a database URL and their mappings to driver.
*/
private static $driverSchemeAliases = array(
'db2' => 'ibm_db2',
'mssql' => 'pdo_sqlsrv',
'mysql' => 'pdo_mysql',
'mysql2' => 'pdo_mysql', // Amazon RDS, for some weird reason
'postgres' => 'pdo_pgsql',
'postgresql' => 'pdo_pgsql',
'pgsql' => 'pdo_pgsql',
'sqlite' => 'pdo_sqlite',
'sqlite3' => 'pdo_sqlite',
);

/**
* Private constructor. This class cannot be instantiated.
*/
Expand Down Expand Up @@ -126,6 +141,8 @@ public static function getConnection(
$eventManager = new EventManager();
}

$params = self::parseDatabaseUrl($params);

// check for existing pdo object
if (isset($params['pdo']) && ! $params['pdo'] instanceof \PDO) {
throw DBALException::invalidPdoInstance();
Expand Down Expand Up @@ -194,4 +211,66 @@ private static function _checkParams(array $params)
throw DBALException::invalidDriverClass($params['driverClass']);
}
}

/**
* Extracts parts from a database URL, if present, and returns an
* updated list of parameters.
*
* @param array $params The list of parameters.
*
* @param array A modified list of parameters with info from a database
* URL extracted into indidivual parameter parts.
*
*/
private static function parseDatabaseUrl(array $params)
{
if (!isset($params['url'])) {
return $params;
}

// (pdo_)?sqlite3?:///... => (pdo_)?sqlite3?://localhost/... or else the URL will be invalid
$url = preg_replace('#^((?:pdo_)?sqlite3?):///#', '$1://localhost/', $params['url']);

$url = parse_url($url);

if ($url === false) {
throw new DBALException('Malformed parameter "url".');
}

if (isset($url['scheme'])) {
$params['driver'] = str_replace('-', '_', $url['scheme']); // URL schemes must not contain underscores, but dashes are ok
if (isset(self::$driverSchemeAliases[$params['driver']])) {
$params['driver'] = self::$driverSchemeAliases[$params['driver']]; // use alias like "postgres", else we just let checkParams decide later if the driver exists (for literal "pdo-pgsql" etc)
}
}

if (isset($url['host'])) {
$params['host'] = $url['host'];
}
if (isset($url['port'])) {
$params['port'] = $url['port'];
}
if (isset($url['user'])) {
$params['user'] = $url['user'];
}
if (isset($url['pass'])) {
$params['password'] = $url['pass'];
}

if (isset($url['path'])) {
if (!isset($url['scheme']) || (strpos($url['scheme'], 'sqlite') !== false && $url['path'] == ':memory:')) {
$params['dbname'] = $url['path']; // if the URL was just "sqlite::memory:", which parses to scheme and path only
} else {
$params['dbname'] = substr($url['path'], 1); // strip the leading slash from the URL
}
}

if (isset($url['query'])) {
$query = array();
parse_str($url['query'], $query); // simply ingest query as extra params, e.g. charset or sslmode
$params = array_merge($params, $query); // parse_str wipes existing array elements
}

return $params;
}
}
87 changes: 87 additions & 0 deletions tests/Doctrine/Tests/DBAL/DriverManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,91 @@ public function testValidDriverClass()
$conn = \Doctrine\DBAL\DriverManager::getConnection($options);
$this->assertInstanceOf('Doctrine\DBAL\Driver\PDOMySql\Driver', $conn->getDriver());
}

/**
* @dataProvider databaseUrls
*/
public function testDatabaseUrl($url, $expected)
{
$options = is_array($url) ? $url : array(
'url' => $url,
);

if ($expected === false) {
$this->setExpectedException('Doctrine\DBAL\DBALException');
}

$conn = \Doctrine\DBAL\DriverManager::getConnection($options);

$params = $conn->getParams();
foreach ($expected as $key => $value) {
if ($key == 'driver') {
$this->assertInstanceOf($value, $conn->getDriver());
} else {
$this->assertEquals($value, $params[$key]);
}
}
}

public function databaseUrls()
{
return array(
'simple URL' => array(
'mysql://foo:bar@localhost/baz',
array('user' => 'foo', 'password' => 'bar', 'host' => 'localhost', 'dbname' => 'baz', 'driver' => 'Doctrine\DBAL\Driver\PDOMySQL\Driver'),
),
'simple URL with port' => array(
'mysql://foo:bar@localhost:11211/baz',
array('user' => 'foo', 'password' => 'bar', 'host' => 'localhost', 'port' => 11211, 'dbname' => 'baz', 'driver' => 'Doctrine\DBAL\Driver\PDOMySQL\Driver'),
),
'sqlite relative URL with host' => array(
'sqlite://localhost/foo/dbname.sqlite',
array('dbname' => 'foo/dbname.sqlite', 'driver' => 'Doctrine\DBAL\Driver\PDOSqlite\Driver'),
),
'sqlite absolute URL with host' => array(
'sqlite://localhost//tmp/dbname.sqlite',
array('dbname' => '/tmp/dbname.sqlite', 'driver' => 'Doctrine\DBAL\Driver\PDOSqlite\Driver'),
),
'sqlite relative URL without host' => array(
'sqlite:///foo/dbname.sqlite',
array('dbname' => 'foo/dbname.sqlite', 'driver' => 'Doctrine\DBAL\Driver\PDOSqlite\Driver'),
),
'sqlite absolute URL without host' => array(
'sqlite:////tmp/dbname.sqlite',
array('dbname' => '/tmp/dbname.sqlite', 'driver' => 'Doctrine\DBAL\Driver\PDOSqlite\Driver'),
),
'sqlite memory' => array(
'sqlite:///:memory:',
array('dbname' => ':memory:', 'driver' => 'Doctrine\DBAL\Driver\PDOSqlite\Driver'),
),
'sqlite memory with host' => array(
'sqlite://localhost/:memory:',
array('dbname' => ':memory:', 'driver' => 'Doctrine\DBAL\Driver\PDOSqlite\Driver'),
),
'params parsed from URL override individual params' => array(
array('url' => 'mysql://foo:bar@localhost/baz', 'password' => 'lulz'),
array('user' => 'foo', 'password' => 'bar', 'host' => 'localhost', 'dbname' => 'baz', 'driver' => 'Doctrine\DBAL\Driver\PDOMySQL\Driver'),
),
'params not parsed from URL but individual params are preserved' => array(
array('url' => 'mysql://foo:bar@localhost/baz', 'port' => 1234),
array('user' => 'foo', 'password' => 'bar', 'host' => 'localhost', 'port' => 1234, 'dbname' => 'baz', 'driver' => 'Doctrine\DBAL\Driver\PDOMySQL\Driver'),
),
'query params from URL are used as extra params' => array(
'url' => 'mysql://foo:bar@localhost/dbname?charset=UTF-8',
array('charset' => 'UTF-8'),
),
'simple URL with fallthrough scheme not defined in map' => array(
'sqlsrv://foo:bar@localhost/baz',
array('user' => 'foo', 'password' => 'bar', 'host' => 'localhost', 'dbname' => 'baz', 'driver' => 'Doctrine\DBAL\Driver\SQLSrv\Driver'),
),
'simple URL with fallthrough scheme containing underscores fails' => array(
'drizzle_pdo_mysql://foo:bar@localhost/baz',
false,
),
'simple URL with fallthrough scheme containing dashes works' => array(
'drizzle-pdo-mysql://foo:bar@localhost/baz',
array('user' => 'foo', 'password' => 'bar', 'host' => 'localhost', 'dbname' => 'baz', 'driver' => 'Doctrine\DBAL\Driver\DrizzlePDOMySql\Driver'),
),
);
}
}

0 comments on commit 7d17c01

Please sign in to comment.