Skip to content

Commit

Permalink
add ability to override url with dsn params (#1290)
Browse files Browse the repository at this point in the history
Co-authored-by: Gabriel Ostrolucký <[email protected]>
  • Loading branch information
jrushlow and ostrolucky authored Mar 16, 2021
1 parent 011cb96 commit 4e46ad9
Show file tree
Hide file tree
Showing 14 changed files with 243 additions and 23 deletions.
8 changes: 6 additions & 2 deletions ConnectionFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,12 @@ public function createConnection(array $params, Configuration $config = null, Ev
$this->initializeTypes();
}

if (! isset($params['pdo']) && ! isset($params['charset'])) {
$overriddenOptions = $params['connection_override_options'] ?? [];
unset($params['connection_override_options']);

if (! isset($params['pdo']) && (! isset($params['charset']) || $overriddenOptions)) {
$wrapperClass = null;

if (isset($params['wrapperClass'])) {
if (! is_subclass_of($params['wrapperClass'], Connection::class)) {
if (class_exists(DBALException::class)) {
Expand All @@ -61,7 +65,7 @@ public function createConnection(array $params, Configuration $config = null, Ev
}

$connection = DriverManager::getConnection($params, $config, $eventManager);
$params = $connection->getParams();
$params = array_merge($connection->getParams(), $overriddenOptions);
$driver = $connection->getDriver();

if ($driver instanceof AbstractMySQLDriver) {
Expand Down
19 changes: 15 additions & 4 deletions DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -217,10 +217,11 @@ private function configureDbalDriverNode(ArrayNodeDefinition $node): void
->children()
->scalarNode('url')->info('A URL with connection information; any parameter value parsed from this string will override explicitly set parameters')->end()
->scalarNode('dbname')->end()
->scalarNode('host')->defaultValue('localhost')->end()
->scalarNode('port')->defaultNull()->end()
->scalarNode('user')->defaultValue('root')->end()
->scalarNode('password')->defaultNull()->end()
->scalarNode('host')->info('Defaults to "localhost" at runtime.')->end()
->scalarNode('port')->info('Defaults to null at runtime.')->end()
->scalarNode('user')->info('Defaults to "root" at runtime.')->end()
->scalarNode('password')->info('Defaults to null at runtime.')->end()
->booleanNode('override_url')->defaultValue(false)->info('Allows overriding parts of the "url" parameter with dbname, host, port, user, and/or password parameters.')->end()
->scalarNode('application_name')->end()
->scalarNode('charset')->end()
->scalarNode('path')->end()
Expand Down Expand Up @@ -313,6 +314,16 @@ private function configureDbalDriverNode(ArrayNodeDefinition $node): void
$v['MultipleActiveResultSets'] = $v['multiple_active_result_sets'];
unset($v['multiple_active_result_sets']);

return $v;
})
->end()
->beforeNormalization()
->ifTrue(static function ($v) {
return empty($v['override_url']) && isset($v['url']);
})
->then(static function ($v) {
@trigger_error('Not setting doctrine.dbal.override_url to true is deprecated. True is the only value that will be supported in doctrine-bundle 3.0.', E_USER_DEPRECATED);

return $v;
})
->end();
Expand Down
23 changes: 22 additions & 1 deletion DependencyInjection/DoctrineExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,28 @@ protected function loadDbalConnection($name, array $connection, ContainerBuilder

protected function getConnectionOptions($connection)
{
$options = $connection;
$options = ['connection_override_options' => []] + $connection;

$connectionDefaults = [
'host' => 'localhost',
'port' => null,
'user' => 'root',
'password' => null,
];

if ($options['override_url']) {
$options['connection_override_options'] = array_intersect_key($options, ['dbname' => null] + $connectionDefaults);
}

unset($options['override_url']);

$options += $connectionDefaults;

foreach (['shards', 'replicas', 'slaves'] as $connectionKey) {
foreach (array_keys($options[$connectionKey]) as $name) {
$options[$connectionKey][$name] += $connectionDefaults;
}
}

if (isset($options['platform_service'])) {
$options['platform'] = new Reference($options['platform_service']);
Expand Down
1 change: 1 addition & 0 deletions Resources/config/schema/doctrine-1.0.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
<xsd:attribute name="port" type="xsd:string" />
<xsd:attribute name="user" type="xsd:string" />
<xsd:attribute name="password" type="xsd:string" />
<xsd:attribute name="override-url" type="xsd:boolean" />
<xsd:attribute name="application-name" type="xsd:string" />
<xsd:attribute name="path" type="xsd:string" />
<xsd:attribute name="unix-socket" type="xsd:string" />
Expand Down
13 changes: 9 additions & 4 deletions Resources/doc/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ Configuration Reference
connections:
# A collection of different named connections (e.g. default, conn2, etc)
default:
# If true, allows overriding url parameters with explicitly set parameters.
# "dbname", "host", "port", "user", and/or "password" can be overridden.
override_url: ~
dbname: ~
host: localhost
port: ~
Expand Down Expand Up @@ -914,10 +917,11 @@ Doctrine DBAL Configuration
.. note::

When specifying a ``url`` parameter, any information extracted from that
URL will override explicitly set parameters. An example database URL
would be ``mysql://snoopy:redbaron@localhost/baseball``, and any explicitly
set driver, user, password and dbname parameter would be overridden by
this URL. See the Doctrine `DBAL documentation`_ for more information.
URL will override explicitly set parameters unless ``override_url`` is set
to ``true``. An example database URL would be
``mysql://snoopy:redbaron@localhost/baseball``, and any explicitly set driver,
user, password and dbname parameter would be overridden by this URL.
See the Doctrine `DBAL documentation`_ for more information.

Besides default Doctrine options, there are some Symfony-related ones that you
can configure. The following block shows all possible configuration keys:
Expand All @@ -928,6 +932,7 @@ can configure. The following block shows all possible configuration keys:
doctrine:
dbal:
override_url: true
url: mysql://user:secret@localhost:1234/otherdatabase # this would override the values below
dbname: database
host: localhost
Expand Down
18 changes: 18 additions & 0 deletions Tests/ConnectionFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,24 @@ public function testDefaultCharsetMySql(): void

$this->assertSame('utf8mb4', $connection->getParams()['charset']);
}

public function testConnectionOverrideOptions(): void
{
$params = [
'dbname' => 'main_test',
'host' => 'db_test',
'port' => 5432,
'user' => 'tester',
'password' => 'wordpass',
];

$connection = (new ConnectionFactory([]))->createConnection([
'url' => 'mysql://root:password@database:3306/main?serverVersion=mariadb-10.5.8',
'connection_override_options' => $params,
]);

$this->assertEquals($params, array_intersect_key($connection->getParams(), $params));
}
}

/**
Expand Down
58 changes: 49 additions & 9 deletions Tests/DependencyInjection/AbstractDoctrineExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,7 @@ public function testDbalLoadFromXmlMultipleConnections(): void
public function testDbalLoadFromXmlSingleConnections(): void
{
$container = $this->loadContainer('dbal_service_single_connection');

// doctrine.dbal.mysql_connection
$config = $container->getDefinition('doctrine.dbal.default_connection')->getArgument(0);
$config = $container->getDefinition('doctrine.dbal.default_connection')->getArgument(0);

$this->assertEquals('mysql_s3cr3t', $config['password']);
$this->assertEquals('mysql_user', $config['user']);
Expand All @@ -113,12 +111,49 @@ public function testDbalLoadFromXmlSingleConnections(): void
$this->assertEquals('5.6.20', $config['serverVersion']);
}

public function testDbalLoadUrlOverride(): void
{
$container = $this->loadContainer('dbal_allow_url_override');
$config = $container->getDefinition('doctrine.dbal.default_connection')->getArgument(0);

$this->assertSame('mysql://root:password@database:3306/main?serverVersion=mariadb-10.5.8', $config['url']);

$expectedOverrides = [
'dbname' => 'main_test',
'user' => 'tester',
'password' => 'wordpass',
'host' => 'localhost',
'port' => 4321,
];

$this->assertEquals($expectedOverrides, array_intersect_key($config, $expectedOverrides));
$this->assertSame($expectedOverrides, $config['connection_override_options']);
$this->assertFalse(isset($config['override_url']));
}

public function testDbalLoadPartialUrlOverrideSetsDefaults(): void
{
$container = $this->loadContainer('dbal_allow_partial_url_override');
$config = $container->getDefinition('doctrine.dbal.default_connection')->getArgument(0);

$expectedDefaults = [
'host' => 'localhost',
'user' => 'root',
'password' => null,
'port' => null,
];

$this->assertEquals($expectedDefaults, array_intersect_key($config, $expectedDefaults));
$this->assertSame('mysql://root:password@database:3306/main?serverVersion=mariadb-10.5.8', $config['url']);
$this->assertCount(1, $config['connection_override_options']);
$this->assertSame('main_test', $config['connection_override_options']['dbname']);
$this->assertFalse(isset($config['override_url']));
}

public function testDbalLoadSingleMasterSlaveConnection(): void
{
$container = $this->loadContainer('dbal_service_single_master_slave_connection');

// doctrine.dbal.mysql_connection
$param = $container->getDefinition('doctrine.dbal.default_connection')->getArgument(0);
$param = $container->getDefinition('doctrine.dbal.default_connection')->getArgument(0);

$this->assertEquals(
class_exists(PrimaryReadReplicaConnection::class) ?
Expand All @@ -135,6 +170,7 @@ class_exists(PrimaryReadReplicaConnection::class) ?
'dbname' => 'mysql_db',
'host' => 'localhost',
'unix_socket' => '/path/to/mysqld.sock',
'connection_override_options' => [],
],
$param['primary'] ?? $param['master'] // TODO: Remove 'master' support here when we require dbal >= 2.11
);
Expand All @@ -146,6 +182,7 @@ class_exists(PrimaryReadReplicaConnection::class) ?
'dbname' => 'replica_db',
'host' => 'localhost',
'unix_socket' => '/path/to/mysqld_replica.sock',
'override_url' => false,
],
$param['replica']['replica1']
);
Expand All @@ -155,9 +192,7 @@ class_exists(PrimaryReadReplicaConnection::class) ?
public function testDbalLoadPoolShardingConnection(): void
{
$container = $this->loadContainer('dbal_service_pool_sharding_connection');

// doctrine.dbal.mysql_connection
$param = $container->getDefinition('doctrine.dbal.default_connection')->getArgument(0);
$param = $container->getDefinition('doctrine.dbal.default_connection')->getArgument(0);

$this->assertEquals('Doctrine\\DBAL\\Sharding\\PoolingShardConnection', $param['wrapperClass']);
$this->assertEquals(new Reference('foo.shard_choser'), $param['shardChoser']);
Expand All @@ -169,6 +204,7 @@ public function testDbalLoadPoolShardingConnection(): void
'dbname' => 'mysql_db',
'host' => 'localhost',
'unix_socket' => '/path/to/mysqld.sock',
'connection_override_options' => [],
],
$param['global']
);
Expand All @@ -181,6 +217,7 @@ public function testDbalLoadPoolShardingConnection(): void
'host' => 'localhost',
'unix_socket' => '/path/to/mysqld_shard.sock',
'id' => 1,
'override_url' => false,
],
$param['shards'][0]
);
Expand Down Expand Up @@ -223,6 +260,7 @@ public function testLoadSimpleSingleConnection(): void
'driver' => 'pdo_mysql',
'driverOptions' => [],
'defaultTableOptions' => [],
'connection_override_options' => [],
],
new Reference('doctrine.dbal.default_connection.configuration'),
new Reference('doctrine.dbal.default_connection.event_manager'),
Expand Down Expand Up @@ -262,6 +300,7 @@ public function testLoadSimpleSingleConnectionWithoutDbName(): void
'driver' => 'pdo_mysql',
'driverOptions' => [],
'defaultTableOptions' => [],
'connection_override_options' => [],
],
new Reference('doctrine.dbal.default_connection.configuration'),
new Reference('doctrine.dbal.default_connection.event_manager'),
Expand Down Expand Up @@ -302,6 +341,7 @@ public function testLoadSingleConnection(): void
'dbname' => 'sqlite_db',
'memory' => true,
'defaultTableOptions' => [],
'connection_override_options' => [],
],
new Reference('doctrine.dbal.default_connection.configuration'),
new Reference('doctrine.dbal.default_connection.event_manager'),
Expand Down
11 changes: 8 additions & 3 deletions Tests/DependencyInjection/Fixtures/DbalTestKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,16 @@

class DbalTestKernel extends Kernel
{
/** @var array<string, mixed> */
private $dbalConfig;

/** @var string|null */
private $projectDir;

public function __construct()
public function __construct(array $dbalConfig = ['driver' => 'pdo_sqlite'])
{
$this->dbalConfig = $dbalConfig;

parent::__construct('test', true);
}

Expand All @@ -30,11 +35,11 @@ public function registerBundles(): iterable

public function registerContainerConfiguration(LoaderInterface $loader): void
{
$loader->load(static function (ContainerBuilder $container): void {
$loader->load(function (ContainerBuilder $container): void {
$container->loadFromExtension('framework', ['secret' => 'F00']);

$container->loadFromExtension('doctrine', [
'dbal' => ['driver' => 'pdo_sqlite'],
'dbal' => $this->dbalConfig,
]);

// Register a NullLogger to avoid getting the stderr default logger of FrameworkBundle
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" ?>

<srv:container xmlns="http://symfony.com/schema/dic/doctrine"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:srv="http://symfony.com/schema/dic/services"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/doctrine http://symfony.com/schema/dic/doctrine/doctrine-1.0.xsd">

<config>
<dbal override-url="true" dbname="main_test" url="mysql://root:password@database:3306/main?serverVersion=mariadb-10.5.8" />
</config>
</srv:container>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" ?>

<srv:container xmlns="http://symfony.com/schema/dic/doctrine"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:srv="http://symfony.com/schema/dic/services"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/doctrine http://symfony.com/schema/dic/doctrine/doctrine-1.0.xsd">

<config>
<dbal override-url="true" dbname="main_test" user="tester" password="wordpass" host="localhost" port="4321" url="mysql://root:password@database:3306/main?serverVersion=mariadb-10.5.8" />
</config>
</srv:container>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
doctrine:
dbal:
override_url: true
url: 'mysql://root:password@database:3306/main?serverVersion=mariadb-10.5.8'
dbname: main_test
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
doctrine:
dbal:
override_url: true
url: 'mysql://root:password@database:3306/main?serverVersion=mariadb-10.5.8'
dbname: main_test
user: tester
password: wordpass
host: localhost
port: 4321
Loading

0 comments on commit 4e46ad9

Please sign in to comment.