Passed
Branch dev (d14b82)
by Wilmer
12:57
created

TransactionPDOPgsql::isActive()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 1
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 2
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Pgsql\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 TransactionPDOPgsql 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 8
    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 1
                $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
69 8
            $this->db->getPDO()->beginTransaction();
70 8
            $this->level = 1;
71
72 8
            return;
73
        }
74
75 2
        $schema = $this->db->getSchema();
76
77 2
        if ($schema->supportsSavepoint()) {
78 1
            $this->logger?->log(LogLevel::DEBUG, 'Set savepoint ' . $this->level . ' ' . __METHOD__);
79 1
            $schema->createSavepoint('LEVEL' . $this->level);
80
        } else {
81 1
            $this->logger?->log(
82
                LogLevel::DEBUG,
83 1
                'Transaction not started: nested transaction not supported ' . __METHOD__
84
            );
85 1
            throw new NotSupportedException('Transaction not started: nested transaction not supported.');
86
        }
87
88 1
        $this->level++;
89
    }
90
91
    /**
92
     * Commits a transaction.
93
     *
94
     * @throws Exception|Throwable if the transaction is not active
95
     */
96 5
    public function commit(): void
97
    {
98 5
        if (!$this->isActive()) {
99
            throw new Exception('Failed to commit transaction: transaction was inactive.');
100
        }
101
102 5
        $this->level--;
103
104 5
        if ($this->level === 0) {
105 5
            $this->logger?->log(LogLevel::DEBUG, 'Commit transaction ' . __METHOD__);
106 5
            $this->db->getPDO()->commit();
107 5
            return;
108
        }
109
110
        $schema = $this->db->getSchema();
111
112
        if ($schema->supportsSavepoint()) {
113
            $this->logger?->log(LogLevel::DEBUG, 'Release savepoint ' . $this->level . ' ' . __METHOD__);
114
            $schema->releaseSavepoint('LEVEL' . $this->level);
115
        } else {
116
            $this->logger?->log(
117
                LogLevel::INFO,
118
                'Transaction not committed: nested transaction not supported ' . __METHOD__
119
            );
120
        }
121
    }
122
123 5
    public function getLevel(): int
124
    {
125 5
        return $this->level;
126
    }
127
128 8
    public function isActive(): bool
129
    {
130 8
        return $this->level > 0 && $this->db->isActive();
131
    }
132
133
    /**
134
     * @throws Exception|InvalidConfigException|Throwable
135
     */
136 4
    public function rollBack(): void
137
    {
138 4
        if (!$this->isActive()) {
139
            /**
140
             * do nothing if transaction is not active: this could be the transaction is committed but the event handler
141
             * to "commitTransaction" throw an exception
142
             */
143
            return;
144
        }
145
146 4
        $this->level--;
147 4
        if ($this->level === 0) {
148 3
            $this->logger?->log(LogLevel::INFO, 'Roll back transaction ' . __METHOD__);
149 3
            $this->db->getPDO()->rollBack();
150 3
            return;
151
        }
152
153 1
        $schema = $this->db->getSchema();
154 1
        if ($schema->supportsSavepoint()) {
155 1
            $this->logger?->log(LogLevel::DEBUG, 'Roll back to savepoint ' . $this->level . ' ' . __METHOD__);
156 1
            $schema->rollBackSavepoint('LEVEL' . $this->level);
157
        } else {
158
            $this->logger?->log(
159
                LogLevel::INFO,
160
                'Transaction not rolled back: nested transaction not supported ' . __METHOD__
161
            );
162
        }
163
    }
164
165 1
    public function setIsolationLevel(string $level): void
166
    {
167 1
        if (!$this->isActive()) {
168
            throw new Exception('Failed to set isolation level: transaction was inactive.');
169
        }
170
171 1
        $this->logger?->log(
172
            LogLevel::DEBUG,
173 1
            'Setting transaction isolation level to ' . $this->level . ' ' . __METHOD__
174
        );
175
176 1
        $this->db->getSchema()->setTransactionIsolationLevel($level);
177
    }
178
}
179