Passed
Push — master ( 151185...42f330 )
by P.R.
04:09
created

Wrapper::createRoutineWrapper()   C

Complexity

Conditions 15
Paths 15

Size

Total Lines 67
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 43
CRAP Score 15.0623

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 15
eloc 46
nc 15
nop 3
dl 0
loc 67
ccs 43
cts 46
cp 0.9348
crap 15.0623
rs 5.9166
c 1
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 PhpCodeStore $codeStore;
26
27
  /**
28
   * Array with fully qualified names that must be imported for this wrapper method.
29
   *
30
   * @var array
31
   */
32
  protected array $imports = [];
33
34
  /**
35
   * The name mangler for wrapper and parameter names.
36
   *
37
   * @var NameMangler
38
   */
39
  protected NameMangler $nameMangler;
40
41
  /**
42
   * The metadata of the stored routine.
43
   *
44
   * @var array
45
   */
46
  protected array $routine;
47
48
  /**
49
   * The exceptions that the wrapper can throw.
50
   *
51
   * @var string[]
52
   */
53
  private array $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
  }
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)
175
      {
176 1
        $hasBlob = $hasBlob || DataTypeHelper::isBlobParameter($parameter['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
  }
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
  }
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
    $wrapperArgs = $this->getWrapperArgs();
223 1
    $routineArgs = $this->getRoutineArgs();
224 1
    $methodName  = $this->nameMangler->getMethodName($this->routine['routine_name']);
225 1
    $returnType  = $this->getReturnTypeDeclaration();
226
227 1
    $bindings = '';
228 1
    $nulls    = '';
229 1
    foreach ($this->routine['parameters'] as $parameter)
230
    {
231 1
      $binding = DataTypeHelper::getBindVariableType($parameter);
232 1
      if ($binding=='b')
233
      {
234 1
        $bindings .= 'b';
235 1
        if ($nulls!=='') $nulls .= ', ';
236 1
        $nulls .= '$null';
237
      }
238
    }
239
240 1
    $this->codeStore->appendSeparator();
241 1
    $this->generatePhpDocBlock();
242 1
    $this->codeStore->append('public function '.$methodName.'('.$wrapperArgs.')'.$returnType);
243 1
    $this->codeStore->append('{');
244 1
    $this->codeStore->append('$query = \'call '.$this->routine['routine_name'].'('.$routineArgs.')\';');
245 1
    $this->codeStore->append('$stmt  = @$this->mysqli->prepare($query);');
246 1
    $this->codeStore->append('if (!$stmt) throw $this->dataLayerError(\'mysqli::prepare\');');
247 1
    $this->codeStore->append('');
248 1
    $this->codeStore->append('$null = null;');
249 1
    $this->codeStore->append('$success = @$stmt->bind_param(\''.$bindings.'\', '.$nulls.');');
250 1
    $this->codeStore->append('if (!$success) throw $this->dataLayerError(\'mysqli_stmt::bind_param\');');
251 1
    $this->codeStore->append('');
252 1
    $this->codeStore->append('$this->getMaxAllowedPacket();');
253 1
    $this->codeStore->append('');
254
255 1
    $blobArgumentIndex = 0;
256 1
    foreach ($this->routine['parameters'] as $parameter)
257
    {
258 1
      if (DataTypeHelper::getBindVariableType($parameter)=='b')
259
      {
260 1
        $mangledName = $this->nameMangler->getParameterName($parameter['parameter_name']);
261
262 1
        $this->codeStore->append('$this->sendLongData($stmt, '.$blobArgumentIndex.', $'.$mangledName.');');
263
264 1
        $blobArgumentIndex++;
265
      }
266
    }
267
268 1
    if ($blobArgumentIndex>0)
269
    {
270 1
      $this->codeStore->append('');
271
    }
272
273 1
    $this->codeStore->append('if ($this->logQueries)');
274 1
    $this->codeStore->append('{');
275 1
    $this->codeStore->append('$time0 = microtime(true);');
276 1
    $this->codeStore->append('');
277 1
    $this->codeStore->append('$success = @$stmt->execute();');
278 1
    $this->codeStore->append('if (!$success) throw $this->queryError(\'mysqli_stmt::execute\', $query);');
279 1
    $this->codeStore->append('');
280 1
    $this->codeStore->append('$this->queryLog[] = [\'query\' => $query,');
281 1
    $this->codeStore->append('                     \'time\'  => microtime(true) - $time0];', false);
282 1
    $this->codeStore->append('}');
283 1
    $this->codeStore->append('else');
284 1
    $this->codeStore->append('{');
285 1
    $this->codeStore->append('$success = $stmt->execute();');
286 1
    $this->codeStore->append('if (!$success) throw $this->queryError(\'mysqli_stmt::execute\', $query);');
287 1
    $this->codeStore->append('}');
288 1
    $this->codeStore->append('');
289 1
    $this->writeRoutineFunctionLobFetchData();
290 1
    $this->codeStore->append('$stmt->close();');
291 1
    $this->codeStore->append('if ($this->mysqli->more_results()) $this->mysqli->next_result();');
292 1
    $this->codeStore->append('');
293 1
    $this->writeRoutineFunctionLobReturnData();
294 1
    $this->codeStore->append('}');
295 1
    $this->codeStore->append('');
296
  }
