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());
+ }
+}