|
1
|
|
|
<?php |
|
2
|
|
|
namespace Fwlib\Db\Diff; |
|
3
|
|
|
|
|
4
|
|
|
use Fwlib\Db\Diff\Executor; |
|
5
|
|
|
use Fwlib\Db\Diff\ExecutorInterface; |
|
6
|
|
|
use Fwlib\Db\Diff\Row; |
|
7
|
|
|
use Fwlib\Db\Diff\RowSet; |
|
8
|
|
|
|
|
9
|
|
|
/** |
|
10
|
|
|
* Manage and execute RowSet |
|
11
|
|
|
* |
|
12
|
|
|
* The execute of RowSet is done by Executor. |
|
13
|
|
|
* |
|
14
|
|
|
* @copyright Copyright 2012-2015 Fwolf |
|
15
|
|
|
* @license http://www.gnu.org/licenses/lgpl.html LGPL-3.0+ |
|
16
|
|
|
*/ |
|
17
|
|
|
class Manager |
|
18
|
|
|
{ |
|
19
|
|
|
/** |
|
20
|
|
|
* @var ExecutorInterface |
|
21
|
|
|
*/ |
|
22
|
|
|
protected $executor = null; |
|
23
|
|
|
|
|
24
|
|
|
/** |
|
25
|
|
|
* Cache of table primaryKey, reduce db query |
|
26
|
|
|
* |
|
27
|
|
|
* @var array |
|
28
|
|
|
*/ |
|
29
|
|
|
protected $primaryKeyCache = []; |
|
30
|
|
|
|
|
31
|
|
|
/** |
|
32
|
|
|
* @var RowSet |
|
33
|
|
|
*/ |
|
34
|
|
|
protected $rowSet = null; |
|
35
|
|
|
|
|
36
|
|
|
|
|
37
|
|
|
/** |
|
38
|
|
|
* Constructor |
|
39
|
|
|
* |
|
40
|
|
|
* @param RowSet|string $rowSet |
|
41
|
|
|
*/ |
|
42
|
|
|
public function __construct($rowSet = null) |
|
43
|
|
|
{ |
|
44
|
|
|
if (empty($rowSet)) { |
|
45
|
|
|
$this->renew(); |
|
46
|
|
|
} else { |
|
47
|
|
|
$this->setRowSet($rowSet); |
|
48
|
|
|
} |
|
49
|
|
|
} |
|
50
|
|
|
|
|
51
|
|
|
|
|
52
|
|
|
/** |
|
53
|
|
|
* Add a row to $rowSet |
|
54
|
|
|
* |
|
55
|
|
|
* @param string $table |
|
56
|
|
|
* @param array|null $old |
|
57
|
|
|
* @param array|null $new |
|
58
|
|
|
* @return Manager |
|
59
|
|
|
*/ |
|
60
|
|
|
public function addRow($table, $old, $new) |
|
61
|
|
|
{ |
|
62
|
|
|
$this->checkIfCanAddRow(); |
|
63
|
|
|
|
|
64
|
|
|
if ($this->isSame($old, $new)) { |
|
65
|
|
|
return $this; |
|
66
|
|
|
} |
|
67
|
|
|
|
|
68
|
|
|
$primaryKey = $this->getPrimaryKey($table); |
|
69
|
|
|
|
|
70
|
|
|
if (empty($old)) { |
|
71
|
|
|
// INSERT mode |
|
72
|
|
|
$this->checkPrimaryKeyExist($primaryKey, $new); |
|
|
|
|
|
|
73
|
|
|
$old = null; |
|
74
|
|
|
|
|
75
|
|
|
} elseif (empty($new)) { |
|
76
|
|
|
// DELETE mode |
|
77
|
|
|
$this->checkPrimaryKeyExist($primaryKey, $old); |
|
78
|
|
|
$new = null; |
|
79
|
|
|
|
|
80
|
|
|
} else { |
|
81
|
|
|
// UPDATE mode |
|
82
|
|
|
$this->checkPrimaryKeyExist($primaryKey, $old); |
|
83
|
|
|
$this->checkPrimaryKeyExist($primaryKey, $new); |
|
84
|
|
|
} |
|
85
|
|
|
|
|
86
|
|
|
$this->rowSet->addRow( |
|
87
|
|
|
$this->createRow($table, $primaryKey, $old, $new) |
|
88
|
|
|
); |
|
89
|
|
|
|
|
90
|
|
|
return $this; |
|
91
|
|
|
} |
|
92
|
|
|
|
|
93
|
|
|
|
|
94
|
|
|
/** |
|
95
|
|
|
* Add multiple rows to $rowSet |
|
96
|
|
|
* |
|
97
|
|
|
* If both $oldRows and $newRows are not empty, their key should keep same |
|
98
|
|
|
* value and sequence. |
|
99
|
|
|
* |
|
100
|
|
|
* @param string $table |
|
101
|
|
|
* @param array|null $oldRows |
|
102
|
|
|
* @param array|null $newRows |
|
103
|
|
|
* @return Manager |
|
104
|
|
|
*/ |
|
105
|
|
|
public function addRows($table, $oldRows, $newRows) |
|
106
|
|
|
{ |
|
107
|
|
|
$this->checkIfCanAddRow(); |
|
108
|
|
|
|
|
109
|
|
|
if (empty($oldRows) && empty($newRows)) { |
|
110
|
|
|
return $this; |
|
111
|
|
|
} |
|
112
|
|
|
|
|
113
|
|
|
if (empty($oldRows)) { |
|
114
|
|
|
$keys = array_keys($newRows); |
|
115
|
|
|
} else { |
|
116
|
|
|
$keys = array_keys($oldRows); |
|
117
|
|
|
} |
|
118
|
|
|
|
|
119
|
|
|
foreach ($keys as $key) { |
|
120
|
|
|
$this->addRow( |
|
121
|
|
|
$table, |
|
122
|
|
|
empty($oldRows) ? null : $oldRows[$key], |
|
123
|
|
|
empty($newRows) ? null : $newRows[$key] |
|
124
|
|
|
); |
|
125
|
|
|
} |
|
126
|
|
|
|
|
127
|
|
|
return $this; |
|
128
|
|
|
} |
|
129
|
|
|
|
|
130
|
|
|
|
|
131
|
|
|
/** |
|
132
|
|
|
* If it can add row now ? |
|
133
|
|
|
* |
|
134
|
|
|
* If current row set is null or executed, it can't |
|
135
|
|
|
*/ |
|
136
|
|
|
protected function checkIfCanAddRow() |
|
137
|
|
|
{ |
|
138
|
|
|
if (is_null($this->rowSet)) { |
|
139
|
|
|
throw new \Exception('No RowSet set'); |
|
140
|
|
|
} |
|
141
|
|
|
|
|
142
|
|
|
if ($this->rowSet->isExecuted()) { |
|
143
|
|
|
throw new \Exception( |
|
144
|
|
|
'Can\'t add row when RowSet is already executed' |
|
145
|
|
|
); |
|
146
|
|
|
} |
|
147
|
|
|
} |
|
148
|
|
|
|
|
149
|
|
|
|
|
150
|
|
|
/** |
|
151
|
|
|
* Check primary key exists as index in data array, or throw exception |
|
152
|
|
|
* |
|
153
|
|
|
* @param array|string $primaryKey |
|
154
|
|
|
* @param array $data |
|
155
|
|
|
*/ |
|
156
|
|
|
protected function checkPrimaryKeyExist($primaryKey, $data) |
|
157
|
|
|
{ |
|
158
|
|
|
foreach ((array)$primaryKey as $key) { |
|
159
|
|
|
if (!isset($data[$key])) { |
|
160
|
|
|
throw new \Exception( |
|
161
|
|
|
"Primary key $key is not included in data array" |
|
162
|
|
|
); |
|
163
|
|
|
} |
|
164
|
|
|
} |
|
165
|
|
|
} |
|
166
|
|
|
|
|
167
|
|
|
|
|
168
|
|
|
/** |
|
169
|
|
|
* Commit row set, change db |
|
170
|
|
|
* |
|
171
|
|
|
* @return Manager |
|
172
|
|
|
*/ |
|
173
|
|
|
public function commit() |
|
174
|
|
|
{ |
|
175
|
|
|
$this->getExecutor()->commit($this->rowSet); |
|
176
|
|
|
|
|
177
|
|
|
return $this; |
|
178
|
|
|
} |
|
179
|
|
|
|
|
180
|
|
|
|
|
181
|
|
|
/** |
|
182
|
|
|
* Create a new Row instance |
|
183
|
|
|
* |
|
184
|
|
|
* This method can be extend to use custom Row class. |
|
185
|
|
|
* |
|
186
|
|
|
* @param string $table |
|
187
|
|
|
* @param array|string $primaryKey |
|
188
|
|
|
* @param array|null $old |
|
189
|
|
|
* @param array|null $new |
|
190
|
|
|
* @return Row |
|
191
|
|
|
*/ |
|
192
|
|
|
protected function createRow($table, $primaryKey, $old, $new) |
|
193
|
|
|
{ |
|
194
|
|
|
return new Row($table, $primaryKey, $old, $new); |
|
195
|
|
|
} |
|
196
|
|
|
|
|
197
|
|
|
|
|
198
|
|
|
/** |
|
199
|
|
|
* Execute row set if not executed |
|
200
|
|
|
* |
|
201
|
|
|
* @return Manager |
|
202
|
|
|
*/ |
|
203
|
|
|
public function execute() |
|
204
|
|
|
{ |
|
205
|
|
|
$this->getExecutor()->execute($this->rowSet); |
|
206
|
|
|
|
|
207
|
|
|
return $this; |
|
208
|
|
|
} |
|
209
|
|
|
|
|
210
|
|
|
|
|
211
|
|
|
/** |
|
212
|
|
|
* Getter of RowSet Executor |
|
213
|
|
|
* |
|
214
|
|
|
* @return ExecutorInterface |
|
215
|
|
|
*/ |
|
216
|
|
|
protected function getExecutor() |
|
217
|
|
|
{ |
|
218
|
|
|
if (is_null($this->executor)) { |
|
219
|
|
|
$this->executor = new Executor(); |
|
220
|
|
|
} |
|
221
|
|
|
|
|
222
|
|
|
return $this->executor; |
|
223
|
|
|
} |
|
224
|
|
|
|
|
225
|
|
|
|
|
226
|
|
|
/** |
|
227
|
|
|
* Get table primary key |
|
228
|
|
|
* |
|
229
|
|
|
* This method can be extend to use different db connection. |
|
230
|
|
|
* |
|
231
|
|
|
* @param string $table |
|
232
|
|
|
* @return array|string |
|
233
|
|
|
*/ |
|
234
|
|
|
protected function getPrimaryKey($table) |
|
|
|
|
|
|
235
|
|
|
{ |
|
236
|
|
|
return 'uuid'; |
|
237
|
|
|
|
|
238
|
|
|
/** |
|
239
|
|
|
* After removed dependence of Adodb, there are no way to retrieve |
|
240
|
|
|
* table primary key, so here is 2 solution: |
|
241
|
|
|
* |
|
242
|
|
|
* - Use stable primary key or have a map here to got it |
|
243
|
|
|
* - Query from db by Adodb or other db library |
|
244
|
|
|
* |
|
245
|
|
|
* By default we use solid 'uuid' as primary key, the old code using |
|
246
|
|
|
* Adodb is commented below. |
|
247
|
|
|
*/ |
|
248
|
|
|
|
|
249
|
|
|
/* |
|
250
|
|
|
if (isset($this->primaryKeyCache[$table])) { |
|
251
|
|
|
return $this->primaryKeyCache[$table]; |
|
252
|
|
|
} |
|
253
|
|
|
|
|
254
|
|
|
$primaryKey = $this->getDb()->getMetaPrimaryKey($table); |
|
255
|
|
|
|
|
256
|
|
|
if (empty($primaryKey)) { |
|
257
|
|
|
throw new \Exception("Table $table has no primary key"); |
|
258
|
|
|
} |
|
259
|
|
|
|
|
260
|
|
|
$this->primaryKeyCache[$table] = $primaryKey; |
|
261
|
|
|
return $primaryKey; |
|
262
|
|
|
*/ |
|
263
|
|
|
} |
|
264
|
|
|
|
|
265
|
|
|
|
|
266
|
|
|
/** |
|
267
|
|
|
* Getter of $rowSet |
|
268
|
|
|
* |
|
269
|
|
|
* @return RowSet |
|
270
|
|
|
*/ |
|
271
|
|
|
public function getRowSet() |
|
272
|
|
|
{ |
|
273
|
|
|
return $this->rowSet; |
|
274
|
|
|
} |
|
275
|
|
|
|
|
276
|
|
|
|
|
277
|
|
|
/** |
|
278
|
|
|
* Check if old and new data array are same |
|
279
|
|
|
* |
|
280
|
|
|
* @param array|null $old |
|
281
|
|
|
* @param array|null $new |
|
282
|
|
|
* @return boolean |
|
283
|
|
|
*/ |
|
284
|
|
|
protected function isSame($old, $new) |
|
285
|
|
|
{ |
|
286
|
|
|
if (empty($old) && empty($new)) { |
|
287
|
|
|
return true; |
|
288
|
|
|
} |
|
289
|
|
|
|
|
290
|
|
|
if ((empty($old) && !empty($new)) || |
|
291
|
|
|
(!empty($old) && empty($old)) |
|
292
|
|
|
) { |
|
293
|
|
|
return false; |
|
294
|
|
|
} |
|
295
|
|
|
|
|
296
|
|
|
$diff = array_diff_assoc((array)$old, (array)$new); |
|
297
|
|
|
|
|
298
|
|
|
return empty($diff); |
|
299
|
|
|
} |
|
300
|
|
|
|
|
301
|
|
|
|
|
302
|
|
|
/** |
|
303
|
|
|
* Create a new empty $rowSet |
|
304
|
|
|
* |
|
305
|
|
|
* @return Manager |
|
306
|
|
|
*/ |
|
307
|
|
|
public function renew() |
|
308
|
|
|
{ |
|
309
|
|
|
$this->rowSet = new RowSet(); |
|
310
|
|
|
|
|
311
|
|
|
return $this; |
|
312
|
|
|
} |
|
313
|
|
|
|
|
314
|
|
|
|
|
315
|
|
|
/** |
|
316
|
|
|
* Rollback committed row set |
|
317
|
|
|
* |
|
318
|
|
|
* @return Manager |
|
319
|
|
|
*/ |
|
320
|
|
|
public function rollback() |
|
321
|
|
|
{ |
|
322
|
|
|
$this->getExecutor()->rollback($this->rowSet); |
|
323
|
|
|
|
|
324
|
|
|
return $this; |
|
325
|
|
|
} |
|
326
|
|
|
|
|
327
|
|
|
|
|
328
|
|
|
/** |
|
329
|
|
|
* Setter of RowSet Executor |
|
330
|
|
|
* |
|
331
|
|
|
* @param ExecutorInterface $executor |
|
332
|
|
|
* @return Manager |
|
333
|
|
|
*/ |
|
334
|
|
|
public function setExecutor(ExecutorInterface $executor) |
|
335
|
|
|
{ |
|
336
|
|
|
$this->executor = $executor; |
|
337
|
|
|
|
|
338
|
|
|
return $this; |
|
339
|
|
|
} |
|
340
|
|
|
|
|
341
|
|
|
|
|
342
|
|
|
/** |
|
343
|
|
|
* Setter of RowSet |
|
344
|
|
|
* |
|
345
|
|
|
* @param RowSet|string $rowSet |
|
346
|
|
|
* @return Manager |
|
347
|
|
|
*/ |
|
348
|
|
|
public function setRowSet($rowSet) |
|
349
|
|
|
{ |
|
350
|
|
|
if (is_string($rowSet)) { |
|
351
|
|
|
$rowSet = new RowSet($rowSet); |
|
352
|
|
|
} |
|
353
|
|
|
|
|
354
|
|
|
$this->rowSet = $rowSet; |
|
355
|
|
|
|
|
356
|
|
|
return $this; |
|
357
|
|
|
} |
|
358
|
|
|
} |
|
359
|
|
|
|
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.