From c3ecd966ffc2aaf90e0f4e84352b3099832ccfe7 Mon Sep 17 00:00:00 2001 From: mpyw Date: Wed, 20 Sep 2023 18:46:15 +0900 Subject: [PATCH] Fix nested transaction callbacks handling --- .../Database/Concerns/ManagesTransactions.php | 22 +------- .../Database/DatabaseTransactionsManager.php | 50 ++++++++++++++++--- 2 files changed, 45 insertions(+), 27 deletions(-) diff --git a/src/Illuminate/Database/Concerns/ManagesTransactions.php b/src/Illuminate/Database/Concerns/ManagesTransactions.php index 14661cc76ebf..6254076b8901 100644 --- a/src/Illuminate/Database/Concerns/ManagesTransactions.php +++ b/src/Illuminate/Database/Concerns/ManagesTransactions.php @@ -48,10 +48,7 @@ public function transaction(Closure $callback, $attempts = 1) } $this->transactions = max(0, $this->transactions - 1); - - if ($this->afterCommitCallbacksShouldBeExecuted()) { - $this->transactionsManager?->commit($this->getName()); - } + $this->transactionsManager?->commit($this->getName()); } catch (Throwable $e) { $this->handleCommitTransactionException( $e, $currentAttempt, $attempts @@ -195,26 +192,11 @@ public function commit() } $this->transactions = max(0, $this->transactions - 1); - - if ($this->afterCommitCallbacksShouldBeExecuted()) { - $this->transactionsManager?->commit($this->getName()); - } + $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 8d145188f065..af24e6184c40 100755 --- a/src/Illuminate/Database/DatabaseTransactionsManager.php +++ b/src/Illuminate/Database/DatabaseTransactionsManager.php @@ -68,19 +68,53 @@ public function rollback($connection, $level) */ public function commit($connection) { - [$forThisConnection, $forOtherConnections] = $this->transactions->partition( + if ($this->afterCommitCallbacksShouldBeExecuted($connection)) { + [$forThisConnection, $forOtherConnections] = $this->transactions->partition( + fn ($transaction) => $transaction->connection == $connection + ); + + $this->transactions = $forOtherConnections->values(); + + $forThisConnection->map->executeCallbacks(); + + if ($this->transactions->isEmpty()) { + $this->callbacksShouldIgnore = null; + } + + return; + } + + $last = $this->transactions->last( fn ($transaction) => $transaction->connection == $connection ); + $parent = $this->transactions->last( + fn ($transaction) => $transaction->connection == $connection && $last !== $transaction, + ); - $this->transactions = $forOtherConnections->values(); + $this->transactions = $this->transactions->reject( + fn ($transaction) => $transaction === $last, + )->values(); - $forThisConnection->map->executeCallbacks(); + foreach ($last?->getCallbacks() ?? [] as $callback) { + $parent?->addCallback($callback); + } if ($this->transactions->isEmpty()) { $this->callbacksShouldIgnore = null; } } + /** + * Determine if after commit callbacks should be executed. + * + * @param string $connection + * @return bool + */ + protected function afterCommitCallbacksShouldBeExecuted($connection) + { + return $this->callbackApplicableTransactions($connection)->count() === 1; + } + /** * Register a transaction callback. * @@ -112,13 +146,15 @@ public function callbacksShouldIgnore(DatabaseTransactionRecord $transaction) /** * Get the transactions that are applicable to callbacks. * + * @param string $connection * @return \Illuminate\Support\Collection */ - public function callbackApplicableTransactions() + public function callbackApplicableTransactions($connection) { - return $this->transactions->reject(function ($transaction) { - return $transaction === $this->callbacksShouldIgnore; - })->values(); + return $this->transactions->reject( + fn ($transaction) => $transaction->connection == $connection + && $transaction === $this->callbacksShouldIgnore, + )->values(); } /**