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 save points 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 Operation[] |
77
|
|
|
* The operations found when closing save points. |
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
|
|
|
|
89
|
2 |
|
$operations = []; |
90
|
|
|
|
91
|
|
|
// Collect the operations. |
92
|
2 |
|
if (isset($idx)) { |
93
|
2 |
|
end($this->operations); |
94
|
2 |
|
$lastIdx = key($this->operations); |
95
|
2 |
|
for ($removeIdx = $idx; $removeIdx <= $lastIdx; $removeIdx++) { |
96
|
2 |
|
if (isset($this->operations[$removeIdx])) { |
97
|
2 |
|
$operations[$removeIdx] = $this->operations[$removeIdx]; |
98
|
2 |
|
} |
99
|
2 |
|
} |
100
|
2 |
|
reset($this->operations); |
101
|
2 |
|
} |
102
|
2 |
|
return $operations; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* Run commit on operations and remove them from the buffer. |
107
|
|
|
* |
108
|
|
|
* @param Operation[] |
109
|
|
|
* The operations to commit. |
110
|
|
|
*/ |
111
|
1 |
|
protected function commitOperations($operations) { |
112
|
1 |
|
foreach ($operations as $operation) { |
113
|
1 |
|
$operation->commit($this); |
114
|
1 |
|
$this->removeOperation($operation); |
115
|
1 |
|
} |
116
|
1 |
|
} |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* Run commit on operations and remove them from the buffer. |
120
|
|
|
* |
121
|
|
|
* @param Operation[] |
122
|
|
|
* The operations to commit. |
123
|
|
|
*/ |
124
|
1 |
|
protected function rollbackOperations($operations) { |
125
|
1 |
|
foreach ($operations as $operation) { |
126
|
1 |
|
$operation->rollback($this); |
127
|
1 |
|
$this->removeOperation($operation); |
128
|
1 |
|
} |
129
|
1 |
|
} |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* Start transaction. |
133
|
|
|
* |
134
|
|
|
* @param int $newDepth |
135
|
|
|
* (optional) If specified, use as new depth, otherwise increment current depth. |
136
|
|
|
* |
137
|
|
|
*/ |
138
|
1 |
|
public function startTransaction($newDepth = null) |
139
|
|
|
{ |
140
|
1 |
|
$this->depth = isset($newDepth) ? $newDepth : $this->depth + 1; |
141
|
1 |
|
$this->savePoints[$this->depth] = $this->idx; |
142
|
1 |
|
} |
143
|
|
|
|
144
|
|
|
/** |
145
|
|
|
* Commit transaction. |
146
|
|
|
* |
147
|
|
|
* @param int $newDepth |
148
|
|
|
* (optional) If specified, use as new depth, otherwise decrement current depth. |
149
|
|
|
* |
150
|
|
|
*/ |
151
|
2 |
|
public function commitTransaction($newDepth = null) |
152
|
|
|
{ |
153
|
2 |
|
$oldDepth = $this->depth; |
154
|
2 |
|
$this->depth = isset($newDepth) ? $newDepth : $oldDepth - 1; |
155
|
2 |
|
if ($this->depth < 0) { |
156
|
1 |
|
throw new \RuntimeException('Trying to commit non-existant transaction.'); |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
// Collect operations and commit if applicable. |
160
|
2 |
|
$operations = $this->closeSavePoints($oldDepth, $this->depth); |
161
|
|
|
|
162
|
|
|
// Is this a real commit. |
163
|
2 |
|
if ($this->depth == 0 && $operations) { |
|
|
|
|
164
|
1 |
|
$this->commitOperations($operations); |
165
|
1 |
|
} |
166
|
2 |
|
} |
167
|
|
|
|
168
|
|
|
/** |
169
|
|
|
* Rollback transaction. |
170
|
|
|
* |
171
|
|
|
* @param int $newDepth |
172
|
|
|
* (optional) If specified, use as new depth, otherwise decrement current depth. |
173
|
|
|
* |
174
|
|
|
*/ |
175
|
2 |
|
public function rollbackTransaction($newDepth = null) |
176
|
|
|
{ |
177
|
2 |
|
$oldDepth = $this->depth; |
178
|
2 |
|
$this->depth = isset($newDepth) ? $newDepth : $oldDepth - 1; |
179
|
2 |
|
if ($this->depth < 0) { |
180
|
1 |
|
throw new \RuntimeException('Trying to rollback non-existant transaction.'); |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
// Collect operations and rollback. |
184
|
2 |
|
$operations = $this->closeSavePoints($oldDepth, $this->depth); |
185
|
2 |
|
$this->rollbackOperations($operations); |
186
|
2 |
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* Add operation. |
190
|
|
|
* |
191
|
|
|
* @param Operation $operation |
192
|
|
|
* The operation to add to the connection. |
193
|
|
|
* |
194
|
|
|
* @return Operation |
195
|
|
|
* The operation added. |
196
|
|
|
*/ |
197
|
1 |
|
public function addOperation(Operation $operation) |
198
|
|
|
{ |
199
|
1 |
|
if ($this->depth <= 0) { |
200
|
1 |
|
$operation->commit(); |
201
|
1 |
|
return $operation; |
202
|
|
|
} |
203
|
1 |
|
$idx = $this->idx; |
204
|
1 |
|
$this->idx++; |
205
|
1 |
|
$this->operations[$idx] = $operation; |
206
|
1 |
|
$operation->setIdx($this, $idx); |
207
|
1 |
|
return $operation; |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
/** |
211
|
|
|
* Check if the connection has an operation. |
212
|
|
|
* |
213
|
|
|
* @param Operation $operation |
214
|
|
|
* The operation to check for. |
215
|
|
|
* |
216
|
|
|
* @return bool |
217
|
|
|
* TRUE if the operation exists. |
218
|
|
|
*/ |
219
|
2 |
|
public function hasOperation(Operation $operation) |
220
|
|
|
{ |
221
|
2 |
|
return isset($this->operations[$operation->idx($this)]); |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
/** |
225
|
|
|
* Remove operation. |
226
|
|
|
* |
227
|
|
|
* @param Operation $operation |
228
|
|
|
* The operation to remove from the connection. |
229
|
|
|
*/ |
230
|
1 |
|
public function removeOperation(Operation $operation) |
231
|
|
|
{ |
232
|
1 |
|
unset($this->operations[$operation->idx($this)]); |
233
|
1 |
|
} |
234
|
|
|
|
235
|
|
|
/** |
236
|
|
|
* Short-hand notation for adding code to be run on commit. |
237
|
|
|
* |
238
|
|
|
* @param callable $callback |
239
|
|
|
* The code to run on commit. |
240
|
|
|
* |
241
|
|
|
* @return Operation |
242
|
|
|
* The operation created. |
243
|
|
|
*/ |
244
|
1 |
|
public function onCommit(callable $callback) |
245
|
|
|
{ |
246
|
1 |
|
return $this->addOperation((new Operation()) |
247
|
1 |
|
->onCommit($callback)); |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
/** |
251
|
|
|
* Short-hand notation for adding code to be run on rollback. |
252
|
|
|
* |
253
|
|
|
* @param callable $callback |
254
|
|
|
* The code to run on rollback. |
255
|
|
|
* |
256
|
|
|
* @return Operation |
257
|
|
|
* The operation created. |
258
|
|
|
*/ |
259
|
1 |
|
public function onRollback(callable $callback) |
260
|
|
|
{ |
261
|
1 |
|
return $this->addOperation((new Operation()) |
262
|
1 |
|
->onRollback($callback)); |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
/** |
266
|
|
|
* Short-hand notation for adding code to be run on rollback. |
267
|
|
|
* |
268
|
|
|
* @param mixed $value |
269
|
|
|
* The value to add. |
270
|
|
|
* |
271
|
|
|
* @return Operation |
272
|
|
|
* The operation created. |
273
|
|
|
*/ |
274
|
1 |
|
public function addValue($value) |
275
|
|
|
{ |
276
|
1 |
|
return $this->addOperation((new Operation()) |
277
|
1 |
|
->setValue($value)); |
278
|
|
|
} |
279
|
|
|
} |
280
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.