Problem/Motivation

Follow-up: #2853118-57: Replace all calls to db_insert, which is deprecated

We have different methods of how to get the connection to the database that currently used in the different places of the codebase.

We need to determine which method is preferred in a particular situation.

For example:

With procedural code situation is pretty clear - use \Drupal::database()

Remaining tasks

- discuss
- determine which method is preferred in a particular situation.
Make a follow up - See #13

Proposed resolution

  1. In OOP code:
    1. If it possible, use DI to use @database service or $container->get('database'); to inject the database connection
    2. If not possible (as in a static method of a class), use \Drupal::database().
    3. If services are not yet available, \Drupal\Core\Database\Database::getConnection() can get a database connection.
    4. In unit tests, we do not have a booted kernel or a built container. Unit tests should generally not access a database. A unit test which need a database service should be converted to a kernel test.
    5. In kernel and functional test classes we have $this->container->get('database'). Some test authors might discover that the container referenced by the test class will be out of sync with the current container during the request in a functional test. In that circumstance, a test author might call $this->rebuildContainer() and then access $this->container->get('database') again.
  2. In the Procedural code, i.e. *.module, *.inc or script files:
    1. Use \Drupal::database(); to get database connection;

Issue fork drupal-2991337

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

voleger created an issue. See original summary.

cilefen’s picture

\Drupal::database() and Database::getConnection() are equivalent if 'database' is in the container. Because \Drupal::database() is:

  public static function database() {
    return static::getContainer()->get('database');
  }

and the service definition for 'database' is:

  database:
    class: Drupal\Core\Database\Connection
    factory: Drupal\Core\Database\Database::getConnection
alexpott’s picture

Well method requires the container to exist and one does not. But they do return exactly the same object.

>>> spl_object_id(\Drupal::database());
=> 136
>>> spl_object_id(\Drupal\Core\Database\Database::getConnection());
=> 136

Obviously in most object we prefer dependency injection but for procedural code and tests is there really any difference? I'm not sure there is. Both \Drupal::database() and Database::getConnection() represent anti-patterns - ie. using the container as a service locator and having a static singleton so it doesn't feel as if there is a right answer.

I think it might be okay to shrug here and just say we'll cope with both when and if we get to a place when either the \Drupal class can be removed or the Database::getConnection() static can be.

mondrake’s picture

My 2 cents:

in runtime code, since we are preaching injection, use \Drupal::database whenever it’s not possible to inject (like procedural code), and the container is initialised.

in test code, add a $connection property to base test classes like KernelTestBase and BrowserTestBase and then use that for any db operation, so we won’t have to bother.

alexpott’s picture

@mondrake I'm not too keen on adding a connection property to all tests because it is something that must be kept in sync and whilst we don't have any tests that change real database connection credentials it is possible to write such tests.

Mile23’s picture

Code which depends on the database should use $container->get('database') and IoC, or \Drupal->database() for functional code. That way the database could be a mocked service for testing purposes, consistently across all the codebase. That's a big part of why one bothers with a container in the first place.

We shouldn't add a connection property to tests because there are already two 'right' ways to get the database. :-)

voleger’s picture

Issue summary: View changes
Status: Active » Needs review

So, as the summary:

  1. In the OOP code:
    1. If it possible, use DI to use @database service or $container->get('database'); to inject the database connection
    2. Otherwise, use equialent \Drupal\Core\Database\Database::getConnection();
  2. In the Procedural code, i.e. *.module, *.inc or script files:
    1. Use \Drupal::database(); to get database connection;
voleger’s picture

How about code example in the comments? How to represent the connection object?
$connection
Database::getConnection()
\Drupal::database()

Mile23’s picture

So there's one question to ask: Is there a container available? That is, are we at sufficient bootstrap level to have a container?

Yes: Use either the DI $container->get('database') or \Drupal::database(), in OOP or procedural code. Use \Drupal::database() if there is no opportunity to inject, as with a procedural function.

No: Use Database::getConnection().

In a test which boots the container (functional, kernel), use $this->container->get('database').

alexpott’s picture

