Passed
Push — master ( f71b04...7dcb0c )
by P.R.
12:30
created

BaseRoutine::keyColumns()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 23
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 9
nc 5
nop 0
dl 0
loc 23
rs 9.6111
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
namespace SetBased\Stratum\MySql\Crud\Helper;
5
6
use SetBased\Helper\CodeStore\MySqlCompoundSyntaxCodeStore;
7
use SetBased\Stratum\Middle\Helper\RowSetHelper;
8
9
/**
10
 * Abstract parent class for classes for generating CRUD stored routines.
11
 */
12
abstract class BaseRoutine
13
{
14
  //--------------------------------------------------------------------------------------------------------------------
15
  /**
16
   * The code of the generated stored routine.
17
   *
18
   * @var MySqlCompoundSyntaxCodeStore
19
   */
20
  protected MySqlCompoundSyntaxCodeStore $codeStore;
21
22
  /**
23
   * Metadata about the stored routine parameters.
24
   *
25
   * @var array[]
26
   */
27
  protected array $parameters;
28
29
  /**
30
   * The primary key of the table.
31
   *
32
   * @var array[]
33
   */
34
  protected array $primaryKey;
35
36
  /**
37
   * The name of the generated stored procedure.
38
   *
39
   * @var string
40
   */
41
  protected string $routineName;
42
43
  /**
44
   * Metadata about the columns of the table.
45
   *
46
   * @var array[]
47
   */
48
  protected array $tableColumns;
49
50
  /**
51
   * The name of the table for which a stored routine must be generated.
52
   *
53
   * @var string
54
   */
55
  protected string $tableName;
56
57
  /**
58
   * The unique indexes on the table.
59
   *
60
   * @var array[]
61
   */
62
  protected array $uniqueIndexes;
63
64
  //--------------------------------------------------------------------------------------------------------------------
65
  /**
66
   * Object constructor.
67
   *
68
   * @param string  $tableName     The name of the table for which a stored routine must be generated.
69
   * @param string  $routineName   The name of the generated stored procedure.
70
   * @param array[] $tableColumns  Metadata about the columns of the table.
71
   * @param array[] $primaryKey    The primary key of the table.
72
   * @param array[] $uniqueIndexes The unique indexes on the table.
73
   */
74
  public function __construct(string $tableName,
75
                              string $routineName,
76
                              array  $tableColumns,
77
                              array  $primaryKey,
78
                              array  $uniqueIndexes)
79
  {
80
    $this->tableName     = $tableName;
81
    $this->routineName   = $routineName;
82
    $this->tableColumns  = $tableColumns;
83
    $this->primaryKey    = $primaryKey;
84
    $this->uniqueIndexes = $uniqueIndexes;
85
86
    $this->codeStore = new MySqlCompoundSyntaxCodeStore();
87
  }
88
89
  //--------------------------------------------------------------------------------------------------------------------
90
  /**
91
   * Returns the generated code of the stored routine.
92
   */
93
  public function getCode(): string
94
  {
95
    $this->generateRoutine();
96
97
    return $this->codeStore->getCode();
98
  }
99
100
  //--------------------------------------------------------------------------------------------------------------------
101
  /**
102
   * Returns tre if and only if the table has an auto_increment column.
103
   *
104
   * @param array[] $columns Columns from table.
105
   */
106
  protected function checkAutoIncrement(array $columns): bool
107
  {
108
    foreach ($columns as $column)
109
    {
110
      if ($column['extra']=='auto_increment')
111
      {
112
        return true;
113
      }
114
    }
115
116
    return false;
117
  }
118
119
  //--------------------------------------------------------------------------------------------------------------------
120
  /**
121
   * Generates the body of the stored routine.
122
   */
123
  abstract protected function generateBody(): void;
124
125
  //--------------------------------------------------------------------------------------------------------------------
126
  /**
127
   * Generates the doc block for the stored routine.
128
   */
129
  abstract protected function generateDocBlock(): void;
130
131
  //--------------------------------------------------------------------------------------------------------------------
132
  /**
133
   * Generates the doc block for a stored routine that uses all columns and (preferably) a key (i.e. update).
134
   */
135
  protected function generateDocBlockAllColumnsWithKeyList(): void
136
  {
137
    $this->codeStore->append('/**');
138
    $this->codeStore->append(' * @todo describe routine', false);
139
    $this->codeStore->append(' * ', false);
140
141
    $padding = $this->maxColumnNameLength($this->tableColumns);
142
    $format  = sprintf(' * @param p_%%-%ds @todo describe parameter', $padding);
143
    foreach ($this->tableColumns as $column)
144
    {
145
      $this->codeStore->append(sprintf($format, $column['column_name']), false);
146
    }
147
148
    $this->generateKeyListInDocBlock();
149
150
    $this->codeStore->append(' */', false);
151
  }
152
153
  //--------------------------------------------------------------------------------------------------------------------
154
  /**
155
   * Generates the doc block for a stored routine that uses all columns except auto increment column (i.e. insert).
156
   */
157
  protected function generateDocBlockAllColumnsWithoutAutoIncrement(): void
158
  {
159
    $this->codeStore->append('/**');
160
    $this->codeStore->append(' * @todo describe routine', false);
161
    $this->codeStore->append(' * ', false);
162
163
    $columns = $this->tableColumnsWithoutAutoIncrement();
164
    $width   = $this->maxColumnNameLength($columns);
165
    $format  = sprintf(' * @param p_%%-%ds @todo describe parameter', $width);
166
    foreach ($this->tableColumns as $column)
167
    {
168
      $this->codeStore->append(sprintf($format, $column['column_name']), false);
169
    }
170
171
    $this->codeStore->append(' */', false);
172
  }
173
174
  //--------------------------------------------------------------------------------------------------------------------
175
  /**
176
   * Generates the doc block for a stored routine that (preferably) uses a key (i.e. select and delete).
177
   */
178
  protected function generateDocBlockWithKey(): void
179
  {
180
    $this->codeStore->append('/**');
181
    $this->codeStore->append(' * @todo describe routine', false);
182
    $this->codeStore->append(' * ', false);
183
184
    $columns = $this->keyColumns();
185
    $padding = $this->maxColumnNameLength($columns);
186
    $format  = sprintf(' * @param p_%%-%ds @todo describe parameter', $padding);
187
    foreach ($columns as $column)
188
    {
189
      $this->codeStore->append(sprintf($format, $column['column_name']), false);
190
    }
191
192
    $this->generateKeyListInDocBlock();
193
194
    $this->codeStore->append(' */', false);
195
  }
196
197
  //--------------------------------------------------------------------------------------------------------------------
198
  /**
199
   * Generates an overview of all keys on a table in a doc block.
200
   */
201
  protected function generateKeyListInDocBlock(): void
202
  {
203
    $keys = $this->keyList();
204
    if (!empty($keys))
205
    {
206
      if (sizeof($keys)>1 || !isset($keys['PRIMARY']))
207
      {
208
        $this->codeStore->append(' * ', false);
209
        $this->codeStore->append(' * Possible keys:', false);
210
        foreach ($keys as $keyName => $columns)
211
        {
212
          $this->codeStore->append(sprintf(' *   %s: %s', $keyName, $columns), false);
213
        }
214
      }
215
    }
216
    else
217
    {
218
      $this->codeStore->append(' * ', false);
219
      $this->codeStore->append(' * NOTE: Table does not have a key.', false);
220
    }
221
  }
222
223
  //--------------------------------------------------------------------------------------------------------------------
224
  /**
225
   * Generates the function name and parameters of the stored routine.
226
   */
227
  abstract protected function generateRoutineDeclaration(): void;
228
229
  //--------------------------------------------------------------------------------------------------------------------
230
  /**
231
   * Generates the function name and parameters of a stored routine that uses all columns except auto increment column
232
   * (i.e. insert).
233
   */
234
  protected function generateRoutineDeclarationAllColumnsWithoutAutoIncrement(): void
235
  {
236
    $this->codeStore->append(sprintf('create procedure %s(', $this->routineName));
237
238
    $columns = $this->tableColumnsWithoutAutoIncrement();
239
    $padding = $this->maxColumnNameLength($columns);
240
    $offset  = mb_strlen($this->codeStore->getLastLine());
241
242
    $first = true;
243
    foreach ($columns as $column)
244
    {
245
      if ($first)
246
      {
247
        $format = sprintf('in p_%%-%ds @%%s.%%s%%s@', $padding);
248
        $this->codeStore->appendToLastLine(strtolower(sprintf($format,
249
                                                              $column['column_name'],
250
                                                              $this->tableName,
251
                                                              $column['column_name'],
252
                                                              '%type')));
253
      }
254
      else
255
      {
256
        $format = sprintf('%%%ds p_%%-%ds @%%s.%%s%%s@', $offset + 2, $padding);
257
        $this->codeStore->append(strtolower(sprintf($format,
258
                                                    'in',
259
                                                    $column['column_name'],
260
                                                    $this->tableName,
261
                                                    $column['column_name'],
262
                                                    '%type')),
263
                                 false);
264
      }
265
266
      if ($column!=end($this->tableColumns))
267
      {
268
        $this->codeStore->appendToLastLine(',');
269
      }
270
      else
271
      {
272
        $this->codeStore->appendToLastLine(')');
273
      }
274
275
      $first = false;
276
    }
277
  }
278
279
  //--------------------------------------------------------------------------------------------------------------------
280
  /**
281
   * Generates the function name and parameters of a stored routine that (preferably) uses a key (i.e. select, update,
282
   * and delete).
283
   */
284
  protected function generateRoutineDeclarationWithKey(): void
285
  {
286
    $this->codeStore->append(sprintf('create procedure %s(', $this->routineName));
287
288
    $offset  = mb_strlen($this->codeStore->getLastLine());
289
    $columns = $this->keyColumns();
290
    $width   = $this->maxColumnNameLength($columns);
291
292
    $first = true;
293
    foreach ($columns as $column)
294
    {
295
      if ($first)
296
      {
297
        $format = sprintf('in p_%%-%ds @%%s.%%s%%s@', $width);
298
        $this->codeStore->appendToLastLine(strtolower(sprintf($format,
299
                                                              $column['column_name'],
300
                                                              $this->tableName,
301
                                                              $column['column_name'],
302
                                                              '%type')));
303
      }
304
      else
305
      {
306
        $format = sprintf('%%%ds p_%%-%ds @%%s.%%s%%s@', $offset + 2, $width);
307
        $this->codeStore->append(strtolower(sprintf($format,
308
                                                    'in',
309
                                                    $column['column_name'],
310
                                                    $this->tableName,
311
                                                    $column['column_name'],
312
                                                    '%type')),
313
                                 false);
314
      }
315
316
      if ($column!=end($columns))
317
      {
318
        $this->codeStore->appendToLastLine(',');
319
      }
320
      else
321
      {
322
        $this->codeStore->appendToLastLine(')');
323
      }
324
325
      $first = false;
326
    }
327
  }
328
329
  //--------------------------------------------------------------------------------------------------------------------
330
  /**
331
   * Generates the modifies/reads sql data and designation type comment of the stored routine.
332
   */
333
  abstract protected function generateSqlDataAndDesignationType(): void;
334
335
  //--------------------------------------------------------------------------------------------------------------------
336
  /**
337
   * Returns all columns that are in one or more keys. If the table does not have any keys all columns are returned.
338
   */
339
  protected function keyColumns(): array
340
  {
341
    $columns = [];
342
343
    if (!empty($this->uniqueIndexes))
344
    {
345
      foreach ($this->tableColumns as $column)
346
      {
347
        if (RowSetHelper::searchInRowSet($this->uniqueIndexes, 'Column_name', $column['column_name'])!==null)
348
        {
349
          $columns[] = $column;
350
        }
351
      }
352
    }
353
    else
354
    {
355
      foreach ($this->tableColumns as $column)
356
      {
357
        $columns[] = $column;
358
      }
359
    }
360
361
    return $columns;
362
  }
363
364
  //--------------------------------------------------------------------------------------------------------------------
365
  /**
366
   */
367
  protected function keyList(): array
368
  {
369
    $nested = $this->nestedKeys();
370
    $keys   = [];
371
    foreach ($nested as $keyName => $columnNames)
372
    {
373
      $keys[$keyName] = implode(', ', $columnNames);
374
    }
375
376
    return $keys;
377
  }
378
379
  //--------------------------------------------------------------------------------------------------------------------
380
  /**
381
   * Returns the length the longest column name in a list of columns.
382
   *
383
   * @param array[] $columns The list of columns.
384
   */
385
  protected function maxColumnNameLength(array $columns): int
386
  {
387
    $length = 0;
388
    foreach ($columns as $column)
389
    {
390
      $length = max(mb_strlen($column['column_name']), $length);
391
    }
392
393
    return $length;
394
  }
395
396
  //--------------------------------------------------------------------------------------------------------------------
397
  /**
398
   * Returns all keys (primary and unique indexes) on the table as nested array.
399
   */
400
  protected function nestedKeys(): array
401
  {
402
    $keys = [];
403
    $last = '';
404
    foreach ($this->uniqueIndexes as $row)
405
    {
406
      if ($last!==$row['Key_name'])
407
      {
408
        $keys[$row['Key_name']] = [];
409
      }
410
411
      $keys[$row['Key_name']][] = $row['Column_name'];
412
413
      $last = $row['Key_name'];
414
    }
415
416
    return $keys;
417
  }
418
419
  //--------------------------------------------------------------------------------------------------------------------
420
  /**
421
   * Returns all columns of the table except any auto increment column.
422
   */
423
  protected function tableColumnsWithoutAutoIncrement(): array
424
  {
425
    $columns = [];
426
427
    foreach ($this->tableColumns as $column)
428
    {
429
      if ($column['extra']!='auto_increment')
430
      {
431
        $columns[] = $column;
432
      }
433
    }
434
435
    return $columns;
436
  }
437
438
  //--------------------------------------------------------------------------------------------------------------------
439
  /**
440
   * Generates the code of the stored routine.
441
   */
442
  private function generateRoutine(): void
443
  {
444
    $this->generateDocBlock();
445
    $this->generateRoutineDeclaration();
446
    $this->generateSqlDataAndDesignationType();
447
    $this->codeStore->append('begin');
448
    $this->generateBody();
449
    $this->codeStore->append('end');
450
  }
451
}
452
453
//----------------------------------------------------------------------------------------------------------------------
454