297
298
  //--------------------------------------------------------------------------------------------------------------------
299
  /**
300
   * Returns a wrapper method for a stored routine without LOB parameters.
301
   */
302 1
  public function writeRoutineFunctionWithoutLob(): void
303
  {
304 1
    $wrapperArgs = $this->getWrapperArgs();
305 1
    $methodName  = $this->nameMangler->getMethodName($this->routine['routine_name']);
306 1
    $returnType  = $this->getReturnTypeDeclaration();
307
308 1
    $tmp             = $this->codeStore;
309 1
    $this->codeStore = new PhpCodeStore();
310 1
    $this->writeResultHandler();
311 1
    $body            = $this->codeStore->getRawCode();
312 1
    $this->codeStore = $tmp;
313
314 1
    $this->codeStore->appendSeparator();
315 1
    $this->generatePhpDocBlock();
316 1
    $this->codeStore->append('public function '.$methodName.'('.$wrapperArgs.')'.$returnType);
317 1
    $this->codeStore->append('{');
318 1
    $this->codeStore->append($body, false);
319 1
    $this->codeStore->append('}');
320 1
    $this->codeStore->append('');
321
  }
322
323
  //--------------------------------------------------------------------------------------------------------------------
324
  /**
325
   * Enhances the metadata of the parameters of the store routine wrapper.
326
   *
327
   * @param array[] $parameters The metadata of the parameters. For each parameter the
328
   *                            following keys must be defined:
329
   *                            <ul>
330
   *                            <li> php_name       The name of the parameter (including $).
331
   *                            <li> description    The description of the parameter.
332
   *                            <li> php_type       The type of the parameter.
333
   *                            <li> dtd_identifier The data type of the corresponding parameter of the stored routine.
334
   *                                                Null if there is no corresponding parameter.
335
   *                            </ul>
336
   */
337
  protected function enhancePhpDocBlockParameters(array &$parameters): void
338
  {
339
    // Nothing to do.
340
  }
341
342
  //--------------------------------------------------------------------------------------------------------------------
343
  /**
344
   * Returns the return type the be used in the DocBlock.
345
   *
346
   * @return string
347
   */
348
  abstract protected function getDocBlockReturnType(): string;
349
350
  //--------------------------------------------------------------------------------------------------------------------
351
  /**
352
   * Returns the return type declaration of the wrapper method.
353
   *
354
   * @return string
355
   */
356
  abstract protected function getReturnTypeDeclaration(): string;
357
358
  //--------------------------------------------------------------------------------------------------------------------
359
  /**
360
   * Returns code for the arguments for calling the stored routine in a wrapper method.
361
   *
362
   * @return string
363
   */
364 1
  protected function getRoutineArgs(): string
