Commit 81165a1b authored by Alex Bronstein's avatar Alex Bronstein
Browse files

Revert "Issue #3120096 by alexpott, daffie, effulgentsia, Neslee Canil Pinto,...

Revert "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"

This reverts commit 8a0856c7.
parent 8a0856c7
Loading
Loading
Loading
Loading
+0 −24
Changes for core/assets/scaffold/files/default.settings.php: 0 added lines, 24 removed lines.
Original line number Diff line number Diff line
@@ -105,16 +105,6 @@
 * 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.
@@ -234,20 +224,6 @@
 *     '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
 */

/**
+1 −6
Changes for core/includes/install.core.inc: 1 added line, 6 removed lines.
Original line number Diff line number Diff line
@@ -375,11 +375,6 @@ 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.
@@ -1212,7 +1207,7 @@ function install_database_errors($database, $settings_file) {
    // calling function.
    Database::addConnectionInfo('default', 'default', $database);

    $errors = db_installer_object($driver, $database['namespace'] ?? NULL)->runTasks();
    $errors = db_installer_object($driver)->runTasks();
  }
  return $errors;
}
+4 −53
Changes for core/includes/install.inc: 4 added lines, 53 removed lines.
Original line number Diff line number Diff line
@@ -171,8 +171,6 @@ 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]);
@@ -181,43 +179,11 @@ function drupal_get_database_types() {
  }
  foreach ($files as $file) {
    if (file_exists($file->uri . '/Install/Tasks.php')) {
      // The namespace doesn't need to be added here, because
      // db_installer_object() will find it.
      $drivers[$file->filename] = NULL;
    }
  }

  // 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);
        }
      $drivers[$file->filename] = $file->uri;
    }
  }
  }

  foreach ($drivers as $driver => $namespace) {
    $installer = db_installer_object($driver, $namespace);
  foreach ($drivers as $driver => $file) {
    $installer = db_installer_object($driver);
    if ($installer->installable()) {
      $databases[$driver] = $installer;
    }
@@ -1203,35 +1169,20 @@ 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, $namespace = NULL) {
function db_installer_object($driver) {
  // 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();
  }
+10 −17
Changes for core/lib/Drupal/Core/Database/Connection.php: 10 added lines, 17 removed lines.
Original line number Diff line number Diff line
@@ -1378,7 +1378,7 @@ abstract public function queryTemporary($query, array $args = [], array $options
   * 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 mysqlMock. This
   * instance, there could be two MySQL drivers, mysql and mysql_mock. This
   * function would return different values for each, but both would return
   * "mysql" for databaseType().
   *
@@ -1572,6 +1572,10 @@ public function __sleep() {
  /**
   * 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
@@ -1585,10 +1589,6 @@ 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) {
@@ -1634,10 +1634,12 @@ public static function createConnectionOptionsFromUrl($url, $root) {
  /**
   * 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. An additional
   *   key of 'module' is added by Database::getConnectionInfoAsUrl() for
   *   drivers provided my contributed or custom modules for convenience.
   *   The array of connection options for a database connection.
   *
   * @return string
   *   The connection info as a URL.
@@ -1646,10 +1648,6 @@ 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) {
@@ -1676,11 +1674,6 @@ public static function createUrlFromConnectionOptions(array $connection_options)

    $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'];
    }
+4 −158
Changes for core/lib/Drupal/Core/Database/Database.php: 4 added lines, 158 removed lines.
Original line number Diff line number Diff line
@@ -2,9 +2,6 @@

namespace Drupal\Core\Database;

use Composer\Autoload\ClassLoader;
use Drupal\Core\Extension\ExtensionDiscovery;

/**
 * Primary front-controller for the database system.
 *
@@ -451,8 +448,6 @@ 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
@@ -462,130 +457,18 @@ public static function convertDbUrlToConnectionInfo($url, $root) {
    }
    $driver = $matches[1];

    // 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';
      }
    }

    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.
    // 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");
      }

    $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;
    return $connection_class::createConnectionOptionsFromUrl($url, $root);
  }

  /**
@@ -605,16 +488,7 @@ public static function getConnectionInfoAsUrl($key = 'default') {
    if (empty($db_info) || empty($db_info['default'])) {
      throw new \RuntimeException("Database connection $key not defined or missing the 'default' settings");
    }
    $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';
    $connection_class = static::getDatabaseDriverNamespace($db_info['default']) . '\\Connection';
    return $connection_class::createUrlFromConnectionOptions($db_info['default']);
  }

@@ -637,32 +511,4 @@ protected static function getDatabaseDriverNamespace(array $connection_info) {
    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);
  }

}
Loading