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

TxHandle   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 164
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 0
Metric Value
wmc 24
dl 0
loc 164
rs 10
c 0
b 0
f 0
lcom 1
cbo 10

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A __destruct() 0 9 2
A ensureIdentSerializer() 0 7 2
A ensureStringSerializer() 0 7 2
A assertOpen() 0 12 3
A isOpen() 0 4 1
A getTxConfig() 0 10 1
A setupTransaction() 0 6 1
A setTransactionSnapshot() 0 6 1
A exportTransactionSnapshot() 0 8 1
A commit() 0 7 1
A rollback() 0 7 1
A rollbackIfOpen() 0 8 2
A savepoint() 0 7 1
A rollbackToSavepoint() 0 7 1
A releaseSavepoint() 0 7 1
A prepareTransaction() 0 17 2
1
<?php
2
declare(strict_types=1);
3
4
namespace Ivory\Connection;
5
6
use Ivory\Exception\InvalidStateException;
7
use Ivory\Result\IQueryResult;
8
use Ivory\Type\IValueSerializer;
9
use Ivory\Type\Ivory\IdentifierSerializer;
10
use Ivory\Type\Std\StringType;
11
12
/**
13
 * {@inheritdoc}
14
 *
15
 * This implementation checks, upon destruction, whether the transaction has properly been closed. If not, i.e., when
16
 * the transaction handle gets lost and thus no further means of controlling the transaction are available, a warning is
17
 * emitted.
18
 */
19
class TxHandle implements ITxHandle
20
{
21
    private $open = true;
22
    private $stmtExec;
23
    private $txCtl;
24
    private $sessionCtl;
25
    private $identSerializer = null;
26
    private $stringSerializer = null;
27
28
    public function __construct(
29
        IStatementExecution $stmtExec,
30
        IObservableTransactionControl $observableTxCtl,
31
        ISessionControl $sessionCtl
32
    ) {
33
        $this->stmtExec = $stmtExec;
34
        $this->txCtl = $observableTxCtl;
35
        $this->sessionCtl = $sessionCtl;
36
    }
37
38
    public function __destruct()
39
    {
40
        if ($this->open) {
41
            trigger_error(
42
                'An open transaction handle has been released from memory. The transaction has probably stayed open.',
43
                E_USER_WARNING
44
            );
45
        }
46
    }
47
48
    private function ensureIdentSerializer(): IValueSerializer
49
    {
50
        if ($this->identSerializer === null) {
51
            $this->identSerializer = new IdentifierSerializer();
52
        }
53
        return $this->identSerializer;
54
    }
55
56
    private function ensureStringSerializer(): IValueSerializer
57
    {
58
        if ($this->stringSerializer === null) {
59
            $this->stringSerializer = new StringType('pg_catalog', 'text');
60
        }
61
        return $this->stringSerializer;
62
    }
63
64
    private function assertOpen(): void
65
    {
66
        if (!$this->open) {
67
            if ($this->txCtl->inTransaction()) {
68
                throw new InvalidStateException(
69
                    'Controlling the transaction using a wrong handle - this has already been closed'
70
                );
71
            } else {
72
                throw new InvalidStateException('The transaction is not open anymore');
73
            }
74
        }
75
    }
76
77
    public function isOpen(): bool
78
    {
79
        return $this->open;
80
    }
81
82
    public function getTxConfig(): TxConfig
83
    {
84
        $this->assertOpen();
85
        $connConfig = $this->sessionCtl->getConfig();
86
        return TxConfig::createFromParams(
87
            $connConfig->get('transaction_isolation'),
88
            $connConfig->get('transaction_read_only'),
89
            $connConfig->get('transaction_deferrable')
90
        );
91
    }
92
93
    public function setupTransaction($transactionOptions): void
94
    {
95
        $this->assertOpen();
96
        $txConfig = TxConfig::create($transactionOptions);
97
        $this->stmtExec->rawCommand('SET TRANSACTION ' . $txConfig->toSql());
98
    }
99
100
    public function setTransactionSnapshot(string $snapshotId): void
101
    {
102
        $this->assertOpen();
103
        $str = $this->ensureStringSerializer()->serializeValue($snapshotId);
104
        $this->stmtExec->rawCommand("SET TRANSACTION SNAPSHOT $str");
105
    }
106
107
    public function exportTransactionSnapshot(): string
108
    {
109
        $this->assertOpen();
110
111
        /** @var IQueryResult $r */
112
        $r = $this->stmtExec->rawQuery('SELECT pg_export_snapshot()');
113
        return $r->value();
114
    }
115
116
    public function commit(): void
117
    {
118
        $this->assertOpen();
119
        $this->stmtExec->rawCommand('COMMIT');
120
        $this->open = false;
121
        $this->txCtl->notifyTransactionCommit();
122
    }
123
124
    public function rollback(): void
125
    {
126
        $this->assertOpen();
127
        $this->stmtExec->rawCommand('ROLLBACK');
128
        $this->open = false;
129
        $this->txCtl->notifyTransactionRollback();
130
    }
131
132
    public function rollbackIfOpen(): void
133
    {
134
        if ($this->open) {
135
            $this->stmtExec->rawCommand('ROLLBACK');
136
            $this->open = false;
137
            $this->txCtl->notifyTransactionRollback();
138
        }
139
    }
140
141
    public function savepoint(string $name): void
142
    {
143
        $this->assertOpen();
144
        $ident = $this->ensureIdentSerializer()->serializeValue($name);
145
        $this->stmtExec->rawCommand("SAVEPOINT $ident");
146
        $this->txCtl->notifySavepointSaved($name);
147
    }
148
149
    public function rollbackToSavepoint(string $name): void
150
    {
151
        $this->assertOpen();
152
        $ident = $this->ensureIdentSerializer()->serializeValue($name);
153
        $this->stmtExec->rawCommand("ROLLBACK TO SAVEPOINT $ident");
154
        $this->txCtl->notifyRollbackToSavepoint($name);
155
    }
156
157
    public function releaseSavepoint(string $name): void
158
    {
159
        $this->assertOpen();
160
        $ident = $this->ensureIdentSerializer()->serializeValue($name);
161
        $this->stmtExec->rawCommand("RELEASE SAVEPOINT $ident");
162
        $this->txCtl->notifySavepointReleased($name);
163
    }
164
165
    public function prepareTransaction(?string $name = null): string
166
    {
167
        $this->assertOpen();
168
169
        if ($name === null) {
170
            $len = 16;
171
            $bytes = random_bytes(ceil($len / 2));
172
            $name = substr(bin2hex($bytes), 0, $len);
173
        }
174
175
        $str = $this->ensureStringSerializer()->serializeValue($name);
176
        $this->stmtExec->rawCommand("PREPARE TRANSACTION $str");
177
        $this->open = false;
178
        $this->txCtl->notifyTransactionPrepared($name);
179
180
        return $name;
181
    }
182
}
183