Passed
Push — master ( e0c5e8...7926db )
by P.R.
04:00
created

Wrapper::generatePhpDocBlockSortDescription()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 2
nc 2
nop 0
dl 0
loc 5
ccs 3
cts 3
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
namespace SetBased\Stratum\MySql\Wrapper;
5
6
use SetBased\Exception\FallenException;
7
use SetBased\Helper\CodeStore\PhpCodeStore;
8
use SetBased\Stratum\Middle\Exception\ResultException;
9
use SetBased\Stratum\Middle\NameMangler\NameMangler;
10
use SetBased\Stratum\MySql\Exception\MySqlDataLayerException;
11
use SetBased\Stratum\MySql\Exception\MySqlQueryErrorException;
12
use SetBased\Stratum\MySql\Helper\DataTypeHelper;
13
14
/**
15
 * Abstract parent class for all wrapper generators.
16
 */
17
abstract class Wrapper
18
{
19
  //--------------------------------------------------------------------------------------------------------------------
20
  /**
21
   * The code store for the generated PHP code.
22
   *
23
   * @var PhpCodeStore
24
   */
25
  protected $codeStore;
26
27
  /**
28
   * Array with fully qualified names that must be imported for this wrapper method.
29
   *
30
   * @var array
31
   */
32
  protected $imports = [];
33
34
  /**
35
   * The name mangler for wrapper and parameter names.
36
   *
37
   * @var NameMangler
38
   */
39
  protected $nameMangler;
40
41
  /**
42
   * The metadata of the stored routine.
43
   *
44
   * @var array
45
   */
46
  protected $routine;
47
48
  /**
49
   * The exceptions that the wrapper can throw.
50
   *
51
   * @var string[]
52
   */
53
  private $throws;
54
55
  //--------------------------------------------------------------------------------------------------------------------
56
  /**
57
   * Object constructor.
58
   *
59
   * @param array        $routine     The metadata of the stored routine.
60
   * @param PhpCodeStore $codeStore   The code store for the generated code.
61
   * @param NameMangler  $nameMangler The mangler for wrapper and parameter names.
62
   */
63 1
  public function __construct(array $routine, PhpCodeStore $codeStore, NameMangler $nameMangler)
64
  {
65 1
    $this->routine     = $routine;
66 1
    $this->codeStore   = $codeStore;
67 1
    $this->nameMangler = $nameMangler;
68 1
  }
69
70
  //--------------------------------------------------------------------------------------------------------------------
71
  /**
72
   * A factory for creating the appropriate object for generating a wrapper method for a stored routine.
73
   *
74
   * @param array        $routine     The metadata of the stored routine.
75
   * @param PhpCodeStore $codeStore   The code store for the generated code.
76
   * @param NameMangler  $nameMangler The mangler for wrapper and parameter names.
77
   *
78
   * @return Wrapper
79
   */
80 1
  public static function createRoutineWrapper(array $routine,
81
                                              PhpCodeStore $codeStore,
82
                                              NameMangler $nameMangler): Wrapper
83
  {
84 1
    switch ($routine['designation'])
85
    {
86 1
      case 'bulk':
87
        $wrapper = new BulkWrapper($routine, $codeStore, $nameMangler);
88
        break;
89
90 1
      case 'bulk_insert':
91 1
        $wrapper = new BulkInsertWrapper($routine, $codeStore, $nameMangler);
92 1
        break;
93
94 1
      case 'log':
95 1
        $wrapper = new LogWrapper($routine, $codeStore, $nameMangler);
96 1
        break;
97
98 1
      case 'map':
99 1
        $wrapper = new MapWrapper($routine, $codeStore, $nameMangler);
100 1
        break;
101
102 1
      case 'none':
103 1
        $wrapper = new NoneWrapper($routine, $codeStore, $nameMangler);
104 1
        break;
105
106 1
      case 'row0':
107 1
        $wrapper = new Row0Wrapper($routine, $codeStore, $nameMangler);
108 1
        break;
109
110 1
      case 'row1':
111 1
        $wrapper = new Row1Wrapper($routine, $codeStore, $nameMangler);
112 1
        break;
113
114 1
      case 'rows':
115 1
        $wrapper = new RowsWrapper($routine, $codeStore, $nameMangler);
116 1
        break;
117
118 1
      case 'rows_with_key':
119 1
        $wrapper = new RowsWithKeyWrapper($routine, $codeStore, $nameMangler);
120 1
        break;
121
122 1
      case 'rows_with_index':
123 1
        $wrapper = new RowsWithIndexWrapper($routine, $codeStore, $nameMangler);
124 1
        break;
125
126 1
      case 'singleton0':
127 1
        $wrapper = new Singleton0Wrapper($routine, $codeStore, $nameMangler);
128 1
        break;
129
130 1
      case 'singleton1':
131 1
        $wrapper = new Singleton1Wrapper($routine, $codeStore, $nameMangler);
132 1
        break;
133
134 1
      case 'function':
135 1
        $wrapper = new FunctionWrapper($routine, $codeStore, $nameMangler);
136 1
        break;
137
138 1
      case 'table':
139 1
        $wrapper = new TableWrapper($routine, $codeStore, $nameMangler);
140 1
        break;
141
142
      default:
143
        throw new FallenException('routine type', $routine['designation']);
144
    }
145
146 1
    return $wrapper;
147
  }
148
149
  //--------------------------------------------------------------------------------------------------------------------
150
  /**
151
   * Returns an array with fully qualified names that must be imported in the stored routine wrapper class.
152
   *
153
   * @return array
154
   */
155 1
  public function getImports(): array
156
  {
157 1
    return $this->imports;
158
  }
159
160
  //--------------------------------------------------------------------------------------------------------------------
161
  /**
162
   * Returns true if one of the parameters is a BLOB or CLOB.
163
   *
164
   * @param array|null $parameters The parameters info (name, type, description).
165
   *
166
   * @return bool
167
   */
168 1
  public function isBlobParameter(?array $parameters): bool
169
  {
170 1
    $hasBlob = false;
171
172 1
    if (!empty($parameters))
173
    {
174 1
      foreach ($parameters as $parameter_info)
175
      {
176 1
        $hasBlob = $hasBlob || DataTypeHelper::isBlobParameter($parameter_info['data_type']);
177
      }
178
    }
179
180 1
    return $hasBlob;
181
  }
182
183
  //--------------------------------------------------------------------------------------------------------------------
184
  /**
185
   * Adds a throw tag to the odc block of the generated method.
186
   *
187
   * @param string $class The name of the exception.
188
   */
189 1
  public function throws(string $class): void
190
  {
191 1
    $parts                = explode('\\', $class);
192 1
    $this->throws[$class] = array_pop($parts);
193 1
    $this->imports[]      = $class;
194 1
  }
195
196
  //--------------------------------------------------------------------------------------------------------------------
197
  /**
198
   * Generates a complete wrapper method.
199
   */
200 1
  public function writeRoutineFunction(): void
201
  {
202 1
    if ($this->isBlobParameter($this->routine['parameters']))
203
    {
204 1
      $this->writeRoutineFunctionWithLob();
205
    }
206
    else
207
    {
208 1
      $this->writeRoutineFunctionWithoutLob();
209
    }
210 1
  }
211
212
  //--------------------------------------------------------------------------------------------------------------------
213
  /**
214
   * Generates a complete wrapper method for a stored routine with a LOB parameter.
215
   */
216 1
  public function writeRoutineFunctionWithLob(): void
217
  {
218 1
    $this->throws(MySqlDataLayerException::class);
219 1
    $this->throws(MysqlQueryErrorException::class);
220 1
    $this->throws(ResultException::class);
221
222 1
    $wrapper_args = $this->getWrapperArgs();
223 1
    $routine_args = $this->getRoutineArgs();
224 1
    $method_name  = $this->nameMangler->getMethodName($this->routine['routine_name']);
225
226 1
    $bindings = '';
227 1
    $nulls    = '';
228 1
    foreach ($this->routine['parameters'] as $parameter_info)
229
    {
230 1
      $binding = DataTypeHelper::getBindVariableType($parameter_info);
231 1
      if ($binding=='b')
232
      {
233 1
        $bindings .= 'b';
234 1
        if ($nulls!=='') $nulls .= ', ';
235 1
        $nulls .= '$null';
236
      }
237
    }
238
239 1
    $this->codeStore->appendSeparator();
240 1
    $this->generatePhpDocBlock();
241 1
    $this->codeStore->append('public function '.$method_name.'('.$wrapper_args.')');
242 1
    $this->codeStore->append('{');
243 1
    $this->codeStore->append('$query = \'call '.$this->routine['routine_name'].'('.$routine_args.')\';');
244 1
    $this->codeStore->append('$stmt  = @$this->mysqli->prepare($query);');
245 1
    $this->codeStore->append('if (!$stmt) throw $this->dataLayerError(\'mysqli::prepare\');');
246 1
    $this->codeStore->append('');
247 1
    $this->codeStore->append('$null = null;');
248 1
    $this->codeStore->append('$success = @$stmt->bind_param(\''.$bindings.'\', '.$nulls.');');
249 1
    $this->codeStore->append('if (!$success) throw $this->dataLayerError(\'mysqli_stmt::bind_param\');');
250 1
    $this->codeStore->append('');
251 1
    $this->codeStore->append('$this->getMaxAllowedPacket();');
252 1
    $this->codeStore->append('');
253
254 1
    $blobArgumentIndex = 0;
255 1
    foreach ($this->routine['parameters'] as $parameter_info)
256
    {
257 1
      if (DataTypeHelper::getBindVariableType($parameter_info)=='b')
258
      {
259 1
        $mangledName = $this->nameMangler->getParameterName($parameter_info['parameter_name']);
260
261 1
        $this->codeStore->append('$this->sendLongData($stmt, '.$blobArgumentIndex.', $'.$mangledName.');');
262
263 1
        $blobArgumentIndex++;
264
      }
265
    }
266
267 1
    if ($blobArgumentIndex>0)
268
    {
269 1
      $this->codeStore->append('');
270
    }
271
272 1
    $this->codeStore->append('if ($this->logQueries)');
273 1
    $this->codeStore->append('{');
274 1
    $this->codeStore->append('$time0 = microtime(true);');
275 1
    $this->codeStore->append('');
276 1
    $this->codeStore->append('$success = @$stmt->execute();');
277 1
    $this->codeStore->append('if (!$success) throw $this->queryError(\'mysqli_stmt::execute\', $query);');
278 1
    $this->codeStore->append('');
279 1
    $this->codeStore->append('$this->queryLog[] = [\'query\' => $query,');
280 1
    $this->codeStore->append('                     \'time\'  => microtime(true) - $time0];', false);
281 1
    $this->codeStore->append('}');
282 1
    $this->codeStore->append('else');
283 1
    $this->codeStore->append('{');
284 1
    $this->codeStore->append('$success = $stmt->execute();');
285 1
    $this->codeStore->append('if (!$success) throw $this->queryError(\'mysqli_stmt::execute\', $query);');
286 1
    $this->codeStore->append('}');
287 1
    $this->codeStore->append('');
288 1
    $this->writeRoutineFunctionLobFetchData();
289 1
    $this->codeStore->append('$stmt->close();');
290 1
    $this->codeStore->append('if ($this->mysqli->more_results()) $this->mysqli->next_result();');
291 1
    $this->codeStore->append('');
292 1
    $this->writeRoutineFunctionLobReturnData();
293 1
    $this->codeStore->append('}');
294 1
    $this->codeStore->append('');
295 1
  }
296
297
  //--------------------------------------------------------------------------------------------------------------------
298
  /**
299
   * Returns a wrapper method for a stored routine without LOB parameters.
300
   */
301 1
  public function writeRoutineFunctionWithoutLob(): void
302
  {
303 1
    $wrapperArgs = $this->getWrapperArgs();
304 1
    $methodName  = $this->nameMangler->getMethodName($this->routine['routine_name']);
305 1
    $returnType  = $this->getReturnTypeDeclaration();
306
307 1
    $tmp             = $this->codeStore;
308 1
    $this->codeStore = new PhpCodeStore();
309 1
    $this->writeResultHandler();
310 1
    $body            = $this->codeStore->getRawCode();
311 1
    $this->codeStore = $tmp;
312
313 1
    $this->codeStore->appendSeparator();
314 1
    $this->generatePhpDocBlock();
315 1
    $this->codeStore->append('public function '.$methodName.'('.$wrapperArgs.')'.$returnType);
316 1
    $this->codeStore->append('{');
317 1
    $this->codeStore->append($body, false);
318 1
    $this->codeStore->append('}');
319 1
    $this->codeStore->append('');
320 1
  }
321
322
  //--------------------------------------------------------------------------------------------------------------------
323
  /**
324
   * Enhances the metadata of the parameters of the store routine wrapper.
325
   *
326
   * @param array[] $parameters The metadata of the parameters. For each parameter the
327
   *                            following keys must be defined:
328
   *                            <ul>
329
   *                            <li> php_name             The name of the parameter (including $).
330
   *                            <li> description          The description of the parameter.
331
   *                            <li> php_type             The type of the parameter.
332
   *                            <li> data_type_descriptor The data type of the corresponding parameter of the stored
333
   *                            routine. Null if there is no corresponding parameter.
334
   *                            </ul>
335
   */
336 1
  protected function enhancePhpDocBlockParameters(array &$parameters): void
337
  {
338
    // Nothing to do.
339 1
  }
340
341
  //--------------------------------------------------------------------------------------------------------------------
342
  /**
343
   * Returns the return type the be used in the DocBlock.
344
   *
345
   * @return string
346
   */
347
  abstract protected function getDocBlockReturnType(): string;
348
349
  //--------------------------------------------------------------------------------------------------------------------
350
  /**
351
   * Returns the return type declaration of the wrapper method.
352
   *
353
   * @return string
354
   */
355
  abstract protected function getReturnTypeDeclaration(): string;
356
357
  //--------------------------------------------------------------------------------------------------------------------
358
  /**
359
   * Returns code for the arguments for calling the stored routine in a wrapper method.
360
   *
361
   * @return string
362
   */
363 1
  protected function getRoutineArgs(): string
364
  {
365 1
    $ret = '';
366
367 1
    foreach ($this->routine['parameters'] as $parameter_info)
368
    {
369 1
      $mangledName = $this->nameMangler->getParameterName($parameter_info['parameter_name']);
370
371 1
      if ($ret) $ret .= ',';
372 1
      $ret .= DataTypeHelper::escapePhpExpression($parameter_info, '$'.$mangledName);
373
    }
374
375 1
    return $ret;
376
  }
377
378
  //--------------------------------------------------------------------------------------------------------------------
379
  /**
380
   * Returns code for the parameters of the wrapper method for the stored routine.
381
   *
382
   * @return string
383
   */
384 1
  protected function getWrapperArgs(): string
385
  {
386 1
    $ret = '';
387
388 1
    if ($this->routine['designation']=='bulk')
389
    {
390
      $ret .= 'BulkHandler $bulkHandler';
391
    }
392
393 1
    foreach ($this->routine['parameters'] as $i => $parameter)
394
    {
395 1
      if ($ret!='') $ret .= ', ';
396
397 1
      $dataType    = DataTypeHelper::columnTypeToPhpTypeHinting($parameter);
398 1
      $declaration = DataTypeHelper::phpTypeHintingToPhpTypeDeclaration($dataType.'|null');
399 1
      if ($declaration!=='')
400
      {
401 1
        $ret .= $declaration.' ';
402
      }
403
404 1
      $ret .= '$'.$this->nameMangler->getParameterName($parameter['parameter_name']);
405
    }
406
407 1
    return $ret;
408
  }
409
410
  //--------------------------------------------------------------------------------------------------------------------
411
  /**
412
   * Generates code for calling the stored routine in the wrapper method.
413
   *
414
   * @return void
415
   */
416
  abstract protected function writeResultHandler(): void;
417
  //--------------------------------------------------------------------------------------------------------------------
418
  /**
419
   * Generates code for fetching data of a stored routine with one or more LOB parameters.
420
   *
421
   * @return void
422
   */
423
  abstract protected function writeRoutineFunctionLobFetchData(): void;
424
425
  //--------------------------------------------------------------------------------------------------------------------
426
  /**
427
   * Generates code for retuning the data returned by a stored routine with one or more LOB parameters.
428
   *
429
   * @return void
430
   */
431
  abstract protected function writeRoutineFunctionLobReturnData(): void;
432
433
  //--------------------------------------------------------------------------------------------------------------------
434
  /**
435
   * Generate php doc block in the data layer for stored routine.
436
   */
437 1
  private function generatePhpDocBlock(): void
438
  {
439 1
    $this->codeStore->append('/**', false);
440
441
    // Generate phpdoc with short description of routine wrapper.
442 1
    $this->generatePhpDocBlockSortDescription();
443
444
    // Generate phpdoc with long description of routine wrapper.
445 1
    $this->generatePhpDocBlockLongDescription();
446
447
    // Generate phpDoc with parameters and descriptions of parameters.
448 1
    $this->generatePhpDocBlockParameters();
449
450
    // Generate return parameter doc.
451 1
    $this->generatePhpDocBlockReturn();
452
453
    // Generate throw tags.
454 1
    $this->generatePhpDocBlockThrow();
455
456 1
    $this->codeStore->append(' */', false);
457 1
  }
458
459
  //--------------------------------------------------------------------------------------------------------------------
460
  /**
461
   * Generates the long description of stored routine wrapper.
462
   */
463 1
  private function generatePhpDocBlockLongDescription(): void
464
  {
465 1
    if ($this->routine['phpdoc']['long_description']!=='')
466
    {
467
      $this->codeStore->append(' * '.$this->routine['phpdoc']['long_description'], false);
468
    }
469 1
  }
470
471
  //--------------------------------------------------------------------------------------------------------------------
472
  /**
473
   * Generates the doc block for parameters of stored routine wrapper.
474
   */
475 1
  private function generatePhpDocBlockParameters(): void
476
  {
477 1
    $parameters = [];
478 1
    foreach ($this->routine['phpdoc']['parameters'] as $parameter)
479
    {
480 1
      $mangledName = $this->nameMangler->getParameterName($parameter['parameter_name']);
481
482 1
      $parameters[] = ['php_name'             => '$'.$mangledName,
483 1
                       'description'          => $parameter['description'],
484 1
                       'php_type'             => $parameter['php_type'],
485 1
                       'data_type_descriptor' => $parameter['data_type_descriptor']];
486
    }
487
488 1
    $this->enhancePhpDocBlockParameters($parameters);
489
490 1
    if (!empty($parameters))
491
    {
492
      // Compute the max lengths of parameter names and the PHP types of the parameters.
493 1
      $max_name_length = 0;
494 1
      $max_type_length = 0;
495 1
      foreach ($parameters as $parameter)
496
      {
497 1
        $max_name_length = max($max_name_length, mb_strlen($parameter['php_name']));
498 1
        $max_type_length = max($max_type_length, mb_strlen($parameter['php_type']));
499
      }
500
501 1
      $this->codeStore->append(' *', false);
502
503
      // Generate phpDoc for the parameters of the wrapper method.
504 1
      foreach ($parameters as $parameter)
505
      {
506 1
        $format = sprintf(' * %%-%ds %%-%ds %%-%ds %%s', mb_strlen('@param'), $max_type_length, $max_name_length);
507
508 1
        $lines = explode(PHP_EOL, $parameter['description'] ?? '');
509 1
        if (!empty($lines))
510
        {
511 1
          $line = array_shift($lines);
512 1
          $this->codeStore->append(sprintf($format, '@param', $parameter['php_type'], $parameter['php_name'], $line), false);
513 1
          foreach ($lines as $line)
514
          {
515
            $this->codeStore->append(sprintf($format, ' ', ' ', ' ', $line), false);
516
          }
517
        }
518
        else
519
        {
520
          $this->codeStore->append(sprintf($format, '@param', $parameter['php_type'], $parameter['php_name'], ''), false);
521
        }
522
523 1
        if ($parameter['data_type_descriptor']!==null)
524
        {
525 1
          $this->codeStore->append(sprintf($format, ' ', ' ', ' ', $parameter['data_type_descriptor']), false);
526
        }
527
      }
528
    }
529 1
  }
530
531
  //--------------------------------------------------------------------------------------------------------------------
532
  /**
533
   * Generates the PHP doc block for the return type of the stored routine wrapper.
534
   */
535 1
  private function generatePhpDocBlockReturn(): void
536
  {
537 1
    $return = $this->getDocBlockReturnType();
538 1
    if ($return!=='')
539
    {
540 1
      $this->codeStore->append(' *', false);
541 1
      $this->codeStore->append(' * @return '.$return, false);
542
    }
543 1
  }
544
545
  //--------------------------------------------------------------------------------------------------------------------
546
  /**
547
   * Generates the sort description of stored routine wrapper.
548
   */
549 1
  private function generatePhpDocBlockSortDescription(): void
550
  {
551 1
    if ($this->routine['phpdoc']['sort_description']!=='')
552
    {
553 1
      $this->codeStore->append(' * '.$this->routine['phpdoc']['sort_description'], false);
554
    }
555 1
  }
556
557
  //--------------------------------------------------------------------------------------------------------------------
558
  /**
559
   * Generates the PHP doc block with throw tags.
560
   */
561 1
  private function generatePhpDocBlockThrow()
562
  {
563 1
    if (!empty($this->throws))
564
    {
565 1
      $this->codeStore->append(' *', false);
566
567 1
      $this->throws = array_unique($this->throws, SORT_REGULAR);
568 1
      foreach ($this->throws as $class)
569
      {
570 1
        $this->codeStore->append(sprintf(' * @throws %s;', $class), false);
571
      }
572
    }
573 1
  }
574
575
  //--------------------------------------------------------------------------------------------------------------------
576
}
577
578
//----------------------------------------------------------------------------------------------------------------------
579