Transaction::getLevel()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @link https://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license https://www.yiiframework.com/license/
6
 */
7
8
namespace yii\db;
9
10
use Yii;
11
use yii\base\InvalidConfigException;
12
use yii\base\NotSupportedException;
13
14
/**
15
 * Transaction represents a DB transaction.
16
 *
17
 * It is usually created by calling [[Connection::beginTransaction()]].
18
 *
19
 * The following code is a typical example of using transactions (note that some
20
 * DBMS may not support transactions):
21
 *
22
 * ```php
23
 * $transaction = $connection->beginTransaction();
24
 * try {
25
 *     $connection->createCommand($sql1)->execute();
26
 *     $connection->createCommand($sql2)->execute();
27
 *     //.... other SQL executions
28
 *     $transaction->commit();
29
 * } catch (\Exception $e) {
30
 *     $transaction->rollBack();
31
 *     throw $e;
32
 * } catch (\Throwable $e) {
33
 *     $transaction->rollBack();
34
 *     throw $e;
35
 * }
36
 * ```
37
 *
38
 * > Note: in the above code we have two catch-blocks for compatibility
39
 * > with PHP 5.x and PHP 7.x. `\Exception` implements the [`\Throwable` interface](https://www.php.net/manual/en/class.throwable.php)
40
 * > since PHP 7.0, so you can skip the part with `\Exception` if your app uses only PHP 7.0 and higher.
41
 *
42
 * @property-read bool $isActive Whether this transaction is active. Only an active transaction can
43
 * [[commit()]] or [[rollBack()]].
44
 * @property-write string $isolationLevel The transaction isolation level to use for this transaction. This
45
 * can be one of [[READ_UNCOMMITTED]], [[READ_COMMITTED]], [[REPEATABLE_READ]] and [[SERIALIZABLE]] but also a
46
 * string containing DBMS specific syntax to be used after `SET TRANSACTION ISOLATION LEVEL`.
47
 * @property-read int $level The current nesting level of the transaction.
48
 *
49
 * @author Qiang Xue <[email protected]>
50
 * @since 2.0
51
 */
