Commit dcce6f86 authored by Marco Pivetta's avatar Marco Pivetta

Merge branch 'fix/#2287-#1183-parse-schemaless-connection-uris-2.5' into 2.5

Close #2287
Close #1183
parents 9d09d2b1 0fd81ad6
......@@ -76,10 +76,22 @@ class DBALException extends \Exception
}
/**
* @param string|null $url The URL that was provided in the connection parameters (if any).
*
* @return \Doctrine\DBAL\DBALException
*/
public static function driverRequired()
public static function driverRequired($url = null)
{
if ($url) {
return new self(
sprintf(
"The options 'driver' or 'driverClass' are mandatory if a connection URL without scheme " .
"is given to DriverManager::getConnection(). Given URL: %s",
$url
)
);
}
return new self("The options 'driver' or 'driverClass' are mandatory if no PDO ".
"instance is given to DriverManager::getConnection().");
}
......
......@@ -212,6 +212,19 @@ final class DriverManager
}
}
/**
* Normalizes the given connection URL path.
*
* @param string $urlPath
*
* @return string The normalized connection URL path
*/
private static function normalizeDatabaseUrlPath($urlPath)
{
// Trim leading slash from URL path.
return substr($urlPath, 1);
}
/**
* Extracts parts from a database URL, if present, and returns an
* updated list of parameters.
......@@ -231,18 +244,25 @@ final class DriverManager
// (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);
// PHP < 5.4.8 doesn't parse schemeless urls properly.
// See: https://php.net/parse-url#refsect1-function.parse-url-changelog
if (PHP_VERSION_ID < 50408 && strpos($url, '//') === 0) {
$url = parse_url('fake:' . $url);
unset($url['scheme']);
} else {
$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 we have a connection URL, we have to unset the default PDO instance connection parameter (if any)
// as we cannot merge connection details from the URL into the PDO instance (URL takes precedence).
unset($params['pdo']);
$params = self::parseDatabaseUrlScheme($url, $params);
if (isset($url['host'])) {
$params['host'] = $url['host'];
......@@ -257,53 +277,97 @@ final class DriverManager
$params['password'] = $url['pass'];
}
if (isset($url['path'])) {
$params = self::parseDatabaseUrlPath($url, $params);
}
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
}
$params = self::parseDatabaseUrlPath($url, $params);
$params = self::parseDatabaseUrlQuery($url, $params);
return $params;
}
/**
* Parses the given URL and resolves the given connection parameters.
* Parses the given connection URL and resolves the given connection parameters.
*
* Assumes that the connection URL scheme is already parsed and resolved into the given connection parameters
* via {@link parseDatabaseUrlScheme}.
*
* @param array $url The URL parts to evaluate.
* @param array $params The connection parameters to resolve.
*
* @return array The resolved connection parameters.
*
* @see parseDatabaseUrlScheme
*/
private static function parseDatabaseUrlPath(array $url, array $params)
{
if (!isset($url['scheme'])) {
$params['dbname'] = $url['path'];
if (! isset($url['path'])) {
return $params;
}
$url['path'] = substr($url['path'], 1);
$url['path'] = self::normalizeDatabaseUrlPath($url['path']);
if (strpos($url['scheme'], 'sqlite') !== false) {
// If we do not have a known DBAL driver, we do not know any connection URL path semantics to evaluate
// and therefore treat the path as regular DBAL connection URL path.
if (! isset($params['driver'])) {
return self::parseRegularDatabaseUrlPath($url, $params);
}
if (strpos($params['driver'], 'sqlite') !== false) {
return self::parseSqliteDatabaseUrlPath($url, $params);
}
return self::parseRegularDatabaseUrlPath($url, $params);
}
/**
* Parses the query part of the given connection URL and resolves the given connection parameters.
*
* @param array $url The connection URL parts to evaluate.
* @param array $params The connection parameters to resolve.
*
* @return array The resolved connection parameters.
*/
private static function parseDatabaseUrlQuery(array $url, array $params)
{
if (! isset($url['query'])) {
return $params;
}
$query = array();
parse_str($url['query'], $query); // simply ingest query as extra params, e.g. charset or sslmode
return array_merge($params, $query); // parse_str wipes existing array elements
}
/**
* Parses the given regular connection URL and resolves the given connection parameters.
*
* Assumes that the "path" URL part is already normalized via {@link normalizeDatabaseUrlPath}.
*
* @param array $url The regular connection URL parts to evaluate.
* @param array $params The connection parameters to resolve.
*
* @return array The resolved connection parameters.
*
* @see normalizeDatabaseUrlPath
*/
private static function parseRegularDatabaseUrlPath(array $url, array $params)
{
$params['dbname'] = $url['path'];
return $params;
}
/**
* Parses the given SQLite URL and resolves the given connection parameters.
* Parses the given SQLite connection URL and resolves the given connection parameters.
*
* Assumes that the "path" URL part is already normalized via {@link normalizeDatabaseUrlPath}.
*
* @param array $url The SQLite URL parts to evaluate.
* @param array $url The SQLite connection URL parts to evaluate.
* @param array $params The connection parameters to resolve.
*
* @return array The resolved connection parameters.
*
* @see normalizeDatabaseUrlPath
*/
private static function parseSqliteDatabaseUrlPath(array $url, array $params)
{
......@@ -317,4 +381,44 @@ final class DriverManager
return $params;
}
/**
* Parses the scheme part from given connection URL and resolves the given connection parameters.
*
* @param array $url The connection URL parts to evaluate.
* @param array $params The connection parameters to resolve.
*
* @return array The resolved connection parameters.
*
* @throws DBALException if parsing failed or resolution is not possible.
*/
private static function parseDatabaseUrlScheme(array $url, array $params)
{
if (isset($url['scheme'])) {
// The requested driver from the URL scheme takes precedence
// over the default custom driver from the connection parameters (if any).
unset($params['driverClass']);
// URL schemes must not contain underscores, but dashes are ok
$driver = str_replace('-', '_', $url['scheme']);
// The requested driver from the URL scheme takes precedence
// over the default driver from the connection parameters (if any).
$params['driver'] = isset(self::$driverSchemeAliases[$driver])
// use alias like "postgres", else we just let checkParams decide later
// if the driver exists (for literal "pdo-pgsql" etc)
? self::$driverSchemeAliases[$driver]
: $driver;
return $params;
}
// If a schemeless connection URL is given, we require a default driver or default custom driver
// as connection parameter.
if (! isset($params['driverClass']) && ! isset($params['driver'])) {
throw DBALException::driverRequired($params['url']);
}
return $params;
}
}
......@@ -12,4 +12,20 @@ class DBALExceptionTest extends \Doctrine\Tests\DbalTestCase
$e = DBALException::driverExceptionDuringQuery($driver, new \Exception, '', array('ABC', chr(128)));
$this->assertContains('with params ["ABC", "\x80"]', $e->getMessage());
}
public function testDriverRequiredWithUrl()
{
$url = 'mysql://localhost';
$exception = DBALException::driverRequired($url);
$this->assertInstanceOf('Doctrine\DBAL\DBALException', $exception);
$this->assertSame(
sprintf(
"The options 'driver' or 'driverClass' are mandatory if a connection URL without scheme " .
"is given to DriverManager::getConnection(). Given URL: %s",
$url
),
$exception->getMessage()
);
}
}
......@@ -132,7 +132,7 @@ class DriverManagerTest extends \Doctrine\Tests\DbalTestCase
$params = $conn->getParams();
foreach ($expected as $key => $value) {
if ($key == 'driver') {
if (in_array($key, array('pdo', 'driver', 'driverClass'), true)) {
$this->assertInstanceOf($value, $conn->getDriver());
} else {
$this->assertEquals($value, $params[$key]);
......@@ -142,6 +142,8 @@ class DriverManagerTest extends \Doctrine\Tests\DbalTestCase
public function databaseUrls()
{
$pdoMock = $this->getMock('Doctrine\Tests\Mocks\PDOMock');
return array(
'simple URL' => array(
'mysql://foo:bar@localhost/baz',
......@@ -199,6 +201,56 @@ class DriverManagerTest extends \Doctrine\Tests\DbalTestCase
'drizzle-pdo-mysql://foo:bar@localhost/baz',
array('user' => 'foo', 'password' => 'bar', 'host' => 'localhost', 'dbname' => 'baz', 'driver' => 'Doctrine\DBAL\Driver\DrizzlePDOMySql\Driver'),
),
// DBAL-1234
'URL without scheme and without any driver information' => array(
array('url' => '//foo:bar@localhost/baz'),
false,
),
'URL without scheme but default PDO driver' => array(
array('url' => '//foo:bar@localhost/baz', 'pdo' => $pdoMock),
false,
),
'URL without scheme but default driver' => array(
array('url' => '//foo:bar@localhost/baz', 'driver' => 'pdo_mysql'),
array('user' => 'foo', 'password' => 'bar', 'host' => 'localhost', 'dbname' => 'baz', 'driver' => 'Doctrine\DBAL\Driver\PDOMySQL\Driver'),
),
'URL without scheme but custom driver' => array(
array('url' => '//foo:bar@localhost/baz', 'driverClass' => 'Doctrine\Tests\Mocks\DriverMock'),
array('user' => 'foo', 'password' => 'bar', 'host' => 'localhost', 'dbname' => 'baz', 'driverClass' => 'Doctrine\Tests\Mocks\DriverMock'),
),
'URL without scheme but default PDO driver and default driver' => array(
array('url' => '//foo:bar@localhost/baz', 'pdo' => $pdoMock, 'driver' => 'pdo_mysql'),
array('user' => 'foo', 'password' => 'bar', 'host' => 'localhost', 'dbname' => 'baz', 'driver' => 'Doctrine\DBAL\Driver\PDOMySQL\Driver'),
),
'URL without scheme but driver and custom driver' => array(
array('url' => '//foo:bar@localhost/baz', 'driver' => 'pdo_mysql', 'driverClass' => 'Doctrine\Tests\Mocks\DriverMock'),
array('user' => 'foo', 'password' => 'bar', 'host' => 'localhost', 'dbname' => 'baz', 'driverClass' => 'Doctrine\Tests\Mocks\DriverMock'),
),
'URL with default PDO driver' => array(
array('url' => 'mysql://foo:bar@localhost/baz', 'pdo' => $pdoMock),
array('user' => 'foo', 'password' => 'bar', 'host' => 'localhost', 'dbname' => 'baz', 'driver' => 'Doctrine\DBAL\Driver\PDOMySQL\Driver'),
),
'URL with default driver' => array(
array('url' => 'mysql://foo:bar@localhost/baz', 'driver' => 'sqlite'),
array('user' => 'foo', 'password' => 'bar', 'host' => 'localhost', 'dbname' => 'baz', 'driver' => 'Doctrine\DBAL\Driver\PDOMySQL\Driver'),
),
'URL with default custom driver' => array(
array('url' => 'mysql://foo:bar@localhost/baz', 'driverClass' => 'Doctrine\Tests\Mocks\DriverMock'),
array('user' => 'foo', 'password' => 'bar', 'host' => 'localhost', 'dbname' => 'baz', 'driver' => 'Doctrine\DBAL\Driver\PDOMySQL\Driver'),
),
'URL with default PDO driver and default driver' => array(
array('url' => 'mysql://foo:bar@localhost/baz', 'pdo' => $pdoMock, 'driver' => 'sqlite'),
array('user' => 'foo', 'password' => 'bar', 'host' => 'localhost', 'dbname' => 'baz', 'driver' => 'Doctrine\DBAL\Driver\PDOMySQL\Driver'),
),
'URL with default driver and default custom driver' => array(
array('url' => 'mysql://foo:bar@localhost/baz', 'driver' => 'sqlite', 'driverClass' => 'Doctrine\Tests\Mocks\DriverMock'),
array('user' => 'foo', 'password' => 'bar', 'host' => 'localhost', 'dbname' => 'baz', 'driver' => 'Doctrine\DBAL\Driver\PDOMySQL\Driver'),
),
'URL with default PDO driver and default driver and default custom driver' => array(
array('url' => 'mysql://foo:bar@localhost/baz', 'pdo' => $pdoMock, 'driver' => 'sqlite', 'driverClass' => 'Doctrine\Tests\Mocks\DriverMock'),
array('user' => 'foo', 'password' => 'bar', 'host' => 'localhost', 'dbname' => 'baz', 'driver' => 'Doctrine\DBAL\Driver\PDOMySQL\Driver'),
),
);
}
}
<?php
namespace Doctrine\Tests\Mocks;
class PDOMock extends \PDO
{
public function __construct()
{
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment