Problem/Motivation

As noted in #3156881-35: Various methods of \Drupal\Core\Database\StatementInterface have changed their signature for PHP 8, the database Statement class is extending from PDO, and is therefore exposed to the changing winds of new PHP versions.

We should have our own Statement envelope instead, encapsulating a PDO (or, potentially, other) lower-level Statement object.

In https://github.com/mondrake/drudbal/blob/master/src/Driver/Database/dbal..., I have an example: the Statement object implemented for the DruDbal driver encapsulates a doctrine/dbal Statement (which is not a PDO Statement).

Steps to reproduce

Proposed resolution

Add a StatementWrapper class that implements \Drupal\Core\Database\StatementInterface and wraps \PDOStatement so that we can be both PHP 7 and PHP 8 compatible. \Drupal\Core\Database\Statement is deprecated.

Remaining tasks

User interface changes

None

API changes

  • A new \Drupal\Core\Database\StatementWrapper class
  • \Drupal\Core\Database\Statement is deprecated.
  • A new property \Drupal\Core\Database\Connection::$statementWrapperClass to control the usage of the wrapper
  • The property \Drupal\Core\Database\Connection::$statementClass is deprecated.

Data model changes

Release notes snippet

New StatementWrapper class added to wrap PHP's built-in \PDOStatement. Custom/contrib database drivers that use PDO need to use this class for PHP 8 compatibility. See https://www.drupal.org/node/3177488 for more information.

CommentFileSizeAuthor
#86 3174662-86.patch33.44 KBalexpott
#86 85-86-interdiff.txt3.67 KBalexpott
#85 3174662-84.patch31.24 KBalexpott
#85 75-84-interdiff.txt5.61 KBalexpott
#75 3174662-75.patch34.05 KBandypost
#75 interdiff.txt1.22 KBandypost
#73 3174662-73.patch31.04 KBandypost
#73 interdiff.txt1.01 KBandypost
#72 interdiff_67_72.txt869 bytesanmolgoyal74
#72 3174662-72.patch31 KBanmolgoyal74
#67 3174662-67.patch30.86 KBhussainweb
#67 interdiff-64-67.txt3.26 KBhussainweb
#64 3174662-64.patch30.85 KBandypost
#64 interdiff.txt11.34 KBandypost
#63 3174662-63.patch30.83 KBmondrake
#63 interdiff_60-63.txt1.85 KBmondrake
#60 3174662-60.patch30.83 KBmondrake
#54 3174662-54.patch30.83 KBmondrake
#54 interdiff_43-54.txt3.76 KBmondrake
#43 interdiff_41-43.txt1.09 KBmondrake
#43 3174662-43.patch31.14 KBmondrake
#42 3174662-42.patch31.14 KBmondrake
#42 interdiff_41-42.txt1.09 KBmondrake
#41 interdiff_39-41.txt9.12 KBmondrake
#41 3174662-41.patch31 KBmondrake
#39 3174662-39.patch32.68 KBmondrake
#39 interdiff_37-39.txt8.87 KBmondrake
#37 3174662-37.patch29.85 KBmondrake
#37 interdiff_33-37.txt6.04 KBmondrake
#33 interdiff_27-33.txt5.94 KBmondrake
#33 3174662-33.patch27.95 KBmondrake
#32 interdiff_27-32.txt5.94 KBmondrake
#32 3174662-32.patch27.96 KBmondrake
#27 3174662-27.patch24.28 KBmondrake
#27 interdiff_26-27.txt6.4 KBmondrake
#26 interdiff_24-26.txt4.46 KBmondrake
#26 3174662-26.patch19.17 KBmondrake
#24 interdiff_20-24.txt9.74 KBmondrake
#24 3174662-24.patch15.09 KBmondrake
#20 interdiff_13-20.txt16.11 KBmondrake
#20 3174662-20.patch15.81 KBmondrake
#13 interdiff_11-13.txt1.94 KBmondrake
#13 3174662-13.patch16.83 KBmondrake
#12 interdiff_10-11.txt8.84 KBmondrake
#12 3174662-11.patch15.27 KBmondrake
#11 interdiff_8-10.txt1.7 KBmondrake
#11 3174662-10.patch10.16 KBmondrake
#8 interdiff_7-8.txt1.57 KBmondrake
#8 3174662-8.patch9.55 KBmondrake
#7 interdiff_5-7.txt1.72 KBmondrake
#7 3174662-7.patch9.49 KBmondrake
#5 3174662-5.patch9.34 KBmondrake
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