52
class Transaction extends \yii\base\BaseObject
53
{
54
    /**
55
     * A constant representing the transaction isolation level `READ UNCOMMITTED`.
56
     * @see https://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels
57
     */
58
    const READ_UNCOMMITTED = 'READ UNCOMMITTED';
59
    /**
60
     * A constant representing the transaction isolation level `READ COMMITTED`.
61
     * @see https://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels
62
     */
63
    const READ_COMMITTED = 'READ COMMITTED';
64
    /**
65
     * A constant representing the transaction isolation level `REPEATABLE READ`.
66
     * @see https://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels
67
     */
68
    const REPEATABLE_READ = 'REPEATABLE READ';
69
    /**
70
     * A constant representing the transaction isolation level `SERIALIZABLE`.
71
     * @see https://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels
72
     */
73
    const SERIALIZABLE = 'SERIALIZABLE';
74
75
    /**
76
     * @var Connection the database connection that this transaction is associated with.
77
     */
78
    public $db;
79
80
    /**
81
     * @var int the nesting level of the transaction. 0 means the outermost level.
82
     */
83
    private $_level = 0;
84
85
86
    /**
87
     * Returns a value indicating whether this transaction is active.
88
     * @return bool whether this transaction is active. Only an active transaction
89
     * can [[commit()]] or [[rollBack()]].
90
     */
91 41
    public function getIsActive()
92
    {
93 41
        return $this->_level > 0 && $this->db && $this->db->isActive;
94
    }
95
96
    /**
97
     * Begins a transaction.
98
     * @param string|null $isolationLevel The [isolation level][] to use for this transaction.
99
     * This can be one of [[READ_UNCOMMITTED]], [[READ_COMMITTED]], [[REPEATABLE_READ]] and [[SERIALIZABLE]] but
100
     * also a string containing DBMS specific syntax to be used after `SET TRANSACTION ISOLATION LEVEL`.
101
     * If not specified (`null`) the isolation level will not be set explicitly and the DBMS default will be used.
102
     *
103
     * > Note: This setting does not work for PostgreSQL, where setting the isolation level before the transaction
104
     * has no effect. You have to call [[setIsolationLevel()]] in this case after the transaction has started.
105
     *
106
     * > Note: Some DBMS allow setting of the isolation level only for the whole connection so subsequent transactions
107
     * may get the same isolation level even if you did not specify any. When using this feature
108
     * you may need to set the isolation level for all transactions explicitly to avoid conflicting settings.
109
     * At the time of this writing affected DBMS are MSSQL and SQLite.
110
     *
111
     * [isolation level]: https://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels
112
     *
113
     * Starting from version 2.0.16, this method throws exception when beginning nested transaction and underlying DBMS
114
     * does not support savepoints.
115
     * @throws InvalidConfigException if [[db]] is `null`
116
     * @throws NotSupportedException if the DBMS does not support nested transactions
117
     * @throws Exception if DB connection fails
118
     */
119 41
    public function begin($isolationLevel = null)
120
    {
121 41
        if ($this->db === null) {
122
            throw new InvalidConfigException('Transaction::db must be set.');
123
        }
124 41
        $this->db->open();
125
126 41
        if ($this->_level === 0) {
127 41
            if ($isolationLevel !== null) {
128 7
                $this->db->getSchema()->setTransactionIsolationLevel($isolationLevel);
129
            }
130 41
            Yii::debug('Begin transaction' . ($isolationLevel ? ' with isolation level ' . $isolationLevel : ''), __METHOD__);
131
132 41
            $this->db->trigger(Connection::EVENT_BEGIN_TRANSACTION);
133 41
            $this->db->pdo->beginTransaction();
0 ignored issues
show
Bug introduced by
The method beginTransaction() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

133
            $this->db->pdo->/** @scrutinizer ignore-call */ 
134
                            beginTransaction();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
134 41
            $this->_level = 1;
135
136 41
            return;
137
        }
138
139 8
        $schema = $this->db->getSchema();
140 8
        if ($schema->supportsSavepoint()) {
141 4
            Yii::debug('Set savepoint ' . $this->_level, __METHOD__);
142
            // make sure the transaction wasn't autocommitted
143 4
            if ($this->db->pdo->inTransaction()) {
144 4
                $schema->createSavepoint('LEVEL' . $this->_level);
145
            }
146
        } else {
147 4
            Yii::info('Transaction not started: nested transaction not supported', __METHOD__);
148 4
            throw new NotSupportedException('Transaction not started: nested transaction not supported.');
149
        }
150 4
        $this->_level++;
151
    }
152
153
    /**
154
     * Commits a transaction.
155
     * @throws Exception if the transaction is not active
156
     */
157 25
    public function commit()
158
    {
159 25
        if (!$this->getIsActive()) {
160
            throw new Exception('Failed to commit transaction: transaction was inactive.');
161
        }
162
163 25
        $this->_level--;
164 25
        if ($this->_level === 0) {
165 25
            Yii::debug('Commit transaction', __METHOD__);
166
            // make sure the transaction wasn't autocommitted
167 25
            if ($this->db->pdo->inTransaction()) {
168 23
                $this->db->pdo->commit();
169
            }
170 25
            $this->db->trigger(Connection::EVENT_COMMIT_TRANSACTION);
171 25
            return;
172
        }
173
174
        $schema = $this->db->getSchema();
175
        if ($schema->supportsSavepoint()) {
176
            Yii::debug('Release savepoint ' . $this->_level, __METHOD__);
177
            // make sure the transaction wasn't autocommitted
178
            if ($this->db->pdo->inTransaction()) {
179
                $schema->releaseSavepoint('LEVEL' . $this->_level);
180
            }
181
        } else {
182
            Yii::info('Transaction not committed: nested transaction not supported', __METHOD__);
183
        }
184
    }
185
186
    /**
187
     * Rolls back a transaction.
188
     */
189 20
    public function rollBack()
190
    {
191 20
        if (!$this->getIsActive()) {
192
            // do nothing if transaction is not active: this could be the transaction is committed
193
            // but the event handler to "commitTransaction" throw an exception
194
            return;
195
        }
196
197 20
        $this->_level--;
198 20
        if ($this->_level === 0) {
199 16
            Yii::debug('Roll back transaction', __METHOD__);
200
            // make sure the transaction wasn't autocommitted
201 16
            if ($this->db->pdo->inTransaction()) {
202 16
                $this->db->pdo->rollBack();
203
            }
204 16
            $this->db->trigger(Connection::EVENT_ROLLBACK_TRANSACTION);
205 16
            return;
206
        }
207
208 4
        $schema = $this->db->getSchema();
209 4
        if ($schema->supportsSavepoint()) {
210 4
            Yii::debug('Roll back to savepoint ' . $this->_level, __METHOD__);
211
            // make sure the transaction wasn't autocommitted
212 4
            if ($this->db->pdo->inTransaction()) {
213 4
                $schema->rollBackSavepoint('LEVEL' . $this->_level);
214
            }
215
        } else {
216
            Yii::info('Transaction not rolled back: nested transaction not supported', __METHOD__);
217
        }
218
    }
219
220
    /**
221
     * Sets the transaction isolation level for this transaction.
222
     *
223
     * This method can be used to set the isolation level while the transaction is already active.
224
     * However this is not supported by all DBMS so you might rather specify the isolation level directly
225
     * when calling [[begin()]].
226
     * @param string $level The transaction isolation level to use for this transaction.
227
     * This can be one of [[READ_UNCOMMITTED]], [[READ_COMMITTED]], [[REPEATABLE_READ]] and [[SERIALIZABLE]] but
228
     * also a string containing DBMS specific syntax to be used after `SET TRANSACTION ISOLATION LEVEL`.
229
     * @throws Exception if the transaction is not active
230
     * @see https://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels
231
     */
232 1
    public function setIsolationLevel($level)
233
    {
234 1
        if (!$this->getIsActive()) {
235
            throw new Exception('Failed to set isolation level: transaction was inactive.');
236
        }
237 1
        Yii::debug('Setting transaction isolation level to ' . $level, __METHOD__);
238 1
        $this->db->getSchema()->setTransactionIsolationLevel($level);
239
    }
240
241
    /**
242
     * @return int The current nesting level of the transaction.
243
     * @since 2.0.8
244
     */
245 25
    public function getLevel()
246
    {
247 25
        return $this->_level;
248
    }
249
}
250