Completed
Push — master ( 5f3c84...0c19f1 )
by Ondřej
03:04
created

TransactionControl::inTransaction()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 0
1
<?php
2
declare(strict_types=1);
3
4
namespace Ivory\Connection;
5
6
use Ivory\Connection\Config\ConfigParam;
7
use Ivory\Exception\InvalidStateException;
8
use Ivory\Ivory;
9
use Ivory\Result\IQueryResult;
10
use Ivory\Type\Std\StringType;
11
12
class TransactionControl implements IObservableTransactionControl
13
{
14
    private $connCtl;
15
    private $stmtExec;
16
    private $sessionCtl;
17
    /** @var ITransactionControlObserver[] */
18
    private $observers = [];
19
20
21
    public function __construct(ConnectionControl $connCtl, IStatementExecution $stmtExec, ISessionControl $sessionCtl)
22
    {
23
        $this->connCtl = $connCtl;
24
        $this->stmtExec = $stmtExec;
25
        $this->sessionCtl = $sessionCtl;
26
    }
27
28
    public function inTransaction(): bool
29
    {
30
        $connHandler = $this->connCtl->requireConnection();
31
        $txStat = pg_transaction_status($connHandler);
32
        return ($txStat == PGSQL_TRANSACTION_INTRANS || $txStat == PGSQL_TRANSACTION_INERROR);
33
    }
34
35
    public function startTransaction($transactionOptions = 0): ITxHandle
36
    {
37
        if ($this->inTransaction()) {
38
            throw new InvalidStateException('A transaction is already active, cannot start a new one.');
39
        }
40
41
        $txConfig = TxConfig::create($transactionOptions);
42
        $txSql = $txConfig->toSql();
43
44
        $command = 'START TRANSACTION';
45
        if (strlen($txSql) > 0) {
46
            $command .= ' ' . $txSql;
47
        }
48
49
        $this->stmtExec->rawCommand($command);
50
        $this->notifyTransactionStart();
51
52
        $coreFactory = Ivory::getCoreFactory();
53
        return $coreFactory->createTransactionHandle($this->stmtExec, $this, $this->sessionCtl);
54
    }
55
56
    public function setupSubsequentTransactions($transactionOptions): void
57
    {
58
        $txConfig = TxConfig::create($transactionOptions);
59
        $this->stmtExec->rawCommand('SET SESSION CHARACTERISTICS AS TRANSACTION ' . $txConfig->toSql());
60
    }
61
62
    public function getDefaultTxConfig(): TxConfig
63
    {
64
        $connConfig = $this->sessionCtl->getConfig();
65
        return TxConfig::createFromParams(
66
            $connConfig->get(ConfigParam::DEFAULT_TRANSACTION_ISOLATION),
67
            $connConfig->get(ConfigParam::DEFAULT_TRANSACTION_READ_ONLY),
68
            $connConfig->get(ConfigParam::DEFAULT_TRANSACTION_DEFERRABLE)
69
        );
70
    }
71
72 View Code Duplication
    public function commitPreparedTransaction(string $name): void
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
73
    {
74
        if ($this->inTransaction()) {
75
            throw new InvalidStateException('Cannot commit a prepared transaction while inside another transaction.');
76
        }
77
78
        $this->stmtExec->rawCommand("COMMIT PREPARED {$this->quoteString($name)}");
79
        $this->notifyPreparedTransactionCommit($name);
80
    }
81
82 View Code Duplication
    public function rollbackPreparedTransaction(string $name): void
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

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