Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 148 | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Illuminate\Database\Concerns;
4
 
5
use Closure;
6
use Illuminate\Database\DeadlockException;
7
use RuntimeException;
8
use Throwable;
9
 
10
trait ManagesTransactions
11
{
12
    /**
13
     * Execute a Closure within a transaction.
14
     *
15
     * @param  \Closure  $callback
16
     * @param  int  $attempts
17
     * @return mixed
18
     *
19
     * @throws \Throwable
20
     */
21
    public function transaction(Closure $callback, $attempts = 1)
22
    {
23
        for ($currentAttempt = 1; $currentAttempt <= $attempts; $currentAttempt++) {
24
            $this->beginTransaction();
25
 
26
            // We'll simply execute the given callback within a try / catch block and if we
27
            // catch any exception we can rollback this transaction so that none of this
28
            // gets actually persisted to a database or stored in a permanent fashion.
29
            try {
30
                $callbackResult = $callback($this);
31
            }
32
 
33
            // If we catch an exception we'll rollback this transaction and try again if we
34
            // are not out of attempts. If we are out of attempts we will just throw the
35
            // exception back out, and let the developer handle an uncaught exception.
36
            catch (Throwable $e) {
37
                $this->handleTransactionException(
38
                    $e, $currentAttempt, $attempts
39
                );
40
 
41
                continue;
42
            }
43
 
44
            try {
45
                if ($this->transactions == 1) {
46
                    $this->fireConnectionEvent('committing');
47
                    $this->getPdo()->commit();
48
                }
49
 
50
                $this->transactions = max(0, $this->transactions - 1);
51
 
52
                if ($this->afterCommitCallbacksShouldBeExecuted()) {
53
                    $this->transactionsManager?->commit($this->getName());
54
                }
55
            } catch (Throwable $e) {
56
                $this->handleCommitTransactionException(
57
                    $e, $currentAttempt, $attempts
58
                );
59
 
60
                continue;
61
            }
62
 
63
            $this->fireConnectionEvent('committed');
64
 
65
            return $callbackResult;
66
        }
67
    }
68
 
69
    /**
70
     * Handle an exception encountered when running a transacted statement.
71
     *
72
     * @param  \Throwable  $e
73
     * @param  int  $currentAttempt
74
     * @param  int  $maxAttempts
75
     * @return void
76
     *
77
     * @throws \Throwable
78
     */
79
    protected function handleTransactionException(Throwable $e, $currentAttempt, $maxAttempts)
80
    {
81
        // On a deadlock, MySQL rolls back the entire transaction so we can't just
82
        // retry the query. We have to throw this exception all the way out and
83
        // let the developer handle it in another way. We will decrement too.
84
        if ($this->causedByConcurrencyError($e) &&
85
            $this->transactions > 1) {
86
            $this->transactions--;
87
 
88
            $this->transactionsManager?->rollback(
89
                $this->getName(), $this->transactions
90
            );
91
 
92
            throw new DeadlockException($e->getMessage(), is_int($e->getCode()) ? $e->getCode() : 0, $e);
93
        }
94
 
95
        // If there was an exception we will rollback this transaction and then we
96
        // can check if we have exceeded the maximum attempt count for this and
97
        // if we haven't we will return and try this query again in our loop.
98
        $this->rollBack();
99
 
100
        if ($this->causedByConcurrencyError($e) &&
101
            $currentAttempt < $maxAttempts) {
102
            return;
103
        }
104
 
105
        throw $e;
106
    }
107
 
108
    /**
109
     * Start a new database transaction.
110
     *
111
     * @return void
112
     *
113
     * @throws \Throwable
114
     */
115
    public function beginTransaction()
116
    {
117
        $this->createTransaction();
118
 
119
        $this->transactions++;
120
 
121
        $this->transactionsManager?->begin(
122
            $this->getName(), $this->transactions
123
        );
124
 
125
        $this->fireConnectionEvent('beganTransaction');
126
    }
127
 
128
    /**
129
     * Create a transaction within the database.
130
     *
131
     * @return void
132
     *
133
     * @throws \Throwable
134
     */
135
    protected function createTransaction()
136
    {
137
        if ($this->transactions == 0) {
138
            $this->reconnectIfMissingConnection();
139
 
140
            try {
141
                $this->getPdo()->beginTransaction();
142
            } catch (Throwable $e) {
143
                $this->handleBeginTransactionException($e);
144
            }
145
        } elseif ($this->transactions >= 1 && $this->queryGrammar->supportsSavepoints()) {
146
            $this->createSavepoint();
147
        }
148
    }
149
 
150
    /**
151
     * Create a save point within the database.
152
     *
153
     * @return void
154
     *
155
     * @throws \Throwable
156
     */
157
    protected function createSavepoint()
158
    {
159
        $this->getPdo()->exec(
160
            $this->queryGrammar->compileSavepoint('trans'.($this->transactions + 1))
161
        );
162
    }
163
 
164
    /**
165
     * Handle an exception from a transaction beginning.
166
     *
167
     * @param  \Throwable  $e
168
     * @return void
169
     *
170
     * @throws \Throwable
171
     */
172
    protected function handleBeginTransactionException(Throwable $e)
173
    {
174
        if ($this->causedByLostConnection($e)) {
175
            $this->reconnect();
176
 
177
            $this->getPdo()->beginTransaction();
178
        } else {
179
            throw $e;
180
        }
181
    }
182
 
183
    /**
184
     * Commit the active database transaction.
185
     *
186
     * @return void
187
     *
188
     * @throws \Throwable
189
     */
190
    public function commit()
191
    {
192
        if ($this->transactionLevel() == 1) {
193
            $this->fireConnectionEvent('committing');
194
            $this->getPdo()->commit();
195
        }
196
 
197
        $this->transactions = max(0, $this->transactions - 1);
198
 
199
        if ($this->afterCommitCallbacksShouldBeExecuted()) {
200
            $this->transactionsManager?->commit($this->getName());
201
        }
202
 
203
        $this->fireConnectionEvent('committed');
204
    }
205
 
206
    /**
207
     * Determine if after commit callbacks should be executed.
208
     *
209
     * @return bool
210
     */
211
    protected function afterCommitCallbacksShouldBeExecuted()
212
    {
213
        return $this->transactions == 0 ||
214
            ($this->transactionsManager &&
215
             $this->transactionsManager->callbackApplicableTransactions()->count() === 1);
216
    }
217
 
218
    /**
219
     * Handle an exception encountered when committing a transaction.
220
     *
221
     * @param  \Throwable  $e
222
     * @param  int  $currentAttempt
223
     * @param  int  $maxAttempts
224
     * @return void
225
     *
226
     * @throws \Throwable
227
     */
228
    protected function handleCommitTransactionException(Throwable $e, $currentAttempt, $maxAttempts)
229
    {
230
        $this->transactions = max(0, $this->transactions - 1);
231
 
232
        if ($this->causedByConcurrencyError($e) && $currentAttempt < $maxAttempts) {
233
            return;
234
        }
235
 
236
        if ($this->causedByLostConnection($e)) {
237
            $this->transactions = 0;
238
        }
239
 
240
        throw $e;
241
    }
242
 
243
    /**
244
     * Rollback the active database transaction.
245
     *
246
     * @param  int|null  $toLevel
247
     * @return void
248
     *
249
     * @throws \Throwable
250
     */
251
    public function rollBack($toLevel = null)
252
    {
253
        // We allow developers to rollback to a certain transaction level. We will verify
254
        // that this given transaction level is valid before attempting to rollback to
255
        // that level. If it's not we will just return out and not attempt anything.
256
        $toLevel = is_null($toLevel)
257
                    ? $this->transactions - 1
258
                    : $toLevel;
259
 
260
        if ($toLevel < 0 || $toLevel >= $this->transactions) {
261
            return;
262
        }
263
 
264
        // Next, we will actually perform this rollback within this database and fire the
265
        // rollback event. We will also set the current transaction level to the given
266
        // level that was passed into this method so it will be right from here out.
267
        try {
268
            $this->performRollBack($toLevel);
269
        } catch (Throwable $e) {
270
            $this->handleRollBackException($e);
271
        }
272
 
273
        $this->transactions = $toLevel;
274
 
275
        $this->transactionsManager?->rollback(
276
            $this->getName(), $this->transactions
277
        );
278
 
279
        $this->fireConnectionEvent('rollingBack');
280
    }
281
 
282
    /**
283
     * Perform a rollback within the database.
284
     *
285
     * @param  int  $toLevel
286
     * @return void
287
     *
288
     * @throws \Throwable
289
     */
290
    protected function performRollBack($toLevel)
291
    {
292
        if ($toLevel == 0) {
621 lars 293
            $pdo = $this->getPdo();
294
 
295
            if ($pdo->inTransaction()) {
296
                $pdo->rollBack();
297
            }
148 lars 298
        } elseif ($this->queryGrammar->supportsSavepoints()) {
299
            $this->getPdo()->exec(
300
                $this->queryGrammar->compileSavepointRollBack('trans'.($toLevel + 1))
301
            );
302
        }
303
    }
304
 
305
    /**
306
     * Handle an exception from a rollback.
307
     *
308
     * @param  \Throwable  $e
309
     * @return void
310
     *
311
     * @throws \Throwable
312
     */
313
    protected function handleRollBackException(Throwable $e)
314
    {
315
        if ($this->causedByLostConnection($e)) {
316
            $this->transactions = 0;
317
 
318
            $this->transactionsManager?->rollback(
319
                $this->getName(), $this->transactions
320
            );
321
        }
322
 
323
        throw $e;
324
    }
325
 
326
    /**
327
     * Get the number of active transactions.
328
     *
329
     * @return int
330
     */
331
    public function transactionLevel()
332
    {
333
        return $this->transactions;
334
    }
335
 
336
    /**
337
     * Execute the callback after a transaction commits.
338
     *
339
     * @param  callable  $callback
340
     * @return void
341
     *
342
     * @throws \RuntimeException
343
     */
344
    public function afterCommit($callback)
345
    {
346
        if ($this->transactionsManager) {
347
            return $this->transactionsManager->addCallback($callback);
348
        }
349
 
350
        throw new RuntimeException('Transactions Manager has not been set.');
351
    }
352
}