mondrake created an issue. See original summary.

mondrake’s picture

Note: I think the reason why currently Statement extends from PDO is because of these lines in Drupal\Core\Database\Connection::__construct:

    // Set a Statement class, unless the driver opted out.
    if (!empty($this->statementClass)) {
      $connection->setAttribute(\PDO::ATTR_STATEMENT_CLASS, [$this->statementClass, [$this]]);
    }

In order for PDO to automatically use Drupal's custom Statement class this way when PDO::query is used, the custom class MUST extend from PDO. However, Drupal already has the possibility to opt-out from using this - and uses it in SQLite to manage prefetching of results to early close the connection.

andypost’s picture

Interesting idea, overall it's nice to be less bound to PDO and mimic less its interfaces

In a long run - PDO is not thread safe so other implementations (native, swoole) drivers could be better choice

mondrake’s picture

Assigned: Unassigned » mondrake

Working on this.

mondrake’s picture

Status: Active » Needs review
FileSize
9.34 KB

This is a first concept patch. The actual PDOStatement object is opened during the Drupal Statement construction, then stored in a property for use.

Status: Needs review » Needs work

The last submitted patch, 5: 3174662-5.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

mondrake’s picture

Status: Needs work » Needs review
FileSize
9.49 KB
1.72 KB
mondrake’s picture

The last submitted patch, 7: 3174662-7.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 8: 3174662-8.patch, failed testing. View results

mondrake’s picture

Status: Needs work » Needs review
FileSize
10.16 KB
1.7 KB
mondrake’s picture

Reshuffling and trying to extend to pgsql, after mysql.

mondrake’s picture

Title: [experiment] Encapsulate PDO::Statement instead of extending from it » Encapsulate PDO::Statement instead of extending from it
Assigned: mondrake » Unassigned

I suppose this is no longer an experiment...

andypost’s picture

Issue tags: +Needs tests