@Mile23 you know that tests use of $this->container->get('database') is totally not agreed - especially in Browser or Javascript tests where that object can get hopelessly out of date. We should be discouraging all container access in those type of tests.

cilefen’s picture

Is it feasible or desired to abstract the details into one of the test bases?

mondrake’s picture

Seems only the logic for test code still needs to be agreed upon. Started adding the non-test part to the CR @ https://www.drupal.org/node/2993033.

Reviews/updates there appreciated.

Mile23’s picture

Issue tags: +Needs followup

Re #10: @Mile23 you know that tests use of $this->container->get('database') is totally not agreed

That's the title of this issue, right? I refrained from updating the IS for this reason. :-)

If the test expects the container to be rebuilt as a consequence of what it's doing, then it should either call $this->rebuildContainer() or manage the expected container changes in some other way.

If the test doesn't expect the container to change, then containers going out of sync is a bug that is proven by the test.

Alternately, if we always expect the container to be in an unknown state, (even the one the kernel keeps track of), then either add FunctionalTestSetupTrait::container() which always fetches a new one, or remove BTB::container and normalize on \Drupal.

Adding 'needs followup' because whatever we decide should inform an issue which changes how tests work.

mondrake’s picture

andypost’s picture

in procedural code the suggestion is to minimize calls to \Drupal::database() via using local variable

andypost’s picture

btw For tests there's \Drupal\Core\Test\TestSetupTrait::getDatabaseConnection which has no usage except simpletests

andypost’s picture

@Mile23 I guess you mean #2066993: Use \Drupal consistently in tests about follow-up

mondrake’s picture

#15

in procedural code the suggestion is to minimize calls to \Drupal::database() via using local variable

code example in the draft CR is like that already

Mile23’s picture

Issue summary: View changes
Issue tags: +Needs followup

@andypost: Based on @alexpott's concerns in #10, it seems that functional tests are broken, because they should expect the container to change during the request. Therefore we need to change the way tests work, so that they don't keep a reference to the container.

However, if we expect we can keep a reference to the container during a functional test, then a) we shouldn't change how tests work, and b) @alexpott's concerns in #10 illustrate that there is a bug in core which changes the container.

If #7 1.2 is correct, then it means we don't care about the test fixture container being the 'right' one, so the bug is in the test.

I'd argue (and just did... :-) that #7 1.2 is incorrect, and the bug is in core.

In fact, I'll just update the IS to fix #7 1.2 and add the 'correct' way to do a test. If that's not the correct way to do a test, then there's a bug either in core or in the test system. It now says this:

In kernel and functional test classes we have $this->container->get('database'). Some test authors might discover that the container referenced by the test class will be out of sync with the current container during the request in a functional test. In that circumstance, a test author might call $this->rebuildContainer() and then access $this->container->get('database') again.

andypost’s picture

It looks that kernel and functional test should use same method to access test database

Actually container can get rebuild in kernel tests as well... so after contacting a child site and expecting container rebuild you need to call static method \Drupal\Core\Database\Database::getConnection()

andypost’s picture

As example - each test case will have own static connection independent from container
As example #2850033-30: Replace all calls to db_delete, which is deprecated

mondrake’s picture

Issue tags: +blocker

Tagging as 'blocker' - need a conclusion on the approach in test contexts to proceed with issue related to deprecation of db_* functions.

Mile23’s picture

Actually container can get rebuild in kernel tests as well... so after contacting a child site and expecting container rebuild you need to call static method \Drupal\Core\Database\Database::getConnection()

Correct. That's the bug I'm talking about. That's not how things should work.

EITHER: a) Tests shouldn't expect the container to be consistent (in which case BTB::container is a lie and should be removed), OR: b) Breaking the container model during a request is a bug that needs to be fixed.

So a project maintainer needs to decide which is which, and we can proceed in a follow-up.

I vote for b, but I'm not a maintainer.

voleger’s picture

As mentioned at #2853118-65: Replace all calls to db_insert, which is deprecated we should notice at CR that it is not a good idea to rely on the container in install hook.

Mile23’s picture

Why?

