|
1
|
|
|
<?php |
|
2
|
|
|
declare(strict_types=1); |
|
3
|
|
|
|
|
4
|
|
|
namespace SetBased\Stratum\MySql\Helper; |
|
5
|
|
|
|
|
6
|
|
|
use SetBased\Exception\FallenException; |
|
7
|
|
|
use SetBased\Stratum\Backend\StratumStyle; |
|
8
|
|
|
use SetBased\Stratum\Common\Exception\RoutineLoaderException; |
|
9
|
|
|
use SetBased\Stratum\Middle\Exception\ResultException; |
|
10
|
|
|
use SetBased\Stratum\MySql\Exception\MySqlQueryErrorException; |
|
11
|
|
|
use SetBased\Stratum\MySql\MySqlMetaDataLayer; |
|
12
|
|
|
use Symfony\Component\Console\Formatter\OutputFormatter; |
|
13
|
|
|
use Zend\Code\Reflection\DocBlock\Tag\ParamTag; |
|
14
|
|
|
use Zend\Code\Reflection\DocBlockReflection; |
|
15
|
|
|
|
|
16
|
|
|
/** |
|
17
|
|
|
* Class for loading a single stored routine into a MySQL instance from pseudo SQL file. |
|
18
|
|
|
*/ |
|
19
|
|
|
class RoutineLoaderHelper |
|
20
|
|
|
{ |
|
21
|
|
|
//-------------------------------------------------------------------------------------------------------------------- |
|
22
|
|
|
/** |
|
23
|
|
|
* The metadata of the table columns of the table for bulk insert. |
|
24
|
|
|
* |
|
25
|
|
|
* @var array[] |
|
26
|
|
|
*/ |
|
27
|
|
|
private $bulkInsertColumns; |
|
28
|
|
|
|
|
29
|
|
|
/** |
|
30
|
|
|
* The keys in the nested array for bulk inserting data. |
|
31
|
|
|
* |
|
32
|
|
|
* @var string[] |
|
33
|
|
|
*/ |
|
34
|
|
|
private $bulkInsertKeys; |
|
35
|
|
|
|
|
36
|
|
|
/** |
|
37
|
|
|
* The name of table for bulk insert. |
|
38
|
|
|
* |
|
39
|
|
|
* @var string |
|
40
|
|
|
*/ |
|
41
|
|
|
private $bulkInsertTableName; |
|
42
|
|
|
|
|
43
|
|
|
/** |
|
44
|
|
|
* The default character set under which the stored routine will be loaded and run. |
|
45
|
|
|
* |
|
46
|
|
|
* @var string |
|
47
|
|
|
*/ |
|
48
|
|
|
private $characterSet; |
|
49
|
|
|
|
|
50
|
|
|
/** |
|
51
|
|
|
* The default collate under which the stored routine will be loaded and run. |
|
52
|
|
|
* |
|
53
|
|
|
* @var string |
|
54
|
|
|
*/ |
|
55
|
|
|
private $collate; |
|
56
|
|
|
|
|
57
|
|
|
/** |
|
58
|
|
|
* The designation type of the stored routine. |
|
59
|
|
|
* |
|
60
|
|
|
* @var string |
|
61
|
|
|
*/ |
|
62
|
|
|
private $designationType; |
|
63
|
|
|
|
|
64
|
|
|
/** |
|
65
|
|
|
* The meta data layer. |
|
66
|
|
|
* |
|
67
|
|
|
* @var MySqlMetaDataLayer |
|
68
|
|
|
*/ |
|
69
|
|
|
private $dl; |
|
70
|
|
|
|
|
71
|
|
|
/** |
|
72
|
|
|
* All DocBlock parts as found in the source of the stored routine. |
|
73
|
|
|
* |
|
74
|
|
|
* @var array |
|
75
|
|
|
*/ |
|
76
|
|
|
private $docBlockPartsSource = []; |
|
77
|
|
|
|
|
78
|
|
|
/** |
|
79
|
|
|
* The DocBlock parts to be used by the wrapper generator. |
|
80
|
|
|
* |
|
81
|
|
|
* @var array |
|
82
|
|
|
*/ |
|
83
|
|
|
private $docBlockPartsWrapper; |
|
84
|
|
|
|
|
85
|
|
|
/** |
|
86
|
|
|
* Information about parameters with specific format (string in CSV format etc.) pass to the stored routine. |
|
87
|
|
|
* |
|
88
|
|
|
* @var array |
|
89
|
|
|
*/ |
|
90
|
|
|
private $extendedParameters; |
|
91
|
|
|
|
|
92
|
|
|
/** |
|
93
|
|
|
* The last modification time of the source file. |
|
94
|
|
|
* |
|
95
|
|
|
* @var int |
|
96
|
|
|
*/ |
|
97
|
|
|
private $filemtime; |
|
98
|
|
|
|
|
99
|
|
|
/** |
|
100
|
|
|
* The key or index columns (depending on the designation type) of the stored routine. |
|
101
|
|
|
* |
|
102
|
|
|
* @var string[] |
|
103
|
|
|
*/ |
|
104
|
|
|
private $indexColumns; |
|
105
|
|
|
|
|
106
|
|
|
/** |
|
107
|
|
|
* The Output decorator |
|
108
|
|
|
* |
|
109
|
|
|
* @var StratumStyle |
|
110
|
|
|
*/ |
|
111
|
|
|
private $io; |
|
112
|
|
|
|
|
113
|
|
|
/** |
|
114
|
|
|
* The information about the parameters of the stored routine. |
|
115
|
|
|
* |
|
116
|
|
|
* @var array[] |
|
117
|
|
|
*/ |
|
118
|
|
|
private $parameters = []; |
|
119
|
|
|
|
|
120
|
|
|
/** |
|
121
|
|
|
* The metadata of the stored routine. Note: this data is stored in the metadata file and is generated by PhpStratum. |
|
122
|
|
|
* |
|
123
|
|
|
* @var array |
|
124
|
|
|
*/ |
|
125
|
|
|
private $phpStratumMetadata; |
|
126
|
|
|
|
|
127
|
|
|
/** |
|
128
|
|
|
* The old metadata of the stored routine. Note: this data comes from the metadata file. |
|
129
|
|
|
* |
|
130
|
|
|
* @var array |
|
131
|
|
|
*/ |
|
132
|
|
|
private $phpStratumOldMetadata; |
|
133
|
|
|
|
|
134
|
|
|
/** |
|
135
|
|
|
* The old metadata of the stored routine. Note: this data comes from information_schema.ROUTINES. |
|
136
|
|
|
* |
|
137
|
|
|
* @var array |
|
138
|
|
|
*/ |
|
139
|
|
|
private $rdbmsOldRoutineMetadata; |
|
140
|
|
|
|
|
141
|
|
|
/** |
|
142
|
|
|
* The replace pairs (i.e. placeholders and their actual values, see strst). |
|
143
|
|
|
* |
|
144
|
|
|
* @var array |
|
145
|
|
|
*/ |
|
146
|
|
|
private $replace = []; |
|
147
|
|
|
|
|
148
|
|
|
/** |
|
149
|
|
|
* A map from placeholders to their actual values. |
|
150
|
|
|
* |
|
151
|
|
|
* @var array |
|
152
|
|
|
*/ |
|
153
|
|
|
private $replacePairs = []; |
|
154
|
|
|
|
|
155
|
|
|
/** |
|
156
|
|
|
* The return type of the stored routine (only if designation type singleton0, singleton1, or function). |
|
157
|
|
|
* |
|
158
|
|
|
* @var string|null |
|
159
|
|
|
*/ |
|
160
|
|
|
private $returnType; |
|
161
|
|
|
|
|
162
|
|
|
/** |
|
163
|
|
|
* The name of the stored routine. |
|
164
|
|
|
* |
|
165
|
|
|
* @var string |
|
166
|
|
|
*/ |
|
167
|
|
|
private $routineName; |
|
168
|
|
|
|
|
169
|
|
|
/** |
|
170
|
|
|
* The source code as a single string of the stored routine. |
|
171
|
|
|
* |
|
172
|
|
|
* @var string |
|
173
|
|
|
*/ |
|
174
|
|
|
private $routineSourceCode; |
|
175
|
|
|
|
|
176
|
|
|
/** |
|
177
|
|
|
* The source code as an array of lines string of the stored routine. |
|
178
|
|
|
* |
|
179
|
|
|
* @var array |
|
180
|
|
|
*/ |
|
181
|
|
|
private $routineSourceCodeLines; |
|
182
|
|
|
|
|
183
|
|
|
/** |
|
184
|
|
|
* The stored routine type (i.e. procedure or function) of the stored routine. |
|
185
|
|
|
* |
|
186
|
|
|
* @var string |
|
187
|
|
|
*/ |
|
188
|
|
|
private $routineType; |
|
189
|
|
|
|
|
190
|
|
|
/** |
|
191
|
|
|
* The source filename holding the stored routine. |
|
192
|
|
|
* |
|
193
|
|
|
* @var string |
|
194
|
|
|
*/ |
|
195
|
|
|
private $sourceFilename; |
|
196
|
|
|
|
|
197
|
|
|
/** |
|
198
|
|
|
* The SQL mode under which the stored routine will be loaded and run. |
|
199
|
|
|
* |
|
200
|
|
|
* @var string |
|
201
|
|
|
*/ |
|
202
|
|
|
private $sqlMode; |
|
203
|
|
|
|
|
204
|
|
|
//-------------------------------------------------------------------------------------------------------------------- |
|
205
|
|
|
/** |
|
206
|
|
|
* Object constructor. |
|
207
|
|
|
* |
|
208
|
|
|
* @param MySqlMetaDataLayer $dl The meta data layer. |
|
209
|
|
|
* @param StratumStyle $io The output for log messages. |
|
210
|
|
|
* @param string $routineFilename The filename of the source of the stored routine. |
|
211
|
|
|
* @param array $phpStratumMetadata The metadata of the stored routine from PhpStratum. |
|
212
|
|
|
* @param array $replacePairs A map from placeholders to their actual values. |
|
213
|
|
|
* @param array $rdbmsOldRoutineMetadata The old metadata of the stored routine from MySQL. |
|
214
|
|
|
* @param string $sqlMode The SQL mode under which the stored routine will be loaded and |
|
215
|
|
|
* run. |
|
216
|
|
|
* @param string $characterSet The default character set under which the stored routine will |
|
217
|
|
|
* be loaded and run. |
|
218
|
|
|
* @param string $collate The key or index columns (depending on the designation type) of |
|
219
|
|
|
* the stored routine. |
|
220
|
|
|
*/ |
|
221
|
1 |
|
public function __construct(MySqlMetaDataLayer $dl, |
|
222
|
|
|
StratumStyle $io, |
|
223
|
|
|
string $routineFilename, |
|
224
|
|
|
array $phpStratumMetadata, |
|
225
|
|
|
array $replacePairs, |
|
226
|
|
|
array $rdbmsOldRoutineMetadata, |
|
227
|
|
|
string $sqlMode, |
|
228
|
|
|
string $characterSet, |
|
229
|
|
|
string $collate) |
|
230
|
|
|
{ |
|
231
|
1 |
|
$this->dl = $dl; |
|
232
|
1 |
|
$this->io = $io; |
|
233
|
1 |
|
$this->sourceFilename = $routineFilename; |
|
234
|
1 |
|
$this->phpStratumMetadata = $phpStratumMetadata; |
|
235
|
1 |
|
$this->replacePairs = $replacePairs; |
|
236
|
1 |
|
$this->rdbmsOldRoutineMetadata = $rdbmsOldRoutineMetadata; |
|
237
|
1 |
|
$this->sqlMode = $sqlMode; |
|
238
|
1 |
|
$this->characterSet = $characterSet; |
|
239
|
1 |
|
$this->collate = $collate; |
|
240
|
1 |
|
} |
|
241
|
|
|
|
|
242
|
|
|
//-------------------------------------------------------------------------------------------------------------------- |
|
243
|
|
|
/** |
|
244
|
|
|
* Extract column metadata from the rows returned by the SQL statement 'describe table'. |
|
245
|
|
|
* |
|
246
|
|
|
* @param array $description The description of the table. |
|
247
|
|
|
* |
|
248
|
|
|
* @return array |
|
249
|
|
|
*/ |
|
250
|
|
|
private static function extractColumnsFromTableDescription(array $description): array |
|
251
|
|
|
{ |
|
252
|
|
|
$ret = []; |
|
253
|
|
|
|
|
254
|
|
|
foreach ($description as $column) |
|
255
|
|
|
{ |
|
256
|
|
|
preg_match('/^(\w+)(.*)?$/', $column['Type'], $parts1); |
|
257
|
|
|
|
|
258
|
|
|
$tmp = ['column_name' => $column['Field'], |
|
259
|
|
|
'data_type' => $parts1[1], |
|
260
|
|
|
'numeric_precision' => null, |
|
261
|
|
|
'numeric_scale' => null, |
|
262
|
|
|
'dtd_identifier' => $column['Type']]; |
|
263
|
|
|
|
|
264
|
|
|
switch ($parts1[1]) |
|
265
|
|
|
{ |
|
266
|
|
|
case 'tinyint': |
|
267
|
|
|
case 'smallint': |
|
268
|
|
|
case 'mediumint': |
|
269
|
|
|
case 'int': |
|
270
|
|
|
case 'bigint': |
|
271
|
|
|
preg_match('/^\((\d+)\)/', $parts1[2], $parts2); |
|
272
|
|
|
$tmp['numeric_precision'] = (int)$parts2[1]; |
|
273
|
|
|
$tmp['numeric_scale'] = 0; |
|
274
|
|
|
break; |
|
275
|
|
|
|
|
276
|
|
|
case 'year': |
|
277
|
|
|
// Nothing to do. |
|
278
|
|
|
break; |
|
279
|
|
|
|
|
280
|
|
|
case 'float': |
|
281
|
|
|
$tmp['numeric_precision'] = 12; |
|
282
|
|
|
break; |
|
283
|
|
|
|
|
284
|
|
|
case 'double': |
|
285
|
|
|
$tmp['numeric_precision'] = 22; |
|
286
|
|
|
break; |
|
287
|
|
|
|
|
288
|
|
|
case 'binary': |
|
289
|
|
|
case 'char': |
|
290
|
|
|
case 'varbinary': |
|
291
|
|
|
case 'varchar': |
|
292
|
|
|
// Nothing to do (binary) strings. |
|
293
|
|
|
break; |
|
294
|
|
|
|
|
295
|
|
|
case 'decimal': |
|
296
|
|
|
preg_match('/^\((\d+),(\d+)\)$/', $parts1[2], $parts2); |
|
297
|
|
|
$tmp['numeric_precision'] = (int)$parts2[1]; |
|
298
|
|
|
$tmp['numeric_scale'] = (int)$parts2[2];; |
|
299
|
|
|
break; |
|
300
|
|
|
|
|
301
|
|
|
case 'time': |
|
302
|
|
|
case 'timestamp': |
|
303
|
|
|
case 'date': |
|
304
|
|
|
case 'datetime': |
|
305
|
|
|
// Nothing to do date and time. |
|
306
|
|
|
break; |
|
307
|
|
|
|
|
308
|
|
|
case 'enum': |
|
309
|
|
|
case 'set': |
|
310
|
|
|
// Nothing to do sets. |
|
311
|
|
|
break; |
|
312
|
|
|
|
|
313
|
|
|
case 'bit': |
|
314
|
|
|
preg_match('/^\((\d+)\)$/', $parts1[2], $parts2); |
|
315
|
|
|
$tmp['numeric_precision'] = (int)$parts2[1]; |
|
316
|
|
|
break; |
|
317
|
|
|
|
|
318
|
|
|
case 'tinytext': |
|
319
|
|
|
case 'text': |
|
320
|
|
|
case 'mediumtext': |
|
321
|
|
|
case 'longtext': |
|
322
|
|
|
case 'tinyblob': |
|
323
|
|
|
case 'blob': |
|
324
|
|
|
case 'mediumblob': |
|
325
|
|
|
case 'longblob': |
|
326
|
|
|
// Nothing to do CLOBs and BLOBs. |
|
327
|
|
|
break; |
|
328
|
|
|
|
|
329
|
|
|
default: |
|
330
|
|
|
throw new FallenException('data type', $parts1[1]); |
|
331
|
|
|
} |
|
332
|
|
|
|
|
333
|
|
|
$ret[] = $tmp; |
|
334
|
|
|
} |
|
335
|
|
|
|
|
336
|
|
|
return $ret; |
|
337
|
|
|
} |
|
338
|
|
|
|
|
339
|
|
|
//-------------------------------------------------------------------------------------------------------------------- |
|
340
|
|
|
/** |
|
341
|
|
|
* Loads the stored routine into the instance of MySQL and returns the metadata of the stored routine. |
|
342
|
|
|
* |
|
343
|
|
|
* @return array |
|
344
|
|
|
* |
|
345
|
|
|
* @throws RoutineLoaderException |
|
346
|
|
|
* @throws MySqlQueryErrorException |
|
347
|
|
|
* @throws ResultException |
|
348
|
|
|
*/ |
|
349
|
1 |
|
public function loadStoredRoutine(): array |
|
350
|
|
|
{ |
|
351
|
1 |
|
$this->routineName = pathinfo($this->sourceFilename, PATHINFO_FILENAME); |
|
352
|
1 |
|
$this->phpStratumOldMetadata = $this->phpStratumMetadata; |
|
353
|
1 |
|
$this->filemtime = filemtime($this->sourceFilename); |
|
354
|
|
|
|
|
355
|
1 |
|
$load = $this->mustLoadStoredRoutine(); |
|
356
|
1 |
|
if ($load) |
|
357
|
|
|
{ |
|
358
|
|
|
$this->io->text(sprintf('Loading routine <dbo>%s</dbo>', OutputFormatter::escape($this->routineName))); |
|
359
|
|
|
|
|
360
|
|
|
$this->readSourceCode(); |
|
361
|
|
|
$this->extractPlaceholders(); |
|
362
|
|
|
$this->extractDesignationType(); |
|
363
|
|
|
$this->extractReturnType(); |
|
364
|
|
|
$this->extractRoutineTypeAndName(); |
|
365
|
|
|
$this->validateReturnType(); |
|
366
|
|
|
$this->loadRoutineFile(); |
|
367
|
|
|
$this->extractBulkInsertTableColumnsInfo(); |
|
368
|
|
|
$this->extractExtendedParametersInfo(); |
|
369
|
|
|
$this->extractRoutineParametersInfo(); |
|
370
|
|
|
$this->extractDocBlockPartsWrapper(); |
|
371
|
|
|
$this->validateParameterLists(); |
|
372
|
|
|
$this->updateMetadata(); |
|
373
|
|
|
} |
|
374
|
|
|
|
|
375
|
1 |
|
return $this->phpStratumMetadata; |
|
376
|
|
|
} |
|
377
|
|
|
|
|
378
|
|
|
//-------------------------------------------------------------------------------------------------------------------- |
|
379
|
|
|
/** |
|
380
|
|
|
* Drops the stored routine if it exists. |
|
381
|
|
|
* |
|
382
|
|
|
* @throws MySqlQueryErrorException |
|
383
|
|
|
*/ |
|
384
|
|
|
private function dropRoutine(): void |
|
385
|
|
|
{ |
|
386
|
|
|
if (!empty($this->rdbmsOldRoutineMetadata)) |
|
387
|
|
|
{ |
|
388
|
|
|
$this->dl->dropRoutine($this->rdbmsOldRoutineMetadata['routine_type'], $this->routineName); |
|
389
|
|
|
} |
|
390
|
|
|
} |
|
391
|
|
|
|
|
392
|
|
|
//-------------------------------------------------------------------------------------------------------------------- |
|
393
|
|
|
/** |
|
394
|
|
|
* Extracts the column names and column types of the current table for bulk insert. |
|
395
|
|
|
* |
|
396
|
|
|
* @throws RoutineLoaderException |
|
397
|
|
|
* @throws MySqlQueryErrorException |
|
398
|
|
|
* |
|
399
|
|
|
* @throws ResultException |
|
400
|
|
|
*/ |
|
401
|
|
|
private function extractBulkInsertTableColumnsInfo(): void |
|
402
|
|
|
{ |
|
403
|
|
|
// Return immediately if designation type is not appropriate for this method. |
|
404
|
|
|
if ($this->designationType!='bulk_insert') return; |
|
405
|
|
|
|
|
406
|
|
|
// Check if table is a temporary table or a non-temporary table. |
|
407
|
|
|
$table_is_non_temporary = $this->dl->checkTableExists($this->bulkInsertTableName); |
|
408
|
|
|
|
|
409
|
|
|
// Create temporary table if table is non-temporary table. |
|
410
|
|
|
if (!$table_is_non_temporary) |
|
411
|
|
|
{ |
|
412
|
|
|
$this->dl->callProcedure($this->routineName); |
|
413
|
|
|
} |
|
414
|
|
|
|
|
415
|
|
|
// Get information about the columns of the table. |
|
416
|
|
|
$description = $this->dl->describeTable($this->bulkInsertTableName); |
|
417
|
|
|
|
|
418
|
|
|
// Drop temporary table if table is non-temporary. |
|
419
|
|
|
if (!$table_is_non_temporary) |
|
420
|
|
|
{ |
|
421
|
|
|
$this->dl->dropTemporaryTable($this->bulkInsertTableName); |
|
422
|
|
|
} |
|
423
|
|
|
|
|
424
|
|
|
// Check number of columns in the table match the number of fields given in the designation type. |
|
425
|
|
|
$n1 = sizeof($this->bulkInsertKeys); |
|
426
|
|
|
$n2 = sizeof($description); |
|
427
|
|
|
if ($n1!=$n2) |
|
428
|
|
|
{ |
|
429
|
|
|
throw new RoutineLoaderException("Number of fields %d and number of columns %d don't match.", $n1, $n2); |
|
430
|
|
|
} |
|
431
|
|
|
|
|
432
|
|
|
$this->bulkInsertColumns = self::extractColumnsFromTableDescription($description); |
|
433
|
|
|
} |
|
434
|
|
|
|
|
435
|
|
|
//-------------------------------------------------------------------------------------------------------------------- |
|
436
|
|
|
/** |
|
437
|
|
|
* Extracts the designation type of the stored routine. |
|
438
|
|
|
* |
|
439
|
|
|
* @throws RoutineLoaderException |
|
440
|
|
|
*/ |
|
441
|
|
|
private function extractDesignationType(): void |
|
442
|
|
|
{ |
|
443
|
|
|
$found = true; |
|
444
|
|
|
$key = array_search('begin', $this->routineSourceCodeLines); |
|
445
|
|
|
|
|
446
|
|
|
if ($key!==false) |
|
447
|
|
|
{ |
|
448
|
|
|
for ($i = 1; $i<$key; $i++) |
|
449
|
|
|
{ |
|
450
|
|
|
$n = preg_match('/^\s*--\s+type:\s*(\w+)\s*(.+)?\s*$/', |
|
451
|
|
|
$this->routineSourceCodeLines[$key - $i], |
|
452
|
|
|
$matches); |
|
453
|
|
|
if ($n==1) |
|
454
|
|
|
{ |
|
455
|
|
|
$this->designationType = $matches[1]; |
|
456
|
|
|
switch ($this->designationType) |
|
457
|
|
|
{ |
|
458
|
|
|
case 'bulk_insert': |
|
459
|
|
|
$m = preg_match('/^([a-zA-Z0-9_]+)\s+([a-zA-Z0-9_,]+)$/', |
|
460
|
|
|
$matches[2], |
|
461
|
|
|
$info); |
|
462
|
|
|
if ($m==0) |
|
463
|
|
|
{ |
|
464
|
|
|
throw new RoutineLoaderException('Error: Expected: -- type: bulk_insert <table_name> <columns>'); |
|
465
|
|
|
} |
|
466
|
|
|
$this->bulkInsertTableName = $info[1]; |
|
467
|
|
|
$this->bulkInsertKeys = explode(',', $info[2]); |
|
468
|
|
|
break; |
|
469
|
|
|
|
|
470
|
|
|
case 'rows_with_key': |
|
471
|
|
|
case 'rows_with_index': |
|
472
|
|
|
$this->indexColumns = explode(',', $matches[2]); |
|
473
|
|
|
break; |
|
474
|
|
|
|
|
475
|
|
|
default: |
|
476
|
|
|
if (isset($matches[2])) $found = false; |
|
477
|
|
|
} |
|
478
|
|
|
break; |
|
479
|
|
|
} |
|
480
|
|
|
if ($i==($key - 1)) $found = false; |
|
481
|
|
|
} |
|
482
|
|
|
} |
|
483
|
|
|
else |
|
484
|
|
|
{ |
|
485
|
|
|
$found = false; |
|
486
|
|
|
} |
|
487
|
|
|
|
|
488
|
|
|
if ($found===false) |
|
489
|
|
|
{ |
|
490
|
|
|
throw new RoutineLoaderException('Unable to find the designation type of the stored routine'); |
|
491
|
|
|
} |
|
492
|
|
|
} |
|
493
|
|
|
|
|
494
|
|
|
//-------------------------------------------------------------------------------------------------------------------- |
|
495
|
|
|
/** |
|
496
|
|
|
* Extracts the DocBlock (in parts) from the source of the stored routine. |
|
497
|
|
|
*/ |
|
498
|
|
|
private function extractDocBlockPartsSource(): void |
|
499
|
|
|
{ |
|
500
|
|
|
// Get the DocBlock for the source. |
|
501
|
|
|
$tmp = PHP_EOL; |
|
502
|
|
|
foreach ($this->routineSourceCodeLines as $line) |
|
503
|
|
|
{ |
|
504
|
|
|
$n = preg_match('/create\\s+(procedure|function)\\s+([a-zA-Z0-9_]+)/i', $line); |
|
505
|
|
|
if ($n) break; |
|
506
|
|
|
|
|
507
|
|
|
$tmp .= $line; |
|
508
|
|
|
$tmp .= PHP_EOL; |
|
509
|
|
|
} |
|
510
|
|
|
|
|
511
|
|
|
$phpdoc = new DocBlockReflection($tmp); |
|
512
|
|
|
|
|
513
|
|
|
// Get the short description. |
|
514
|
|
|
$this->docBlockPartsSource['sort_description'] = $phpdoc->getShortDescription(); |
|
515
|
|
|
|
|
516
|
|
|
// Get the long description. |
|
517
|
|
|
$this->docBlockPartsSource['long_description'] = $phpdoc->getLongDescription(); |
|
518
|
|
|
|
|
519
|
|
|
// Get the description for each parameter of the stored routine. |
|
520
|
|
|
foreach ($phpdoc->getTags() as $key => $tag) |
|
521
|
|
|
{ |
|
522
|
|
|
if ($tag->getName()=='param') |
|
523
|
|
|
{ |
|
524
|
|
|
/* @var $tag ParamTag */ |
|
|
|
|
|
|
525
|
|
|
$this->docBlockPartsSource['parameters'][$key] = ['name' => $tag->getTypes()[0], |
|
526
|
|
|
'description' => $tag->getDescription()]; |
|
527
|
|
|
} |
|
528
|
|
|
} |
|
529
|
|
|
} |
|
530
|
|
|
|
|
531
|
|
|
//-------------------------------------------------------------------------------------------------------------------- |
|
532
|
|
|
/** |
|
533
|
|
|
* Extracts DocBlock parts to be used by the wrapper generator. |
|
534
|
|
|
*/ |
|
535
|
|
|
private function extractDocBlockPartsWrapper(): void |
|
536
|
|
|
{ |
|
537
|
|
|
// Get the DocBlock parts from the source of the stored routine. |
|
538
|
|
|
$this->extractDocBlockPartsSource(); |
|
539
|
|
|
|
|
540
|
|
|
// Generate the parameters parts of the DocBlock to be used by the wrapper. |
|
541
|
|
|
$parameters = []; |
|
542
|
|
|
foreach ($this->parameters as $parameter_info) |
|
543
|
|
|
{ |
|
544
|
|
|
$parameters[] = ['parameter_name' => $parameter_info['parameter_name'], |
|
545
|
|
|
'php_type' => DataTypeHelper::columnTypeToPhpTypeHinting($parameter_info).'|null', |
|
546
|
|
|
'data_type_descriptor' => $parameter_info['data_type_descriptor'], |
|
547
|
|
|
'description' => $this->getParameterDocDescription($parameter_info['parameter_name'])]; |
|
548
|
|
|
} |
|
549
|
|
|
|
|
550
|
|
|
// Compose all the DocBlock parts to be used by the wrapper generator. |
|
551
|
|
|
$this->docBlockPartsWrapper = ['sort_description' => $this->docBlockPartsSource['sort_description'], |
|
552
|
|
|
'long_description' => $this->docBlockPartsSource['long_description'], |
|
553
|
|
|
'parameters' => $parameters]; |
|
554
|
|
|
} |
|
555
|
|
|
|
|
556
|
|
|
//-------------------------------------------------------------------------------------------------------------------- |
|
557
|
|
|
/** |
|
558
|
|
|
* Extracts extended info of the routine parameters. |
|
559
|
|
|
* |
|
560
|
|
|
* @throws RoutineLoaderException |
|
561
|
|
|
*/ |
|
562
|
|
|
private function extractExtendedParametersInfo(): void |
|
563
|
|
|
{ |
|
564
|
|
|
$key = array_search('begin', $this->routineSourceCodeLines); |
|
565
|
|
|
|
|
566
|
|
|
if ($key!==false) |
|
567
|
|
|
{ |
|
568
|
|
|
for ($i = 1; $i<$key; $i++) |
|
569
|
|
|
{ |
|
570
|
|
|
$k = preg_match('/^\s*--\s+param:(?:\s*(\w+)\s+(\w+)(?:(?:\s+([^\s-])\s+([^\s-])\s+([^\s-])\s*$)|(?:\s*$)))?/', |
|
571
|
|
|
$this->routineSourceCodeLines[$key - $i + 1], |
|
572
|
|
|
$matches); |
|
573
|
|
|
|
|
574
|
|
|
if ($k==1) |
|
575
|
|
|
{ |
|
576
|
|
|
$count = sizeof($matches); |
|
577
|
|
|
if ($count==3 || $count==6) |
|
578
|
|
|
{ |
|
579
|
|
|
$parameter_name = $matches[1]; |
|
580
|
|
|
$data_type = $matches[2]; |
|
581
|
|
|
|
|
582
|
|
|
if ($count==6) |
|
583
|
|
|
{ |
|
584
|
|
|
$list_delimiter = $matches[3]; |
|
585
|
|
|
$list_enclosure = $matches[4]; |
|
586
|
|
|
$list_escape = $matches[5]; |
|
587
|
|
|
} |
|
588
|
|
|
else |
|
589
|
|
|
{ |
|
590
|
|
|
$list_delimiter = ','; |
|
591
|
|
|
$list_enclosure = '"'; |
|
592
|
|
|
$list_escape = '\\'; |
|
593
|
|
|
} |
|
594
|
|
|
|
|
595
|
|
|
if (!isset($this->extendedParameters[$parameter_name])) |
|
596
|
|
|
{ |
|
597
|
|
|
$this->extendedParameters[$parameter_name] = ['name' => $parameter_name, |
|
598
|
|
|
'data_type' => $data_type, |
|
599
|
|
|
'delimiter' => $list_delimiter, |
|
600
|
|
|
'enclosure' => $list_enclosure, |
|
601
|
|
|
'escape' => $list_escape]; |
|
602
|
|
|
} |
|
603
|
|
|
else |
|
604
|
|
|
{ |
|
605
|
|
|
throw new RoutineLoaderException("Duplicate parameter '%s'", $parameter_name); |
|
606
|
|
|
} |
|
607
|
|
|
} |
|
608
|
|
|
else |
|
609
|
|
|
{ |
|
610
|
|
|
throw new RoutineLoaderException('Error: Expected: -- param: <field_name> <type_of_list> [delimiter enclosure escape]'); |
|
611
|
|
|
} |
|
612
|
|
|
} |
|
613
|
|
|
} |
|
614
|
|
|
} |
|
615
|
|
|
} |
|
616
|
|
|
|
|
617
|
|
|
//-------------------------------------------------------------------------------------------------------------------- |
|
618
|
|
|
/** |
|
619
|
|
|
* Extracts the placeholders from the stored routine source. |
|
620
|
|
|
* |
|
621
|
|
|
* @throws RoutineLoaderException |
|
622
|
|
|
*/ |
|
623
|
|
|
private function extractPlaceholders(): void |
|
624
|
|
|
{ |
|
625
|
|
|
$unknown = []; |
|
626
|
|
|
|
|
627
|
|
|
preg_match_all('(@[A-Za-z0-9_.]+(%(type|sort))?@)', $this->routineSourceCode, $matches); |
|
628
|
|
|
if (!empty($matches[0])) |
|
629
|
|
|
{ |
|
630
|
|
|
foreach ($matches[0] as $placeholder) |
|
631
|
|
|
{ |
|
632
|
|
|
if (isset($this->replacePairs[strtoupper($placeholder)])) |
|
633
|
|
|
{ |
|
634
|
|
|
$this->replace[$placeholder] = $this->replacePairs[strtoupper($placeholder)]; |
|
635
|
|
|
} |
|
636
|
|
|
else |
|
637
|
|
|
{ |
|
638
|
|
|
$unknown[] = $placeholder; |
|
639
|
|
|
} |
|
640
|
|
|
} |
|
641
|
|
|
} |
|
642
|
|
|
|
|
643
|
|
|
$this->logUnknownPlaceholders($unknown); |
|
644
|
|
|
} |
|
645
|
|
|
|
|
646
|
|
|
//-------------------------------------------------------------------------------------------------------------------- |
|
647
|
|
|
/** |
|
648
|
|
|
* Extracts the return type of the stored routine. |
|
649
|
|
|
*/ |
|
650
|
|
|
private function extractReturnType(): void |
|
651
|
|
|
{ |
|
652
|
|
|
// Return immediately if designation type is not appropriate for this method. |
|
653
|
|
|
if (!in_array($this->designationType, ['function', 'singleton0', 'singleton1'])) return; |
|
654
|
|
|
|
|
655
|
|
|
$key = array_search('begin', $this->routineSourceCodeLines); |
|
656
|
|
|
|
|
657
|
|
|
if ($key!==false) |
|
658
|
|
|
{ |
|
659
|
|
|
for ($i = 1; $i<$key; $i++) |
|
660
|
|
|
{ |
|
661
|
|
|
$n = preg_match('/^\s*--\s+return:\s*((\w|\|)+)\s*$/', |
|
662
|
|
|
$this->routineSourceCodeLines[$key - $i], |
|
663
|
|
|
$matches); |
|
664
|
|
|
if ($n==1) |
|
665
|
|
|
{ |
|
666
|
|
|
$this->returnType = $matches[1]; |
|
667
|
|
|
|
|
668
|
|
|
break; |
|
669
|
|
|
} |
|
670
|
|
|
} |
|
671
|
|
|
} |
|
672
|
|
|
|
|
673
|
|
|
if ($this->returnType===null) |
|
674
|
|
|
{ |
|
675
|
|
|
$this->returnType = 'mixed'; |
|
676
|
|
|
|
|
677
|
|
|
$this->io->logNote('Unable to find the return type of stored routine'); |
|
678
|
|
|
} |
|
679
|
|
|
} |
|
680
|
|
|
|
|
681
|
|
|
//-------------------------------------------------------------------------------------------------------------------- |
|
682
|
|
|
/** |
|
683
|
|
|
* Extracts info about the parameters of the stored routine. |
|
684
|
|
|
* |
|
685
|
|
|
* @throws RoutineLoaderException |
|
686
|
|
|
* @throws MySqlQueryErrorException |
|
687
|
|
|
*/ |
|
688
|
|
|
private function extractRoutineParametersInfo(): void |
|
689
|
|
|
{ |
|
690
|
|
|
$routine_parameters = $this->dl->routineParameters($this->routineName); |
|
691
|
|
|
foreach ($routine_parameters as $key => $routine_parameter) |
|
692
|
|
|
{ |
|
693
|
|
|
if ($routine_parameter['parameter_name']) |
|
694
|
|
|
{ |
|
695
|
|
|
$data_type_descriptor = $routine_parameter['dtd_identifier']; |
|
696
|
|
|
if (isset($routine_parameter['character_set_name'])) |
|
697
|
|
|
{ |
|
698
|
|
|
$data_type_descriptor .= ' character set '.$routine_parameter['character_set_name']; |
|
699
|
|
|
} |
|
700
|
|
|
if (isset($routine_parameter['collation_name'])) |
|
701
|
|
|
{ |
|
702
|
|
|
$data_type_descriptor .= ' collation '.$routine_parameter['collation_name']; |
|
703
|
|
|
} |
|
704
|
|
|
|
|
705
|
|
|
$routine_parameter['data_type_descriptor'] = $data_type_descriptor; |
|
706
|
|
|
|
|
707
|
|
|
$this->parameters[$key] = $routine_parameter; |
|
708
|
|
|
} |
|
709
|
|
|
} |
|
710
|
|
|
|
|
711
|
|
|
$this->updateParametersInfo(); |
|
712
|
|
|
} |
|
713
|
|
|
|
|
714
|
|
|
//-------------------------------------------------------------------------------------------------------------------- |
|
715
|
|
|
/** |
|
716
|
|
|
* Extracts the name of the stored routine and the stored routine type (i.e. procedure or function) source. |
|
717
|
|
|
* |
|
718
|
|
|
* @throws RoutineLoaderException |
|
719
|
|
|
*/ |
|
720
|
|
|
private function extractRoutineTypeAndName(): void |
|
721
|
|
|
{ |
|
722
|
|
|
$n = preg_match('/create\\s+(procedure|function)\\s+([a-zA-Z0-9_]+)/i', $this->routineSourceCode, $matches); |
|
723
|
|
|
if ($n==1) |
|
724
|
|
|
{ |
|
725
|
|
|
$this->routineType = strtolower($matches[1]); |
|
726
|
|
|
|
|
727
|
|
|
if ($this->routineName!=$matches[2]) |
|
728
|
|
|
{ |
|
729
|
|
|
throw new RoutineLoaderException("Stored routine name '%s' does not corresponds with filename", $matches[2]); |
|
730
|
|
|
} |
|
731
|
|
|
} |
|
732
|
|
|
else |
|
733
|
|
|
{ |
|
734
|
|
|
throw new RoutineLoaderException('Unable to find the stored routine name and type'); |
|
735
|
|
|
} |
|
736
|
|
|
} |
|
737
|
|
|
|
|
738
|
|
|
//-------------------------------------------------------------------------------------------------------------------- |
|
739
|
|
|
/** |
|
740
|
|
|
* Gets description by name of the parameter as found in the DocBlock of the stored routine. |
|
741
|
|
|
* |
|
742
|
|
|
* @param string $name Name of the parameter. |
|
743
|
|
|
* |
|
744
|
|
|
* @return string|null |
|
745
|
|
|
*/ |
|
746
|
|
|
private function getParameterDocDescription(string $name): ?string |
|
747
|
|
|
{ |
|
748
|
|
|
if (isset($this->docBlockPartsSource['parameters'])) |
|
749
|
|
|
{ |
|
750
|
|
|
foreach ($this->docBlockPartsSource['parameters'] as $parameter_doc_info) |
|
751
|
|
|
{ |
|
752
|
|
|
if ($parameter_doc_info['name']===$name) return $parameter_doc_info['description']; |
|
753
|
|
|
} |
|
754
|
|
|
} |
|
755
|
|
|
|
|
756
|
|
|
return null; |
|
757
|
|
|
} |
|
758
|
|
|
|
|
759
|
|
|
//-------------------------------------------------------------------------------------------------------------------- |
|
760
|
|
|
/** |
|
761
|
|
|
* Loads the stored routine into the database. |
|
762
|
|
|
* |
|
763
|
|
|
* @throws MySqlQueryErrorException |
|
764
|
|
|
*/ |
|
765
|
|
|
private function loadRoutineFile(): void |
|
766
|
|
|
{ |
|
767
|
|
|
// Set magic constants specific for this stored routine. |
|
768
|
|
|
$this->setMagicConstants(); |
|
769
|
|
|
|
|
770
|
|
|
// Replace all place holders with their values. |
|
771
|
|
|
$lines = explode("\n", $this->routineSourceCode); |
|
772
|
|
|
$routine_source = []; |
|
773
|
|
|
foreach ($lines as $i => &$line) |
|
774
|
|
|
{ |
|
775
|
|
|
$this->replace['__LINE__'] = $i + 1; |
|
776
|
|
|
$routine_source[$i] = strtr($line, $this->replace); |
|
777
|
|
|
} |
|
778
|
|
|
$routine_source = implode("\n", $routine_source); |
|
779
|
|
|
|
|
780
|
|
|
// Unset magic constants specific for this stored routine. |
|
781
|
|
|
$this->unsetMagicConstants(); |
|
782
|
|
|
|
|
783
|
|
|
// Drop the stored procedure or function if its exists. |
|
784
|
|
|
$this->dropRoutine(); |
|
785
|
|
|
|
|
786
|
|
|
// Set the SQL-mode under which the stored routine will run. |
|
787
|
|
|
$this->dl->setSqlMode($this->sqlMode); |
|
788
|
|
|
|
|
789
|
|
|
// Set the default character set and collate under which the store routine will run. |
|
790
|
|
|
$this->dl->setCharacterSet($this->characterSet, $this->collate); |
|
791
|
|
|
|
|
792
|
|
|
// Finally, execute the SQL code for loading the stored routine. |
|
793
|
|
|
$this->dl->loadRoutine($routine_source); |
|
794
|
|
|
} |
|
795
|
|
|
|
|
796
|
|
|
//-------------------------------------------------------------------------------------------------------------------- |
|
797
|
|
|
/** |
|
798
|
|
|
* Logs the unknown placeholder (if any). |
|
799
|
|
|
* |
|
800
|
|
|
* @param array $unknown The unknown placeholders. |
|
801
|
|
|
* |
|
802
|
|
|
* @throws RoutineLoaderException |
|
803
|
|
|
*/ |
|
804
|
|
|
private function logUnknownPlaceholders(array $unknown): void |
|
805
|
|
|
{ |
|
806
|
|
|
// Return immediately if there are no unknown placeholders. |
|
807
|
|
|
if (empty($unknown)) return; |
|
808
|
|
|
|
|
809
|
|
|
sort($unknown); |
|
810
|
|
|
$this->io->text('Unknown placeholder(s):'); |
|
811
|
|
|
$this->io->listing($unknown); |
|
812
|
|
|
|
|
813
|
|
|
$replace = []; |
|
814
|
|
|
foreach ($unknown as $placeholder) |
|
815
|
|
|
{ |
|
816
|
|
|
$replace[$placeholder] = '<error>'.$placeholder.'</error>'; |
|
817
|
|
|
} |
|
818
|
|
|
$code = strtr(OutputFormatter::escape($this->routineSourceCode), $replace); |
|
819
|
|
|
|
|
820
|
|
|
$this->io->text(explode(PHP_EOL, $code)); |
|
821
|
|
|
|
|
822
|
|
|
throw new RoutineLoaderException('Unknown placeholder(s) found'); |
|
823
|
|
|
} |
|
824
|
|
|
|
|
825
|
|
|
//-------------------------------------------------------------------------------------------------------------------- |
|
826
|
|
|
/** |
|
827
|
|
|
* Returns true if the source file must be load or reloaded. Otherwise returns false. |
|
828
|
|
|
* |
|
829
|
|
|
* @return bool |
|
830
|
|
|
*/ |
|
831
|
1 |
|
private function mustLoadStoredRoutine(): bool |
|
832
|
|
|
{ |
|
833
|
|
|
// If this is the first time we see the source file it must be loaded. |
|
834
|
1 |
|
if (empty($this->phpStratumOldMetadata)) return true; |
|
835
|
|
|
|
|
836
|
|
|
// If the source file has changed the source file must be loaded. |
|
837
|
1 |
|
if ($this->phpStratumOldMetadata['timestamp']!=$this->filemtime) return true; |
|
838
|
|
|
|
|
839
|
|
|
// If the value of a placeholder has changed the source file must be loaded. |
|
840
|
1 |
|
foreach ($this->phpStratumOldMetadata['replace'] as $place_holder => $old_value) |
|
841
|
|
|
{ |
|
842
|
1 |
|
if (!isset($this->replacePairs[strtoupper($place_holder)]) || |
|
843
|
1 |
|
$this->replacePairs[strtoupper($place_holder)]!==$old_value) |
|
844
|
|
|
{ |
|
845
|
|
|
return true; |
|
846
|
|
|
} |
|
847
|
|
|
} |
|
848
|
|
|
|
|
849
|
|
|
// If stored routine not exists in database the source file must be loaded. |
|
850
|
1 |
|
if (empty($this->rdbmsOldRoutineMetadata)) return true; |
|
851
|
|
|
|
|
852
|
|
|
// If current sql-mode is different the source file must reload. |
|
853
|
1 |
|
if ($this->rdbmsOldRoutineMetadata['sql_mode']!=$this->sqlMode) return true; |
|
854
|
|
|
|
|
855
|
|
|
// If current character set is different the source file must reload. |
|
856
|
1 |
|
if ($this->rdbmsOldRoutineMetadata['character_set_client']!=$this->characterSet) return true; |
|
857
|
|
|
|
|
858
|
|
|
// If current collation is different the source file must reload. |
|
859
|
1 |
|
if ($this->rdbmsOldRoutineMetadata['collation_connection']!=$this->collate) return true; |
|
860
|
|
|
|
|
861
|
1 |
|
return false; |
|
862
|
|
|
} |
|
863
|
|
|
|
|
864
|
|
|
//-------------------------------------------------------------------------------------------------------------------- |
|
865
|
|
|
/** |
|
866
|
|
|
* Reads the source code of the stored routine. |
|
867
|
|
|
* |
|
868
|
|
|
* @throws RoutineLoaderException |
|
869
|
|
|
*/ |
|
870
|
|
|
private function readSourceCode(): void |
|
871
|
|
|
{ |
|
872
|
|
|
$this->routineSourceCode = file_get_contents($this->sourceFilename); |
|
873
|
|
|
$this->routineSourceCodeLines = explode("\n", $this->routineSourceCode); |
|
874
|
|
|
|
|
875
|
|
|
if ($this->routineSourceCodeLines===false) |
|
876
|
|
|
{ |
|
877
|
|
|
throw new RoutineLoaderException('Source file is empty'); |
|
878
|
|
|
} |
|
879
|
|
|
} |
|
880
|
|
|
|
|
881
|
|
|
//-------------------------------------------------------------------------------------------------------------------- |
|
882
|
|
|
/** |
|
883
|
|
|
* Adds magic constants to replace list. |
|
884
|
|
|
*/ |
|
885
|
|
|
private function setMagicConstants(): void |
|
886
|
|
|
{ |
|
887
|
|
|
$real_path = realpath($this->sourceFilename); |
|
888
|
|
|
|
|
889
|
|
|
$this->replace['__FILE__'] = "'".$this->dl->realEscapeString($real_path)."'"; |
|
890
|
|
|
$this->replace['__ROUTINE__'] = "'".$this->routineName."'"; |
|
891
|
|
|
$this->replace['__DIR__'] = "'".$this->dl->realEscapeString(dirname($real_path))."'"; |
|
892
|
|
|
} |
|
893
|
|
|
|
|
894
|
|
|
//-------------------------------------------------------------------------------------------------------------------- |
|
895
|
|
|
/** |
|
896
|
|
|
* Removes magic constants from current replace list. |
|
897
|
|
|
*/ |
|
898
|
|
|
private function unsetMagicConstants(): void |
|
899
|
|
|
{ |
|
900
|
|
|
unset($this->replace['__FILE__']); |
|
901
|
|
|
unset($this->replace['__ROUTINE__']); |
|
902
|
|
|
unset($this->replace['__DIR__']); |
|
903
|
|
|
unset($this->replace['__LINE__']); |
|
904
|
|
|
} |
|
905
|
|
|
|
|
906
|
|
|
//-------------------------------------------------------------------------------------------------------------------- |
|
907
|
|
|
/** |
|
908
|
|
|
* Updates the metadata for the stored routine. |
|
909
|
|
|
*/ |
|
910
|
|
|
private function updateMetadata(): void |
|
911
|
|
|
{ |
|
912
|
|
|
$this->phpStratumMetadata['routine_name'] = $this->routineName; |
|
913
|
|
|
$this->phpStratumMetadata['designation'] = $this->designationType; |
|
914
|
|
|
$this->phpStratumMetadata['return'] = $this->returnType; |
|
915
|
|
|
$this->phpStratumMetadata['parameters'] = $this->parameters; |
|
916
|
|
|
$this->phpStratumMetadata['timestamp'] = $this->filemtime; |
|
917
|
|
|
$this->phpStratumMetadata['replace'] = $this->replace; |
|
918
|
|
|
$this->phpStratumMetadata['phpdoc'] = $this->docBlockPartsWrapper; |
|
919
|
|
|
$this->phpStratumMetadata['spec_params'] = $this->extendedParameters; |
|
920
|
|
|
$this->phpStratumMetadata['index_columns'] = $this->indexColumns; |
|
921
|
|
|
$this->phpStratumMetadata['bulk_insert_table_name'] = $this->bulkInsertTableName; |
|
922
|
|
|
$this->phpStratumMetadata['bulk_insert_columns'] = $this->bulkInsertColumns; |
|
923
|
|
|
$this->phpStratumMetadata['bulk_insert_keys'] = $this->bulkInsertKeys; |
|
924
|
|
|
} |
|
925
|
|
|
|
|
926
|
|
|
//-------------------------------------------------------------------------------------------------------------------- |
|
927
|
|
|
/** |
|
928
|
|
|
* Update information about specific parameters of stored routine. |
|
929
|
|
|
* |
|
930
|
|
|
* @throws RoutineLoaderException |
|
931
|
|
|
*/ |
|
932
|
|
|
private function updateParametersInfo(): void |
|
933
|
|
|
{ |
|
934
|
|
|
if (!empty($this->extendedParameters)) |
|
935
|
|
|
{ |
|
936
|
|
|
foreach ($this->extendedParameters as $spec_param_name => $spec_param_info) |
|
937
|
|
|
{ |
|
938
|
|
|
$param_not_exist = true; |
|
939
|
|
|
foreach ($this->parameters as $key => $param_info) |
|
940
|
|
|
{ |
|
941
|
|
|
if ($param_info['parameter_name']==$spec_param_name) |
|
942
|
|
|
{ |
|
943
|
|
|
$this->parameters[$key] = array_merge($this->parameters[$key], $spec_param_info); |
|
944
|
|
|
$param_not_exist = false; |
|
945
|
|
|
break; |
|
946
|
|
|
} |
|
947
|
|
|
} |
|
948
|
|
|
if ($param_not_exist) |
|
949
|
|
|
{ |
|
950
|
|
|
throw new RoutineLoaderException("Specific parameter '%s' does not exist", $spec_param_name); |
|
951
|
|
|
} |
|
952
|
|
|
} |
|
953
|
|
|
} |
|
954
|
|
|
} |
|
955
|
|
|
|
|
956
|
|
|
//-------------------------------------------------------------------------------------------------------------------- |
|
957
|
|
|
/** |
|
958
|
|
|
* Validates the parameters found the DocBlock in the source of the stored routine against the parameters from the |
|
959
|
|
|
* metadata of MySQL and reports missing and unknown parameters names. |
|
960
|
|
|
*/ |
|
961
|
|
|
private function validateParameterLists(): void |
|
962
|
|
|
{ |
|
963
|
|
|
// Make list with names of parameters used in database. |
|
964
|
|
|
$database_parameters_names = []; |
|
965
|
|
|
foreach ($this->parameters as $parameter_info) |
|
966
|
|
|
{ |
|
967
|
|
|
$database_parameters_names[] = $parameter_info['parameter_name']; |
|
968
|
|
|
} |
|
969
|
|
|
|
|
970
|
|
|
// Make list with names of parameters used in dock block of routine. |
|
971
|
|
|
$doc_block_parameters_names = []; |
|
972
|
|
|
if (isset($this->docBlockPartsSource['parameters'])) |
|
973
|
|
|
{ |
|
974
|
|
|
foreach ($this->docBlockPartsSource['parameters'] as $parameter) |
|
975
|
|
|
{ |
|
976
|
|
|
$doc_block_parameters_names[] = $parameter['name']; |
|
977
|
|
|
} |
|
978
|
|
|
} |
|
979
|
|
|
|
|
980
|
|
|
// Check and show warning if any parameters is missing in doc block. |
|
981
|
|
|
$tmp = array_diff($database_parameters_names, $doc_block_parameters_names); |
|
982
|
|
|
foreach ($tmp as $name) |
|
983
|
|
|
{ |
|
984
|
|
|
$this->io->logNote('Parameter <dbo>%s</dbo> is missing from doc block', $name); |
|
985
|
|
|
} |
|
986
|
|
|
|
|
987
|
|
|
// Check and show warning if find unknown parameters in doc block. |
|
988
|
|
|
$tmp = array_diff($doc_block_parameters_names, $database_parameters_names); |
|
989
|
|
|
foreach ($tmp as $name) |
|
990
|
|
|
{ |
|
991
|
|
|
$this->io->logNote('Unknown parameter <dbo>%s</dbo> found in doc block', $name); |
|
992
|
|
|
} |
|
993
|
|
|
} |
|
994
|
|
|
|
|
995
|
|
|
//-------------------------------------------------------------------------------------------------------------------- |
|
996
|
|
|
/** |
|
997
|
|
|
* Validates the specified return type of the stored routine. |
|
998
|
|
|
* |
|
999
|
|
|
* @throws RoutineLoaderException |
|
1000
|
|
|
*/ |
|
1001
|
|
|
private function validateReturnType(): void |
|
1002
|
|
|
{ |
|
1003
|
|
|
// Return immediately if designation type is not appropriate for this method. |
|
1004
|
|
|
if (!in_array($this->designationType, ['function', 'singleton0', 'singleton1'])) return; |
|
1005
|
|
|
|
|
1006
|
|
|
$types = explode('|', $this->returnType); |
|
1007
|
|
|
$diff = array_diff($types, ['string', 'int', 'float', 'double', 'bool', 'null']); |
|
1008
|
|
|
|
|
1009
|
|
|
if (!($this->returnType=='mixed' || $this->returnType=='bool' || empty($diff))) |
|
1010
|
|
|
{ |
|
1011
|
|
|
throw new RoutineLoaderException("Return type must be 'mixed', 'bool', or a combination of 'int', 'float', 'string', and 'null'"); |
|
1012
|
|
|
} |
|
1013
|
|
|
|
|
1014
|
|
|
// The following tests are applicable for singleton0 routines only. |
|
1015
|
|
|
if (!in_array($this->designationType, ['singleton0'])) return; |
|
1016
|
|
|
|
|
1017
|
|
|
// Return mixed is OK. |
|
1018
|
|
|
if (in_array($this->returnType, ['bool', 'mixed'])) return; |
|
1019
|
|
|
|
|
1020
|
|
|
// In all other cases return type must contain null. |
|
1021
|
|
|
$parts = explode('|', $this->returnType); |
|
1022
|
|
|
$key = array_search('null', $parts); |
|
1023
|
|
|
if ($key===false) |
|
1024
|
|
|
{ |
|
1025
|
|
|
throw new RoutineLoaderException("Return type must be 'mixed', 'bool', or contain 'null' (with a combination of 'int', 'float', and 'string')"); |
|
1026
|
|
|
} |
|
1027
|
|
|
} |
|
1028
|
|
|
|
|
1029
|
|
|
//-------------------------------------------------------------------------------------------------------------------- |
|
1030
|
|
|
} |
|
1031
|
|
|
|
|
1032
|
|
|
//---------------------------------------------------------------------------------------------------------------------- |
|
1033
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.