365
  {
366 1
    $ret = '';
367
368 1
    foreach ($this->routine['parameters'] as $parameter)
369
    {
370 1
      $mangledName = $this->nameMangler->getParameterName($parameter['parameter_name']);
371
372 1
      if ($ret) $ret .= ',';
373 1
      $ret .= DataTypeHelper::escapePhpExpression($parameter, '$'.$mangledName);
374
    }
375
376 1
    return $ret;
377
  }
378
379
  //--------------------------------------------------------------------------------------------------------------------
380
  /**
381
   * Returns code for the parameters of the wrapper method for the stored routine.
382
   *
383
   * @return string
384
   */
385 1
  protected function getWrapperArgs(): string
386
  {
387 1
    $code = '';
388
389 1
    if ($this->routine['designation']==='bulk')
390
    {
391
      $code .= 'BulkHandler $bulkHandler';
392
    }
393
394 1
    foreach ($this->routine['parameters'] as $parameter)
395
    {
396 1
      if ($code!=='') $code .= ', ';
397
398 1
      $dataType    = DataTypeHelper::columnTypeToPhpTypeHinting($parameter);
399 1
      $declaration = DataTypeHelper::phpTypeHintingToPhpTypeDeclaration($dataType.'|null');
400 1
      if ($declaration!=='')
401
      {
402 1
        $code .= $declaration.' ';
403
      }
404
405 1
      $code .= '$'.$this->nameMangler->getParameterName($parameter['parameter_name']);
406
    }
407
408 1
    return $code;
409
  }
410
411
  //--------------------------------------------------------------------------------------------------------------------
412
  /**
413
   * Generates code for calling the stored routine in the wrapper method.
414
   *
415
   * @return void
416
   */
417
  abstract protected function writeResultHandler(): void;
418
419
  //--------------------------------------------------------------------------------------------------------------------
420
  /**
421
   * Generates code for fetching data of a stored routine with one or more LOB parameters.
422
   *
423
   * @return void
424
   */
425
  abstract protected function writeRoutineFunctionLobFetchData(): void;
426
427
  //--------------------------------------------------------------------------------------------------------------------
428
  /**
429
   * Generates code for retuning the data returned by a stored routine with one or more LOB parameters.
430
   *
431
   * @return void
432
   */
433
  abstract protected function writeRoutineFunctionLobReturnData(): void;
434
435
  //--------------------------------------------------------------------------------------------------------------------
436
  /**
437
   * Generate php doc block in the data layer for stored routine.
438
   */
439 1
  private function generatePhpDocBlock(): void
440
  {
441 1
    $this->codeStore->append('/**', false);
442
443
    // Generate phpdoc with short description of routine wrapper.
444 1
    $this->generatePhpDocBlockSortDescription();
445
446
    // Generate phpdoc with long description of routine wrapper.
447 1
    $this->generatePhpDocBlockLongDescription();
448
449
    // Generate phpDoc with parameters and descriptions of parameters.
450 1
    $this->generatePhpDocBlockParameters();
451
452
    // Generate return parameter doc.
453 1
    $this->generatePhpDocBlockReturn();
454
455
    // Generate throw tags.
456 1
    $this->generatePhpDocBlockThrow();
457
458 1
    $this->codeStore->append(' */', false);
459
  }
460
461
  //--------------------------------------------------------------------------------------------------------------------
462
  /**
463
   * Generates the long description of stored routine wrapper.
464
   */
465 1
  private function generatePhpDocBlockLongDescription(): void
466
  {
467 1
    if (!empty($this->routine['phpdoc']['long_description']))
468
    {
469
      foreach ($this->routine['phpdoc']['long_description'] as $line)
470
      {
471
        $this->codeStore->append(' * '.$line, false);
472
      }
473
    }
474
  }
475
476
  //--------------------------------------------------------------------------------------------------------------------
477
  /**
478
   * Generates the doc block for parameters of stored routine wrapper.
479
   */
480 1
  private function generatePhpDocBlockParameters(): void
