Passed
Push — master ( 157f19...d1b1cc )
by P.R.
03:58 queued 51s
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 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)
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 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
    $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 1
  }
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 1
  }
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 1
  protected function enhancePhpDocBlockParameters(array &$parameters): void
338
  {
339
    // Nothing to do.
340 1
  }
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
    $ret = '';
388
389 1
    if ($this->routine['designation']==='bulk')
390
    {
391
      $ret .= 'BulkHandler $bulkHandler';
392
    }
393
394 1
    foreach ($this->routine['parameters'] as $parameter)
395
    {
396 1
      if ($ret!=='') $ret .= ', ';
397
398 1
      $dataType    = DataTypeHelper::columnTypeToPhpTypeHinting($parameter);
399 1
      $declaration = DataTypeHelper::phpTypeHintingToPhpTypeDeclaration($dataType.'|null');
400 1
      if ($declaration!=='')
401
      {
402 1
        $ret .= $declaration.' ';
403
      }
404
405 1
      $ret .= '$'.$this->nameMangler->getParameterName($parameter['parameter_name']);
406
    }
407
408 1
    return $ret;
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
   * Generates code for fetching data of a stored routine with one or more LOB parameters.
421
   *
422
   * @return void
423
   */
424
  abstract protected function writeRoutineFunctionLobFetchData(): void;
425
426
  //--------------------------------------------------------------------------------------------------------------------
427
  /**
428
   * Generates code for retuning the data returned by a stored routine with one or more LOB parameters.
429
   *
430
   * @return void
431
   */
432
  abstract protected function writeRoutineFunctionLobReturnData(): void;
433
434
  //--------------------------------------------------------------------------------------------------------------------
435
  /**
436
   * Generate php doc block in the data layer for stored routine.
437
   */
438 1
  private function generatePhpDocBlock(): void
439
  {
440 1
    $this->codeStore->append('/**', false);
441
442
    // Generate phpdoc with short description of routine wrapper.
443 1
    $this->generatePhpDocBlockSortDescription();
444
445
    // Generate phpdoc with long description of routine wrapper.
446 1
    $this->generatePhpDocBlockLongDescription();
447
448
    // Generate phpDoc with parameters and descriptions of parameters.
449 1
    $this->generatePhpDocBlockParameters();
450
451
    // Generate return parameter doc.
452 1
    $this->generatePhpDocBlockReturn();
453
454
    // Generate throw tags.
455 1
    $this->generatePhpDocBlockThrow();
456
457 1
    $this->codeStore->append(' */', false);
458 1
  }
459
460
  //--------------------------------------------------------------------------------------------------------------------
461
  /**
462
   * Generates the long description of stored routine wrapper.
463
   */
464 1
  private function generatePhpDocBlockLongDescription(): void
465
  {
466 1
    if (!empty($this->routine['phpdoc']['long_description']))
467
    {
468
      foreach ($this->routine['phpdoc']['long_description'] as $line)
469
      {
470
        $this->codeStore->append(' * '.$line, false);
471
      }
472
    }
473 1
  }
474
475
  //--------------------------------------------------------------------------------------------------------------------
476
  /**
477
   * Generates the doc block for parameters of stored routine wrapper.
478
   */
479 1
  private function generatePhpDocBlockParameters(): void
480
  {
481 1
    $parameters = [];
482 1
    foreach ($this->routine['phpdoc']['parameters'] as $parameter)
483
    {
484 1
      $mangledName = $this->nameMangler->getParameterName($parameter['parameter_name']);
485
486 1
      $parameters[] = ['php_name'       => '$'.$mangledName,
487 1
                       'description'    => $parameter['description'],
488 1
                       'php_type'       => $parameter['php_type'],
489 1
                       'dtd_identifier' => $parameter['dtd_identifier']];
490
    }
491
492 1
    $this->enhancePhpDocBlockParameters($parameters);
493
494 1
    if (!empty($parameters))
495
    {
496
      // Compute the max lengths of parameter names and the PHP types of the parameters.
497 1
      $maxNameLength = 0;
498 1
      $maxTypeLength = 0;
499 1
      foreach ($parameters as $parameter)
500
      {
501 1
        $maxNameLength = max($maxNameLength, mb_strlen($parameter['php_name']));
502 1
        $maxTypeLength = max($maxTypeLength, mb_strlen($parameter['php_type']));
503
      }
504
505 1
      $this->codeStore->append(' *', false);
506
507
      // Generate phpDoc for the parameters of the wrapper method.
508
509 1
      foreach ($parameters as $parameter)
510
      {
511 1
        $format = sprintf(' * %%-%ds %%-%ds %%-%ds %%s', mb_strlen('@param'), $maxTypeLength, $maxNameLength);
512
513 1
        $lines = $parameter['description'];
514 1
        if (!empty($lines))
515
        {
516 1
          $line = array_shift($lines);
517 1
          $this->codeStore->append(sprintf($format, '@param', $parameter['php_type'], $parameter['php_name'], $line), false);
518 1
          foreach ($lines as $line)
519
          {
520 1
            $this->codeStore->append(sprintf($format, ' ', ' ', ' ', $line), false);
521
          }
522
        }
523
        else
524
        {
525
          $this->codeStore->append(sprintf($format, '@param', $parameter['php_type'], $parameter['php_name'], ''), false);
526
        }
527
528 1
        if ($parameter['dtd_identifier']!==null)
529
        {
530 1
          $this->codeStore->append(sprintf($format, ' ', ' ', ' ', $parameter['dtd_identifier']), false);
531
        }
532
      }
533
    }
534 1
  }
535
536
  //--------------------------------------------------------------------------------------------------------------------
537
  /**
538
   * Generates the PHP doc block for the return type of the stored routine wrapper.
539
   */
540 1
  private function generatePhpDocBlockReturn(): void
541
  {
542 1
    $return = $this->getDocBlockReturnType();
543 1
    if ($return!=='')
544
    {
545 1
      $this->codeStore->append(' *', false);
546 1
      $this->codeStore->append(' * @return '.$return, false);
547
    }
548 1
  }
549
550
  //--------------------------------------------------------------------------------------------------------------------
551
  /**
552
   * Generates the sort description of stored routine wrapper.
553
   */
554 1
  private function generatePhpDocBlockSortDescription(): void
555
  {
556 1
    if (!empty($this->routine['phpdoc']['short_description']))
557
    {
558 1
      foreach ($this->routine['phpdoc']['short_description'] as $line)
559
      {
560 1
        $this->codeStore->append(' * '.$line, false);
561
      }
562
    }
563 1
  }
564
565
  //--------------------------------------------------------------------------------------------------------------------
566
  /**
567
   * Generates the PHP doc block with throw tags.
568
   */
569 1
  private function generatePhpDocBlockThrow()
570
  {
571 1
    if (!empty($this->throws))
572
    {
573 1
      $this->codeStore->append(' *', false);
574
575 1
      $this->throws = array_unique($this->throws, SORT_REGULAR);
576 1
      foreach ($this->throws as $class)
577
      {
578 1
        $this->codeStore->append(sprintf(' * @throws %s', $class), false);
579
      }
580
    }
581 1
  }
582
583
  //--------------------------------------------------------------------------------------------------------------------
584
}
585
586
//----------------------------------------------------------------------------------------------------------------------
587