diff --git a/core/assets/scaffold/files/default.settings.php b/core/assets/scaffold/files/default.settings.php index bec626cd16..9b82c3a1d1 100644 --- a/core/assets/scaffold/files/default.settings.php +++ b/core/assets/scaffold/files/default.settings.php @@ -105,6 +105,10 @@ * webserver. For most other drivers, you must specify a * username, password, host, and database name. * + * Drivers contained in contributed or custom modules need to set the + * "namespace" and "namespace_dir" properties. This allows their database driver + * to cache the container. + * * Transaction support is enabled by default for all drivers that support it, * including MySQL. To explicitly disable it, set the 'transactions' key to * FALSE. @@ -224,6 +228,20 @@ * 'database' => '/path/to/databasefilename', * ]; * @endcode + * + * Sample Database configuration format for a driver in a contributed module: + * @code + * $databases['default']['default'] = [ + * 'driver' => 'mydriver', + * 'database' => 'databasename', + * 'username' => 'sqlusername', + * 'password' => 'sqlpassword', + * 'host' => 'localhost', + * 'prefix' => '', + * 'namespace' => 'Drupal\mymodule\DatabaseDriver\mydriver', + * 'namespace_dir' => 'modules/mymodule/src/DatabaseDriver/mydriver/', + * ]; + * @endcode */ /** diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index a155778e83..776b1fc08d 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -373,6 +373,11 @@ function install_begin_request($class_loader, &$install_state) { ->addArgument(Settings::getInstance()) ->addArgument((new LoggerChannelFactory())->get('file')); + // Register the class loader so contrib and custom database drivers can be + // autoloaded. + // @see drupal_get_database_types() + $container->set('class_loader', $class_loader); + \Drupal::setContainer($container); // Determine whether base system services are ready to operate. @@ -1188,8 +1193,10 @@ function install_database_errors($database, $settings_file) { $errors = []; // Check database type. - $database_types = drupal_get_database_types(); + $include_tests = Settings::get('extension_discovery_scan_tests') || drupal_valid_test_ua(); + $database_types = drupal_get_database_types($include_tests); $driver = $database['driver']; + $module = $database['module'] ?? NULL; if (!isset($database_types[$driver])) { $errors['driver'] = t("In your %settings_file file you have configured @drupal to use a %driver server, however your PHP installation currently does not support this database type.", ['%settings_file' => $settings_file, '@drupal' => drupal_install_profile_distribution_name(), '%driver' => $driver]); } @@ -1204,7 +1211,7 @@ function install_database_errors($database, $settings_file) { // calling function. Database::addConnectionInfo('default', 'default', $database); - $errors = db_installer_object($driver)->runTasks(); + $errors = db_installer_object($driver, $module)->runTasks(); } return $errors; } diff --git a/core/includes/install.inc b/core/includes/install.inc index 587480dbe6..fad1508a65 100644 --- a/core/includes/install.inc +++ b/core/includes/install.inc @@ -8,6 +8,7 @@ use Drupal\Component\Utility\OpCodeCache; use Drupal\Component\Utility\Unicode; use Drupal\Component\Utility\UrlHelper; +use Drupal\Core\Database\Database; use Drupal\Core\Extension\Dependency; use Drupal\Core\Extension\ExtensionDiscovery; use Drupal\Core\Installer\InstallerKernel; @@ -160,10 +161,13 @@ function drupal_detect_database_types() { /** * Returns all supported database driver installer objects. * + * @param bool $including_test_drivers + * To include test database drivers in the returned list. Defaults to FALSE. + * * @return \Drupal\Core\Database\Install\Tasks[] * An array of available database driver installer objects. */ -function drupal_get_database_types() { +function drupal_get_database_types($including_test_drivers = FALSE) { $databases = []; $drivers = []; @@ -177,11 +181,36 @@ function drupal_get_database_types() { } foreach ($files as $file) { if (file_exists($file->uri . '/Install/Tasks.php')) { - $drivers[$file->filename] = $file->uri; + $drivers[$file->filename] = NULL; + } + } + + /** @var \Composer\Autoload\ClassLoader $class_loader */ + $class_loader = \Drupal::service('class_loader'); + // We cannot use the file cache because it does not always exists. + $extension_discovery = new ExtensionDiscovery(DRUPAL_ROOT, FALSE, []); + $modules = $extension_discovery->scan('module', $including_test_drivers); + foreach ($modules as $module) { + $module_driver_path = DRUPAL_ROOT . '/' . $module->getPath() . '/src/DatabaseDriver'; + if (is_dir($module_driver_path)) { + $driver_files = $file_system->scanDirectory($module_driver_path, $mask, ['recurse' => FALSE]); + foreach ($driver_files as $driver_file) { + // We are testing every module if they have the file Tasks.php in the + // directory MODULE_ROOT/src/DatabaseDriver/{$driver}/Install/. The + // chance that a non-database driver has that file in the given + // directory is almost zero. + $tasks_file = $module_driver_path . '/' . $driver_file->filename . '/Install/Tasks.php'; + if (file_exists($tasks_file)) { + $class_loader->addPsr4('Drupal\\' . $module->getName() . '\\DatabaseDriver\\' . $driver_file->filename . '\\', $module->getPath() . '/src/DatabaseDriver/' . $driver_file->filename); + // Module provided drivers take precedence over core provided drivers. + $drivers[$driver_file->filename] = $module->getName(); + } + } } } - foreach ($drivers as $driver => $file) { - $installer = db_installer_object($driver); + + foreach ($drivers as $driver => $module) { + $installer = db_installer_object($driver, $module); if ($installer->installable()) { $databases[$driver] = $installer; } @@ -247,7 +276,7 @@ function drupal_rewrite_settings($settings = [], $settings_file = NULL) { $state = 'default'; foreach (token_get_all($contents) as $token) { if (is_array($token)) { - list($type, $value) = $token; + [$type, $value] = $token; } else { $type = -1; @@ -1084,19 +1113,18 @@ function install_profile_info($profile, $langcode = 'en') { * * @param $driver * The name of the driver. + * @param $module + * The name of the module that is the provider of the driver. For the by core + * supported drivers the module name is the driver name. * * @return \Drupal\Core\Database\Install\Tasks * A class defining the requirements and tasks for installing the database. */ -function db_installer_object($driver) { +function db_installer_object($driver, $module = NULL) { // We cannot use Database::getConnection->getDriverClass() here, because // the connection object is not yet functional. - $task_class = "Drupal\\Driver\\Database\\{$driver}\\Install\\Tasks"; - if (class_exists($task_class)) { - return new $task_class(); - } - else { - $task_class = "Drupal\\Core\\Database\\Driver\\{$driver}\\Install\\Tasks"; + $task_class = Database::getDatabaseClass($driver, $module, "Install\\Tasks"); + if ($task_class) { return new $task_class(); } } diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index ba1b0216c8..b60c74ab61 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -1677,6 +1677,10 @@ public function __sleep() { * Exception thrown when the provided URL does not meet the minimum * requirements. * + * @internal + * This method should only be called from + * \Drupal\Core\Database\Database::convertDbUrlToConnectionInfo(). + * * @see \Drupal\Core\Database\Database::convertDbUrlToConnectionInfo() */ public static function createConnectionOptionsFromUrl($url, $root) { @@ -1736,6 +1740,10 @@ public static function createConnectionOptionsFromUrl($url, $root) { * Exception thrown when the provided array of connection options does not * meet the minimum requirements. * + * @internal + * This method should only be called from + * \Drupal\Core\Database\Database::getConnectionInfoAsUrl(). + * * @see \Drupal\Core\Database\Database::getConnectionInfoAsUrl() */ public static function createUrlFromConnectionOptions(array $connection_options) { @@ -1762,6 +1770,11 @@ public static function createUrlFromConnectionOptions(array $connection_options) $db_url .= '/' . $connection_options['database']; + // Add the module if the driver is provided by a contributed module. + if (isset($connection_options['module'])) { + $db_url .= '?module=' . $connection_options['module']; + } + if (isset($connection_options['prefix']['default']) && $connection_options['prefix']['default'] !== '') { $db_url .= '#' . $connection_options['prefix']['default']; } diff --git a/core/lib/Drupal/Core/Database/Database.php b/core/lib/Drupal/Core/Database/Database.php index 173caf3ece..eff07b3175 100644 --- a/core/lib/Drupal/Core/Database/Database.php +++ b/core/lib/Drupal/Core/Database/Database.php @@ -2,6 +2,9 @@ namespace Drupal\Core\Database; +use Composer\Autoload\ClassLoader; +use Drupal\Core\Extension\ExtensionDiscovery; + /** * Primary front-controller for the database system. * @@ -448,6 +451,8 @@ public static function ignoreTarget($key, $target) { * @throws \InvalidArgumentException * Exception thrown when the provided URL does not meet the minimum * requirements. + * @throws \RuntimeException + * Exception thrown when a module provided database driver does not exist. */ public static function convertDbUrlToConnectionInfo($url, $root) { // Check that the URL is well formed, starting with 'scheme://', where @@ -457,20 +462,141 @@ public static function convertDbUrlToConnectionInfo($url, $root) { } $driver = $matches[1]; - // Discover if the URL has a valid driver scheme. Try with custom drivers - // first, since those can override/extend the core ones. - $connection_class = $custom_connection_class = "Drupal\\Driver\\Database\\{$driver}\\Connection"; - if (!class_exists($connection_class)) { - // If the URL is not relative to a custom driver, try with core ones. - $connection_class = "Drupal\\Core\\Database\\Driver\\{$driver}\\Connection"; - if (!class_exists($connection_class)) { - throw new \InvalidArgumentException("Can not convert '$url' to a database connection, class '$custom_connection_class' does not exist"); + // Determine if the database driver is provided by a module. + $module = NULL; + $url_components = parse_url($url); + if (isset($url_components['query'])) { + parse_str($url_components['query'], $query); + if ($query['module']) { + $module = $query['module']; + // Set up an additional autoloader. We don't use the main autoloader as + // this method can be called before Drupal is installed and is never + // called during regular runtime. + $namespace = "Drupal\\$module\\DatabaseDriver\\$driver"; + $namespace_dir = Database::getModuleNamespaceDir($namespace, $root, TRUE); + // Set up an additional classloader. + $additional_class_loader = new ClassLoader(); + $additional_class_loader->addPsr4($namespace . '\\', $namespace_dir); + $additional_class_loader->register(TRUE); + } + } + + $connection_class = static::getDatabaseClass($driver, $module, 'Connection'); + + if (!$connection_class) { + if ($module) { + $suggested_class = "Drupal\\{$module}\\DatabaseDriver\\{$driver}\\Connection"; + } + else { + $suggested_class = "Drupal\\Driver\\Database\\{$driver}\\Connection"; + } + throw new \InvalidArgumentException("Can not convert '$url' to a database connection, class '$suggested_class' does not exist"); + } + + $options = $connection_class::createConnectionOptionsFromUrl($url, $root); + + // If the driver is provided by a module add the necessary information to + // autoload the code. + // @see \Drupal\Core\Site\Settings::initialize() + if (isset($namespace_dir)) { + $options['namespace_dir'] = $namespace_dir; + } + + return $options; + } + + /** + * Gets a fully qualified class name for a database driver. + * + * @todo more docs. + * + * @param string $driver + * The database driver name. + * @param string|null $module + * The module that provides the driver or NULL if the driver is a core + * driver or in the Drupal 8 style custom driver namespace. + * @param string $class + * The database class to find. + * + * @return string|null + * The fully qualified class name or NULL if a class cannot be found. + */ + public static function getDatabaseClass($driver, $module, $class) { + $namespaces = []; + if ($module) { + // Drupal 9 style module supplied driver namespace. + $namespaces[] = "Drupal\\{$module}\\DatabaseDriver\\{$driver}\\$class"; + } + // Drupal 8 style custom driver namespace. + $namespaces[] = "Drupal\\Driver\\Database\\{$driver}\\$class"; + // Core drivers are checked last. + $namespaces[] = "Drupal\\Core\\Database\\Driver\\{$driver}\\$class"; + foreach ($namespaces as $namespace) { + if (class_exists($namespace)) { + return $namespace; } } + return NULL; + } - return $connection_class::createConnectionOptionsFromUrl($url, $root); + /** + * Determines if the namespace is provided by a module. + * + * @param string $namespace + * The namespace to check. + * + * @return string|false + * The directory to add to the autoloader for namespace if it provided by a + * module, FALSE if the namespace is a core namespace or a Drupal 8 style + * contrib namespace. + * + * @throws \RuntimeException + * Exception thrown when a module provided database driver does not exist. + */ + public static function getModuleNamespaceDir($namespace, $root, $including_test_drivers = FALSE) { + $result = preg_match("#Drupal\\\\([^\\\\]*)\\\\DatabaseDriver\\\\([^\\\\]*)#", $namespace, $matches); + if (!$result) { + return FALSE; + } + $module = $matches[1]; + $driver = $matches[2]; + if ($module === 'Driver') { + // Core provided namespace or a Drupal 8 style contrib namespace. + return FALSE; + } + // Find the module. + $extension_discovery = new ExtensionDiscovery($root, FALSE, []); + $modules = $extension_discovery->scan('module', $including_test_drivers); + if (!isset($modules[$module])) { + throw new \RuntimeException(sprintf("Cannot find the module '%s' for the database driver namespace '%s'", $module, $namespace)); + } + $directory = $modules[$module]->getPath() . '/src/DatabaseDriver/' . $driver . '/'; + if (!is_dir($root . '/' . $directory)) { + throw new \RuntimeException(sprintf("Cannot find the driver '%s' in the module '%s' for the database driver namespace '%s'", $driver, $module, $namespace)); + } + return $directory; } + /** + * Determines the providing module from the namespace. + * + * @param string $namespace + * The namespace to check. + * + * @return string|false + * The providing module from the given namespace, or FALSE if the there is + * no providing module. + */ + public static function getModuleFromNamespace($namespace) { + // The driver namespace that has a providing module in it has the formula + // Drupal\{$module_name}\DatabaseDriver\{$driver_name}. + $result = preg_match("#Drupal\\\\([^\\\\]*)\\\\DatabaseDriver\\\\([^\\\\]*)#", $namespace, $matches); + if (!$result || $matches[1] === 'Driver' || count($matches) < 3) { + return FALSE; + } + return $matches[1]; + } + /** * Gets database connection info as a URL. * @@ -489,6 +615,17 @@ public static function getConnectionInfoAsUrl($key = 'default') { throw new \RuntimeException("Database connection $key not defined or missing the 'default' settings"); } $connection_class = static::getDatabaseDriverNamespace($db_info['default']) . '\\Connection'; + // If the connection options has a namespace_dir entry then it is provided + // be a module. Add the module name to connection options to make it easy + // for contrib drivers that provide their own implementation of + // \Drupal\Core\Database\Connection::createUrlFromConnectionOptions() to add + // this info to the URL. + if (isset($db_info['default']['namespace_dir'])) { + $result = preg_match("#Drupal\\\\([^\\\\]*)\\\\DatabaseDriver\\\\([^\\\\]*)#", $connection_class, $matches); + if ($result && $matches[1] !== 'Driver') { + $db_info['default']['module'] = $matches[1]; + } + } return $connection_class::createUrlFromConnectionOptions($db_info['default']); } diff --git a/core/lib/Drupal/Core/Database/Driver/mysql/Install/Tasks.php b/core/lib/Drupal/Core/Database/Driver/mysql/Install/Tasks.php index 833bce7507..51625d9df5 100644 --- a/core/lib/Drupal/Core/Database/Driver/mysql/Install/Tasks.php +++ b/core/lib/Drupal/Core/Database/Driver/mysql/Install/Tasks.php @@ -46,6 +46,13 @@ class Tasks extends InstallTasks { */ protected $pdoDriver = 'mysql'; + /** + * The driver name for MySQL and equivalent databases. + * + * @var string + */ + protected $driver = 'mysql'; + /** * Constructs a \Drupal\Core\Database\Driver\mysql\Install\Tasks object. */ diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Install/Tasks.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Install/Tasks.php index 6e184aea2f..8a5b1e80bf 100644 --- a/core/lib/Drupal/Core/Database/Driver/pgsql/Install/Tasks.php +++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Install/Tasks.php @@ -25,6 +25,11 @@ class Tasks extends InstallTasks { */ protected $pdoDriver = 'pgsql'; + /** + * {@inheritdoc} + */ + protected $driver = 'pgsql'; + /** * Constructs a \Drupal\Core\Database\Driver\pgsql\Install\Tasks object. */ diff --git a/core/lib/Drupal/Core/Database/Driver/sqlite/Install/Tasks.php b/core/lib/Drupal/Core/Database/Driver/sqlite/Install/Tasks.php index 81bdea39bb..d8bc18c520 100644 --- a/core/lib/Drupal/Core/Database/Driver/sqlite/Install/Tasks.php +++ b/core/lib/Drupal/Core/Database/Driver/sqlite/Install/Tasks.php @@ -25,6 +25,11 @@ class Tasks extends InstallTasks { */ protected $pdoDriver = 'sqlite'; + /** + * {@inheritdoc} + */ + protected $driver = 'sqlite'; + /** * {@inheritdoc} */ diff --git a/core/lib/Drupal/Core/Database/Install/Tasks.php b/core/lib/Drupal/Core/Database/Install/Tasks.php index 5d36603ae8..ea82a9bbe7 100644 --- a/core/lib/Drupal/Core/Database/Install/Tasks.php +++ b/core/lib/Drupal/Core/Database/Install/Tasks.php @@ -19,6 +19,16 @@ */ protected $pdoDriver; + /** + * The name of the driver. + * + * @var string + * + * For the by Drupal core supported drivers the driver name is the same as the + * $pdoDriver. For contrib database driver this does not have to be the case. + */ + protected $driver; + /** * Structure that describes each task to run. * @@ -218,6 +228,13 @@ protected function checkEngineVersion() { * The options form array. */ public function getFormOptions(array $database) { + // The class variable $driver is new in drupal:9.0.0 and custom drivers need + // to set this variable. This code shall be removed in drupal:10.0.0. + // See https:///www.drupal.org/node/ + if (empty($this->driver)) { + $this->driver = $this->pdoDriver; + } + $form['database'] = [ '#type' => 'textfield', '#title' => t('Database name'), @@ -226,7 +243,7 @@ public function getFormOptions(array $database) { '#required' => TRUE, '#states' => [ 'required' => [ - ':input[name=driver]' => ['value' => $this->pdoDriver], + ':input[name=driver]' => ['value' => $this->driver], ], ], ]; @@ -239,7 +256,7 @@ public function getFormOptions(array $database) { '#required' => TRUE, '#states' => [ 'required' => [ - ':input[name=driver]' => ['value' => $this->pdoDriver], + ':input[name=driver]' => ['value' => $this->driver], ], ], ]; diff --git a/core/lib/Drupal/Core/Installer/Form/SiteSettingsForm.php b/core/lib/Drupal/Core/Installer/Form/SiteSettingsForm.php index aa2ce36680..4cd72a10b4 100644 --- a/core/lib/Drupal/Core/Installer/Form/SiteSettingsForm.php +++ b/core/lib/Drupal/Core/Installer/Form/SiteSettingsForm.php @@ -68,7 +68,8 @@ public function buildForm(array $form, FormStateInterface $form_state) { $form['#title'] = $this->t('Database configuration'); - $drivers = drupal_get_database_types(); + $include_tests = Settings::get('extension_discovery_scan_tests') || drupal_valid_test_ua(); + $drivers = drupal_get_database_types($include_tests); $drivers_keys = array_keys($drivers); // Unless there is input for this form (for a non-interactive installation, @@ -154,12 +155,24 @@ public function buildForm(array $form, FormStateInterface $form_state) { public function validateForm(array &$form, FormStateInterface $form_state) { $driver = $form_state->getValue('driver'); $database = $form_state->getValue($driver); - $drivers = drupal_get_database_types(); + $include_tests = Settings::get('extension_discovery_scan_tests') || drupal_valid_test_ua(); + $drivers = drupal_get_database_types($include_tests); $reflection = new \ReflectionClass($drivers[$driver]); $install_namespace = $reflection->getNamespaceName(); // Cut the trailing \Install from namespace. $database['namespace'] = substr($install_namespace, 0, strrpos($install_namespace, '\\')); $database['driver'] = $driver; + // If the namespace is provided by a module then we need to map it to a + // directory so we can add the driver to autoloader in + // \Drupal\Core\Site\Settings::initialize(). + if ($namespace_dir = Database::getModuleNamespaceDir($database['namespace'], DRUPAL_ROOT, $include_tests)) { + $database['namespace_dir'] = $namespace_dir; + } + // We need to set the module key, because the function + // install_database_errors() needs it. + if ($module = Database::getModuleFromNamespace($database['namespace'])) { + $database['module'] = $module; + } $form_state->set('database', $database); foreach ($this->getDatabaseErrors($database, $form_state->getValue('settings_file')) as $name => $message) { diff --git a/core/lib/Drupal/Core/Site/Settings.php b/core/lib/Drupal/Core/Site/Settings.php index adecc0f1a9..8dd6bc1013 100644 --- a/core/lib/Drupal/Core/Site/Settings.php +++ b/core/lib/Drupal/Core/Site/Settings.php @@ -100,6 +100,9 @@ public static function getAll() { /** * Bootstraps settings.php and the Settings singleton. * + * Additionally, adds module provided database drivers to the classloader so + * that databases that require a contrib driver can cache the container. + * * @param string $app_root * The app root. * @param string $site_path @@ -122,8 +125,18 @@ public static function initialize($app_root, $site_path, &$class_loader) { require $app_root . '/' . $site_path . '/settings.php'; } - // Initialize Database. - Database::setMultipleConnectionInfo($databases); + // Initialize databases. + foreach ($databases as $key => $targets) { + foreach ($targets as $target => $info) { + Database::addConnectionInfo($key, $target, $info); + // If the database is provided module then add the namespace to the + // autoloader so the database can be used prior to the container being + // booted. + if (isset($info['namespace_dir'])) { + $class_loader->addPsr4($info['namespace'] . '\\', $info['namespace_dir']); + } + } + } // Initialize Settings. new Settings($settings); diff --git a/core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php b/core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php index a10af1e48c..04579dc419 100644 --- a/core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php +++ b/core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php @@ -691,7 +691,7 @@ protected function getDatabaseTypes() { if (isset($this->originalContainer) && $this->originalContainer) { \Drupal::setContainer($this->originalContainer); } - $database_types = drupal_get_database_types(); + $database_types = drupal_get_database_types(TRUE); if (isset($this->originalContainer) && $this->originalContainer) { \Drupal::unsetContainer(); } diff --git a/core/modules/system/tests/modules/driver_test/driver_test.info.yml b/core/modules/system/tests/modules/driver_test/driver_test.info.yml new file mode 100644 index 0000000000..c36161d270 --- /dev/null +++ b/core/modules/system/tests/modules/driver_test/driver_test.info.yml @@ -0,0 +1,5 @@ +name: 'Contrib database driver test' +type: module +description: 'Support database contrib driver testing.' +package: Testing +version: VERSION diff --git a/core/modules/system/tests/modules/driver_test/src/DatabaseDriver/drivertestmysql/Connection.php b/core/modules/system/tests/modules/driver_test/src/DatabaseDriver/drivertestmysql/Connection.php new file mode 100644 index 0000000000..97878c1ca0 --- /dev/null +++ b/core/modules/system/tests/modules/driver_test/src/DatabaseDriver/drivertestmysql/Connection.php @@ -0,0 +1,19 @@ +xpath('//label[@for="edit-driver-drivertestmysql"]'); + $this->assertEqual(current($elements)->getText(), 'MySQL by the driver_test module'); + $elements = $this->xpath('//label[@for="edit-driver-drivertestpgsql"]'); + $this->assertEqual(current($elements)->getText(), 'PostgreSQL by the driver_test module'); + + $settings = $this->parameters['forms']['install_settings_form']; + $settings['driver'] = 'drivertestmysql'; + $settings['drivertestmysql'] = $settings['mysql']; + unset($settings['mysql']); + $edit = $this->translatePostValues($settings); + $this->drupalPostForm(NULL, $edit, $this->translations['Save and continue']); + } + + /** + * Confirms that the installation succeeded. + */ + public function testInstalled() { + $this->assertUrl('user/1'); + $this->assertResponse(200); + + // Assert that in the settings.php the database connection array has the + // correct values set. + $settings_filename = $this->container->getParameter('app.root') . '/' . $this->siteDirectory . '/settings.php'; + $contents = file_get_contents($settings_filename); + $this->assertNotFalse($contents); + $this->assertNotFalse(strpos($contents, '\'namespace\' => \'Drupal\\\\driver_test\\\\DatabaseDriver\\\\drivertestmysql\',')); + $this->assertNotFalse(strpos($contents, '\'driver\' => \'drivertestmysql\',')); + $this->assertNotFalse(strpos($contents, '\'namespace_dir\' => \'core/modules/system/tests/modules/driver_test/src/DatabaseDriver/drivertestmysql/\',')); + $this->assertNotFalse(strpos($contents, '\'module\' => \'driver_test\',')); + } + +} diff --git a/core/tests/Drupal/FunctionalTests/Installer/InstallerTest.php b/core/tests/Drupal/FunctionalTests/Installer/InstallerTest.php index ec201bae11..477cd38434 100644 --- a/core/tests/Drupal/FunctionalTests/Installer/InstallerTest.php +++ b/core/tests/Drupal/FunctionalTests/Installer/InstallerTest.php @@ -75,6 +75,13 @@ protected function setUpSettings() { // Assert that the expected title is present. $this->assertEqual('Database configuration', $this->cssSelect('main h2')[0]->getText()); + // Assert that we use the by core supported database drivers by default and + // not the ones from the driver_test module. + $elements = $this->xpath('//label[@for="edit-driver-mysql"]'); + $this->assertEqual(current($elements)->getText(), 'MySQL, MariaDB, Percona Server, or equivalent'); + $elements = $this->xpath('//label[@for="edit-driver-pgsql"]'); + $this->assertEqual(current($elements)->getText(), 'PostgreSQL'); + parent::setUpSettings(); } diff --git a/core/tests/Drupal/FunctionalTests/Update/UpdatePathTestBase.php b/core/tests/Drupal/FunctionalTests/Update/UpdatePathTestBase.php index bf893ef8c2..81447ede39 100644 --- a/core/tests/Drupal/FunctionalTests/Update/UpdatePathTestBase.php +++ b/core/tests/Drupal/FunctionalTests/Update/UpdatePathTestBase.php @@ -296,8 +296,10 @@ protected function runDbTasks() { \Drupal::setContainer($container); require_once __DIR__ . '/../../../../includes/install.inc'; - $connection = Database::getConnection(); - $errors = db_installer_object($connection->driver())->runTasks(); + $connection_info = Database::getConnectionInfo(); + $driver = $connection_info['default']['driver']; + $module = $connection_info['default']['module'] ?? NULL; + $errors = db_installer_object($driver, $module)->runTasks(); if (!empty($errors)) { $this->fail('Failed to run installer database tasks: ' . implode(', ', $errors)); } diff --git a/core/tests/Drupal/KernelTests/KernelTestBase.php b/core/tests/Drupal/KernelTests/KernelTestBase.php index ebb50d631d..5bf7b6e4aa 100644 --- a/core/tests/Drupal/KernelTests/KernelTestBase.php +++ b/core/tests/Drupal/KernelTests/KernelTestBase.php @@ -358,8 +358,10 @@ private function bootKernel() { // Ensure database tasks have been run. require_once __DIR__ . '/../../../includes/install.inc'; - $connection = Database::getConnection(); - $errors = db_installer_object($connection->driver())->runTasks(); + $connection_info = Database::getConnectionInfo(); + $driver = $connection_info['default']['driver']; + $module = $connection_info['default']['module'] ?? NULL; + $errors = db_installer_object($driver, $module)->runTasks(); if (!empty($errors)) { $this->fail('Failed to run installer database tasks: ' . implode(', ', $errors)); } diff --git a/core/tests/Drupal/Tests/Core/Database/DatabaseTest.php b/core/tests/Drupal/Tests/Core/Database/DatabaseTest.php new file mode 100644 index 0000000000..5ddcb03089 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Database/DatabaseTest.php @@ -0,0 +1,168 @@ +additionalClassloader = new ClassLoader(); + $this->additionalClassloader->register(); + // Mock the container so we don;t need to mock drupal_valid_test_ua(). + // @see \Drupal\Core\Extension\ExtensionDiscovery::scan() + $this->root = dirname(dirname(dirname(dirname(dirname(dirname(__DIR__)))))); + $container = $this->createMock(ContainerInterface::class); + $container->expects($this->any()) + ->method('has') + ->with('kernel') + ->willReturn(TRUE); + $container->expects($this->any()) + ->method('getParameter') + ->with('site.path') + ->willReturn(''); + \Drupal::setContainer($container); + } + + /** + * @covers ::getDatabaseClass + * @dataProvider providerGetDatabaseClass + */ + public function testGetDatabaseClass($expected, $driver, $module, $class, $include_d8custom, $include_module) { + $this->additionalClassloader->addPsr4("Drupal\\Driver\\Database\\fake\\", __DIR__ . "/../../../../../tests/fixtures/database_drivers/custom/fake"); + $this->additionalClassloader->addPsr4("Drupal\\Core\\Database\\Driver\\corefake\\", __DIR__ . "/../../../../../tests/fixtures/database_drivers/core/corefake"); + if ($include_d8custom) { + $this->addD8CustomDrivers(); + } + if ($include_module) { + $this->addModuleDrivers(); + } + $this->assertSame($expected, Database::getDatabaseClass($driver, $module, $class)); + } + + /** + * Data provider for ::testGetDatabaseClass(). + * @return array + */ + public function providerGetDatabaseClass() { + return [ + 'core mysql' => ['Drupal\Core\Database\Driver\mysql\Connection', 'mysql', 'NULL', 'Connection', FALSE, FALSE], + 'core fake' => ['Drupal\Core\Database\Driver\corefake\Connection', 'corefake', 'NULL', 'Connection', FALSE, FALSE], + 'D8 custom fake' => ['Drupal\Driver\Database\corefake\Connection', 'corefake', 'NULL', 'Connection', TRUE, FALSE], + 'module fake' => ['Drupal\corefake\DatabaseDriver\corefake\Connection', 'corefake', 'corefake', 'Connection', TRUE, TRUE], + 'module mysql' => ['Drupal\driver_test\DatabaseDriver\drivertestmysql\Connection', 'drivertestmysql', 'driver_test', 'Connection', FALSE, TRUE], + ]; + } + + /** + * @covers ::getModuleNamespaceDir + * @dataProvider providerGetModuleNamespaceDir + */ + public function testGetModuleNamespaceDir($expected, $namespace) { + // The only module that provides a driver in core is a test module. + $this->assertSame($expected, Database::getModuleNamespaceDir($namespace, $this->root, TRUE)); + } + + /** + * Data provider for ::testGetModuleNamespaceDir(). + * + * @return array + */ + public function providerGetModuleNamespaceDir() { + return [ + 'core mysql' => [FALSE, 'Drupal\Core\Database\Driver\mysql'], + 'D8 custom fake' => [FALSE, 'Drupal\Driver\Database\corefake'], + 'module mysql' => ['core/modules/system/tests/modules/driver_test/src/DatabaseDriver/drivertestmysql/', 'Drupal\driver_test\DatabaseDriver\drivertestmysql'], + ]; + } + + /** + * @covers ::getModuleNamespaceDir + * @dataProvider providerGetModuleNamespaceDirException + */ + public function testGetModuleNamespaceDirException($expected_message, $namespace, $include_tests) { + $root = dirname(dirname(dirname(dirname(dirname(dirname(__DIR__)))))); + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage($expected_message); + Database::getModuleNamespaceDir($namespace, $root, $include_tests); + } + + /** + * Data provider for ::testGetModuleNamespaceDirException(). + * + * @return array + */ + public function providerGetModuleNamespaceDirException() { + return [ + 'test module but tests not included' => ["Cannot find the module 'driver_test' for the database driver namespace 'Drupal\driver_test\DatabaseDriver\drivertestmysql'", 'Drupal\driver_test\DatabaseDriver\drivertestmysql', FALSE], + 'non-existent driver in test module' => ["Cannot find the driver 'sqlite' in the module 'driver_test' for the database driver namespace 'Drupal\driver_test\DatabaseDriver\sqlite'", 'Drupal\driver_test\DatabaseDriver\sqlite', TRUE], + 'non-existent module' => ["Cannot find the module 'does_not_exist' for the database driver namespace 'Drupal\does_not_exist\DatabaseDriver\mysql'", 'Drupal\does_not_exist\DatabaseDriver\mysql', TRUE], + ]; + } + + /** + * @covers ::getModuleFromNamespace + * @dataProvider providerGetModuleFromNamespace + */ + public function testgetModuleFromNamespace($expected, $namespace) { + // The only module that provides a driver in core is a test module. + $this->assertSame($expected, Database::getModuleFromNamespace($namespace)); + } + + /** + * Data provider for ::testGetModuleFromNamespace(). + * + * @return array + */ + public function providerGetModuleFromNamespace() { + return [ + 'core mysql' => [FALSE, 'Drupal\Core\Database\Driver\mysql'], + 'D8 custom fake' => [FALSE, 'Drupal\Driver\Database\corefake'], + 'module mysql' => ['driver_test', 'Drupal\driver_test\DatabaseDriver\drivertestmysql'], + ]; + } + + /** + * Adds a database driver that uses the D8's Drupal\Driver\Database namespace. + */ + protected function addD8CustomDrivers() { + $this->additionalClassloader->addPsr4("Drupal\\Driver\\Database\\corefake\\", __DIR__ . "/../../../../../tests/fixtures/database_drivers/custom/corefake"); + } + + /** + * Adds database drivers that are provided by modules. + */ + protected function addModuleDrivers() { + $this->additionalClassloader->addPsr4("Drupal\\driver_test\\DatabaseDriver\\drivertestmysql\\", __DIR__ . "/../../../../../modules/system/tests/modules/driver_test/src/DatabaseDriver/drivertestmysql"); + $this->additionalClassloader->addPsr4("Drupal\\corefake\\DatabaseDriver\\corefake\\", __DIR__ . "/../../../../../tests/fixtures/database_drivers/module/corefake/src/DatabaseDriver/corefake"); + } + +} diff --git a/core/tests/Drupal/Tests/Core/Database/InstallerObjectTest.php b/core/tests/Drupal/Tests/Core/Database/InstallerObjectTest.php index 87cf3208a8..2a54aab72f 100644 --- a/core/tests/Drupal/Tests/Core/Database/InstallerObjectTest.php +++ b/core/tests/Drupal/Tests/Core/Database/InstallerObjectTest.php @@ -6,6 +6,7 @@ use Drupal\Core\Database\Driver\mysql\Install\Tasks as MysqlInstallTasks; use Drupal\Driver\Database\fake\Install\Tasks as FakeInstallTasks; use Drupal\Driver\Database\corefake\Install\Tasks as CustomCoreFakeInstallTasks; +use Drupal\driver_test\DatabaseDriver\drivertestmysql\Install\Tasks as DriverTestMysqlInstallTasks; use Drupal\Tests\UnitTestCase; /** @@ -33,14 +34,15 @@ protected function setUp() { $additional_class_loader->addPsr4("Drupal\\Driver\\Database\\fake\\", __DIR__ . "/../../../../../tests/fixtures/database_drivers/custom/fake"); $additional_class_loader->addPsr4("Drupal\\Core\\Database\\Driver\\corefake\\", __DIR__ . "/../../../../../tests/fixtures/database_drivers/core/corefake"); $additional_class_loader->addPsr4("Drupal\\Driver\\Database\\corefake\\", __DIR__ . "/../../../../../tests/fixtures/database_drivers/custom/corefake"); + $additional_class_loader->addPsr4("Drupal\\driver_test\\DatabaseDriver\\drivertestmysql\\", __DIR__ . "/../../../../../../modules/system/tests/modules/driver_test/src/DatabaseDriver/drivertestmysql"); $additional_class_loader->register(TRUE); } /** * @dataProvider providerDbInstallerObject */ - public function testDbInstallerObject($driver, $expected_class_name) { - $object = db_installer_object($driver); + public function testDbInstallerObject($driver, $module, $expected_class_name) { + $object = db_installer_object($driver, $module); $this->assertEquals(get_class($object), $expected_class_name); } @@ -50,18 +52,22 @@ public function testDbInstallerObject($driver, $expected_class_name) { * @return array * Array of arrays with the following elements: * - driver: The driver name. + * - module: The module providing the driver. * - class: The fully qualified class name of the expected install task. */ public function providerDbInstallerObject() { return [ // A driver only in the core namespace. - ['mysql', MysqlInstallTasks::class], + ['mysql', NULL, MysqlInstallTasks::class], // A driver only in the custom namespace. - ['fake', FakeInstallTasks::class], + ['fake', 'fake', FakeInstallTasks::class], // A driver in both namespaces. The custom one takes precedence. - ['corefake', CustomCoreFakeInstallTasks::class], + ['corefake', 'corefake', CustomCoreFakeInstallTasks::class], + + // A driver from a module that has a different name as the driver. + ['drivertestmysql', 'driver_test', DriverTestMysqlInstallTasks::class], ]; } diff --git a/core/tests/Drupal/Tests/Core/Database/UrlConversionTest.php b/core/tests/Drupal/Tests/Core/Database/UrlConversionTest.php index 8f9b9705b0..9a19f38f93 100644 --- a/core/tests/Drupal/Tests/Core/Database/UrlConversionTest.php +++ b/core/tests/Drupal/Tests/Core/Database/UrlConversionTest.php @@ -2,7 +2,6 @@ namespace Drupal\Tests\Core\Database; -use Composer\Autoload\ClassLoader; use Drupal\Core\Database\Database; use Drupal\Tests\UnitTestCase; @@ -26,11 +25,19 @@ class UrlConversionTest extends UnitTestCase { */ protected function setUp() { parent::setUp(); - $additional_class_loader = new ClassLoader(); - $additional_class_loader->addPsr4("Drupal\\Driver\\Database\\fake\\", __DIR__ . "/../../../../../tests/fixtures/database_drivers/custom/fake"); - $additional_class_loader->addPsr4("Drupal\\Core\\Database\\Driver\\corefake\\", __DIR__ . "/../../../../../tests/fixtures/database_drivers/core/corefake"); - $additional_class_loader->addPsr4("Drupal\\Driver\\Database\\corefake\\", __DIR__ . "/../../../../../tests/fixtures/database_drivers/custom/corefake"); - $additional_class_loader->register(TRUE); + $this->root = dirname(dirname(dirname(dirname(dirname(dirname(dirname(__FILE__))))))); + // Mock the container so we don;t need to mock drupal_valid_test_ua(). + // @see \Drupal\Core\Extension\ExtensionDiscovery::scan() + $container = $this->createMock('Symfony\Component\DependencyInjection\ContainerInterface'); + $container->expects($this->any()) + ->method('has') + ->with('kernel') + ->willReturn(TRUE); + $container->expects($this->any()) + ->method('getParameter') + ->with('site.path') + ->willReturn(''); + \Drupal::setContainer($container); } /** @@ -39,7 +46,7 @@ protected function setUp() { * @dataProvider providerConvertDbUrlToConnectionInfo */ public function testDbUrltoConnectionConversion($root, $url, $database_array) { - $result = Database::convertDbUrlToConnectionInfo($url, $root); + $result = Database::convertDbUrlToConnectionInfo($url, $root ?: $this->root); $this->assertEquals($database_array, $result); } @@ -116,30 +123,96 @@ public function providerConvertDbUrlToConnectionInfo() { 'namespace' => 'Drupal\Core\Database\Driver\sqlite', ], ], - 'Fake custom database driver, without prefix' => [ + 'MySQL contrib test driver without prefix' => [ '', - 'fake://fake_user:fake_pass@fake_host:3456/fake_database', + 'drivertestmysql://test_user:test_pass@test_host:3306/test_database?module=driver_test', [ - 'driver' => 'fake', - 'username' => 'fake_user', - 'password' => 'fake_pass', - 'host' => 'fake_host', - 'database' => 'fake_database', - 'port' => 3456, - 'namespace' => 'Drupal\Driver\Database\fake', + 'driver' => 'drivertestmysql', + 'username' => 'test_user', + 'password' => 'test_pass', + 'host' => 'test_host', + 'database' => 'test_database', + 'port' => 3306, + 'namespace' => 'Drupal\driver_test\DatabaseDriver\drivertestmysql', + 'namespace_dir' => 'core/modules/system/tests/modules/driver_test/src/DatabaseDriver/drivertestmysql/', + ], + ], + 'MySQL contrib test driver with prefix' => [ + '', + 'drivertestmysql://test_user:test_pass@test_host:3306/test_database?module=driver_test#bar', + [ + 'driver' => 'drivertestmysql', + 'username' => 'test_user', + 'password' => 'test_pass', + 'host' => 'test_host', + 'database' => 'test_database', + 'prefix' => [ + 'default' => 'bar', + ], + 'port' => 3306, + 'namespace' => 'Drupal\driver_test\DatabaseDriver\drivertestmysql', + 'namespace_dir' => 'core/modules/system/tests/modules/driver_test/src/DatabaseDriver/drivertestmysql/', + ], + ], + 'PostgreSQL contrib test driver without prefix' => [ + '', + 'drivertestpgsql://test_user:test_pass@test_host:5432/test_database?module=driver_test', + [ + 'driver' => 'drivertestpgsql', + 'username' => 'test_user', + 'password' => 'test_pass', + 'host' => 'test_host', + 'database' => 'test_database', + 'port' => 5432, + 'namespace' => 'Drupal\driver_test\DatabaseDriver\drivertestpgsql', + 'namespace_dir' => 'core/modules/system/tests/modules/driver_test/src/DatabaseDriver/drivertestpgsql/', ], ], - 'Fake core driver with custom override, without prefix' => [ + 'PostgreSQL contrib test driver with prefix' => [ '', - 'corefake://fake_user:fake_pass@fake_host:3456/fake_database', + 'drivertestpgsql://test_user:test_pass@test_host:5432/test_database?module=driver_test#bar', [ - 'driver' => 'corefake', - 'username' => 'fake_user', - 'password' => 'fake_pass', - 'host' => 'fake_host', - 'database' => 'fake_database', - 'port' => 3456, - 'namespace' => 'Drupal\Driver\Database\corefake', + 'driver' => 'drivertestpgsql', + 'username' => 'test_user', + 'password' => 'test_pass', + 'host' => 'test_host', + 'database' => 'test_database', + 'prefix' => [ + 'default' => 'bar', + ], + 'port' => 5432, + 'namespace' => 'Drupal\driver_test\DatabaseDriver\drivertestpgsql', + 'namespace_dir' => 'core/modules/system/tests/modules/driver_test/src/DatabaseDriver/drivertestpgsql/', + ], + ], + 'Fakedriver without prefix' => [ + '', + 'fakedriver://test_user:test_pass@test_host/test_database?module=fakedriver', + [ + 'driver' => 'fakedriver', + 'username' => 'test_user', + 'password' => 'test_pass', + 'host' => 'test_host', + 'database' => 'test_database', + 'namespace' => 'Drupal\fakedriver\DatabaseDriver\fakedriver', + 'namespace_dir' => 'core/modules/system/tests/modules/fakedriver/src/DatabaseDriver/fakedriver/', + ], + ], + 'Fakedriver with prefix' => [ + '', + 'fakedriver://test_user:test_pass@test_host:1234/test_database?module=fakedriver#bar', + [ + 'driver' => 'fakedriver', + 'username' => 'test_user', + 'password' => 'test_pass', + 'host' => 'test_host', + 'database' => 'test_database', + 'prefix' => [ + 'default' => 'bar', + ], + 'port' => 1234, + 'namespace' => 'Drupal\fakedriver\DatabaseDriver\fakedriver', + 'namespace_dir' => 'core/modules/system/tests/modules/fakedriver/src/DatabaseDriver/fakedriver/', ], ], ]; @@ -233,11 +306,94 @@ public function providerGetConnectionInfoAsUrl() { ]; $expected_url4 = 'sqlite://localhost/test_database#pre'; + $info5 = [ + 'database' => 'test_database', + 'username' => 'test_user', + 'password' => 'test_pass', + 'prefix' => '', + 'host' => 'test_host', + 'port' => '3306', + 'driver' => 'drivertestmysql', + 'namespace' => 'Drupal\\driver_test\\DatabaseDriver\\drivertestmysql', + 'namespace_dir' => 'core/modules/system/tests/modules/driver_test/src/DatabaseDriver/drivertestmysql/', + ]; + $expected_url5 = 'drivertestmysql://test_user:test_pass@test_host:3306/test_database?module=driver_test'; + + $info6 = [ + 'database' => 'test_database', + 'username' => 'test_user', + 'password' => 'test_pass', + 'prefix' => 'pre', + 'host' => 'test_host', + 'port' => '3306', + 'driver' => 'drivertestmysql', + 'namespace' => 'Drupal\\driver_test\\DatabaseDriver\\drivertestmysql', + 'namespace_dir' => 'core/modules/system/tests/modules/driver_test/src/DatabaseDriver/drivertestmysql/', + ]; + $expected_url6 = 'drivertestmysql://test_user:test_pass@test_host:3306/test_database?module=driver_test#pre'; + + $info7 = [ + 'database' => 'test_database', + 'username' => 'test_user', + 'password' => 'test_pass', + 'prefix' => '', + 'host' => 'test_host', + 'port' => '5432', + 'driver' => 'drivertestpgsql', + 'namespace' => 'Drupal\\driver_test\\DatabaseDriver\\drivertestpgsql', + 'namespace_dir' => 'core/modules/system/tests/modules/driver_test/src/DatabaseDriver/drivertestpqsql/', + ]; + $expected_url7 = 'drivertestpgsql://test_user:test_pass@test_host:5432/test_database?module=driver_test'; + + $info8 = [ + 'database' => 'test_database', + 'username' => 'test_user', + 'password' => 'test_pass', + 'prefix' => 'pre', + 'host' => 'test_host', + 'port' => '5432', + 'driver' => 'drivertestpgsql', + 'namespace' => 'Drupal\\driver_test\\DatabaseDriver\\drivertestpgsql', + 'namespace_dir' => 'core/modules/system/tests/modules/driver_test/src/DatabaseDriver/drivertestpqsql/', + ]; + $expected_url8 = 'drivertestpgsql://test_user:test_pass@test_host:5432/test_database?module=driver_test#pre'; + + $info9 = [ + 'database' => 'test_database', + 'username' => 'test_user', + 'password' => 'test_pass', + 'prefix' => '', + 'host' => 'test_host', + 'driver' => 'fakedriver', + 'namespace' => 'Drupal\\fakedriver\\DatabaseDriver\\fakedriver', + 'namespace_dir' => 'core/modules/system/tests/modules/fakedriver/src/DatabaseDriver/fakedriver/', + ]; + $expected_url9 = 'fakedriver://test_user:test_pass@test_host/test_database?module=fakedriver'; + + $info10 = [ + 'database' => 'test_database', + 'username' => 'test_user', + 'password' => 'test_pass', + 'prefix' => 'pre', + 'host' => 'test_host', + 'port' => '1234', + 'driver' => 'fakedriver', + 'namespace' => 'Drupal\\fakedriver\\DatabaseDriver\\fakedriver', + 'namespace_dir' => 'core/modules/system/tests/modules/fakedriver/src/DatabaseDriver/fakedriver/', + ]; + $expected_url10 = 'fakedriver://test_user:test_pass@test_host:1234/test_database?module=fakedriver#pre'; + return [ [$info1, $expected_url1], [$info2, $expected_url2], [$info3, $expected_url3], [$info4, $expected_url4], + [$info5, $expected_url5], + [$info6, $expected_url6], + [$info7, $expected_url7], + [$info8, $expected_url8], + [$info9, $expected_url9], + [$info10, $expected_url10], ]; } @@ -282,4 +438,14 @@ public function providerInvalidArgumentGetConnectionInfoAsUrl() { ]; } + /** + * @covers ::convertDbUrlToConnectionInfo + */ + public function testDriverModuleDoesNotExist() { + $url = 'mysql://test_user:test_pass@test_host:3306/test_database?module=does_not_exist'; + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage("Cannot find the module 'does_not_exist' for the database driver namespace 'Drupal\does_not_exist\DatabaseDriver\mysql'"); + Database::convertDbUrlToConnectionInfo($url, ''); + } + } diff --git a/core/tests/fixtures/database_drivers/module/corefake/src/DatabaseDriver/corefake/Connection.php b/core/tests/fixtures/database_drivers/module/corefake/src/DatabaseDriver/corefake/Connection.php new file mode 100644 index 0000000000..f99d58564e --- /dev/null +++ b/core/tests/fixtures/database_drivers/module/corefake/src/DatabaseDriver/corefake/Connection.php @@ -0,0 +1,14 @@ + '/path/to/databasefilename', * ]; * @endcode + * + * Sample Database configuration format for a driver in a contributed module: + * @code + * $databases['default']['default'] = [ + * 'driver' => 'mydriver', + * 'database' => 'databasename', + * 'username' => 'sqlusername', + * 'password' => 'sqlpassword', + * 'host' => 'localhost', + * 'prefix' => '', + * 'namespace' => 'Drupal\mymodule\DatabaseDriver\mydriver', + * 'namespace_dir' => 'modules/mymodule/src/DatabaseDriver/mydriver/', + * ]; + * @endcode */ /**