Problem/Motivation

In Drupal 9.3, it was possible to do the following within settings.php:

$databases['default']['default'] = [
...
];
Database::setMultipleConnectionInfo($databases);
try {
  Database::getConnection();
}
catch (Exception $e) {
  // Do something
}

In Drupal 9.4, this now fails because the database driver is in core/modules/$driver, and that's not in the autoloader until after settings.php completes.

This can be worked around by adding $class_loader->addPsr4('Drupal\\mysql\\', 'core/modules/mysql/src/') prior to calling Database::getConnection();, but for sites with settings.php files that don't do that yet, this is a regression.

Acquia Cloud is one example where code like the above in settings.php (or rather, a .inc file included by settings.php) is done. I don't know if other Drupal hosting platforms employ similar constructs.

Steps to reproduce

Proposed resolution

Given that a workaround is possible, I don't know if we want to address this issue in core. If we do, then perhaps something like moving the current Settings::initialize() code for setting up the autoloader into Database::getConnection() might solve it? Maybe other ideas?

Remaining tasks

This is postponed on #3292014: Make Drupal's primary class loader available to code that runs before the container is initialized

User interface changes

API changes

Data model changes

Release notes snippet

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

effulgentsia created an issue. See original summary.

xjm’s picture

Issue tags: +9.4.0 update

 

effulgentsia’s picture

Issue summary: View changes
effulgentsia’s picture

Status: Active » Needs review
FileSize
6.42 KB

Maybe something like this?

effulgentsia’s picture

With a code comment.

catch’s picture

perhaps something like moving the current Settings::initialize() code for setting up the autoloader into Database::getConnection() might solve it?

I didn't like the sound of this when I read the sentence, but the patch itself looks pretty good to me.

The last submitted patch, 4: 3290924-4.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 5: 3290924-5.patch, failed testing. View results

effulgentsia’s picture

Status: Needs work » Needs review
FileSize
6.75 KB
720 bytes
effulgentsia’s picture

#9 would still require people who are affected by this issue to change their settings.php files to pass in $class_loader when calling setMultipleConnectionInfo().

Here's a patch that fixes this issue without requiring any modifications to existing settings.php files, by defaulting to registering an additional class loader when $class_loader isn't passed in.

We already have precedent for doing this in convertDbUrlToConnectionInfo(). There that was deemed okay because that's for test runners and console commands and not part of the typical page request. Doing it here, however, puts it in the typical page request, but only for the cases where settings.php itself needs to open a database connection and doesn't itself pass in $class_loader.

If we really don't like the idea of using an additional class loader, even if limited to only a subset of sites, then I wonder if it's worth adding something like \Drupal::(set|get)ClassLoader() methods, so that we can make the class loader that's in Settings::initialize() available to code that gets called indirectly by settings.php or other early bootstrap code.

Status: Needs review » Needs work

The last submitted patch, 10: 3290924-10.patch, failed testing. View results

effulgentsia’s picture

This doesn't yet fix the failure in #10, but meanwhile, here's a test-only patch that demonstrates the bug, and the full patch is just #10 plus this test.

The last submitted patch, 12: 3290924-12-test-only.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 12: 3290924-12.patch, failed testing. View results

effulgentsia’s picture

This fixes both failures. Since this updates the test as well, uploading the updated test-only patch too.

If we really don't like the idea of using an additional class loader, even if limited to only a subset of sites, then I wonder if it's worth adding something like \Drupal::(set|get)ClassLoader() methods

I'm going to explore this next.

Status: Needs review » Needs work

The last submitted patch, 15: 3290924-15.patch, failed testing. View results

effulgentsia’s picture

Here's an attempt to make Drupal's primary class loader available as a psuedo-service (\Drupal::classLoader()) during the entirety of the request, at least for normal requests that go through DrupalKernel. Test runners might still need to fuss with it, as this patch does in SettingsTest.

daffie’s picture