Quick review

  1. +++ b/core/lib/Drupal/Core/Database/Connection.php
    @@ -247,6 +247,16 @@ public function __construct(\PDO $connection, array $connection_options) {
    +   * @return mixed
    +   *   The client-level database connection, for example \PDO.
    +   */
    +  public function getClientConnection() {
    +    return $this->connection;
    

    I bet it always object|null

  2. +++ b/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php
    @@ -69,6 +70,68 @@ class Connection extends DatabaseConnection {
    +    if (isset($connection_options['transactions'])) {
    +      @trigger_error('Passing a \'transactions\' connection option to Drupal\Core\Database\Connection::__construct is deprecated in drupal:9.1.0 and is removed in drupal:10.0.0. All database drivers must support transactions. See https://www.drupal.org/node/2278745', E_USER_DEPRECATED);
    ...
    +  public function prepare($statement, array $driver_options = []) {
    +    @trigger_error('Connection::prepare() is deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. Database drivers should instantiate \PDOStatement objects by calling \PDO::prepare in their Collection::prepareStatement method instead. \PDO::prepare should not be called outside of driver code. See https://www.drupal.org/node/3137786', E_USER_DEPRECATED);
    +    throw new DatabaseExceptionWrapper('Connection::prepare() is deprecated');
    
    +++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php
    @@ -63,10 +63,35 @@ class Connection extends DatabaseConnection {
    +    if (isset($connection_options['transactions'])) {
    +      @trigger_error('Passing a \'transactions\' connection option to Drupal\Core\Database\Connection::__construct is deprecated in drupal:9.1.0 and is removed in drupal:10.0.0. All database drivers must support transactions. See https://www.drupal.org/node/2278745', E_USER_DEPRECATED);
    +      unset($connection_options['transactions']);
    
    @@ -77,6 +102,22 @@ public function __construct(\PDO $connection, array $connection_options) {
    +  public function prepare($statement, array $driver_options = []) {
    +    @trigger_error('Connection::prepare() is deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. Database drivers should instantiate \PDOStatement objects by calling \PDO::prepare in their Collection::prepareStatement method instead. \PDO::prepare should not be called outside of driver code. See https://www.drupal.org/node/3137786', E_USER_DEPRECATED);
    +    throw new DatabaseExceptionWrapper('Connection::prepare() is deprecated');
    
    +++ b/core/lib/Drupal/Core/Database/Statement.php
    @@ -2,6 +2,8 @@
    +@trigger_error(__CLASS__ . '() is deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. Database drivers should use or extend StatementWrapper instead, and encapsulate client-level statement objects. See https://www.drupal.org/node/TODO', E_USER_DEPRECATED);
    
    @@ -12,6 +14,12 @@
    + * @deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. Database
    + *   drivers should use or extend StatementWrapper instead, and encapsulate
    ...
    + * @see https://www.drupal.org/node/TODO
    

    both needs deprecation tests

  3. +++ b/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php
    @@ -69,6 +70,68 @@ class Connection extends DatabaseConnection {
    +   * Destructs a MySql Connection object.
    +   */
    +  public function __destruct() {
    +    if ($this->needsCleanup) {
    +      $this->nextIdDelete();
    +    }
    +    $this->schema = NULL;
    +    $this->connection = NULL;
    
    @@ -218,16 +281,6 @@ public function serialize() {
    -   * {@inheritdoc}
    -   */
    -  public function __destruct() {
    -    if ($this->needsCleanup) {
    -      $this->nextIdDelete();
    -    }
    -    parent::__destruct();
    

    Why it's not calling parent destructor?
    Also it could use to change existing method to minify diff

  4. +++ b/core/lib/Drupal/Core/Database/StatementWrapper.php
    @@ -0,0 +1,274 @@
    +   * Reference to the database connection object for this statement.
    ...
    +   * @var \Drupal\Core\Database\Connection
    ...
    +  public $dbh;
    ...
    +   * Is rowCount() execution allowed.
    ...
    +   * @var bool
    ...
    +  public $allowRowCount = FALSE;
    

    Why this public?

  5. +++ b/core/lib/Drupal/Core/Database/StatementWrapper.php
    @@ -0,0 +1,274 @@
    +
    +  public function __construct(Connection $connection, string $query, array $options) {
    

    needs docs

andypost’s picture

Issue tags: +Needs change record

Also for TODO link

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

mondrake’s picture

Assigned: Unassigned » mondrake

Thanks for review @andypost

Status: Needs review » Needs work

The last submitted patch, 13: 3174662-13.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

mondrake’s picture

Minimising changes to MySql and PostgreSQL Connection classes, by introducing in the base class a flag to indicate whether the driver is using a Statement that extends from \PDOStatement. Cannot use instanceof because we can not load the class for checking, it will trigger the deprecation.

Re #15:

1. Indeed it will be an object, but not necessarily a PDO one. NULL no, because we would be in an exception context in that case.
2. Added a deprecation test for the Statement class. The others no longer relevant in the patch here.
3. done
4. because other classes in Database are accessing those directly. I'd be +1 on using helpers here now, but I'd leave that to follow up. Note: $dbh HAD to be public for PDO, like the constructor HAD to be protected, but it's no longer necessary here.
5. done

mondrake’s picture

Assigned: mondrake » Unassigned
Priority: Normal » Critical
Issue tags: +PHP 8.0

Bumping to critical since this may be an alternative to #3156881: Various methods of \Drupal\Core\Database\StatementInterface have changed their signature for PHP 8 which is critical. Also tagging for PHP 8.

mondrake’s picture

Title: Encapsulate PDO::Statement instead of extending from it » Encapsulate \PDOStatement instead of extending from it
mondrake’s picture

Assigned: Unassigned » mondrake
Status: Needs review » Needs work

On this again; we can do without the flag and we could use some magic to pass through calls to methods meant for PDOStatement that are not defined for StatementWrapper

mondrake’s picture

Status: Needs review » Needs work

The last submitted patch, 24: 3174662-24.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

mondrake’s picture

mondrake’s picture

mondrake’s picture

Assigned: mondrake » Unassigned
alexpott’s picture

  1. @mondrake thanks for working on this. It looks very promising to me
  2. +++ b/core/lib/Drupal/Core/Database/Connection.php
    @@ -1743,7 +1755,10 @@ public function commit() {
    -    return $this->connection->prepare($statement, $driver_options);
    +    if (!is_a($this->statementClass, StatementWrapper::class, TRUE)) {
    +      return $this->connection->prepare($statement, $driver_options);
    +    }
    +    throw new DatabaseExceptionWrapper('Connection::prepare() is deprecated.');
    

    This looks a bit awkward. If we're using the StatementWrapper class shouldn't the deprecated path but to return $statement?

  3. +++ b/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php
    @@ -47,6 +47,11 @@ class Connection extends DatabaseConnection {
    +  /**
    +   * {@inheritdoc}
    +   */
    +  protected $statementClass = StatementWrapper::class;
    

    I don't think we should use the statementClass system for this. I think we should add a new protected property called $statementWrapper instead.

alexpott’s picture

  1. +++ b/core/lib/Drupal/Core/Database/StatementWrapper.php
    @@ -0,0 +1,367 @@
    +  /**
    +   * Returns the client-level database statement object.
    +   *
    +   * @return object
    +   *   The client-level database statement, for example \PDOStatement.
    +   */
    +  public function getClientStatement() {
    

    Do you think we should document that this should only be called from the database driver?

  2. +++ b/core/lib/Drupal/Core/Database/StatementWrapper.php
    @@ -0,0 +1,367 @@
    +  public function bindColumn($column, &$param, int $type = 0, int $maxlen = 0, $driverdata = NULL): bool {
    

    How come this is here? As far as I can see there are no calls to it (i.e. 0 test coverage) - is this here for completeness?

mondrake’s picture

Assigned: Unassigned » mondrake
Status: Needs review » Needs work

#30.1 - good idea, even though I think it should be a recommendation rather than strict. If contrib wants to access it directly, why not?

#30.2 - because for BC the StatementWrapper should still behave like a PDOStatement, at least for now. The magic __call is there for that. But for this method (as well as for bindParam) call_user_func seems not to work because $param needs to be passed by ref and not by value.

#29.2/3 - not really sure I understand where you're heading to, can you elaborate?

I'm working on some additional testing.

mondrake’s picture

Assigned: mondrake » Unassigned
Status: Needs work » Needs review
FileSize
27.96 KB
5.94 KB

Fixed #30.1 and added a test for the deprecated part of StatementWrapper.

mondrake’s picture

alexpott’s picture

RE #29.3 I think the wrapper thing should be separate from $statementClass... ie. looking at the patch:

+++ b/core/lib/Drupal/Core/Database/Connection.php
@@ -535,7 +545,9 @@ public function prepareStatement(string $query, array $options): StatementInterf
-    return $this->connection->prepare($query, $options['pdo'] ?? []);
+    return is_a($this->statementClass, StatementWrapper::class, TRUE) ?
+    new $this->statementClass($this, $query, $options['pdo'] ?? []) :
+      $this->connection->prepare($query, $options['pdo'] ?? []);

Should look something like:

return $this->statementWrapperClass ?
  new $this->statementWrapperClass($this, $query, $options['pdo'] ?? []) :
  $this->connection->prepare($query, $options['pdo'] ?? []);

This would allow $statementClass to continue working as it does today and separate it from this new statement wrapper logic. And we'd use a new protected $statementWrapperClass instead.

mondrake’s picture

Assigned: Unassigned » mondrake
Status: Needs review » Needs work

#34 Gotcha, thanks. On this.

alexpott’s picture

+++ b/core/lib/Drupal/Core/Database/Connection.php
@@ -1743,7 +1755,10 @@ public function commit() {
     @trigger_error('Connection::prepare() is deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. Database drivers should instantiate \PDOStatement objects by calling \PDO::prepare in their Collection::prepareStatement method instead. \PDO::prepare should not be called outside of driver code. See https://www.drupal.org/node/3137786', E_USER_DEPRECATED);
-    return $this->connection->prepare($statement, $driver_options);
+    if (!is_a($this->statementClass, StatementWrapper::class, TRUE)) {
+      return $this->connection->prepare($statement, $driver_options);
+    }
+    throw new DatabaseExceptionWrapper('Connection::prepare() is deprecated.');

I guess by #29.2 what I'm trying to ask is "Is throwing an exception the correct behaviour here?"

Re #32/#30.2 - I think testing using mocks of this isn't really enough. I think we need to test calling the methods for real because that's the risky thing.

mondrake’s picture

Assigned: mondrake » Unassigned
Status: Needs work » Needs review
FileSize
6.04 KB
29.85 KB

Addressing:

#34 - done
#29.2 - #36.1 - done

Re #36.2 let's see what I can do. Unassigning so that if anyone quicker can step in.

Status: Needs review » Needs work

The last submitted patch, 37: 3174662-37.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

mondrake’s picture

#36.2 added a kernel test to use real PDO backend objects instead of mocks.

Status: Needs review » Needs work

The last submitted patch, 39: 3174662-39.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

mondrake’s picture

Status: Needs work » Needs review
FileSize
31 KB
9.12 KB

mysql and pgsql failures in #39 is another reason why I would like to do #3137883: Deprecate passing a StatementInterface object to Connection::query. Removed the now redundant StatementWrapper unit test.

mondrake’s picture

mondrake’s picture

mondrake’s picture

Status: Needs review » Needs work

The last submitted patch, 43: 3174662-43.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

mondrake’s picture

Status: Needs work » Needs review

#45 seems a random test fail.

mondrake’s picture

Note: IMHO we shouldn’t care too much on testing passthrough to PDOStatement methods. In fact, (Drupal) SQLite’s Statement class is already not compliant wth PDOStatement.

andypost’s picture

++ to #47 PDO has extensive testing in PHP itself, probably it needs follow-up to review current test suite

catch’s picture

Category: Feature request » Task

This looks very encouraging, still need to do a thorough review but +1 to the general approach.

alexpott’s picture

  1. +++ b/core/lib/Drupal/Core/Database/Connection.php
    @@ -247,6 +262,18 @@ public function __construct(\PDO $connection, array $connection_options) {
    +  /**
    +   * Returns the client-level database connection object.
    +   *
    +   * This method should normally be used only within database driver code.
    +   *
    +   * @return object
    +   *   The client-level database connection, for example \PDO.
    +   */
    +  public function getClientConnection() {
    +    return $this->connection;
    +  }
    
    +++ b/core/lib/Drupal/Core/Database/StatementWrapper.php
    @@ -0,0 +1,369 @@
    +  /**
    +   * Constructs a StatementWrapper object.
    +   *
    +   * @param \Drupal\Core\Database\Connection $connection
    +   *   Database connection object.
    +   * @param string $query
    +   *   The SQL query string.
    +   * @param array $options
    +   *   Array of query options.
    +   */
    +  public function __construct(Connection $connection, string $query, array $options) {
    +    $this->dbh = $connection;
    +    $this->clientStatement = $connection->getClientConnection()->prepare($query, $options);
    +    $this->setFetchMode(\PDO::FETCH_OBJ);
    +  }
    

    I think that we should inject the statement into the wrapper instead of the client connection. Or maybe inject the client connection if you want. But it'd be great to not expose a public API for getting the client connection.

  2. +++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php
    @@ -38,6 +39,16 @@ class Connection extends DatabaseConnection {
    +  /**
    +   * {@inheritdoc}
    +   */
    +  protected $statementClass = NULL;
    +
    +  /**
    +   * {@inheritdoc}
    +   */
    +  protected $statementWrapperClass = StatementWrapper::class;
    

    Ah I see now why the patch originally chose to use $statementClass

    I wonder if I've over complicated things. Is there a use-case for both a wrapper and a statement class.

    Not sure. Hmmm.

mondrake’s picture

#50.1 - I was inspired by doctrine/dbal that have a public getWrappedConnection method, https://github.com/doctrine/dbal/blob/2.11.x/lib/Doctrine/DBAL/Connectio.... Contrib may decide to get access to it directly and do stuff, which at that point would not be db-agnostic, but whynot?

#50.2 - now I like it better with two properties, if nothing else so that it's pretty clear that one is deprecated and will be removed in D10.

alexpott’s picture

@mondrake re #50.1 because we've not added that so far so it is a unnecessary API widening at this point. We can add it if someone has a case for it - but I'd rather we keep the new API to a minimum here.

mondrake’s picture

Assigned: Unassigned » mondrake
Status: Needs review » Needs work

OK, I'm on #50.1.

mondrake’s picture

Assigned: mondrake » Unassigned
Status: Needs work » Needs review
FileSize
3.76 KB
30.83 KB

Done #50.1.

alexpott’s picture

+++ b/core/lib/Drupal/Core/Database/StatementWrapper.php
@@ -32,15 +34,17 @@ class StatementWrapper implements \IteratorAggregate, StatementInterface {
+  public function __construct(Connection $connection, $client_connection, string $query, array $options) {
     $this->dbh = $connection;
-    $this->clientStatement = $connection->getClientConnection()->prepare($query, $options);
+    $this->clientStatement = $client_connection->prepare($query, $options);
     $this->setFetchMode(\PDO::FETCH_OBJ);
   }

I get the feeling this is tightly coupled to PDO-ness. See the use of PDO constants. If your DB uses something other than a PDOStatement then I think you'd need a different wrapper. For that reason I'm unsure of why we'd not inject the \PDOStatement here and not the client connection.

mondrake’s picture

#55 the entire Database API is PDO-bound all over the place, for sure. Abstracting from it, shall we want it, will take much longer than this issue. See what doctrine/dbal did, and they're not live yet with their 3.0. Contrib db drivers not implementing PDOStatements will need to extend StatementWrapper anyway (if not the Connection itself). IMHO in here, passing the client connection helps because the prepare is done in statement which knows its db specifics and not in connection that can remain agnostic.

mondrake’s picture

Also, consistency: in StatementPrefetch (used by SQLite), prepare is called within the statement, and we pass both Drupal connection and PDO connection.

alexpott’s picture

#56 & #57 is solid reasoning. And I agree the ideally the abstract implementation of Connection would be PDO agnostic - that said it really is not right now - no harm in not make it worse though.

mondrake’s picture

mondrake’s picture

hussainweb’s picture

I tested this patch manually on Drupal 9.2-dev (as of today) on PHP 8. I tested with installing Umami and then randomly clicking through the site and in admin panel. No errors reported anywhere. Some screenshots in a Twitter thread at https://twitter.com/hussainweb/status/1317449063533600770 and the test code is at https://github.com/hussainweb/drupal-php8-composer2.

mondrake’s picture

Status: Needs review » Needs work
Issue tags: +Needs reroll
mondrake’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll
FileSize
1.85 KB
30.83 KB

Rerolled

andypost’s picture

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.

alexpott’s picture

There are some things that need to be fixed

Checking changed files...
CSPELL: checking all files
/Users/alex/dev/sites/drupal8alt.dev/core/lib/Drupal/Core/Database/StatementWrapper.php:294:18 - Unknown word (maxlen)
/Users/alex/dev/sites/drupal8alt.dev/core/lib/Drupal/Core/Database/StatementWrapper.php:296:20 - Unknown word (driverdata)
/Users/alex/dev/sites/drupal8alt.dev/core/lib/Drupal/Core/Database/StatementWrapper.php:308:68 - Unknown word (maxlen)
/Users/alex/dev/sites/drupal8alt.dev/core/lib/Drupal/Core/Database/StatementWrapper.php:308:81 - Unknown word (driverdata)
/Users/alex/dev/sites/drupal8alt.dev/core/lib/Drupal/Core/Database/StatementWrapper.php:318:76 - Unknown word (maxlen)
/Users/alex/dev/sites/drupal8alt.dev/core/lib/Drupal/Core/Database/StatementWrapper.php:321:76 - Unknown word (maxlen)
/Users/alex/dev/sites/drupal8alt.dev/core/lib/Drupal/Core/Database/StatementWrapper.php:321:85 - Unknown word (driverdata)
/Users/alex/dev/sites/drupal8alt.dev/core/lib/Drupal/Core/Database/StatementWrapper.php:336:32 - Unknown word (INOUT)
CSpell: Files checked: 12, Issues found: 8 in 1 files

FILE: ...rupal8alt.dev/core/lib/Drupal/Core/Database/StatementWrapper.php
----------------------------------------------------------------------
FOUND 4 ERRORS AFFECTING 4 LINES
----------------------------------------------------------------------
  54 | ERROR | [x] The text '@deprecated in drupal:9.1.0 and is
     |       |     removed in drupal:10.0.0.' does not match the
     |       |     standard format: @deprecated in
     |       |     %deprecation-version% and is removed from
     |       |     %removal-version%. %extra-info%.
  68 | ERROR | [x] The text '@deprecated in drupal:9.1.0 and is
     |       |     removed in drupal:10.0.0.' does not match the
     |       |     standard format: @deprecated in
     |       |     %deprecation-version% and is removed from
     |       |     %removal-version%. %extra-info%.
 302 | ERROR | [ ] The text '@deprecated StatementWrapper::bindColumn
     |       |     should not be called in drupal:9.1.0 and will
     |       |     error in drupal:10.0.0. Access the client-level
     |       |     statement object via ::getClientStatement().' does
     |       |     not match the standard format: @deprecated in
     |       |     %deprecation-version% and is removed from
     |       |     %removal-version%. %extra-info%.
 349 | ERROR | [ ] The text '@deprecated StatementWrapper::bindParam
     |       |     should not be called in drupal:9.1.0 and will
     |       |     error in drupal:10.0.0. Access the client-level
     |       |     statement object via ::getClientStatement().' does
     |       |     not match the standard format: @deprecated in
     |       |     %deprecation-version% and is removed from
     |       |     %removal-version%. %extra-info%.
----------------------------------------------------------------------
PHPCBF CAN FIX THE 2 MARKED SNIFF VIOLATIONS AUTOMATICALLY
----------------------------------------------------------------------

Time: 1.88 secs; Memory: 10MB
hussainweb’s picture

Fixing the above issues and changing deprecated text to read that this will be deprecated in Drupal 9.2.0 instead of 9.1.0.

hussainweb’s picture

I missed the errors from cspell earlier, but how should we go about fixing it? These parameter names match the names in PHP and I think it is okay to just add the underscore to make it recognisable. OTOH, I can't think of what to replace `INOUT` with.

alexpott’s picture

For the cspell stuff we can add

// cSpell:ignore maxlen driverdata INOUT

to the file. With PHP 8.0 parameter names are now part of the API!!!

alexpott’s picture

Status: Needs review » Needs work
+++ b/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php
@@ -604,6 +605,18 @@ public function testNamespaceDefault() {
+  /**
+   * Tests deprecation of the Statement class.
+   *
+   * @group legacy
+   */
+  public function testStatementDeprecation() {
+    $this->expectDeprecation('\Drupal\Core\Database\Statement is deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. Database drivers should use or extend StatementWrapper instead, and encapsulate client-level statement objects. See https://www.drupal.org/node/3177488');
+    $mock_statement = $this->getMockBuilder(Statement::class)
+      ->disableOriginalConstructor()
+      ->getMock();
+  }

This will fail in PHP 8 - because the class is not compatible with it at all. So we should add

    if (PHP_VERSION_ID >= 80000) {
      $this->markTestSkipped('Drupal\Core\Database\Statement is incompatible with PHP 8.0.');
    }
anmolgoyal74’s picture

Status: Needs work » Needs review
FileSize
31 KB
869 bytes

Addressed #71

andypost’s picture

With a link to remove

alexpott’s picture

Can someone address #69

andypost’s picture

alexpott’s picture

I wonder what we should be doing with the other things that implement\Drupal\Core\Database\StatementInterface - i.e. \Drupal\Core\Database\StatementPrefetch, \Drupal\Core\Database\Driver\sqlite\Statement and \Drupal\Core\Database\StatementEmpty.

mondrake’s picture

#76 IMHO nothing in this issue - the other things implement StatementInterface as StatementWrapper does. Only, Statement (now deprecated) and StatementWrapper are polymorph in the sense that they also abide to \PDOStatement. We may want to get rid of that polymorphism but later on.

catch’s picture

This looks really good, resolves a longstanding complaint with the database layer which has finally bit us, but turned out to be a scratch rather than fatal.

One nit:

+++ b/core/lib/Drupal/Core/Database/StatementWrapper.php
@@ -0,0 +1,377 @@
+
+  /**
+   * The Drupal database connection object.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  public $dbh;
+

Nit: could we use a more self-descriptive property name? This is only used once in the class to get the logger. Could just be $connection?

mondrake’s picture

#78 no as long as Drupal\Core\Database\Log accesses it directly. The 'dbh' naming comes from earlier tighter PDO integration (where the now deprecated Statement class is passed in as an attribute to the client PDO connection, and here PDO expects a public $dbh property to be there).

IMHO in follow-ups we could move $dbh (and possibly $allowRowCount) to the magic getter for BC with deprecation trigger, make an alternative set of protected properties for that, introduce getters (and setters if necessary), and use them across - will have to touch the other StatementInterface implementations for that, hence better do it separately.

alexpott’s picture

Issue summary: View changes
Status: Needs review » Reviewed & tested by the community

@mondrake hopefully that's the way it works out. I agree that in D10 we should have a good look at the StatementInterface and decouple it a bit from \PDOStatement.

Updating issue summary and change record.

alexpott’s picture

Status: Reviewed & tested by the community » Needs review

Didn't mean to change the status (yet)

mondrake’s picture

Status: Needs review » Needs work

#75 it looks like there are some field related hunks in the patch from other issues?

catch’s picture

Issue tags: +Needs followup

Fair enough in #79, let's open a follow-up.

mondrake’s picture

alexpott’s picture

Status: Needs work » Needs review
FileSize
5.61 KB
31.24 KB

Discussed with @catch - we want to land this in 9.1.0 so changing deprecation notices - which weren't actually consistent with their @trigger_error's anyway.

Removing incorrect hunks added in #75

I also fixed a bug in \Drupal\Core\Database\StatementWrapper::fetch() not passing the correct args. Obvs we don't actually use this code in core.

alexpott’s picture

Fixing StubConnection to not use the deprecated \Drupal\Core\Database\Connection::$statementClass and also properly deprecating that property and testing said deprecation.

alexpott’s picture

Issue summary: View changes
alexpott’s picture

Status: Needs review » Reviewed & tested by the community

I think this is now rtbc.

  • We have passing test on all db drivers (the mariadb fail is a random JS fail)
  • We have passing tests against PHP 8.0
  • We have a change record and release note

I think I'm eligible to rtbc as 99% of the work here is @mondrake's and my two recent patches are tidy up as I result of code review.

  • catch committed f67095e on 9.1.x
    Issue #3174662 by mondrake, andypost, alexpott, hussainweb, anmolgoyal74...
catch’s picture

Version: 9.2.x-dev » 9.1.x-dev
Status: Reviewed & tested by the community » Fixed

This looks ready to go to me, and good to get this in prior to the 8.1.x alpha in case something unexpected comes up - most of the remaining PHP8 compatibility changes after this one are comparatively much smaller.

Committed f67095e and pushed to 9.1.x. Thanks!

  • catch committed 88ea6d6 on 9.2.x
    Issue #3174662 by mondrake, andypost, alexpott, hussainweb, anmolgoyal74...
catch’s picture

Issue tags: +9.1.0 release notes

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.