Viewing file: ManagesTransactions.php (9.32 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
<?php
namespace Illuminate\Database\Concerns;
use Closure; use Illuminate\Database\DeadlockException; use RuntimeException; use Throwable;
trait ManagesTransactions { /** * Execute a Closure within a transaction. * * @param \Closure $callback * @param int $attempts * @return mixed * * @throws \Throwable */ public function transaction(Closure $callback, $attempts = 1) { for ($currentAttempt = 1; $currentAttempt <= $attempts; $currentAttempt++) { $this->beginTransaction();
// We'll simply execute the given callback within a try / catch block and if we // catch any exception we can rollback this transaction so that none of this // gets actually persisted to a database or stored in a permanent fashion. try { $callbackResult = $callback($this); }
// If we catch an exception we'll rollback this transaction and try again if we // are not out of attempts. If we are out of attempts we will just throw the // exception back out, and let the developer handle an uncaught exception. catch (Throwable $e) { $this->handleTransactionException( $e, $currentAttempt, $attempts );
continue; }
try { if ($this->transactions == 1) { $this->getPdo()->commit(); }
$this->transactions = max(0, $this->transactions - 1);
if ($this->afterCommitCallbacksShouldBeExecuted()) { $this->transactionsManager?->commit($this->getName()); } } catch (Throwable $e) { $this->handleCommitTransactionException( $e, $currentAttempt, $attempts );
continue; }
$this->fireConnectionEvent('committed');
return $callbackResult; } }
/** * Handle an exception encountered when running a transacted statement. * * @param \Throwable $e * @param int $currentAttempt * @param int $maxAttempts * @return void * * @throws \Throwable */ protected function handleTransactionException(Throwable $e, $currentAttempt, $maxAttempts) { // On a deadlock, MySQL rolls back the entire transaction so we can't just // retry the query. We have to throw this exception all the way out and // let the developer handle it in another way. We will decrement too. if ($this->causedByConcurrencyError($e) && $this->transactions > 1) { $this->transactions--;
$this->transactionsManager?->rollback( $this->getName(), $this->transactions );
throw new DeadlockException($e->getMessage(), is_int($e->getCode()) ? $e->getCode() : 0, $e); }
// If there was an exception we will rollback this transaction and then we // can check if we have exceeded the maximum attempt count for this and // if we haven't we will return and try this query again in our loop. $this->rollBack();
if ($this->causedByConcurrencyError($e) && $currentAttempt < $maxAttempts) { return; }
throw $e; }
/** * Start a new database transaction. * * @return void * * @throws \Throwable */ public function beginTransaction() { $this->createTransaction();
$this->transactions++;
$this->transactionsManager?->begin( $this->getName(), $this->transactions );
$this->fireConnectionEvent('beganTransaction'); }
/** * Create a transaction within the database. * * @return void * * @throws \Throwable */ protected function createTransaction() { if ($this->transactions == 0) { $this->reconnectIfMissingConnection();
try { $this->getPdo()->beginTransaction(); } catch (Throwable $e) { $this->handleBeginTransactionException($e); } } elseif ($this->transactions >= 1 && $this->queryGrammar->supportsSavepoints()) { $this->createSavepoint(); } }
/** * Create a save point within the database. * * @return void * * @throws \Throwable */ protected function createSavepoint() { $this->getPdo()->exec( $this->queryGrammar->compileSavepoint('trans'.($this->transactions + 1)) ); }
/** * Handle an exception from a transaction beginning. * * @param \Throwable $e * @return void * * @throws \Throwable */ protected function handleBeginTransactionException(Throwable $e) { if ($this->causedByLostConnection($e)) { $this->reconnect();
$this->getPdo()->beginTransaction(); } else { throw $e; } }
/** * Commit the active database transaction. * * @return void * * @throws \Throwable */ public function commit() { if ($this->transactions == 1) { $this->getPdo()->commit(); }
$this->transactions = max(0, $this->transactions - 1);
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. * * @param \Throwable $e * @param int $currentAttempt * @param int $maxAttempts * @return void * * @throws \Throwable */ protected function handleCommitTransactionException(Throwable $e, $currentAttempt, $maxAttempts) { $this->transactions = max(0, $this->transactions - 1);
if ($this->causedByConcurrencyError($e) && $currentAttempt < $maxAttempts) { return; }
if ($this->causedByLostConnection($e)) { $this->transactions = 0; }
throw $e; }
/** * Rollback the active database transaction. * * @param int|null $toLevel * @return void * * @throws \Throwable */ public function rollBack($toLevel = null) { // We allow developers to rollback to a certain transaction level. We will verify // that this given transaction level is valid before attempting to rollback to // that level. If it's not we will just return out and not attempt anything. $toLevel = is_null($toLevel) ? $this->transactions - 1 : $toLevel;
if ($toLevel < 0 || $toLevel >= $this->transactions) { return; }
// Next, we will actually perform this rollback within this database and fire the // rollback event. We will also set the current transaction level to the given // level that was passed into this method so it will be right from here out. try { $this->performRollBack($toLevel); } catch (Throwable $e) { $this->handleRollBackException($e); }
$this->transactions = $toLevel;
$this->transactionsManager?->rollback( $this->getName(), $this->transactions );
$this->fireConnectionEvent('rollingBack'); }
/** * Perform a rollback within the database. * * @param int $toLevel * @return void * * @throws \Throwable */ protected function performRollBack($toLevel) { if ($toLevel == 0) { $this->getPdo()->rollBack(); } elseif ($this->queryGrammar->supportsSavepoints()) { $this->getPdo()->exec( $this->queryGrammar->compileSavepointRollBack('trans'.($toLevel + 1)) ); } }
/** * Handle an exception from a rollback. * * @param \Throwable $e * @return void * * @throws \Throwable */ protected function handleRollBackException(Throwable $e) { if ($this->causedByLostConnection($e)) { $this->transactions = 0;
$this->transactionsManager?->rollback( $this->getName(), $this->transactions ); }
throw $e; }
/** * Get the number of active transactions. * * @return int */ public function transactionLevel() { return $this->transactions; }
/** * Execute the callback after a transaction commits. * * @param callable $callback * @return void * * @throws \RuntimeException */ public function afterCommit($callback) { if ($this->transactionsManager) { return $this->transactionsManager->addCallback($callback); }
throw new RuntimeException('Transactions Manager has not been set.'); } }
|