The patch from comment #15 looks very good to me. Just 2 nitpicks:

  1. +++ b/core/lib/Drupal/Core/Database/Database.php
    @@ -324,11 +376,17 @@ final public static function getAllConnectionInfo() {
    +    if (!$class_loader) {
    +      $class_loader = self::getClassLoader();
    +    }
    

    We are already doing this in addConnectionInfo(), do we remove it here?

  2. +++ b/core/lib/Drupal/Core/Database/Database.php
    @@ -701,4 +759,23 @@ private static function isWithinModuleNamespace(string $namespace) {
    +    $additional_class_loader = new ClassLoader();
    +    $additional_class_loader->register(TRUE);
    

    The function Database::convertDbUrlToConnectionInfo() also has the same code. Can we replace that code with self::getClassLoader()?

The patch from comment #17 looks to me like as we should do the class loader stuff in a saperate issue and after that is done, fix the problem from this issue. Also there are a couple more instances in DRUPAL_ROOT\core\lib where we have code like new ClassLoader().

@effulgentsia: As you are working on this issue and you are core framework manager, I leave it up to you which route we should to take (the patch from comment #15 or the one from comment #17). My preference is the one from comment #15.

effulgentsia’s picture

Status: Needs review » Postponed

Thanks, @daffie! Good point about spinning off a separate issue for \Drupal::classLoader(). I'll do that this week. In the meantime, I think it'd like to postpone this on that. My concern with #15 is that it creates a class loader that's separate from Drupal's primary one. I think that's messy. For example, if there's code later in Drupal's request that does a Drupal::service('class_loader')->unregister(), it should expect all of Drupal's class loading to be unregistered, not for there to be another autoloader that's still running. Also, if settings.php decorates $class_loader, with say something that does logging, then that logging should work on database driver classes too.

I realize that we have a few other places where we have an additional class loader already, such as for finishing a PHP request after enabling a new module, or in Database::convertDbUrlToConnectionInfo(), but those are special code paths that don't typically run in production. I'm hesitant to extend the pattern to a typical Drupal page request.

If we end up deciding against making the primary class loader available during bootstrap within \Drupal:: or somewhere else, then we can circle back to #15's approach of creating a separate class loader, but I'd like to at least try for the single class loader approach first.

effulgentsia’s picture

masipila’s picture

I was trying to update my 9.3.x to 9.4.1 and I believe I'm affected by this regression. My site is built with composer, using the drupal/recommended-project.

My symptoms:

  • After updating to 9.4.1, I do not have a connection to the database and I get redirected to core/install.php
  • If I say drush status, I see I don't have a connection to the database, which explains the redirect to core/install.php

I read through all comments above and looked at some of the patches of this issue.

Patch #5 has this inline comment:

// Backwards compatibility layer for Drupal 8 style database connection
// arrays. Those have the wrong 'namespace' key set, or not set at all
// for core supported database drivers.

I believe I have this Drupal 8 style database connection array, since my site originates from the early Drupal 8 versions.

My database array in settings.php was this, and it indeed has the 'namespace' element that the inline comment quoted above talks about.

$databases['default']['default'] = array (
   'database' => getenv('DRUPAL_DATABASE_NAME'),
   'username' => getenv('DRUPAL_DATABASE_USERNAME'),
   'password' => getenv('DRUPAL_DATABASE_PASSWORD'),
   'prefix' => '',
   'host' => getenv('DRUPAL_DATABASE_HOST'),
   'port' => '3306',
   'namespace' => 'Drupal\\Core\\Database\\Driver\\mysql',
   'driver' => 'mysql',
);

However, even if I try to update it to this, what is currently present in the 9.4.x default.settings.php, my issue persists:

$databases['default']['default'] = [
  'database' => getenv('DRUPAL_DATABASE_NAME'),
  'username' => getenv('DRUPAL_DATABASE_USERNAME'),
  'password' => getenv('DRUPAL_DATABASE_PASSWORD'),
  'host' => getenv('DRUPAL_DATABASE_HOST'),
  'port' => '3306',
  'driver' => 'mysql',
  'prefix' => '',
  'collation' => 'utf8mb4_general_ci',
];

I'd like to update my settings.php to be of the current style, but I can't see any other difference in the $databases array than that the 'namespace' has been removed and 'collation' has been added.

I understand the discussion above about the workarounds, but I guess the more elegant approach for me and other sites from the early D8 days would be to modify the settings.php to reflect what it currently needs to be, it's just that I don't spot what other aspects I'm missing... Any hints would be appreciated!

Cheers,
Markus

p.s. I do not want to de-rail the scope of this issue. The reason why I'm posting this comment here is that this specific issue is linked from the 9.4.0 release notes, which I did read. And I'm posting this comment here so that other community members who read the release notes would find this as well.

Edit: Added the link to 9.4.x default.settings.php

masipila’s picture

Okay, figured it out. Writing here for others that might be affected by the same things.

It turned out that I had two separate issues:

1. I was affected by the Composer July Timebomb. The 'allow-plugins' section needs to be added to composer.json, see
https://github.com/drupal/recommended-project/blob/9.4.x/composer.json#L32

2. After I added that and removed the old 'namespace' => 'Drupal\\Core\\Database\\Driver\\mysql' from the $databases array in settings.php, my site was back to normal and I was able to complete the 9.4.x updates.

Hope this helps if somebody else is affected in the same way that I was.

Cheers,
Markus

quietone’s picture

Issue summary: View changes

Add postponed issue to the IS>

vlad.dancer’s picture

Acquia Cloud is one example where code like the above in settings.php (or rather, a .inc file included by settings.php) is done.

We are affected by this too on Acquia environments:

On drush cr:

Fatal error: Uncaught Error: Cannot instantiate abstract class Drupal\Core\Database\Query\Upsert in /core/lib/Drupal/Core/Database/Connection.php:1312

On drush status:

Cannot instantiate abstract class Drupal\Core\Database\Schema

The recommended workaround on Acquia (only for dev/staging) is to set databases classes namespace in settings.(acquia.)php:

if (file_exists('/var/www/site-php')) {
  // Workaround for database error:
  // Cannot instantiate abstract class Drupal\Core\Database\Schema.
  $class_loader->addPsr4('Drupal\\mysql\\', DRUPAL_ROOT . '/core/modules/mysql/src/');
  // Load database and other configuration.
  require '/var/www/site-php/PROJ_KEY/PROJ_KEY-settings.inc';
}
nomisgnos’s picture

We noticed the error when upgrading from 9.4.7 to 9.4.8. The workaround does fix the issue.

We are also on Acquia.

@vlad.dancer

The recommended workaround on Acquia (only for dev/staging)

Why only for dev and staging? Why not prod?

peezy’s picture

Acquia's article has been updated to read "this workaround should be tested on lower environments (dev or stage) before applying to a production environment."

effulgentsia’s picture

Issue tags: +9.4.8 update

Related to #24 - #26, Drupal 9.4.8 introduced a new variant to this issue's original regression here as outlined in #3294695-24: Drupal 8 BC for database driver namespace fails for replicas. In 9.4.0 - 9.4.7, only the getConnection() call was affected. In 9.4.8, all database-related code that runs between the conclusion of settings.php and the completion of Drupal's full bootstrap is affected. Primarily, that's Drush commands. The workaround in the issue summary is successful at resolving both flavors of this regression.

Acquia employed a slightly different workaround at the platform level to resolve the 9.4.0 regression for our customers, and is presently working on releasing an updated workaround to resolve the 9.4.8 regression. In the meantime, individual sites can resolve it via this issue summary's workaround as mentioned in #24.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.9 was released on December 7, 2022 and is the final full bugfix release for the Drupal 9.4.x series. Drupal 9.4.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.5.x-dev branch from now on, and new development or disruptive changes should be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.