@@ -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