diff --git a/docs/en/reference/configuration.rst b/docs/en/reference/configuration.rst index 0db09d2497d..300d0b1d873 100644 --- a/docs/en/reference/configuration.rst +++ b/docs/en/reference/configuration.rst @@ -21,6 +21,18 @@ You can get a DBAL Connection through the ); $conn = \Doctrine\DBAL\DriverManager::getConnection($connectionParams, $config); +Or, using the simpler URL form: + +.. code-block:: php + + '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). @@ -28,6 +40,84 @@ 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 ~~~~~~ diff --git a/lib/Doctrine/DBAL/DriverManager.php b/lib/Doctrine/DBAL/DriverManager.php index 89004b578d1..75004a40718 100644 --- a/lib/Doctrine/DBAL/DriverManager.php +++ b/lib/Doctrine/DBAL/DriverManager.php @@ -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. */ @@ -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(); @@ -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; + } } diff --git a/tests/Doctrine/Tests/DBAL/DriverManagerTest.php b/tests/Doctrine/Tests/DBAL/DriverManagerTest.php index 055b2aeb63f..58347465b11 100644 --- a/tests/Doctrine/Tests/DBAL/DriverManagerTest.php +++ b/tests/Doctrine/Tests/DBAL/DriverManagerTest.php @@ -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'), + ), + ); + } } \ No newline at end of file