481
  {
482 1
    $parameters = [];
483 1
    foreach ($this->routine['phpdoc']['parameters'] as $parameter)
484
    {
485 1
      $mangledName = $this->nameMangler->getParameterName($parameter['parameter_name']);
486
487 1
      $parameters[] = ['php_name'       => '$'.$mangledName,
488 1
                       'description'    => $parameter['description'],
489 1
                       'php_type'       => $parameter['php_type'],
490 1
                       'dtd_identifier' => $parameter['dtd_identifier']];
491
    }
492
493 1
    $this->enhancePhpDocBlockParameters($parameters);
494
495 1
    if (!empty($parameters))
496
    {
497
      // Compute the max lengths of parameter names and the PHP types of the parameters.
498 1
      $maxNameLength = 0;
499 1
      $maxTypeLength = 0;
500 1
      foreach ($parameters as $parameter)
501
      {
502 1
        $maxNameLength = max($maxNameLength, mb_strlen($parameter['php_name']));
503 1
        $maxTypeLength = max($maxTypeLength, mb_strlen($parameter['php_type']));
504
      }
505
506 1
      $this->codeStore->append(' *', false);
507
508
      // Generate phpDoc for the parameters of the wrapper method.
509
510 1
      foreach ($parameters as $parameter)
511
      {
512 1
        $format = sprintf(' * %%-%ds %%-%ds %%-%ds %%s', mb_strlen('@param'), $maxTypeLength, $maxNameLength);
513
514 1
        $lines = $parameter['description'];
515 1
        if (!empty($lines))
516
        {
517 1
          $line = array_shift($lines);
518 1
          $this->codeStore->append(sprintf($format, '@param', $parameter['php_type'], $parameter['php_name'], $line), false);
519 1
          foreach ($lines as $line)
520
          {
521 1
            $this->codeStore->append(sprintf($format, ' ', ' ', ' ', $line), false);
522
          }
523
        }
524
        else
525
        {
526
          $this->codeStore->append(sprintf($format, '@param', $parameter['php_type'], $parameter['php_name'], ''), false);
527
        }
528
529 1
        if ($parameter['dtd_identifier']!==null)
530
        {
531 1
          $this->codeStore->append(sprintf($format, ' ', ' ', ' ', $parameter['dtd_identifier']), false);
532
        }
533
      }
534
    }
535
  }
536
537
  //--------------------------------------------------------------------------------------------------------------------
538
  /**
539
   * Generates the PHP doc block for the return type of the stored routine wrapper.
540
   */
541 1
  private function generatePhpDocBlockReturn(): void
542
  {
543 1
    $return = $this->getDocBlockReturnType();
544 1
    if ($return!=='')
545
    {
546 1
      $this->codeStore->append(' *', false);
547 1
      $this->codeStore->append(' * @return '.$return, false);
548
    }
549
  }
550
551
  //--------------------------------------------------------------------------------------------------------------------
552
  /**
553
   * Generates the sort description of stored routine wrapper.
554
   */
555 1
  private function generatePhpDocBlockSortDescription(): void
556
  {
557 1
    if (!empty($this->routine['phpdoc']['short_description']))
558
    {
559 1
      foreach ($this->routine['phpdoc']['short_description'] as $line)
560
      {
561 1
        $this->codeStore->append(' * '.$line, false);
562
      }
563
    }
564
  }
565
566
  //--------------------------------------------------------------------------------------------------------------------
567
  /**
568
   * Generates the PHP doc block with throw tags.
569
   */
570 1
  private function generatePhpDocBlockThrow()
571
  {
572 1
    if (!empty($this->throws))
573
    {
574 1
      $this->codeStore->append(' *', false);
575
576 1
      $this->throws = array_unique($this->throws, SORT_REGULAR);
577 1
      foreach ($this->throws as $class)
578
      {
579 1
        $this->codeStore->append(sprintf(' * @throws %s', $class), false);
580
      }
581
    }
582
  }
583
584
  //--------------------------------------------------------------------------------------------------------------------
585
}
586
587
//----------------------------------------------------------------------------------------------------------------------
588