Install-time hooks are invoked from a service that is located using the container. It's true that someone *could* make a bespoke installer that does it another way, but I'd think that would be unsupported.

andypost’s picture

Looks there's agreement that kernel tests should use services (database, database.replica) when they accessible
Functional tests should use Database::getConnection()
What's left here?

I think we should update CR in #2947946: Create change record for all deprecated db_* functions with cons from here

voleger’s picture

Any specific cases? Like install, update, uninstall processes like hook_install ?

Mile23’s picture

#26: Functional tests should use Database::getConnection()

Incorrect. See IS or update it.

voleger’s picture

Status: Needs review » Reviewed & tested by the community

Looks like there nothing to add for this issue. At least for now.
So some work left in #2947946: Create change record for all deprecated db_* functions.

andypost’s picture

Looks it needs some patch to update api.d.o and some docs pages

voleger’s picture

https://www.drupal.org/docs/8/api/database-api/instantiating-a-connectio...
This doc page is perfect place for discussed scenarios.

voleger’s picture

Patch attached for database.api.php

catch’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/lib/Drupal/Core/Database/database.api.php
    @@ -220,6 +220,25 @@
      * also the @link container Services and Dependency Injection topic. @endlink
    + * In OOP code:
    + *  - If it possible, use DI to use @database service or
    + *    $container->get('database'); to inject the database connection;
    + *  - If not possible (as in a static method of a class),
    + *    use \Drupal::database();
    + *  - If services are not yet available,
    + *    \Drupal\Core\Database\Database::getConnection() can get a database
    + *    connection;
    + *  - In unit tests, we do not have a booted kernel or a built container. Unit
    + *    tests should generally not access a database. A unit test which need
    + *    a database service should be converted to a kernel test;
    + *  - In kernel and functional test classes we have
    + *    $this->container->get('database'). Some test authors might discover that
    + *    the container referenced by the test class will be out of sync with
    + *    the current container during the request in a functional test. In that
    + *    circumstance, a test author might call $this->rebuildContainer() and
    + *    then access $this->container->get('database') again;
    + * In the Procedural code, i.e. *.module, *.inc or script files:
    + *  - Use \Drupal::database(); to get database connection;
      *
    

    I think there's too much detail here. We should just list how to get a database in order of preference, but also it should include some kind of code examples.

  2. +++ b/core/lib/Drupal/Core/Installer/Form/SiteConfigureForm.php
    @@ -9,6 +9,7 @@
     use Drupal\Core\State\StateInterface;
     use Drupal\user\UserStorageInterface;
    +use Drupal\user\UserInterface;
     use Symfony\Component\DependencyInjection\ContainerInterface;
     
     /**
    @@ -175,7 +176,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    
    @@ -175,7 +176,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
         $form['admin_account']['account']['name'] = [
           '#type' => 'textfield',
           '#title' => $this->t('Username'),
    -      '#maxlength' => USERNAME_MAX_LENGTH,
    +      '#maxlength' => UserInterface::USERNAME_MAX_LENGTH,
           '#description' => $this->t("Several special characters are allowed, including space, period (.), hyphen (-), apostrophe ('), underscore (_), and the @ sign."),
    

    This looks like a patch for a different issue crept in.

voleger’s picture

Added a few examples, removed nonrelevant changes.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

andypost’s picture

Category: Plan » Task
Priority: Normal » Major
quietone’s picture

Component: database system » documentation
Issue summary: View changes

Add details about the follow up to the Remaining Tasks.
Since this is about documentation, and according to Issue tags -- special tags, changing to the documentation component.

  1. +++ b/core/lib/Drupal/Core/Database/database.api.php
    @@ -220,6 +220,42 @@
    + * In OOP code:
    

    OOP has not been defined prior to this so should not be used. Can this be 'Object Oriented code'?

  2. +++ b/core/lib/Drupal/Core/Database/database.api.php
    @@ -220,6 +220,42 @@
    + *  - If it possible, use DI to use @database service or
    

    In this documentation there are a couple of instances of 'dependency injection' and no use of an acronym for it. Therefor, this should also use the full text, 'dependency injection'.

    And s/If it/If/

  3. +++ b/core/lib/Drupal/Core/Database/database.api.php
    @@ -220,6 +220,42 @@
    + *  - If not possible (as in a static method of a class),
    

    Suggest this instead
    "If it is not possible to use dependency injection, e.g. in a static method,"

  4. +++ b/core/lib/Drupal/Core/Database/database.api.php
    @@ -220,6 +220,42 @@
    + * In the Procedural code, i.e. *.module, *.inc or script files:
    

    s/In the Procedural/In procedural/

  5. +++ b/core/lib/Drupal/Core/Database/database.api.php
    @@ -220,6 +220,42 @@
    + *    ```
    

    s/```/@code/
    This is needed in other places to identify the code examples.

  6. +++ b/core/lib/Drupal/Core/Database/database.api.php
    @@ -220,6 +220,42 @@
    + *    ```
    

    s/```/@endcode/
    This is needed in other places to identify the code examples.

voleger’s picture

Status: Needs work » Needs review

Addressed #42
Hiding patches as there is MR.

quietone’s picture

Status: Needs review » Needs work

Just a few things but I would like to get other eyes on this as well.

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

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.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.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.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 » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now 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.

voleger’s picture

Version: 10.1.x-dev » 11.x-dev
Status: Needs work » Needs review

Rebased against 11.x

smustgrave’s picture

Status: Needs review » Needs work

Seems to have CI failures.

Have not reviewed.

cilefen’s picture

Title: How to get database connection in the code? Which method is recomended? » Document the recommended ways to obtain the database connection object

Marios Anagnostopoulos made their first commit to this issue’s fork.

Marios Anagnostopoulos’s picture

Status: Needs work » Needs review
smustgrave’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: +Needs Review Queue Initiative

There doesn't appear to be any open threads and reviewing the text I think that makes sense.

The examples for when to use for what test type is good. Wonder if this should also be updated https://www.drupal.org/docs/drupal-apis/database-api/instantiating-a-dat...

Will leave the Follow up tag but it's from 5 years ago I think the issue being discussed then may be fixed as far as referencing the database from functional?

needs-review-queue-bot’s picture

Status: Reviewed & tested by the community » Needs work
FileSize
698 bytes

The Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

This does not mean that the patch needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

voleger’s picture

Status: Needs work » Reviewed & tested by the community

I'm unsure why phpstan failed, as MR introduced no code changes except docblock.

needs-review-queue-bot’s picture

Status: Reviewed & tested by the community » Needs work
FileSize
698 bytes

The Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

This does not mean that the patch needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

nod_’s picture

Status: Needs work » Needs review
Issue tags: +no-needs-review-bot

Not sure how to fix the problem with phpstan. When I try to manually check the changed file it tells me [ERROR] No files found to analyse.

For now let's tell the bot to skip this one.

voleger’s picture

Status: Needs review » Reviewed & tested by the community

Thanks for looking into the bot check issue.
I'm restoring the status before the bot checks.

catch’s picture

Status: Reviewed & tested by the community » Needs work

Added some comments to the MR.

TR’s picture

+ * In Object Oriented code:
+ *  - If possible, use dependency injection to use "database" service or
+ *    $container->get('database'); to inject the database connection;

The indentation is wrong, isn't it? The dash should be in the same column as the "I" on the previous line.
Also, ending bullet points with a semi-colon is weird. They should end in a period. If this were all one sentence without bullet points, then perhaps using a semi-colon to separate the clauses would be acceptable, but it would be a really long and confusing sentence with all those clauses. Better IMO to stick with the bullet points and the conventional bullet point punctuation.

+ *  - In unit tests, we do not have a booted kernel or a built container. A
+ *    unit tests which need a database service should be converted to a
+ *    kernel test;

Instead of "A unit tests which need" it should be "A unit test that needs" or "Unit tests that need". Note the use of the singular vs plural forms, as well as the use of "that" instead of "which".

Are we still using i.e. and e.g. in documentation? I thought the community was against it, going back more than 10 years now ... I have no problems with this usage, just pointing this out because we don't want to have a regression in our usage. See for example #2760911: Use of "e.g." and "i.e.".