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

Wrapper::generatePhpDocBlockParameters()   B

Complexity

Conditions 8
Paths 22

Size

Total Lines 52
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 8.0032

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 27
c 1
b 0
f 0
nc 22
nop 0
dl 0
loc 52
ccs 26
cts 27
cp 0.963
crap 8.0032
rs 8.4444

How to fix   Long Method   

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