diff --git a/src/Illuminate/Database/DatabaseTransactionsManager.php b/src/Illuminate/Database/DatabaseTransactionsManager.php index a2b9932e72ee..0122971f33fb 100755 --- a/src/Illuminate/Database/DatabaseTransactionsManager.php +++ b/src/Illuminate/Database/DatabaseTransactionsManager.php @@ -170,6 +170,10 @@ protected function removeAllTransactionsForConnection($connection) fn ($transaction) => $transaction->connection == $connection )->values(); + $this->committedTransactions + ->filter(fn ($transaction) => $transaction->connection == $connection) + ->each(fn ($transaction) => $transaction->executeCallbacksForRollback()); + $this->committedTransactions = $this->committedTransactions->reject( fn ($transaction) => $transaction->connection == $connection )->values(); @@ -188,6 +192,8 @@ protected function removeCommittedTransactionsThatAreChildrenOf(DatabaseTransact $committed->parent === $transaction ); + $removedTransactions->each(fn ($transaction) => $transaction->executeCallbacksForRollback()); + // There may be multiple deeply nested transactions that have already committed that we // also need to remove. We will recurse down the children of all removed transaction // instances until there are no more deeply nested child transactions for removal. diff --git a/tests/Database/DatabaseTransactionsManagerTest.php b/tests/Database/DatabaseTransactionsManagerTest.php index 9cd4b6d0105d..36a343d79501 100755 --- a/tests/Database/DatabaseTransactionsManagerTest.php +++ b/tests/Database/DatabaseTransactionsManagerTest.php @@ -253,6 +253,59 @@ public function testRollbackTransactionsExecutesCallbacks() $this->assertEquals(['default', 1], $callbacks[1]); } + public function testRollbackExecutesCallbacksForCommittedSavepointsWhenOuterRollsBack() + { + $callbacks = []; + + $manager = new DatabaseTransactionsManager; + + $manager->begin('default', 1); + + $manager->addCallbackForRollback(function () use (&$callbacks) { + $callbacks[] = ['default', 1]; + }); + + $manager->begin('default', 2); + + $manager->addCallbackForRollback(function () use (&$callbacks) { + $callbacks[] = ['default', 2]; + }); + + $manager->commit('default', 2, 1); + $manager->rollback('default', 0); + + $this->assertCount(2, $callbacks); + $this->assertContains(['default', 2], $callbacks); + $this->assertContains(['default', 1], $callbacks); + } + + public function testPartialRollbackExecutesCallbacksForCommittedDescendantSavepoints() + { + $callbacks = []; + + $manager = new DatabaseTransactionsManager; + + $manager->begin('default', 1); + $manager->begin('default', 2); + + $manager->addCallbackForRollback(function () use (&$callbacks) { + $callbacks[] = ['default', 2]; + }); + + $manager->begin('default', 3); + + $manager->addCallbackForRollback(function () use (&$callbacks) { + $callbacks[] = ['default', 3]; + }); + + $manager->commit('default', 3, 2); + $manager->rollback('default', 1); + + $this->assertCount(2, $callbacks); + $this->assertContains(['default', 3], $callbacks); + $this->assertContains(['default', 2], $callbacks); + } + public function testRollbackExecutesOnlyCallbacksOfTheConnection() { $callbacks = [];