123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- <?php
-
- namespace Illuminate\Database\Concerns;
-
- use Closure;
- use Exception;
- use Throwable;
-
- trait ManagesTransactions
- {
- /**
- * Execute a Closure within a transaction.
- *
- * @param \Closure $callback
- * @param int $attempts
- * @return mixed
- *
- * @throws \Exception|\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 {
- return tap($callback($this), function ($result) {
- $this->commit();
- });
- }
-
- // 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 exceptions.
- catch (Exception $e) {
- $this->handleTransactionException(
- $e, $currentAttempt, $attempts
- );
- } catch (Throwable $e) {
- $this->rollBack();
-
- throw $e;
- }
- }
- }
-
- /**
- * Handle an exception encountered when running a transacted statement.
- *
- * @param \Exception $e
- * @param int $currentAttempt
- * @param int $maxAttempts
- * @return void
- *
- * @throws \Exception
- */
- protected function handleTransactionException($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->causedByDeadlock($e) &&
- $this->transactions > 1) {
- $this->transactions--;
-
- throw $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->causedByDeadlock($e) &&
- $currentAttempt < $maxAttempts) {
- return;
- }
-
- throw $e;
- }
-
- /**
- * Start a new database transaction.
- *
- * @return void
- * @throws \Exception
- */
- public function beginTransaction()
- {
- $this->createTransaction();
-
- $this->transactions++;
-
- $this->fireConnectionEvent('beganTransaction');
- }
-
- /**
- * Create a transaction within the database.
- *
- * @return void
- */
- protected function createTransaction()
- {
- if ($this->transactions == 0) {
- try {
- $this->getPdo()->beginTransaction();
- } catch (Exception $e) {
- $this->handleBeginTransactionException($e);
- }
- } elseif ($this->transactions >= 1 && $this->queryGrammar->supportsSavepoints()) {
- $this->createSavepoint();
- }
- }
-
- /**
- * Create a save point within the database.
- *
- * @return void
- */
- 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 \Exception
- */
- protected function handleBeginTransactionException($e)
- {
- if ($this->causedByLostConnection($e)) {
- $this->reconnect();
-
- $this->pdo->beginTransaction();
- } else {
- throw $e;
- }
- }
-
- /**
- * Commit the active database transaction.
- *
- * @return void
- */
- public function commit()
- {
- if ($this->transactions == 1) {
- $this->getPdo()->commit();
- }
-
- $this->transactions = max(0, $this->transactions - 1);
-
- $this->fireConnectionEvent('committed');
- }
-
- /**
- * Rollback the active database transaction.
- *
- * @param int|null $toLevel
- * @return void
- */
- 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.
- $this->performRollBack($toLevel);
-
- $this->transactions = $toLevel;
-
- $this->fireConnectionEvent('rollingBack');
- }
-
- /**
- * Perform a rollback within the database.
- *
- * @param int $toLevel
- * @return void
- */
- protected function performRollBack($toLevel)
- {
- if ($toLevel == 0) {
- $this->getPdo()->rollBack();
- } elseif ($this->queryGrammar->supportsSavepoints()) {
- $this->getPdo()->exec(
- $this->queryGrammar->compileSavepointRollBack('trans'.($toLevel + 1))
- );
- }
- }
-
- /**
- * Get the number of active transactions.
- *
- * @return int
- */
- public function transactionLevel()
- {
- return $this->transactions;
- }
- }
|