Completed
Push — master ( e05762...eb2a9a )
by Thomas
02:43
created

Connection::rollbackTransaction()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4

Importance

Changes 8
Bugs 3 Features 0
Metric Value
c 8
b 3
f 0
dl 0
loc 16
ccs 10
cts 10
cp 1
rs 9.2
cc 4
eloc 8
nc 6
nop 1
crap 4
1
<?php
2
3
namespace Gielfeldt\TransactionalPHP;
4
5
/**
6
 * Class Connection
7
 *
8
 * @package Gielfeldt\TransactionalPHP
9
 */
10
class Connection
11
{
12
    /**
13
     * @var Operation[]
14
     */
15
    protected $operations = [];
16
17
    /**
18
     * @var int
19
     */
20
    protected $idx = 0;
21
22
    /**
23
     * @var int[]
24
     */
25
    protected $savePoints = [];
26
27
    /**
28
     * @var int
29
     */
30
    protected $depth = 0;
31
32
    /**
33
     * @var null|string
34
     */
35
    protected $connectionId;
36
37
    /**
38
     * Connection constructor.
39
     *
40
     * @param null|string $connectionId
41
     *   (optional) The id of the connection.
42
     */
43 1
    public function __construct($connectionId = null)
44
    {
45 1
        $this->connectionId = isset($connectionId) ? $connectionId : uniqid();
46 1
    }
47
48
    /**
49
     * Get connection id.
50
     *
51
     * @return null|string
52
     */
53 1
    public function connectionId()
54
    {
55 1
        return $this->connectionId;
56
    }
57
58
    /**
59
     * Get current depth.
60
     *
61
     * @return int
62
     */
63 1
    public function getDepth()
64
    {
65 1
        return $this->depth;
66
    }
67
68
    /**
69
     * Remove savepoints to and acquire index of latest active savepoint.
70
     *
71
     * @param int $oldDepth
72
     *   The old depth.
73
     * @param $newDepth
74
     *   The new depth.
75
     *
76
     * @return int|null
77
     *   The last index, if found.
78
     */
79 2
    protected function closeSavePoints($oldDepth, $newDepth)
80
    {
81 2
        $idx = null;
82 2
        for ($depth = $newDepth + 1; $depth <= $oldDepth; $depth++) {
83 2
            if (isset($this->savePoints[$depth])) {
84 2
                $idx = isset($idx) ? $idx : $this->savePoints[$depth];
85 2
                unset($this->savePoints[$depth]);
86 2
            }
87 2
        }
88 2
        return $idx;
89
    }
90
91
    /**
92
     * Run commit on operations and remove them from the buffer, back to the index point specified.
93
     *
94
     * @param int $idx
95
     *   Index to commit to.
96
     */
97 1
    protected function commitSavePoints($idx) {
98
        // Perform the operations if any found.
99 1
        end($this->operations);
100 1
        $lastIdx = key($this->operations);
101 1
        for ($removeIdx = $idx; $removeIdx <= $lastIdx; $removeIdx++) {
102 1
            if (isset($this->operations[$removeIdx])) {
103 1
                $this->operations[$removeIdx]->commit($this);
104 1
                $this->removeOperation($this->operations[$removeIdx]);
105 1
            }
106 1
        }
107 1
        reset($this->operations);
108 1
    }
109
110
    /**
111
     * Run rollback on operations and remove them from the buffer, back to the index point specified.
112
     *
113
     * @param int $idx
114
     *   Index to rollback to.
115
     */
116 1
    protected function rollbackSavePoints($idx)
117
    {
118 1
        end($this->operations);
119 1
        $lastIdx = key($this->operations);
120 1
        for ($removeIdx = $idx; $removeIdx <= $lastIdx; $removeIdx++) {
121 1
            if (isset($this->operations[$removeIdx])) {
122 1
                $this->operations[$removeIdx]->rollback($this);
123 1
                $this->removeOperation($this->operations[$removeIdx]);
124 1
            }
125 1
        }
126 1
        reset($this->operations);
127 1
    }
128
129
    /**
130
     * Start transaction.
131
     *
132
     * @param int $newDepth
133
     *   (optional) If specified, use as new depth, otherwise increment current depth.
134
     *
135
     */
136 1
    public function startTransaction($newDepth = null)
137
    {
138 1
        $this->depth = isset($newDepth) ? $newDepth : $this->depth + 1;
139 1
        $this->savePoints[$this->depth] = $this->idx;
140 1
    }
141
142
    /**
143
     * Commit transaction.
144
     *
145
     * @param int $newDepth
146
     *   (optional) If specified, use as new depth, otherwise decrement current depth.
147
     *
148
     */
149 2
    public function commitTransaction($newDepth = null)
150
    {
151 2
        $oldDepth = $this->depth;
152 2
        $this->depth = isset($newDepth) ? $newDepth : $oldDepth - 1;
153 2
        if ($this->depth < 0) {
154 1
            throw new \RuntimeException('Trying to commit non-existant transaction.');
155
        }
156
157
        // Remove savepoints to and acquire index of latest active savepoint.
158 2
        $idx = $this->closeSavePoints($oldDepth, $this->depth);
159
160
        // Is this a real commit.
161 2
        if ($this->depth == 0 && isset($idx)) {
162 2
            $this->commitSavePoints($idx);
163 2
        }
164 2
    }
165
166
    /**
167
     * Rollback transaction.
168
     *
169
     * @param int $newDepth
170
     *   (optional) If specified, use as new depth, otherwise decrement current depth.
171
     *
172
     */
173 2
    public function rollbackTransaction($newDepth = null)
174
    {
175 2
        $oldDepth = $this->depth;
176 2
        $this->depth = isset($newDepth) ? $newDepth : $oldDepth - 1;
177 2
        if ($this->depth < 0) {
178 1
            throw new \RuntimeException('Trying to rollback non-existant transaction.');
179
        }
180
181
        // Remove savepoints to and acquire index of latest active savepoint.
182 2
        $idx = $this->closeSavePoints($oldDepth, $this->depth);
183
184
        // Remove operations up until latest active savepoint.
185 2
        if (isset($idx)) {
186 2
            $this->rollbackSavePoints($idx);
187 2
        }
188 2
    }
189
190
    /**
191
     * Add operation.
192
     *
193
     * @param Operation $operation
194
     *   The operation to add to the connection.
195
     *
196
     * @return Operation
197
     *   The operation added.
198
     */
199 1
    public function addOperation(Operation $operation)
200
    {
201 1
        if ($this->depth <= 0) {
202 1
            $operation->commit();
203 1
            return $operation;
204
        }
205 1
        $idx = $this->idx;
206 1
        $this->idx++;
207 1
        $this->operations[$idx] = $operation;
208 1
        $operation->setIdx($this, $idx);
209 1
        return $operation;
210
    }
211
212
    /**
213
     * Check if the connection has an operation.
214
     *
215
     * @param Operation $operation
216
     *   The operation to check for.
217
     *
218
     * @return bool
219
     *   TRUE if the operation exists.
220
     */
221 2
    public function hasOperation(Operation $operation)
222
    {
223 2
        return isset($this->operations[$operation->idx($this)]);
224
    }
225
226
    /**
227
     * Remove operation.
228
     *
229
     * @param Operation $operation
230
     *   The operation to remove from the connection.
231
     */
232 1
    public function removeOperation(Operation $operation)
233
    {
234 1
        unset($this->operations[$operation->idx($this)]);
235 1
    }
236
237
    /**
238
     * Short-hand notation for adding code to be run on commit.
239
     *
240
     * @param callable $callback
241
     *   The code to run on commit.
242
     *
243
     * @return Operation
244
     *   The operation created.
245
     */
246 1
    public function onCommit(callable $callback)
247
    {
248 1
        return $this->addOperation((new Operation())
249 1
            ->onCommit($callback));
250
    }
251
252
    /**
253
     * Short-hand notation for adding code to be run on rollback.
254
     *
255
     * @param callable $callback
256
     *   The code to run on rollback.
257
     *
258
     * @return Operation
259
     *   The operation created.
260
     */
261 1
    public function onRollback(callable $callback)
262
    {
263 1
        return $this->addOperation((new Operation())
264 1
            ->onRollback($callback));
265
    }
266
267
    /**
268
     * Short-hand notation for adding code to be run on rollback.
269
     *
270
     * @param mixed $value
271
     *   The value to add.
272
     *
273
     * @return Operation
274
     *   The operation created.
275
     */
276 1
    public function addValue($value)
277
    {
278 1
        return $this->addOperation((new Operation())
279 1
            ->setValue($value));
280
    }
281
}
282