From c8eecae38e8cf65c1e18bafe2788573b0a89a095 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Wed, 9 Oct 2024 03:48:33 +0200 Subject: [PATCH] Leverage the new PDO subclasses --- UPGRADE.md | 8 +++ phpstan.neon.dist | 4 ++ psalm.xml.dist | 16 ++++++ src/Driver/PDO/MySQL/Driver.php | 5 +- src/Driver/PDO/OCI/Driver.php | 5 +- src/Driver/PDO/PDOConnect.php | 28 ++++++++++ src/Driver/PDO/PgSQL/Driver.php | 5 +- src/Driver/PDO/SQLSrv/Driver.php | 5 +- src/Driver/PDO/SQLite/Driver.php | 6 ++- .../Functional/Driver/PDO/PDOSubclassTest.php | 53 +++++++++++++++++++ 10 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 src/Driver/PDO/PDOConnect.php create mode 100644 tests/Functional/Driver/PDO/PDOSubclassTest.php diff --git a/UPGRADE.md b/UPGRADE.md index 2a383739bc7..32d5945e257 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -8,6 +8,14 @@ awareness about deprecated code. # Upgrade to 4.2 +## Support for new PDO subclasses on PHP 8.4 + +On PHP 8.4, if you call `getNativeConnection()` on a connection established through one of the PDO drivers, +you will get an instance of the new PDO subclasses, e.g. `Pdo\Mysql` or `Pdo\Ppgsql` instead of just `PDO`. + +However, this currently does not apply to persistent connections. +See https://github.com/php/php-src/issues/16314 for details. + ## Minor BC break: incompatible query cache format The query cache format has been changed to address the issue where a cached result with no rows would miss the metadata. diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 2a04d32d39c..8ff5ab63b79 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -115,6 +115,10 @@ parameters: # Type check for legacy implementations of the Result interface # TODO: remove in 5.0.0 - '~^Call to function method_exists\(\) with Doctrine\\DBAL\\Driver\\Result and ''getColumnName'' will always evaluate to true\.$~' + + # PHPStan does not know the new PDO classes yet. + - '~^Class Pdo\\\w+ not found\.$~' + - '~^Call to an undefined static method PDO\:\:connect\(\)\.$~' includes: - vendor/phpstan/phpstan-phpunit/extension.neon - vendor/phpstan/phpstan-phpunit/rules.neon diff --git a/psalm.xml.dist b/psalm.xml.dist index af28777385c..37858d3723e 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -293,6 +293,16 @@ + + + + + + + + + + @@ -304,6 +314,12 @@ + + + + + + diff --git a/src/Driver/PDO/MySQL/Driver.php b/src/Driver/PDO/MySQL/Driver.php index fad4b55954c..919f8475583 100644 --- a/src/Driver/PDO/MySQL/Driver.php +++ b/src/Driver/PDO/MySQL/Driver.php @@ -8,6 +8,7 @@ use Doctrine\DBAL\Driver\PDO\Connection; use Doctrine\DBAL\Driver\PDO\Exception; use Doctrine\DBAL\Driver\PDO\Exception\InvalidConfiguration; +use Doctrine\DBAL\Driver\PDO\PDOConnect; use PDO; use PDOException; use SensitiveParameter; @@ -16,6 +17,8 @@ final class Driver extends AbstractMySQLDriver { + use PDOConnect; + /** * {@inheritDoc} */ @@ -39,7 +42,7 @@ public function connect( unset($safeParams['password']); try { - $pdo = new PDO( + $pdo = $this->doConnect( $this->constructPdoDsn($safeParams), $params['user'] ?? '', $params['password'] ?? '', diff --git a/src/Driver/PDO/OCI/Driver.php b/src/Driver/PDO/OCI/Driver.php index 45f3ea25b01..49882b0d6cb 100644 --- a/src/Driver/PDO/OCI/Driver.php +++ b/src/Driver/PDO/OCI/Driver.php @@ -8,6 +8,7 @@ use Doctrine\DBAL\Driver\PDO\Connection; use Doctrine\DBAL\Driver\PDO\Exception; use Doctrine\DBAL\Driver\PDO\Exception\InvalidConfiguration; +use Doctrine\DBAL\Driver\PDO\PDOConnect; use PDO; use PDOException; use SensitiveParameter; @@ -16,6 +17,8 @@ final class Driver extends AbstractOracleDriver { + use PDOConnect; + /** * {@inheritDoc} */ @@ -39,7 +42,7 @@ public function connect( unset($safeParams['password']); try { - $pdo = new PDO( + $pdo = $this->doConnect( $this->constructPdoDsn($params), $params['user'] ?? '', $params['password'] ?? '', diff --git a/src/Driver/PDO/PDOConnect.php b/src/Driver/PDO/PDOConnect.php new file mode 100644 index 00000000000..f14a97c07aa --- /dev/null +++ b/src/Driver/PDO/PDOConnect.php @@ -0,0 +1,28 @@ + $options */ + private function doConnect( + string $dsn, + string $username, + string $password, + array $options, + ): PDO { + // see https://github.com/php/php-src/issues/16314 + if (PHP_VERSION_ID < 80400 || ($options[PDO::ATTR_PERSISTENT] ?? false) === true) { + return new PDO($dsn, $username, $password, $options); + } + + return PDO::connect($dsn, $username, $password, $options); + } +} diff --git a/src/Driver/PDO/PgSQL/Driver.php b/src/Driver/PDO/PgSQL/Driver.php index 39e8a9452f1..cdf411aff54 100644 --- a/src/Driver/PDO/PgSQL/Driver.php +++ b/src/Driver/PDO/PgSQL/Driver.php @@ -8,6 +8,7 @@ use Doctrine\DBAL\Driver\PDO\Connection; use Doctrine\DBAL\Driver\PDO\Exception; use Doctrine\DBAL\Driver\PDO\Exception\InvalidConfiguration; +use Doctrine\DBAL\Driver\PDO\PDOConnect; use PDO; use PDOException; use SensitiveParameter; @@ -16,6 +17,8 @@ final class Driver extends AbstractPostgreSQLDriver { + use PDOConnect; + /** * {@inheritDoc} */ @@ -39,7 +42,7 @@ public function connect( unset($safeParams['password']); try { - $pdo = new PDO( + $pdo = $this->doConnect( $this->constructPdoDsn($safeParams), $params['user'] ?? '', $params['password'] ?? '', diff --git a/src/Driver/PDO/SQLSrv/Driver.php b/src/Driver/PDO/SQLSrv/Driver.php index 7950c41a5e5..2d7490b8ae3 100644 --- a/src/Driver/PDO/SQLSrv/Driver.php +++ b/src/Driver/PDO/SQLSrv/Driver.php @@ -10,6 +10,7 @@ use Doctrine\DBAL\Driver\PDO\Connection as PDOConnection; use Doctrine\DBAL\Driver\PDO\Exception as PDOException; use Doctrine\DBAL\Driver\PDO\Exception\InvalidConfiguration; +use Doctrine\DBAL\Driver\PDO\PDOConnect; use PDO; use SensitiveParameter; @@ -19,6 +20,8 @@ final class Driver extends AbstractSQLServerDriver { + use PDOConnect; + /** * {@inheritDoc} */ @@ -52,7 +55,7 @@ public function connect( unset($safeParams['password']); try { - $pdo = new PDO( + $pdo = $this->doConnect( $this->constructDsn($safeParams, $dsnOptions), $params['user'] ?? '', $params['password'] ?? '', diff --git a/src/Driver/PDO/SQLite/Driver.php b/src/Driver/PDO/SQLite/Driver.php index 4bef08bf997..fbd4187532d 100644 --- a/src/Driver/PDO/SQLite/Driver.php +++ b/src/Driver/PDO/SQLite/Driver.php @@ -8,7 +8,7 @@ use Doctrine\DBAL\Driver\PDO\Connection; use Doctrine\DBAL\Driver\PDO\Exception; use Doctrine\DBAL\Driver\PDO\Exception\InvalidConfiguration; -use PDO; +use Doctrine\DBAL\Driver\PDO\PDOConnect; use PDOException; use SensitiveParameter; @@ -17,6 +17,8 @@ final class Driver extends AbstractSQLiteDriver { + use PDOConnect; + /** * {@inheritDoc} */ @@ -31,7 +33,7 @@ public function connect( } try { - $pdo = new PDO( + $pdo = $this->doConnect( $this->constructPdoDsn(array_intersect_key($params, ['path' => true, 'memory' => true])), $params['user'] ?? '', $params['password'] ?? '', diff --git a/tests/Functional/Driver/PDO/PDOSubclassTest.php b/tests/Functional/Driver/PDO/PDOSubclassTest.php new file mode 100644 index 00000000000..619b7c596ea --- /dev/null +++ b/tests/Functional/Driver/PDO/PDOSubclassTest.php @@ -0,0 +1,53 @@ +connection->getNativeConnection()); + } + + public function testOCISubclass(): void + { + if (! TestUtil::isDriverOneOf('pdo_oci')) { + self::markTestSkipped('This test requires the pdo_oci driver.'); + } + + self::assertInstanceOf(Oci::class, $this->connection->getNativeConnection()); + } + + public function testPgSQLSubclass(): void + { + if (! TestUtil::isDriverOneOf('pdo_pgsql')) { + self::markTestSkipped('This test requires the pdo_pgsql driver.'); + } + + self::assertInstanceOf(Pgsql::class, $this->connection->getNativeConnection()); + } + + public function testSQLiteSubclass(): void + { + if (! TestUtil::isDriverOneOf('pdo_sqlite')) { + self::markTestSkipped('This test requires the pdo_sqlite driver.'); + } + + self::assertInstanceOf(Sqlite::class, $this->connection->getNativeConnection()); + } +}