diff --git a/src/Illuminate/Database/Concerns/ManagesTransactions.php b/src/Illuminate/Database/Concerns/ManagesTransactions.php index a690f7b5cb46..8f7dc5277b14 100644 --- a/src/Illuminate/Database/Concerns/ManagesTransactions.php +++ b/src/Illuminate/Database/Concerns/ManagesTransactions.php @@ -117,9 +117,10 @@ public function beginTransaction() $this->createTransaction(); $this->transactions++; + $this->uniqueTransactionsCounter++; $this->transactionsManager?->begin( - $this->getName(), $this->transactions + $this->getName(), $this->transactions, $this->uniqueTransactionsCounter ); $this->fireConnectionEvent('beganTransaction'); @@ -271,7 +272,7 @@ public function rollBack($toLevel = null) $this->transactions = $toLevel; $this->transactionsManager?->rollback( - $this->getName(), $this->transactions + $this->getName(), $this->transactions, $this->uniqueTransactionsCounter ); $this->fireConnectionEvent('rollingBack'); diff --git a/src/Illuminate/Database/Connection.php b/src/Illuminate/Database/Connection.php index 24583f4a4079..0f9231a7d0e0 100755 --- a/src/Illuminate/Database/Connection.php +++ b/src/Illuminate/Database/Connection.php @@ -126,6 +126,13 @@ class Connection implements ConnectionInterface */ protected $transactions = 0; + /** + * The global counter for the unique number of transactions. + * + * @var int + */ + protected $uniqueTransactionsCounter = 0; + /** * The transaction manager instance. * diff --git a/src/Illuminate/Database/DatabaseTransactionRecord.php b/src/Illuminate/Database/DatabaseTransactionRecord.php index 4736ee9224d2..d14fb5d96003 100755 --- a/src/Illuminate/Database/DatabaseTransactionRecord.php +++ b/src/Illuminate/Database/DatabaseTransactionRecord.php @@ -18,6 +18,13 @@ class DatabaseTransactionRecord */ public $level; + /** + * The transaction counter. + * + * @var int + */ + public $counter; + /** * The callbacks that should be executed after committing. * @@ -30,12 +37,14 @@ class DatabaseTransactionRecord * * @param string $connection * @param int $level + * @param int $counter * @return void */ - public function __construct($connection, $level) + public function __construct($connection, $level, $counter) { $this->connection = $connection; $this->level = $level; + $this->counter = $counter; } /** diff --git a/src/Illuminate/Database/DatabaseTransactionsManager.php b/src/Illuminate/Database/DatabaseTransactionsManager.php index e198f4f3f6d6..714b1d593fa1 100755 --- a/src/Illuminate/Database/DatabaseTransactionsManager.php +++ b/src/Illuminate/Database/DatabaseTransactionsManager.php @@ -26,12 +26,13 @@ public function __construct() * * @param string $connection * @param int $level + * @param int $counter * @return void */ - public function begin($connection, $level) + public function begin($connection, $level, $counter) { $this->transactions->push( - new DatabaseTransactionRecord($connection, $level) + new DatabaseTransactionRecord($connection, $level, $counter) ); } @@ -40,12 +41,13 @@ public function begin($connection, $level) * * @param string $connection * @param int $level + * @param int $counter * @return void */ - public function rollback($connection, $level) + public function rollback($connection, $level, $counter) { $this->transactions = $this->transactions->reject( - fn ($transaction) => $transaction->connection == $connection && $transaction->level > $level + fn ($transaction) => $transaction->connection == $connection && $transaction->level > $level && $transaction->counter === $counter )->values(); } diff --git a/tests/Integration/Database/DatabaseTransactionsTest.php b/tests/Integration/Database/DatabaseTransactionsTest.php index 79e8596891ee..a2a8c74cfda2 100644 --- a/tests/Integration/Database/DatabaseTransactionsTest.php +++ b/tests/Integration/Database/DatabaseTransactionsTest.php @@ -14,6 +14,9 @@ public function testTransactionCallbacksDoNotInterfereWithOneAnother() new TestObjectForTransactions(), ]; + // The problem here is that we're initiating a base transaction, and then two nested transactions. + // Although these two nested transactions are not the same, they share the same level (2). + // Since they are not the same, the latter one failing should not affect the first one. DB::transaction(function () use ($thirdObject, $secondObject, $firstObject) { // Adds a transaction @ level 1 DB::transaction(function () use ($firstObject) { // Adds a transaction @ level 2 DB::afterCommit(fn () => $firstObject->handle()); // Adds a callback to be executed after transaction level 2 is committed