diff --git a/src/Illuminate/Database/Concerns/ManagesTransactions.php b/src/Illuminate/Database/Concerns/ManagesTransactions.php index 8ab8517f7ce1..d8932d4ee092 100644 --- a/src/Illuminate/Database/Concerns/ManagesTransactions.php +++ b/src/Illuminate/Database/Concerns/ManagesTransactions.php @@ -47,7 +47,7 @@ public function transaction(Closure $callback, $attempts = 1) $this->transactions = max(0, $this->transactions - 1); - if ($this->transactions == 0) { + if ($this->afterCommitCallbacksShouldBeExecuted()) { $this->transactionsManager?->commit($this->getName()); } } catch (Throwable $e) { @@ -193,13 +193,25 @@ public function commit() $this->transactions = max(0, $this->transactions - 1); - if ($this->transactions == 0) { + if ($this->afterCommitCallbacksShouldBeExecuted()) { $this->transactionsManager?->commit($this->getName()); } $this->fireConnectionEvent('committed'); } + /** + * Determine if after commit callbacks should be executed. + * + * @return bool + */ + protected function afterCommitCallbacksShouldBeExecuted() + { + return $this->transactions == 0 || + ($this->transactionsManager && + $this->transactionsManager->callbackApplicableTransactions()->count() === 1); + } + /** * Handle an exception encountered when committing a transaction. * diff --git a/src/Illuminate/Database/DatabaseTransactionsManager.php b/src/Illuminate/Database/DatabaseTransactionsManager.php index cb137ec87176..8d145188f065 100755 --- a/src/Illuminate/Database/DatabaseTransactionsManager.php +++ b/src/Illuminate/Database/DatabaseTransactionsManager.php @@ -11,6 +11,13 @@ class DatabaseTransactionsManager */ protected $transactions; + /** + * The database transaction that should be ignored by callbacks. + * + * @var \Illuminate\Database\DatabaseTransactionRecord + */ + protected $callbacksShouldIgnore; + /** * Create a new database transactions manager instance. * @@ -47,6 +54,10 @@ public function rollback($connection, $level) $this->transactions = $this->transactions->reject( fn ($transaction) => $transaction->connection == $connection && $transaction->level > $level )->values(); + + if ($this->transactions->isEmpty()) { + $this->callbacksShouldIgnore = null; + } } /** @@ -64,6 +75,10 @@ public function commit($connection) $this->transactions = $forOtherConnections->values(); $forThisConnection->map->executeCallbacks(); + + if ($this->transactions->isEmpty()) { + $this->callbacksShouldIgnore = null; + } } /** @@ -74,13 +89,38 @@ public function commit($connection) */ public function addCallback($callback) { - if ($current = $this->transactions->last()) { + if ($current = $this->callbackApplicableTransactions()->last()) { return $current->addCallback($callback); } $callback(); } + /** + * Specify that callbacks should ignore the given transaction when determining if they should be executed. + * + * @param \Illuminate\Database\DatabaseTransactionRecord $transaction + * @return $this + */ + public function callbacksShouldIgnore(DatabaseTransactionRecord $transaction) + { + $this->callbacksShouldIgnore = $transaction; + + return $this; + } + + /** + * Get the transactions that are applicable to callbacks. + * + * @return \Illuminate\Support\Collection + */ + public function callbackApplicableTransactions() + { + return $this->transactions->reject(function ($transaction) { + return $transaction === $this->callbacksShouldIgnore; + })->values(); + } + /** * Get all the transactions. * diff --git a/src/Illuminate/Foundation/Testing/RefreshDatabase.php b/src/Illuminate/Foundation/Testing/RefreshDatabase.php index 3239d5d762b6..ef37e2166f81 100644 --- a/src/Illuminate/Foundation/Testing/RefreshDatabase.php +++ b/src/Illuminate/Foundation/Testing/RefreshDatabase.php @@ -93,6 +93,12 @@ public function beginDatabaseTransaction() $connection->unsetEventDispatcher(); $connection->beginTransaction(); $connection->setEventDispatcher($dispatcher); + + if ($this->app->resolved('db.transactions')) { + $this->app->make('db.transactions')->callbacksShouldIgnore( + $this->app->make('db.transactions')->getTransactions()->first() + ); + } } $this->beforeApplicationDestroyed(function () use ($database) {