Passed
Push — master ( c88dac...d03307 )
by Ondřej
03:02
created

TransactionControl::startAutoTransaction()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 5
rs 10
1
<?php
2
declare(strict_types=1);
3
namespace Ivory\Connection;
4
5
use Ivory\Connection\Config\ConfigParam;
6
use Ivory\Exception\InvalidStateException;
7
use Ivory\Ivory;
8
use Ivory\Lang\Sql\Types;
9
use Ivory\Result\IQueryResult;
10
11
class TransactionControl implements IObservableTransactionControl
12
{
13
    private $connCtl;
14
    private $stmtExec;
15
    private $sessionCtl;
16
    /** @var ITransactionControlObserver[] */
17
    private $observers = [];
18
19
20
    public function __construct(ConnectionControl $connCtl, IStatementExecution $stmtExec, ISessionControl $sessionCtl)
21
    {
22
        $this->connCtl = $connCtl;
23
        $this->stmtExec = $stmtExec;
24
        $this->sessionCtl = $sessionCtl;
25
    }
26
27
    public function inTransaction(): bool
28
    {
29
        $connHandler = $this->connCtl->requireConnection();
30
        $txStat = pg_transaction_status($connHandler);
31
        return ($txStat == PGSQL_TRANSACTION_INTRANS || $txStat == PGSQL_TRANSACTION_INERROR);
32
    }
33
34
    public function startTransaction($transactionOptions = 0): ITxHandle
35
    {
36
        $this->executeTransactionStart($transactionOptions);
37
38
        $coreFactory = Ivory::getCoreFactory();
39
        return $coreFactory->createTransactionHandle($this->stmtExec, $this, $this->sessionCtl);
40
    }
41
42
    public function startAutoTransaction($transactionOptions = 0): ITxHandle
43
    {
44
        $this->executeTransactionStart($transactionOptions);
45
46
        return new AutoTxHandle($this->stmtExec, $this, $this->sessionCtl);
47
    }
48
49
    private function executeTransactionStart($transactionOptions = 0): void
50
    {
51
        if ($this->inTransaction()) {
52
            throw new InvalidStateException('A transaction is already active, cannot start a new one.');
53
        }
54
55
        $txConfig = TxConfig::create($transactionOptions);
56
        $txSql = $txConfig->toSql();
57
58
        $command = 'START TRANSACTION';
59
        if (strlen($txSql) > 0) {
60
            $command .= ' ' . $txSql;
61
        }
62
63
        $this->stmtExec->rawCommand($command);
64
        $this->notifyTransactionStart();
65
    }
66
67
    public function setupSubsequentTransactions($transactionOptions): void
68
    {
69
        $txConfig = TxConfig::create($transactionOptions);
70
        $this->stmtExec->rawCommand('SET SESSION CHARACTERISTICS AS TRANSACTION ' . $txConfig->toSql());
71
    }
72
73
    public function getDefaultTxConfig(): TxConfig
74
    {
75
        $connConfig = $this->sessionCtl->getConfig();
76
        return TxConfig::createFromParams(
77
            $connConfig->get(ConfigParam::DEFAULT_TRANSACTION_ISOLATION),
0 ignored issues
show
Bug introduced by
It seems like $connConfig->get(Ivory\C..._TRANSACTION_ISOLATION) can also be of type boolean; however, parameter $isolationLevel of Ivory\Connection\TxConfig::createFromParams() does only seem to accept null|string, maybe add an additional type check? ( Ignorable by Annotation )

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

77
            /** @scrutinizer ignore-type */ $connConfig->get(ConfigParam::DEFAULT_TRANSACTION_ISOLATION),
Loading history...
78
            $connConfig->get(ConfigParam::DEFAULT_TRANSACTION_READ_ONLY),
79
            $connConfig->get(ConfigParam::DEFAULT_TRANSACTION_DEFERRABLE)
80
        );
81
    }
82
83
    public function commitPreparedTransaction(string $name): void
84
    {
85
        if ($this->inTransaction()) {
86
            throw new InvalidStateException('Cannot commit a prepared transaction while inside another transaction.');
87
        }
88
89
        $this->stmtExec->rawCommand('COMMIT PREPARED ' . Types::serializeString($name));
90
        $this->notifyPreparedTransactionCommit($name);
91
    }
92
93
    public function rollbackPreparedTransaction(string $name): void
94
    {
95
        if ($this->inTransaction()) {
96
            throw new InvalidStateException('Cannot rollback a prepared transaction while inside another transaction.');
97
        }
98
99
        $this->stmtExec->rawCommand('ROLLBACK PREPARED ' . Types::serializeString($name));
100
        $this->notifyPreparedTransactionRollback($name);
101
    }
102
103
    public function listPreparedTransactions(): IQueryResult
104
    {
105
        return $this->stmtExec->rawQuery('SELECT * FROM pg_catalog.pg_prepared_xacts');
106
    }
107
108
109
    //region IObservableTransactionControl
110
111
    public function addObserver(ITransactionControlObserver $observer): void
112
    {
113
        $hash = spl_object_hash($observer);
114
        $this->observers[$hash] = $observer;
115
    }
116
117
    public function removeObserver(ITransactionControlObserver $observer): void
118
    {
119
        $hash = spl_object_hash($observer);
120
        unset($this->observers[$hash]);
121
    }
122
123
    public function removeAllObservers(): void
124
    {
125
        $this->observers = [];
126
    }
127
128
    public function notifyTransactionStart(): void
129
    {
130
        foreach ($this->observers as $observer) {
131
            $observer->handleTransactionStart();
132
        }
133
    }
134
135
    public function notifyTransactionCommit(): void
136
    {
137
        foreach ($this->observers as $observer) {
138
            $observer->handleTransactionCommit();
139
        }
140
    }
141
142
    public function notifyTransactionRollback(): void
143
    {
144
        foreach ($this->observers as $observer) {
145
            $observer->handleTransactionRollback();
146
        }
147
    }
148
149
    public function notifySavepointSaved(string $name): void
150
    {
151
        foreach ($this->observers as $observer) {
152
            $observer->handleSavepointSaved($name);
153
        }
154
    }
155
156
    public function notifySavepointReleased(string $name): void
157
    {
158
        foreach ($this->observers as $observer) {
159
            $observer->handleSavepointReleased($name);
160
        }
161
    }
162
163
    public function notifyRollbackToSavepoint(string $name): void
164
    {
165
        foreach ($this->observers as $observer) {
166
            $observer->handleRollbackToSavepoint($name);
167
        }
168
    }
169
170
    public function notifyTransactionPrepared(string $name): void
171
    {
172
        foreach ($this->observers as $observer) {
173
            $observer->handleTransactionPrepared($name);
174
        }
175
    }
176
177
    public function notifyPreparedTransactionCommit(string $name): void
178
    {
179
        foreach ($this->observers as $observer) {
180
            $observer->handlePreparedTransactionCommit($name);
181
        }
182
    }
183
184
    public function notifyPreparedTransactionRollback(string $name): void
185
    {
186
        foreach ($this->observers as $observer) {
187
            $observer->handlePreparedTransactionRollback($name);
188
        }
189
    }
190
191
    //endregion
192
}
193