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.
Comment | File | Size | Author |
---|---|---|---|
#86 | 3174662-86.patch | 33.44 KB | alexpott |
#86 | 85-86-interdiff.txt | 3.67 KB | alexpott |
#85 | 3174662-84.patch | 31.24 KB | alexpott |
#85 | 75-84-interdiff.txt | 5.61 KB | alexpott |
#75 | 3174662-75.patch | 34.05 KB | andypost |
Comments
Comment #2
mondrakeNote: I think the reason why currently Statement extends from PDO is because of these lines in Drupal\Core\Database\Connection::__construct:
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.
Comment #3
andypostInteresting 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
Comment #4
mondrakeWorking on this.
Comment #5
mondrakeThis is a first concept patch. The actual PDOStatement object is opened during the Drupal Statement construction, then stored in a property for use.
Comment #7
mondrakeComment #8
mondrakeComment #11
mondrakeComment #12
mondrakeReshuffling and trying to extend to pgsql, after mysql.
Comment #13
mondrakeComment #14
mondrakeI suppose this is no longer an experiment...
Comment #15
andypostQuick review
I bet it always object|null
both needs deprecation tests
Why it's not calling parent destructor?
Also it could use to change existing method to minify diff
Why this public?
needs docs
Comment #16
andypostAlso for TODO link
Comment #18
mondrakeThanks for review @andypost
Comment #20
mondrakeMinimising 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
Comment #21
mondrakeBumping 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.
Comment #22
mondrakeComment #23
mondrakeOn 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
Comment #24
mondrakeDone #23.
Comment #26
mondrakeComment #27
mondrakeComment #28
mondrakeComment #29
alexpottThis looks a bit awkward. If we're using the StatementWrapper class shouldn't the deprecated path but to return $statement?
I don't think we should use the statementClass system for this. I think we should add a new protected property called $statementWrapper instead.
Comment #30
alexpottDo you think we should document that this should only be called from the database driver?
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?
Comment #31
mondrake#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.
Comment #32
mondrakeFixed #30.1 and added a test for the deprecated part of StatementWrapper.
Comment #33
mondrakeOps, sorry.
Comment #34
alexpottRE #29.3 I think the wrapper thing should be separate from $statementClass... ie. looking at the patch:
Should look something like:
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.
Comment #35
mondrake#34 Gotcha, thanks. On this.
Comment #36
alexpottI 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.
Comment #37
mondrakeAddressing:
#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.
Comment #39
mondrake#36.2 added a kernel test to use real PDO backend objects instead of mocks.
Comment #41
mondrakemysql 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.
Comment #42
mondrakeComment #43
mondrakeToo much hurry.
Comment #44
mondrakeComment #46
mondrake#45 seems a random test fail.
Comment #47
mondrakeNote: 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.
Comment #48
andypost++ to #47 PDO has extensive testing in PHP itself, probably it needs follow-up to review current test suite
Comment #49
catchThis looks very encouraging, still need to do a thorough review but +1 to the general approach.
Comment #50
alexpottI 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.
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.
Comment #51
mondrake#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.
Comment #52
alexpott@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.
Comment #53
mondrakeOK, I'm on #50.1.
Comment #54
mondrakeDone #50.1.
Comment #55
alexpottI 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.
Comment #56
mondrake#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.Comment #57
mondrakeAlso, consistency: in StatementPrefetch (used by SQLite), prepare is called within the statement, and we pass both Drupal connection and PDO connection.
Comment #58
alexpott#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.
Comment #59
mondrakeComment #60
mondrakeRerolled
Comment #61
hussainwebI 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.
Comment #62
mondrakeComment #63
mondrakeRerolled
Comment #64
andypostDrafted CR https://www.drupal.org/node/3177488 and follow-up for TODO #3177490: Remove \Drupal\Core\Database\Connection::$statementClass
Re-roll and fix for CR and TODO links
Comment #66
alexpottThere are some things that need to be fixed
Comment #67
hussainwebFixing the above issues and changing deprecated text to read that this will be deprecated in Drupal 9.2.0 instead of 9.1.0.
Comment #68
hussainwebI 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.
Comment #69
alexpottFor the cspell stuff we can add
to the file. With PHP 8.0 parameter names are now part of the API!!!
Comment #70
andypostRemaining 2 cs errors looks related to #3137883: Deprecate passing a StatementInterface object to Connection::query
Comment #71
alexpottThis will fail in PHP 8 - because the class is not compatible with it at all. So we should add
Comment #72
anmolgoyal74 CreditAttribution: anmolgoyal74 at OpenSense Labs for DrupalFit commentedAddressed #71
Comment #73
andypostWith a link to remove
Comment #74
alexpottCan someone address #69
Comment #75
andypostFix for #69 and CS
Comment #76
alexpottI 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.
Comment #77
mondrake#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.
Comment #78
catchThis 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:
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?
Comment #79
mondrake#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.
Comment #80
alexpott@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.
Comment #81
alexpottDidn't mean to change the status (yet)
Comment #82
mondrake#75 it looks like there are some field related hunks in the patch from other issues?
Comment #83
catchFair enough in #79, let's open a follow-up.
Comment #84
mondrakeFiled #3177660: Remove public properties from StatementInterface implementations.
Comment #85
alexpottDiscussed 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.
Comment #86
alexpottFixing StubConnection to not use the deprecated
\Drupal\Core\Database\Connection::$statementClass
and also properly deprecating that property and testing said deprecation.Comment #87
alexpottComment #88
alexpottI think this is now rtbc.
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.
Comment #90
catchThis 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!
Comment #92
catch