Change record status: 
Project: 
Introduced in branch: 
8.6.x
Introduced in version: 
8.6.0
Description: 

Drupal's database abstraction layer uses the concept of a "connection array" (the one that is commonly defined in the settings.php file) to establish connection to the database. However, in order to pass database information between installs (and specifically, during test runs to pass this information to child sites) a URL version of this connection array is used.

The conversion between the "connection URL" and the "connection array" was not centralized neither encapsulated in any way.

Some methods on the database layer have now been implemented to assist and isolate these conversions, that also now rely on the database drivers themselves to provide any custom or database specific alterations if needed:

  • \Drupal\Core\Database::convertDbUrlToConnectionInfo($url, $root): to convert a connection URL into a connection array.
  • \Drupal\Core\Database::convertConnectionInfoToUrl(array $db_info): to convert a connection array to a connection URL.

Custom/contrib database drivers

Custom/contrib drivers can extend/override the default implementations in order to add/retrieve to/from the URL additional connection elements they may require.

In the example below, the SQLite driver's Connection class implements overrides for Connection::createConnectionOptionsFromUrl and for Connection::createUrlFromConnectionOptions, in order to adjust the results:

  /**
   * {@inheritdoc}
   */
  public static function createConnectionOptionsFromUrl($url, $root) {
    $database = parent::createConnectionOptionsFromUrl($url, $root);

    // A SQLite database path with two leading slashes indicates a system path.
    // Otherwise the path is relative to the Drupal root.
    $url_components = parse_url($url);
    if ($url_components['path'][0] === '/') {
      $url_components['path'] = substr($url_components['path'], 1);
    }
    if ($url_components['path'][0] === '/') {
      $database['database'] = $url_components['path'];
    }
    else {
      $database['database'] = $root . '/' . $url_components['path'];
    }

    // User credentials and system port are irrelevant for SQLite.
    unset(
      $database['username'],
      $database['password'],
      $database['port']
    );

    return $database;
  }

  /**
   * {@inheritdoc}
   */
  public static function createUrlFromConnectionOptions(array $connection_options) {
    if (!isset($connection_options['driver'], $connection_options['database'])) {
      throw new \InvalidArgumentException("As a minimum, the connection options array must contain at least the 'driver' and 'database' keys");
    }

    $db_url = 'sqlite://localhost/' . $connection_options['database'];

    if (isset($connection_options['prefix']['default']) && $connection_options['prefix']['default'] !== NULL && $connection_options['prefix']['default'] !== '') {
      $db_url .= '#' . $connection_options['prefix']['default'];
    }

    return $db_url;
  }

Specifying the database URL in env variables or in phpunit.xml for test runs

Contrib database drivers will specify the driver name in the schema of the URL, for example:

dbal://username:password@host:port/database?dbal_driver=pdo_mysql#prefix

Note that if you use the URL in the phpunit.xml file to set the db environment for testing, and the driver adds multiple parts to the querystring, you have to manually convert the querystring separators & to HTML entities counterparts &, to comply to XML syntax.
So, if you get something like

mydriver://username:password@host:port/database?my_driver=xxxx&my_param=yyyy#prefix

from call to \Drupal\Core\Database\Database::getConnectionInfoAsUrl, your phpunit.xml entry should be

<env name="SIMPLETEST_DB" value="mydriver://username:password@host:port/database?my_driver=xxxx&amp;my_param=yyyy#prefix"/>

Impacts: 
Site builders, administrators, editors
Module developers
Updates Done (doc team, etc.)
Online documentation: 
Not done
Theming guide: 
Not done
Module developer documentation: 
Not done
Examples project: 
Not done
Coder Review: 
Not done
Coder Upgrade: 
Not done
Other: 
Other updates done