Commit 31098001226 for php

commit 31098001226e6a39f251c9ad78328d4c0bf4a231
Author: Lazizbek Ergashev <lazerg2@gmail.com>
Date:   Tue Sep 29 02:52:45 2026 +0500

    Fix GH-23962: Don't roll back a persistent PDO handle still in use (#23963)

diff --git a/NEWS b/NEWS
index d36d4b56bfc..4ea8ab37052 100644
--- a/NEWS
+++ b/NEWS
@@ -92,6 +92,8 @@ PHP                                                                        NEWS
     column index. (Ilia Alshanetsky)
   . Fixed PDOStatement::bindColumn() registering a binding for a column name
     that is not in the result set. (Ilia Alshanetsky)
+  . Fixed bug GH-23962 (Destroying a persistent PDO instance rolls back a
+    transaction still in use by another instance). (Lazizbek Ergashev)

 - Readline:
   . Fixed a heap over-read in the interactive shell prompt when cli.prompt is
diff --git a/ext/pdo/pdo_dbh.c b/ext/pdo/pdo_dbh.c
index 9f3d20745df..1dc43c2b972 100644
--- a/ext/pdo/pdo_dbh.c
+++ b/ext/pdo/pdo_dbh.c
@@ -1521,7 +1521,9 @@ static void pdo_dbh_free_storage(zend_object *std)
 		return;
 	}

-	if (dbh->driver_data && dbh->methods && dbh->methods->rollback && pdo_is_in_transaction(dbh)) {
+	/* The persistent list holds one reference, other objects may hold the rest */
+	if (dbh->driver_data && dbh->methods && dbh->methods->rollback
+			&& (!dbh->is_persistent || dbh->refcount <= 2) && pdo_is_in_transaction(dbh)) {
 		dbh->methods->rollback(dbh);
 		dbh->in_txn = false;
 	}
diff --git a/ext/pdo_sqlite/tests/gh23962.phpt b/ext/pdo_sqlite/tests/gh23962.phpt
new file mode 100644
index 00000000000..d4c1cc4e4e0
--- /dev/null
+++ b/ext/pdo_sqlite/tests/gh23962.phpt
@@ -0,0 +1,31 @@
+--TEST--
+GH-23962 (Destroying a persistent PDO instance rolls back a transaction still in use by another instance)
+--CREDITS--
+BoyeMagnus
+--EXTENSIONS--
+pdo_sqlite
+--FILE--
+<?php
+$dsn = 'sqlite::memory:';
+
+$a = new PDO($dsn, null, null, [PDO::ATTR_PERSISTENT => true]);
+$b = new PDO($dsn, null, null, [PDO::ATTR_PERSISTENT => true]);
+
+$b->beginTransaction();
+unset($a);
+var_dump($b->inTransaction());
+var_dump($b->commit());
+
+$b->beginTransaction();
+$b->exec('CREATE TABLE test (a int)');
+unset($b);
+
+$c = new PDO($dsn, null, null, [PDO::ATTR_PERSISTENT => true]);
+var_dump($c->inTransaction());
+var_dump($c->query("SELECT count(*) FROM sqlite_master WHERE name = 'test'")->fetchColumn());
+?>
+--EXPECT--
+bool(true)
+bool(true)
+bool(false)
+int(0)