Issue #3120096 by alexpott, daffie, effulgentsia, Neslee Canil Pinto, xjm, mondrake, catch, ravi.shankar: Support contrib database driver directories in a fixed location in a module
(cherry picked from commitmerge-requests/64/headff6279aa78
) (cherry picked from commit5ca020f139
)
parent
efa1e2aee7
commit
90f5f95851
|
@ -105,6 +105,16 @@ $databases = [];
|
|||
* webserver. For most other drivers, you must specify a
|
||||
* username, password, host, and database name.
|
||||
*
|
||||
* Drupal core implements drivers for mysql, pgsql, and sqlite. Other drivers
|
||||
* can be provided by contributed or custom modules. To use a contributed or
|
||||
* custom driver, the "namespace" property must be set to the namespace of the
|
||||
* driver. The code in this namespace must be autoloadable prior to connecting
|
||||
* to the database, and therefore, prior to when module root namespaces are
|
||||
* added to the autoloader. To add the driver's namespace to the autoloader,
|
||||
* set the "autoload" property to the PSR-4 base directory of the driver's
|
||||
* namespace. This is optional for projects managed with Composer if the
|
||||
* driver's namespace is in Composer's autoloader.
|
||||
*
|
||||
* 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 +234,20 @@ $databases = [];
|
|||
* 'database' => '/path/to/databasefilename',
|
||||
* ];
|
||||
* @endcode
|
||||
*
|
||||
* Sample Database configuration format for a driver in a contributed module:
|
||||
* @code
|
||||
* $databases['default']['default'] = [
|
||||
* 'driver' => 'mydriver',
|
||||
* 'namespace' => 'Drupal\mymodule\Driver\Database\mydriver',
|
||||
* 'autoload' => 'modules/mymodule/src/Driver/Database/mydriver/',
|
||||
* 'database' => 'databasename',
|
||||
* 'username' => 'sqlusername',
|
||||
* 'password' => 'sqlpassword',
|
||||
* 'host' => 'localhost',
|
||||
* 'prefix' => '',
|
||||
* ];
|
||||
* @endcode
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
|
@ -375,6 +375,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.
|
||||
|
@ -1207,7 +1212,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, $database['namespace'] ?? NULL)->runTasks();
|
||||
}
|
||||
return $errors;
|
||||
}
|
||||
|
|
|
@ -171,6 +171,8 @@ function drupal_get_database_types() {
|
|||
|
||||
// The internal database driver name is any valid PHP identifier.
|
||||
$mask = ExtensionDiscovery::PHP_FUNCTION_PATTERN;
|
||||
|
||||
// Find drivers in the Drupal\Core and Drupal\Driver namespaces.
|
||||
/** @var \Drupal\Core\File\FileSystemInterface $file_system */
|
||||
$file_system = \Drupal::service('file_system');
|
||||
$files = $file_system->scanDirectory(DRUPAL_ROOT . '/core/lib/Drupal/Core/Database/Driver', $mask, ['recurse' => FALSE]);
|
||||
|
@ -179,11 +181,43 @@ function drupal_get_database_types() {
|
|||
}
|
||||
foreach ($files as $file) {
|
||||
if (file_exists($file->uri . '/Install/Tasks.php')) {
|
||||
$drivers[$file->filename] = $file->uri;
|
||||
// The namespace doesn't need to be added here, because
|
||||
// db_installer_object() will find it.
|
||||
$drivers[$file->filename] = NULL;
|
||||
}
|
||||
}
|
||||
foreach ($drivers as $driver => $file) {
|
||||
$installer = db_installer_object($driver);
|
||||
|
||||
// Find drivers in Drupal module namespaces.
|
||||
/** @var \Composer\Autoload\ClassLoader $class_loader */
|
||||
$class_loader = \Drupal::service('class_loader');
|
||||
// We cannot use the file cache because it does not always exist.
|
||||
$extension_discovery = new ExtensionDiscovery(DRUPAL_ROOT, FALSE, []);
|
||||
$modules = $extension_discovery->scan('module');
|
||||
foreach ($modules as $module) {
|
||||
$module_driver_path = DRUPAL_ROOT . '/' . $module->getPath() . '/src/Driver/Database';
|
||||
if (is_dir($module_driver_path)) {
|
||||
$driver_files = $file_system->scanDirectory($module_driver_path, $mask, ['recurse' => FALSE]);
|
||||
foreach ($driver_files as $driver_file) {
|
||||
$tasks_file = $module_driver_path . '/' . $driver_file->filename . '/Install/Tasks.php';
|
||||
if (file_exists($tasks_file)) {
|
||||
$namespace = 'Drupal\\' . $module->getName() . '\\Driver\\Database\\' . $driver_file->filename;
|
||||
|
||||
// The namespace needs to be added for db_installer_object() to find
|
||||
// it.
|
||||
$drivers[$driver_file->filename] = $namespace;
|
||||
|
||||
// The directory needs to be added to the autoloader, because this is
|
||||
// early in the installation process: the module hasn't been enabled
|
||||
// yet and the database connection info array (including its 'autoload'
|
||||
// key) hasn't been created yet.
|
||||
$class_loader->addPsr4($namespace . '\\', $module->getPath() . '/src/Driver/Database/' . $driver_file->filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($drivers as $driver => $namespace) {
|
||||
$installer = db_installer_object($driver, $namespace);
|
||||
if ($installer->installable()) {
|
||||
$databases[$driver] = $installer;
|
||||
}
|
||||
|
@ -1169,20 +1203,35 @@ function install_profile_info($profile, $langcode = 'en') {
|
|||
/**
|
||||
* Returns a database installer object.
|
||||
*
|
||||
* Before calling this function it is important the database installer object
|
||||
* is autoloadable. Database drivers provided by contributed modules are added
|
||||
* to the autoloader in drupal_get_database_types() and Settings::initialize().
|
||||
*
|
||||
* @param $driver
|
||||
* The name of the driver.
|
||||
* @param string $namespace
|
||||
* (optional) The database driver namespace.
|
||||
*
|
||||
* @return \Drupal\Core\Database\Install\Tasks
|
||||
* A class defining the requirements and tasks for installing the database.
|
||||
*
|
||||
* @see drupal_get_database_types()
|
||||
* @see \Drupal\Core\Site\Settings::initialize()
|
||||
*/
|
||||
function db_installer_object($driver) {
|
||||
function db_installer_object($driver, $namespace = NULL) {
|
||||
// We cannot use Database::getConnection->getDriverClass() here, because
|
||||
// the connection object is not yet functional.
|
||||
if ($namespace) {
|
||||
$task_class = $namespace . "\\Install\\Tasks";
|
||||
return new $task_class();
|
||||
}
|
||||
// Old Drupal 8 style contrib namespace.
|
||||
$task_class = "Drupal\\Driver\\Database\\{$driver}\\Install\\Tasks";
|
||||
if (class_exists($task_class)) {
|
||||
return new $task_class();
|
||||
}
|
||||
else {
|
||||
// Core provided driver.
|
||||
$task_class = "Drupal\\Core\\Database\\Driver\\{$driver}\\Install\\Tasks";
|
||||
return new $task_class();
|
||||
}
|
||||
|
|
|
@ -1378,7 +1378,7 @@ abstract class Connection {
|
|||
* Returns the type of database driver.
|
||||
*
|
||||
* This is not necessarily the same as the type of the database itself. For
|
||||
* instance, there could be two MySQL drivers, mysql and mysql_mock. This
|
||||
* instance, there could be two MySQL drivers, mysql and mysqlMock. This
|
||||
* function would return different values for each, but both would return
|
||||
* "mysql" for databaseType().
|
||||
*
|
||||
|
@ -1572,10 +1572,6 @@ abstract class Connection {
|
|||
/**
|
||||
* Creates an array of database connection options from a URL.
|
||||
*
|
||||
* @internal
|
||||
* This method should not be called. Use
|
||||
* \Drupal\Core\Database\Database::convertDbUrlToConnectionInfo() instead.
|
||||
*
|
||||
* @param string $url
|
||||
* The URL.
|
||||
* @param string $root
|
||||
|
@ -1589,6 +1585,10 @@ abstract class Connection {
|
|||
* 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) {
|
||||
|
@ -1634,12 +1634,10 @@ abstract class Connection {
|
|||
/**
|
||||
* Creates a URL from an array of database connection options.
|
||||
*
|
||||
* @internal
|
||||
* This method should not be called. Use
|
||||
* \Drupal\Core\Database\Database::getConnectionInfoAsUrl() instead.
|
||||
*
|
||||
* @param array $connection_options
|
||||
* The array of connection options for a database connection.
|
||||
* The array of connection options for a database connection. An additional
|
||||
* key of 'module' is added by Database::getConnectionInfoAsUrl() for
|
||||
* drivers provided my contributed or custom modules for convenience.
|
||||
*
|
||||
* @return string
|
||||
* The connection info as a URL.
|
||||
|
@ -1648,6 +1646,10 @@ abstract class Connection {
|
|||
* 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) {
|
||||
|
@ -1674,6 +1676,11 @@ abstract class Connection {
|
|||
|
||||
$db_url .= '/' . $connection_options['database'];
|
||||
|
||||
// Add the module when the driver is provided by a 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'];
|
||||
}
|
||||
|
|
|
@ -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 @@ abstract class Database {
|
|||
* @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,18 +462,130 @@ abstract class Database {
|
|||
}
|
||||
$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;
|
||||
$connection_class = 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\\Driver\\Database\\$driver";
|
||||
$psr4_base_directory = Database::findDriverAutoloadDirectory($namespace, $root, TRUE);
|
||||
$additional_class_loader = new ClassLoader();
|
||||
$additional_class_loader->addPsr4($namespace . '\\', $psr4_base_directory);
|
||||
$additional_class_loader->register(TRUE);
|
||||
$connection_class = $custom_connection_class = $namespace . '\\Connection';
|
||||
}
|
||||
}
|
||||
|
||||
return $connection_class::createConnectionOptionsFromUrl($url, $root);
|
||||
if (!$module) {
|
||||
// Determine the connection class to use. Discover if the URL has a valid
|
||||
// driver scheme. Try with Drupal 8 style 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");
|
||||
}
|
||||
|
||||
$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($psr4_base_directory)) {
|
||||
$options['autoload'] = $psr4_base_directory;
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the directory to add to the autoloader for the driver's namespace.
|
||||
*
|
||||
* For Drupal sites that manage their codebase with Composer, the package
|
||||
* that provides the database driver should add the driver's namespace to
|
||||
* Composer's autoloader. However, to support sites that add Drupal modules
|
||||
* without Composer, and because the database connection must be established
|
||||
* before Drupal adds the module's entire namespace to the autoloader, the
|
||||
* database connection info array can include an "autoload" key containing
|
||||
* the autoload directory for the driver's namespace. For requests that
|
||||
* connect to the database via a connection info array, the value of the
|
||||
* "autoload" key is automatically added to the autoloader.
|
||||
*
|
||||
* This method can be called to find the default value of that key when the
|
||||
* database connection info array isn't available. This includes:
|
||||
* - Console commands and test runners that connect to a database specified
|
||||
* by a database URL rather than a connection info array.
|
||||
* - During installation, prior to the connection info array being written to
|
||||
* settings.php.
|
||||
*
|
||||
* This method returns the directory that must be added to the autoloader for
|
||||
* the given namespace.
|
||||
* - If the namespace is a sub-namespace of a Drupal module, then this method
|
||||
* returns the autoload directory for that namespace, allowing Drupal
|
||||
* modules containing database drivers to be added to a Drupal website
|
||||
* without Composer.
|
||||
* - If the namespace is a sub-namespace of Drupal\Core or Drupal\Driver,
|
||||
* then this method returns FALSE, because Drupal core's autoloader already
|
||||
* includes these namespaces, so no additional autoload directory is
|
||||
* required for any code within them.
|
||||
* - If the namespace is anything else, then this method returns FALSE,
|
||||
* because neither drupal_get_database_types() nor
|
||||
* static::convertDbUrlToConnectionInfo() support that anyway. One can
|
||||
* manually edit the connection info array in settings.php to reference
|
||||
* any arbitrary namespace, but requests using that would use the
|
||||
* corresponding 'autoload' key in that connection info rather than calling
|
||||
* this method.
|
||||
*
|
||||
* @param string $namespace
|
||||
* The database driver's namespace.
|
||||
* @param string $root
|
||||
* The root directory of the Drupal installation.
|
||||
*
|
||||
* @return string|false
|
||||
* The PSR-4 directory to add to the autoloader for the namespace if the
|
||||
* namespace is a sub-namespace of a Drupal module. FALSE otherwise, as
|
||||
* explained above.
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
* Exception thrown when a module provided database driver does not exist.
|
||||
*/
|
||||
public static function findDriverAutoloadDirectory($namespace, $root) {
|
||||
// As explained by this method's documentation, return FALSE if the
|
||||
// namespace is not a sub-namespace of a Drupal module.
|
||||
if (!static::isWithinModuleNamespace($namespace)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Extract the module information from the namespace.
|
||||
[, $module, $module_relative_namespace] = explode('\\', $namespace, 3);
|
||||
|
||||
// The namespace is within a Drupal module. Find the directory where the
|
||||
// module is located.
|
||||
$extension_discovery = new ExtensionDiscovery($root, FALSE, []);
|
||||
$modules = $extension_discovery->scan('module');
|
||||
if (!isset($modules[$module])) {
|
||||
throw new \RuntimeException(sprintf("Cannot find the module '%s' for the database driver namespace '%s'", $module, $namespace));
|
||||
}
|
||||
$module_directory = $modules[$module]->getPath();
|
||||
|
||||
// All code within the Drupal\MODULE namespace is expected to follow a
|
||||
// PSR-4 layout within the module's "src" directory.
|
||||
$driver_directory = $module_directory . '/src/' . str_replace('\\', '/', $module_relative_namespace) . '/';
|
||||
if (!is_dir($root . '/' . $driver_directory)) {
|
||||
throw new \RuntimeException(sprintf("Cannot find the database driver namespace '%s' in module '%s'", $namespace, $module));
|
||||
}
|
||||
return $driver_directory;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -488,7 +605,16 @@ abstract class Database {
|
|||
if (empty($db_info) || empty($db_info['default'])) {
|
||||
throw new \RuntimeException("Database connection $key not defined or missing the 'default' settings");
|
||||
}
|
||||
$connection_class = static::getDatabaseDriverNamespace($db_info['default']) . '\\Connection';
|
||||
$namespace = static::getDatabaseDriverNamespace($db_info['default']);
|
||||
|
||||
// If the driver namespace is within a Drupal module, add the module name
|
||||
// to the connection options to make it easy for the connection class's
|
||||
// createUrlFromConnectionOptions() method to add it to the URL.
|
||||
if (static::isWithinModuleNamespace($namespace)) {
|
||||
$db_info['default']['module'] = explode('\\', $namespace)[1];
|
||||
}
|
||||
|
||||
$connection_class = $namespace . '\\Connection';
|
||||
return $connection_class::createUrlFromConnectionOptions($db_info['default']);
|
||||
}
|
||||
|
||||
|
@ -511,4 +637,32 @@ abstract class Database {
|
|||
return 'Drupal\\Core\\Database\\Driver\\' . $connection_info['driver'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a namespace is within the namespace of a Drupal module.
|
||||
*
|
||||
* This can be used to determine if a database driver's namespace is provided
|
||||
* by a Drupal module.
|
||||
*
|
||||
* @param string $namespace
|
||||
* The namespace (for example, of a database driver) to check.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the passed in namespace is a sub-namespace of a Drupal module's
|
||||
* namespace.
|
||||
*
|
||||
* @todo https://www.drupal.org/project/drupal/issues/3125476 Remove if we
|
||||
* add this to the extension API or if
|
||||
* \Drupal\Core\Database\Database::getConnectionInfoAsUrl() is removed.
|
||||
*/
|
||||
private static function isWithinModuleNamespace(string $namespace) {
|
||||
[$first, $second] = explode('\\', $namespace, 3);
|
||||
|
||||
// The namespace for Drupal modules is Drupal\MODULE_NAME, and the module
|
||||
// name must be all lowercase. Second-level namespaces containing uppercase
|
||||
// letters (e.g., "Core", "Component", "Driver") are not modules.
|
||||
// @see \Drupal\Core\DrupalKernel::getModuleNamespacesPsr4()
|
||||
// @see https://www.drupal.org/docs/8/creating-custom-modules/naming-and-placing-your-drupal-8-module#s-name-your-module
|
||||
return ($first === 'Drupal' && strtolower($second) === $second);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -202,6 +202,13 @@ abstract class Tasks {
|
|||
* The options form array.
|
||||
*/
|
||||
public function getFormOptions(array $database) {
|
||||
// Use reflection to determine the driver name.
|
||||
// @todo https:///www.drupal.org/node/3123240 Provide a better way to get
|
||||
// the driver name.
|
||||
$reflection = new \ReflectionClass($this);
|
||||
$dir_parts = explode(DIRECTORY_SEPARATOR, dirname(dirname($reflection->getFileName())));
|
||||
$driver = array_pop($dir_parts);
|
||||
|
||||
$form['database'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Database name'),
|
||||
|
@ -210,7 +217,7 @@ abstract class Tasks {
|
|||
'#required' => TRUE,
|
||||
'#states' => [
|
||||
'required' => [
|
||||
':input[name=driver]' => ['value' => $this->pdoDriver],
|
||||
':input[name=driver]' => ['value' => $driver],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
@ -223,7 +230,7 @@ abstract class Tasks {
|
|||
'#required' => TRUE,
|
||||
'#states' => [
|
||||
'required' => [
|
||||
':input[name=driver]' => ['value' => $this->pdoDriver],
|
||||
':input[name=driver]' => ['value' => $driver],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
|
|
@ -160,6 +160,10 @@ class SiteSettingsForm extends FormBase {
|
|||
// Cut the trailing \Install from namespace.
|
||||
$database['namespace'] = substr($install_namespace, 0, strrpos($install_namespace, '\\'));
|
||||
$database['driver'] = $driver;
|
||||
// See default.settings.php for an explanation of the 'autoload' key.
|
||||
if ($autoload = Database::findDriverAutoloadDirectory($database['namespace'], DRUPAL_ROOT)) {
|
||||
$database['autoload'] = $autoload;
|
||||
}
|
||||
|
||||
$form_state->set('database', $database);
|
||||
foreach ($this->getDatabaseErrors($database, $form_state->getValue('settings_file')) as $name => $message) {
|
||||
|
|
|
@ -125,8 +125,21 @@ final class Settings {
|
|||
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 driver is provided by a module, then its code may
|
||||
// need to be instantiated prior to when the module's root namespace
|
||||
// is added to the autoloader, because that happens during service
|
||||
// container initialization but the container definition is likely in
|
||||
// the database. Therefore, allow the connection info to specify an
|
||||
// autoload directory for the driver.
|
||||
if (isset($info['autoload'])) {
|
||||
$class_loader->addPsr4($info['namespace'] . '\\', $info['autoload']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For BC ensure the $config_directories global is set both in the global
|
||||
// and settings.
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\database_statement_monitoring_test\mysql\Install;
|
||||
|
||||
use Drupal\Core\Database\Driver\mysql\Install\Tasks as BaseTasks;
|
||||
|
||||
class Tasks extends BaseTasks {
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\database_statement_monitoring_test\pgsql\Install;
|
||||
|
||||
use Drupal\Core\Database\Driver\pgsql\Install\Tasks as BaseTasks;
|
||||
|
||||
class Tasks extends BaseTasks {
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\database_statement_monitoring_test\sqlite\Install;
|
||||
|
||||
use Drupal\Core\Database\Driver\sqlite\Install\Tasks as BaseTasks;
|
||||
|
||||
class Tasks extends BaseTasks {
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
name: 'Contrib database driver test'
|
||||
type: module
|
||||
description: 'Support database contrib driver testing.'
|
||||
package: Testing
|
||||
version: VERSION
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\driver_test\Driver\Database\DrivertestMysql;
|
||||
|
||||
use Drupal\Core\Database\Driver\mysql\Connection as CoreConnection;
|
||||
|
||||
/**
|
||||
* MySQL test implementation of \Drupal\Core\Database\Connection.
|
||||
*/
|
||||
class Connection extends CoreConnection {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function driver() {
|
||||
return 'DrivertestMysql';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\driver_test\Driver\Database\DrivertestMysql;
|
||||
|
||||
use Drupal\Core\Database\Driver\mysql\Delete as CoreDelete;
|
||||
|
||||
/**
|
||||
* MySQL test implementation of \Drupal\Core\Database\Query\Delete.
|
||||
*/
|
||||
class Delete extends CoreDelete {}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\driver_test\Driver\Database\DrivertestMysql;
|
||||
|
||||
use Drupal\Core\Database\Driver\mysql\Insert as CoreInsert;
|
||||
|
||||
/**
|
||||
* MySQL test implementation of \Drupal\Core\Database\Query\Insert.
|
||||
*/
|
||||
class Insert extends CoreInsert {}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\driver_test\Driver\Database\DrivertestMysql\Install;
|
||||
|
||||
use Drupal\Core\Database\Driver\mysql\Install\Tasks as CoreTasks;
|
||||
|
||||
/**
|
||||
* Specifies installation tasks for MySQL test databases.
|
||||
*/
|
||||
class Tasks extends CoreTasks {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function name() {
|
||||
return t('MySQL by the driver_test module');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\driver_test\Driver\Database\DrivertestMysql;
|
||||
|
||||
use Drupal\Core\Database\Driver\mysql\Merge as CoreMerge;
|
||||
|
||||
/**
|
||||
* MySQL test implementation of \Drupal\Core\Database\Query\Merge.
|
||||
*/
|
||||
class Merge extends CoreMerge {}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\driver_test\Driver\Database\DrivertestMysql;
|
||||
|
||||
use Drupal\Core\Database\Driver\mysql\Schema as CoreSchema;
|
||||
|
||||
/**
|
||||
* MySQL test implementation of \Drupal\Core\Database\Schema.
|
||||
*/
|
||||
class Schema extends CoreSchema {}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\driver_test\Driver\Database\DrivertestMysql;
|
||||
|
||||
use Drupal\Core\Database\Driver\mysql\Select as CoreSelect;
|
||||
|
||||
/**
|
||||
* MySQL test implementation of \Drupal\Core\Database\Query\Select.
|
||||
*/
|
||||
class Select extends CoreSelect {}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\driver_test\Driver\Database\DrivertestMysql;
|
||||
|
||||
use Drupal\Core\Database\Driver\mysql\Transaction as CoreTransaction;
|
||||
|
||||
/**
|
||||
* MySQL test implementation of \Drupal\Core\Database\Transaction.
|
||||
*/
|
||||
class Transaction extends CoreTransaction {}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\driver_test\Driver\Database\DrivertestMysql;
|
||||
|
||||
use Drupal\Core\Database\Driver\mysql\Truncate as CoreTruncate;
|
||||
|
||||
/**
|
||||
* MySQL test implementation of \Drupal\Core\Database\Query\Truncate.
|
||||
*/
|
||||
class Truncate extends CoreTruncate {}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\driver_test\Driver\Database\DrivertestMysql;
|
||||
|
||||
use Drupal\Core\Database\Driver\mysql\Update as CoreUpdate;
|
||||
|
||||
/**
|
||||
* MySQL test implementation of \Drupal\Core\Database\Query\Update.
|
||||
*/
|
||||
class Update extends CoreUpdate {}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\driver_test\Driver\Database\DrivertestMysql;
|
||||
|
||||
use Drupal\Core\Database\Driver\mysql\Upsert as CoreUpsert;
|
||||
|
||||
/**
|
||||
* MySQL test implementation of \Drupal\Core\Database\Query\Upsert.
|
||||
*/
|
||||
class Upsert extends CoreUpsert {}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\driver_test\Driver\Database\DrivertestPgsql;
|
||||
|
||||
use Drupal\Core\Database\Driver\pgsql\Connection as CoreConnection;
|
||||
|
||||
/**
|
||||
* PostgreSQL implementation of \Drupal\Core\Database\Connection.
|
||||
*/
|
||||
class Connection extends CoreConnection {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function driver() {
|
||||
return 'DrivertestPgsql';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\driver_test\Driver\Database\DrivertestPgsql;
|
||||
|
||||
use Drupal\Core\Database\Driver\pgsql\Delete as CoreDelete;
|
||||
|
||||
/**
|
||||
* PostgreSQL implementation of \Drupal\Core\Database\Query\Delete.
|
||||
*/
|
||||
class Delete extends CoreDelete {}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\driver_test\Driver\Database\DrivertestPgsql;
|
||||
|
||||
use Drupal\Core\Database\Driver\pgsql\Insert as CoreInsert;
|
||||
|
||||
/**
|
||||
* PostgreSQL implementation of \Drupal\Core\Database\Query\Insert.
|
||||
*/
|
||||
class Insert extends CoreInsert {}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\driver_test\Driver\Database\DrivertestPgsql\Install;
|
||||
|
||||
use Drupal\Core\Database\Driver\pgsql\Install\Tasks as CoreTasks;
|
||||
|
||||
/**
|
||||
* Specifies installation tasks for PostgreSQL databases.
|
||||
*/
|
||||
class Tasks extends CoreTasks {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function name() {
|
||||
return t('PostgreSQL by the driver_test module');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\driver_test\Driver\Database\DrivertestPgsql;
|
||||
|
||||
use Drupal\Core\Database\Driver\pgsql\Merge as CoreMerge;
|
||||
|
||||
/**
|
||||
* PostgreSQL implementation of \Drupal\Core\Database\Query\Merge.
|
||||
*/
|
||||
class Merge extends CoreMerge {}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\driver_test\Driver\Database\DrivertestPgsql;
|
||||
|
||||
use Drupal\Core\Database\Driver\pgsql\NativeUpsert as CoreNativeUpsert;
|
||||
|
||||
/**
|
||||
* PostgreSQL implementation of native \Drupal\Core\Database\Query\Upsert.
|
||||
*/
|
||||
class NativeUpsert extends CoreNativeUpsert {}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\driver_test\Driver\Database\DrivertestPgsql;
|
||||
|
||||
use Drupal\Core\Database\Driver\pgsql\Schema as CoreSchema;
|
||||
|
||||
/**
|
||||
* PostgreSQL implementation of \Drupal\Core\Database\Schema.
|
||||
*/
|
||||
class Schema extends CoreSchema {}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\driver_test\Driver\Database\DrivertestPgsql;
|
||||
|
||||
use Drupal\Core\Database\Driver\pgsql\Select as CoreSelect;
|
||||
|
||||
/**
|
||||
* PostgreSQL implementation of \Drupal\Core\Database\Query\Select.
|
||||
*/
|
||||
class Select extends CoreSelect {}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\driver_test\Driver\Database\DrivertestPgsql;
|
||||
|
||||
use Drupal\Core\Database\Driver\pgsql\Transaction as CoreTransaction;
|
||||
|
||||
/**
|
||||
* PostgreSQL implementation of \Drupal\Core\Database\Transaction.
|
||||
*/
|
||||
class Transaction extends CoreTransaction {}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\driver_test\Driver\Database\DrivertestPgsql;
|
||||
|
||||
use Drupal\Core\Database\Driver\pgsql\Truncate as CoreTruncate;
|
||||
|
||||
/**
|
||||
* PostgreSQL implementation of \Drupal\Core\Database\Query\Truncate.
|
||||
*/
|
||||
class Truncate extends CoreTruncate {}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\driver_test\Driver\Database\DrivertestPgsql;
|
||||
|
||||
use Drupal\Core\Database\Driver\pgsql\Update as CoreUpdate;
|
||||
|
||||
/**
|
||||
* PostgreSQL implementation of \Drupal\Core\Database\Query\Update.
|
||||
*/
|
||||
class Update extends CoreUpdate {}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\driver_test\Driver\Database\DrivertestPgsql;
|
||||
|
||||
use Drupal\Core\Database\Driver\pgsql\Upsert as CoreUpsert;
|
||||
|
||||
/**
|
||||
* PostgreSQL implementation of \Drupal\Core\Database\Query\Upsert.
|
||||
*/
|
||||
class Upsert extends CoreUpsert {}
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\FunctionalTests\Installer;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
|
||||
/**
|
||||
* Tests the interactive installer.
|
||||
*
|
||||
* @group Installer
|
||||
*/
|
||||
class InstallerNonDefaultDatabaseDriverTest extends InstallerTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The name of the test database driver in use.
|
||||
* @var string
|
||||
*/
|
||||
protected $testDriverName;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpSettings() {
|
||||
$driver = Database::getConnection()->driver();
|
||||
if (!in_array($driver, ['mysql', 'pgsql'])) {
|
||||
$this->markTestSkipped("This test does not support the {$driver} database driver.");
|
||||
}
|
||||
$this->testDriverName = 'Drivertest' . ucfirst($driver);
|
||||
|
||||
// Assert that we are using the database drivers from the driver_test module.
|
||||
$elements = $this->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'] = $this->testDriverName;
|
||||
$settings[$this->testDriverName] = $settings[$driver];
|
||||
unset($settings[$driver]);
|
||||
$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.
|
||||
$contents = file_get_contents($this->root . '/' . $this->siteDirectory . '/settings.php');
|
||||
$this->assertContains("'namespace' => 'Drupal\\\\driver_test\\\\Driver\\\\Database\\\\{$this->testDriverName}',", $contents);
|
||||
$this->assertContains("'driver' => '{$this->testDriverName}',", $contents);
|
||||
$this->assertContains("'autoload' => 'core/modules/system/tests/modules/driver_test/src/Driver/Database/{$this->testDriverName}/',", $contents);
|
||||
}
|
||||
|
||||
}
|
|
@ -75,6 +75,13 @@ class InstallerTest extends InstallerTestBase {
|
|||
// 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();
|
||||
}
|
||||
|
||||
|
|
|
@ -312,8 +312,10 @@ abstract class UpdatePathTestBase extends BrowserTestBase {
|
|||
\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'];
|
||||
$namespace = $connection_info['default']['namespace'] ?? NULL;
|
||||
$errors = db_installer_object($driver, $namespace)->runTasks();
|
||||
if (!empty($errors)) {
|
||||
$this->fail('Failed to run installer database tasks: ' . implode(', ', $errors));
|
||||
}
|
||||
|
|
|
@ -366,8 +366,10 @@ abstract class KernelTestBase extends TestCase implements ServiceProviderInterfa
|
|||
|
||||
// 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'];
|
||||
$namespace = $connection_info['default']['namespace'] ?? NULL;
|
||||
$errors = db_installer_object($driver, $namespace)->runTasks();
|
||||
if (!empty($errors)) {
|
||||
$this->fail('Failed to run installer database tasks: ' . implode(', ', $errors));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Core\Database;
|
||||
|
||||
use Composer\Autoload\ClassLoader;
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Core\Database\Database
|
||||
*
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class DatabaseTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* A classloader to enable testing of contrib drivers.
|
||||
*
|
||||
* @var \Composer\Autoload\ClassLoader
|
||||
*/
|
||||
protected $additionalClassloader;
|
||||
|
||||
/**
|
||||
* Path to DRUPAL_ROOT.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $root;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
$this->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('get')
|
||||
->with('site.path')
|
||||
->willReturn('');
|
||||
\Drupal::setContainer($container);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::findDriverAutoloadDirectory
|
||||
* @dataProvider providerFindDriverAutoloadDirectory
|
||||
*/
|
||||
public function testFindDriverAutoloadDirectory($expected, $namespace) {
|
||||
new Settings(['extension_discovery_scan_tests' => TRUE]);
|
||||
// The only module that provides a driver in core is a test module.
|
||||
$this->assertSame($expected, Database::findDriverAutoloadDirectory($namespace, $this->root));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for ::testFindDriverAutoloadDirectory().
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function providerFindDriverAutoloadDirectory() {
|
||||
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/Driver/Database/DrivertestMysql/', 'Drupal\driver_test\Driver\Database\DrivertestMysql'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::findDriverAutoloadDirectory
|
||||
* @dataProvider providerFindDriverAutoloadDirectoryException
|
||||
*/
|
||||
public function testFindDriverAutoloadDirectoryException($expected_message, $namespace, $include_tests) {
|
||||
new Settings(['extension_discovery_scan_tests' => $include_tests]);
|
||||
if ($include_tests === FALSE) {
|
||||
// \Drupal\Core\Extension\ExtensionDiscovery::scan() needs
|
||||
// drupal_valid_test_ua().
|
||||
include $this->root . '/core/includes/bootstrap.inc';
|
||||
}
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$this->expectExceptionMessage($expected_message);
|
||||
Database::findDriverAutoloadDirectory($namespace, $this->root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for ::testFindDriverAutoloadDirectoryException().
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function providerFindDriverAutoloadDirectoryException() {
|
||||
return [
|
||||
'test module but tests not included' => ["Cannot find the module 'driver_test' for the database driver namespace 'Drupal\driver_test\Driver\Database\DrivertestMysql'", 'Drupal\driver_test\Driver\Database\DrivertestMysql', FALSE],
|
||||
'non-existent driver in test module' => ["Cannot find the database driver namespace 'Drupal\driver_test\Driver\Database\sqlite' in module 'driver_test'", 'Drupal\driver_test\Driver\Database\sqlite', TRUE],
|
||||
'non-existent module' => ["Cannot find the module 'does_not_exist' for the database driver namespace 'Drupal\does_not_exist\Driver\Database\mysql'", 'Drupal\does_not_exist\Driver\Database\mysql', TRUE],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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\\Driver\\Database\\DrivertestMysql\\", __DIR__ . "/../../../../../modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestMysql");
|
||||
$this->additionalClassloader->addPsr4("Drupal\\corefake\\Driver\\Database\\corefake\\", __DIR__ . "/../../../../../tests/fixtures/database_drivers/module/corefake/src/Driver/Database/corefake");
|
||||
}
|
||||
|
||||
}
|
|
@ -6,6 +6,7 @@ use Composer\Autoload\ClassLoader;
|
|||
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\Driver\Database\DrivertestMysql\Install\Tasks as DriverTestMysqlInstallTasks;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
|
@ -33,14 +34,15 @@ class InstallerObjectTest extends UnitTestCase {
|
|||
$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\\Driver\\Database\\DrivertestMysql\\", __DIR__ . "/../../../../../../modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestMysql");
|
||||
$additional_class_loader->register(TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerDbInstallerObject
|
||||
*/
|
||||
public function testDbInstallerObject($driver, $expected_class_name) {
|
||||
$object = db_installer_object($driver);
|
||||
public function testDbInstallerObject($driver, $namespace, $expected_class_name) {
|
||||
$object = db_installer_object($driver, $namespace);
|
||||
$this->assertEquals(get_class($object), $expected_class_name);
|
||||
}
|
||||
|
||||
|
@ -50,18 +52,22 @@ class InstallerObjectTest extends UnitTestCase {
|
|||
* @return array
|
||||
* Array of arrays with the following elements:
|
||||
* - driver: The driver name.
|
||||
* - namespace: The namespace 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', "Drupal\\Driver\\Database\\fake", FakeInstallTasks::class],
|
||||
|
||||
// A driver in both namespaces. The custom one takes precedence.
|
||||
['corefake', CustomCoreFakeInstallTasks::class],
|
||||
['corefake', "Drupal\\Driver\\Database\\corefake", CustomCoreFakeInstallTasks::class],
|
||||
|
||||
// A driver from a module that has a different name as the driver.
|
||||
['DrivertestMysql', "Drupal\\driver_test\\Driver\\Database\\DrivertestMysql", DriverTestMysqlInstallTasks::class],
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
namespace Drupal\Tests\Core\Database;
|
||||
|
||||
use Composer\Autoload\ClassLoader;
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
|
@ -26,11 +26,21 @@ 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('get')
|
||||
->with('site.path')
|
||||
->willReturn('');
|
||||
\Drupal::setContainer($container);
|
||||
|
||||
new Settings(['extension_discovery_scan_tests' => TRUE]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -39,7 +49,7 @@ class UrlConversionTest extends UnitTestCase {
|
|||
* @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 +126,66 @@ class UrlConversionTest extends UnitTestCase {
|
|||
'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\Driver\Database\DrivertestMysql',
|
||||
'autoload' => 'core/modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestMysql/',
|
||||
],
|
||||
],
|
||||
'Fake core driver with custom override, without prefix' => [
|
||||
'MySQL contrib test driver with prefix' => [
|
||||
'',
|
||||
'corefake://fake_user:fake_pass@fake_host:3456/fake_database',
|
||||
'DrivertestMysql://test_user:test_pass@test_host:3306/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' => 'DrivertestMysql',
|
||||
'username' => 'test_user',
|
||||
'password' => 'test_pass',
|
||||
'host' => 'test_host',
|
||||
'database' => 'test_database',
|
||||
'prefix' => [
|
||||
'default' => 'bar',
|
||||
],
|
||||
'port' => 3306,
|
||||
'namespace' => 'Drupal\driver_test\Driver\Database\DrivertestMysql',
|
||||
'autoload' => 'core/modules/system/tests/modules/driver_test/src/Driver/Database/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\Driver\Database\DrivertestPgsql',
|
||||
'autoload' => 'core/modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestPgsql/',
|
||||
],
|
||||
],
|
||||
'PostgreSQL contrib test driver with prefix' => [
|
||||
'',
|
||||
'DrivertestPgsql://test_user:test_pass@test_host:5432/test_database?module=driver_test#bar',
|
||||
[
|
||||
'driver' => 'DrivertestPgsql',
|
||||
'username' => 'test_user',
|
||||
'password' => 'test_pass',
|
||||
'host' => 'test_host',
|
||||
'database' => 'test_database',
|
||||
'prefix' => [
|
||||
'default' => 'bar',
|
||||
],
|
||||
'port' => 5432,
|
||||
'namespace' => 'Drupal\driver_test\Driver\Database\DrivertestPgsql',
|
||||
'autoload' => 'core/modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestPgsql/',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
@ -233,11 +279,67 @@ class UrlConversionTest extends UnitTestCase {
|
|||
];
|
||||
$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\\Driver\\Database\\DrivertestMysql',
|
||||
'autoload' => 'core/modules/system/tests/modules/driver_test/src/Driver/Database/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\\Driver\\Database\\DrivertestMysql',
|
||||
'autoload' => 'core/modules/system/tests/modules/driver_test/src/Driver/Database/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\\Driver\\Database\\DrivertestPgsql',
|
||||
'autoload' => 'core/modules/system/tests/modules/driver_test/src/Driver/Database/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\\Driver\\Database\\DrivertestPgsql',
|
||||
'autoload' => 'core/modules/system/tests/modules/driver_test/src/Driver/Database/drivertestpqsql/',
|
||||
];
|
||||
$expected_url8 = 'DrivertestPgsql://test_user:test_pass@test_host:5432/test_database?module=driver_test#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],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -282,4 +384,24 @@ class UrlConversionTest extends UnitTestCase {
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @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\Driver\Database\mysql'");
|
||||
Database::convertDbUrlToConnectionInfo($url, $this->root);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::convertDbUrlToConnectionInfo
|
||||
*/
|
||||
public function testModuleDriverDoesNotExist() {
|
||||
$url = 'mysql://test_user:test_pass@test_host:3306/test_database?module=driver_test';
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$this->expectExceptionMessage("Cannot find the database driver namespace 'Drupal\driver_test\Driver\Database\mysql' in module 'driver_test'");
|
||||
Database::convertDbUrlToConnectionInfo($url, $this->root);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
14
core/tests/fixtures/database_drivers/module/corefake/src/Driver/Database/corefake/Connection.php
vendored
Normal file
14
core/tests/fixtures/database_drivers/module/corefake/src/Driver/Database/corefake/Connection.php
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\corefake\Driver\Database\corefake;
|
||||
|
||||
use Drupal\Driver\Database\fake\Connection as BaseConnection;
|
||||
|
||||
class Connection extends BaseConnection {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public $driver = 'corefake';
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\corefake\Driver\Database\corefake\Install;
|
||||
|
||||
use Drupal\Core\Database\Install\Tasks as InstallTasks;
|
||||
|
||||
class Tasks extends InstallTasks {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function name() {
|
||||
return 'corefake';
|
||||
}
|
||||
|
||||
}
|
|
@ -105,6 +105,16 @@ $databases = [];
|
|||
* webserver. For most other drivers, you must specify a
|
||||
* username, password, host, and database name.
|
||||
*
|
||||
* Drupal core implements drivers for mysql, pgsql, and sqlite. Other drivers
|
||||
* can be provided by contributed or custom modules. To use a contributed or
|
||||
* custom driver, the "namespace" property must be set to the namespace of the
|
||||
* driver. The code in this namespace must be autoloadable prior to connecting
|
||||
* to the database, and therefore, prior to when module root namespaces are
|
||||
* added to the autoloader. To add the driver's namespace to the autoloader,
|
||||
* set the "autoload" property to the PSR-4 base directory of the driver's
|
||||
* namespace. This is optional for projects managed with Composer if the
|
||||
* driver's namespace is in Composer's autoloader.
|
||||
*
|
||||
* 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 +234,20 @@ $databases = [];
|
|||
* 'database' => '/path/to/databasefilename',
|
||||
* ];
|
||||
* @endcode
|
||||
*
|
||||
* Sample Database configuration format for a driver in a contributed module:
|
||||
* @code
|
||||
* $databases['default']['default'] = [
|
||||
* 'driver' => 'mydriver',
|
||||
* 'namespace' => 'Drupal\mymodule\Driver\Database\mydriver',
|
||||
* 'autoload' => 'modules/mymodule/src/Driver/Database/mydriver/',
|
||||
* 'database' => 'databasename',
|
||||
* 'username' => 'sqlusername',
|
||||
* 'password' => 'sqlpassword',
|
||||
* 'host' => 'localhost',
|
||||
* 'prefix' => '',
|
||||
* ];
|
||||
* @endcode
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue