Passed
Branch dev (ea35c5)
by Wilmer
17:29 queued 12:43
created

TransactionPDOSqlite::rollBack()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 25
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4.1574

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 15
dl 0
loc 25
ccs 11
cts 14
cp 0.7856
rs 9.7666
c 1
b 0
f 0
cc 4
nc 4
nop 0
crap 4.1574
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Sqlite\PDO;
6
7
use Psr\Log\LogLevel;
8
use Throwable;
9
use Yiisoft\Db\AwareTrait\LoggerAwareTrait;
10
use Yiisoft\Db\Connection\ConnectionPDOInterface;
11
use Yiisoft\Db\Exception\Exception;
12
use Yiisoft\Db\Exception\InvalidConfigException;
13
use Yiisoft\Db\Exception\NotSupportedException;
14
use Yiisoft\Db\Transaction\TransactionInterface;
15
16
final class TransactionPDOSqlite implements TransactionInterface
17
{
18
    use LoggerAwareTrait;
19
20
    /**
21
     * A constant representing the transaction isolation level `READ UNCOMMITTED`.
22
     *
23
     * @link http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels}
24
     */
25
    public const READ_UNCOMMITTED = 'READ UNCOMMITTED';
26
27
    /**
28
     * A constant representing the transaction isolation level `READ COMMITTED`.
29
     *
30
     * @link http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels}
31
     */
32
    public const READ_COMMITTED = 'READ COMMITTED';
33
34
    /**
35
     * A constant representing the transaction isolation level `REPEATABLE READ`.
36
     *
37
     * @link http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels}
38
     */
39
    public const REPEATABLE_READ = 'REPEATABLE READ';
40
41
    /**
42
     * A constant representing the transaction isolation level `SERIALIZABLE`.
43
     *
44
     * {@see http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels}
45
     */
46
    public const SERIALIZABLE = 'SERIALIZABLE';
47
48
    private int $level = 0;
49
50
    public function __construct(private ConnectionPDOInterface $db)
51
    {
52
    }
53
54 8
    public function begin(?string $isolationLevel = null): void
55
    {
56 8
        $this->db->open();
57
58 8
        if ($this->level === 0) {
59 8
            if ($isolationLevel !== null) {
60 2
                $this->db->getSchema()->setTransactionIsolationLevel($isolationLevel);
61
            }
62
63 8
            $this->logger?->log(
64
                LogLevel::DEBUG,
65 8
                'Begin transaction' . ($isolationLevel ? ' with isolation level ' . $isolationLevel : '')
66
                . ' ' . __METHOD__
67
            );
68 8
            $this->db->getPDO()?->beginTransaction();
69 8
            $this->level = 1;
70
71 8
            return;
72
        }
73
74 2
        $schema = $this->db->getSchema();
75
76 2
        if ($schema->supportsSavepoint()) {
77 1
            $this->logger?->log(LogLevel::DEBUG, 'Set savepoint ' . $this->level . ' ' . __METHOD__);
78 1
            $schema->createSavepoint('LEVEL' . $this->level);
79
        } else {
80 1
            $this->logger?->log(
81
                LogLevel::DEBUG,
82 1
                'Transaction not started: nested transaction not supported ' . __METHOD__
83
            );
84 1
            throw new NotSupportedException('Transaction not started: nested transaction not supported.');
85
        }
86
87 1
        $this->level++;
88
    }
89
90
    /**
91
     * Commits a transaction.
92
     *
93
     * @throws Exception|Throwable if the transaction is not active
94
     */
95 4
    public function commit(): void
96
    {
97 4
        if (!$this->isActive()) {
98
            throw new Exception('Failed to commit transaction: transaction was inactive.');
99
        }
100
101 4
        $this->level--;
102
103 4
        if ($this->level === 0) {
104 4
            $this->logger?->log(LogLevel::DEBUG, 'Commit transaction ' . __METHOD__);
105 4
            $this->db->getPDO()?->commit();
106 4
            return;
107
        }
108
109
        $schema = $this->db->getSchema();
110
111
        if ($schema->supportsSavepoint()) {
112
            $this->logger?->log(LogLevel::DEBUG, 'Release savepoint ' . $this->level . ' ' . __METHOD__);
113
            $schema->releaseSavepoint('LEVEL' . $this->level);
114
        } else {
115
            $this->logger?->log(
116
                LogLevel::INFO,
117
                'Transaction not committed: nested transaction not supported ' . __METHOD__
118
            );
119
        }
120
    }
121
122 5
    public function getLevel(): int
123
    {
124 5
        return $this->level;
125
    }
126
127 8
    public function isActive(): bool
128
    {
129 8
        return $this->level > 0 && $this->db->isActive();
130
    }
131
132
    /**
133
     * @throws Exception|InvalidConfigException|Throwable
134
     */
135 5
    public function rollBack(): void
136
    {
137 5
        if (!$this->isActive()) {
138
            /**
139
             * do nothing if transaction is not active: this could be the transaction is committed but the event handler
140
             * to "commitTransaction" throw an exception
141
             */
142
            return;
143
        }
144
145 5
        $this->level--;
146 5
        if ($this->level === 0) {
147 4
            $this->logger?->log(LogLevel::INFO, 'Roll back transaction ' . __METHOD__);
148 4
            $this->db->getPDO()?->rollBack();
149 4
            return;
150
        }
151
152 1
        $schema = $this->db->getSchema();
153 1
        if ($schema->supportsSavepoint()) {
154 1
            $this->logger?->log(LogLevel::DEBUG, 'Roll back to savepoint ' . $this->level . ' ' . __METHOD__);
155 1
            $schema->rollBackSavepoint('LEVEL' . $this->level);
156
        } else {
157
            $this->logger?->log(
158
                LogLevel::INFO,
159
                'Transaction not rolled back: nested transaction not supported ' . __METHOD__
160
            );
161
        }
162
    }
163
164
    public function setIsolationLevel(string $level): void
165
    {
166
        if (!$this->isActive()) {
167
            throw new Exception('Failed to set isolation level: transaction was inactive.');
168
        }
169
170
        $this->logger?->log(
171
            LogLevel::DEBUG,
172
            'Setting transaction isolation level to ' . $this->level . ' ' . __METHOD__
173
        );
174
        $this->db->getSchema()->setTransactionIsolationLevel($level);
175
    }
176
}
177