Skip to content

Commit b2a4159

Browse files
committed
Clean up transaction handling.
1 parent 82a2b17 commit b2a4159

File tree

1 file changed

+106
-36
lines changed

1 file changed

+106
-36
lines changed

src/Illuminate/Database/Concerns/ManagesTransactions.php

+106-36
Original file line numberDiff line numberDiff line change
@@ -19,43 +19,64 @@ trait ManagesTransactions
1919
*/
2020
public function transaction(Closure $callback, $attempts = 1)
2121
{
22-
for ($a = 1; $a <= $attempts; $a++) {
22+
for ($currentAttempt = 1; $currentAttempt <= $attempts; $currentAttempt++) {
2323
$this->beginTransaction();
2424

2525
// We'll simply execute the given callback within a try / catch block
2626
// and if we catch any exception we can rollback the transaction
2727
// so that none of the changes are persisted to the database.
2828
try {
29-
$result = $callback($this);
30-
31-
$this->commit();
29+
return tap($callback($this), function ($result) {
30+
$this->commit();
31+
});
3232
}
3333

3434
// If we catch an exception, we will roll back so nothing gets messed
3535
// up in the database. Then we'll re-throw the exception so it can
3636
// be handled how the developer sees fit for their applications.
3737
catch (Exception $e) {
38-
if ($this->causedByDeadlock($e) && $this->transactions > 1) {
39-
--$this->transactions;
40-
41-
throw $e;
42-
}
43-
44-
$this->rollBack();
45-
46-
if ($this->causedByDeadlock($e) && $a < $attempts) {
47-
continue;
48-
}
49-
50-
throw $e;
38+
$this->handleTransactionException(
39+
$e, $currentAttempt, $attempts
40+
);
5141
} catch (Throwable $e) {
5242
$this->rollBack();
5343

5444
throw $e;
5545
}
46+
}
47+
}
48+
49+
/**
50+
* Handle an exception encountered when running a transacted statement.
51+
*
52+
* @param \Exception $e
53+
* @param int $currentAttempt
54+
* @param int $maxAttempts
55+
* @return void
56+
*/
57+
protected function handleTransactionException($e, $currentAttempt, $maxAttempts)
58+
{
59+
// On a deadlock, MySQL rolls back the entire transaction so we can't just
60+
// retry the query. We have to throw this exception all the way out and
61+
// let the developer handle it in another way. We will decrement too.
62+
if ($this->causedByDeadlock($e) &&
63+
$this->transactions > 1) {
64+
--$this->transactions;
65+
66+
throw $e;
67+
}
5668

57-
return $result;
69+
// If there was an exception we will rollback this transaction and then we
70+
// can check if we have exceeded the maximum attempt count for this and
71+
// if we haven't we will return and try this query again in our loop.
72+
$this->rollBack();
73+
74+
if ($this->causedByDeadlock($e) &&
75+
$currentAttempt < $maxAttempts) {
76+
return;
5877
}
78+
79+
throw $e;
5980
}
6081

6182
/**
@@ -65,27 +86,59 @@ public function transaction(Closure $callback, $attempts = 1)
6586
* @throws Exception
6687
*/
6788
public function beginTransaction()
89+
{
90+
$this->createTransaction();
91+
92+
++$this->transactions;
93+
94+
$this->fireConnectionEvent('beganTransaction');
95+
}
96+
97+
/**
98+
* Create a transaction within the database.
99+
*
100+
* @return void
101+
*/
102+
protected function createTransaction()
68103
{
69104
if ($this->transactions == 0) {
70105
try {
71106
$this->getPdo()->beginTransaction();
72107
} catch (Exception $e) {
73-
if ($this->causedByLostConnection($e)) {
74-
$this->reconnect();
75-
$this->pdo->beginTransaction();
76-
} else {
77-
throw $e;
78-
}
108+
$this->handleBeginTransactionException($e);
79109
}
80110
} elseif ($this->transactions >= 1 && $this->queryGrammar->supportsSavepoints()) {
81-
$this->getPdo()->exec(
82-
$this->queryGrammar->compileSavepoint('trans'.($this->transactions + 1))
83-
);
111+
$this->createSavepoint();
84112
}
113+
}
85114

86-
++$this->transactions;
115+
/**
116+
* Create a save point within the database.
117+
*
118+
* @return void
119+
*/
120+
protected function createSavepoint()
121+
{
122+
$this->getPdo()->exec(
123+
$this->queryGrammar->compileSavepoint('trans'.($this->transactions + 1))
124+
);
125+
}
87126

88-
$this->fireConnectionEvent('beganTransaction');
127+
/**
128+
* Handle an exception from a transaction beginning.
129+
*
130+
* @param \Exception $e
131+
* @return void
132+
*/
133+
protected function handleBeginTransactionException($e)
134+
{
135+
if ($this->causedByLostConnection($e)) {
136+
$this->reconnect();
137+
138+
$this->pdo->beginTransaction();
139+
} else {
140+
throw $e;
141+
}
89142
}
90143

91144
/**
@@ -112,25 +165,42 @@ public function commit()
112165
*/
113166
public function rollBack($toLevel = null)
114167
{
115-
if (is_null($toLevel)) {
116-
$toLevel = $this->transactions - 1;
117-
}
168+
// We allow developers to rollback to a certain transaction level. We will verify
169+
// that this given transaction level is valid before attempting to rollback to
170+
// that level. If it's not we will just return out and not attempt anything.
171+
$toLevel = is_null($toLevel)
172+
? $this->transactions - 1
173+
: $toLevel;
118174

119175
if ($toLevel < 0 || $toLevel >= $this->transactions) {
120176
return;
121177
}
122178

179+
// Next, we will actually perform this rollback within this database and fire the
180+
// rollback event. We will also set the current transaction level to the given
181+
// level that was passed into this method so it will be right from here out.
182+
$this->performRollBack($toLevel);
183+
184+
$this->transactions = $toLevel;
185+
186+
$this->fireConnectionEvent('rollingBack');
187+
}
188+
189+
/**
190+
* Perform a rollback within the database.
191+
*
192+
* @param int $toLevel
193+
* @return void
194+
*/
195+
protected function performRollBack($toLevel)
196+
{
123197
if ($toLevel == 0) {
124198
$this->getPdo()->rollBack();
125199
} elseif ($this->queryGrammar->supportsSavepoints()) {
126200
$this->getPdo()->exec(
127201
$this->queryGrammar->compileSavepointRollBack('trans'.($toLevel + 1))
128202
);
129203
}
130-
131-
$this->transactions = $toLevel;
132-
133-
$this->fireConnectionEvent('rollingBack');
134204
}
135205

136206
/**

0 commit comments

Comments
 (0)