Passed
Push — master ( 304ce4...56185b )
by P.R.
04:05
created

RoutineParametersHelper   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 277
Duplicated Lines 0 %

Test Coverage

Coverage 79%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 92
c 1
b 0
f 0
dl 0
loc 277
ccs 79
cts 100
cp 0.79
rs 9.76
wmc 33

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A enhanceParametersWithAddendum() 0 17 5
A enhanceTypeOfParameters() 0 27 4
A enhanceCharacterSet() 0 19 5
A validateParameterLists() 0 28 5
B extractParametersAddendum() 0 26 8
A extractDocBlockPartsWrapper() 0 18 3
A getParameters() 0 3 1
A extractRoutineParameters() 0 8 1
1
<?php
2
declare(strict_types=1);
3
4
namespace SetBased\Stratum\MySql\Helper;
5
6
use SetBased\Stratum\Backend\StratumStyle;
7
use SetBased\Stratum\Common\DocBlock\DocBlockReflection;
8
use SetBased\Stratum\Common\Exception\RoutineLoaderException;
9
use SetBased\Stratum\MySql\Exception\MySqlQueryErrorException;
10
use SetBased\Stratum\MySql\MySqlMetaDataLayer;
11
12
/**
13
 * Class for handling routine parameters.
14
 */
15
class RoutineParametersHelper
16
{
17
  //--------------------------------------------------------------------------------------------------------------------
18
  /**
19
   * The meta data layer.
20
   *
21
   * @var MySqlMetaDataLayer
22
   */
23
  private $dl;
24
25
  /**
26
   * The DocBlock reflection object.
27
   *
28
   * @var DocBlockReflection
29
   */
30
  private $docBlockReflection;
31
32
  /**
33
   * The Output decorator.
34
   *
35
   * @var StratumStyle
36
   */
37
  private $io;
38
39
  /**
40
   * The information about the parameters of the stored routine.
41
   *
42
   * @var array[]
43
   */
44
  private $parameters = [];
45
46
  /**
47
   * Information about parameters with specific format (string in CSV format etc.).
48
   *
49
   * @var array
50
   */
51
  private $parametersAddendum = [];
52
53
  /**
54
   * The name of the stored routine.
55
   *
56
   * @var string
57
   */
58
  private $routineName;
59
60
  //--------------------------------------------------------------------------------------------------------------------
61
  /**
62
   * Object constructor.
63
   *
64
   * @param MySqlMetaDataLayer $dl                 The meta data layer.
65
   * @param StratumStyle       $io                 The Output decorator.
66
   * @param DocBlockReflection $docBlockReflection The DocBlock reflection object.
67
   * @param string             $routineName        The name of the stored routine.
68
   */
69 1
  public function __construct(MySqlMetaDataLayer $dl,
70
                              StratumStyle $io,
71
                              DocBlockReflection $docBlockReflection,
72
                              string $routineName)
73
  {
74 1
    $this->dl                 = $dl;
75 1
    $this->io                 = $io;
76 1
    $this->docBlockReflection = $docBlockReflection;
77 1
    $this->routineName        = $routineName;
78 1
  }
79
80
  //--------------------------------------------------------------------------------------------------------------------
81
  /**
82
   * Extracts DocBlock parts of the stored routine parameters to be used by the wrapper generator.
83
   *
84
   * @return array
85
   */
86 1
  public function extractDocBlockPartsWrapper(): array
87
  {
88 1
    $lookup = [];
89 1
    foreach ($this->docBlockReflection->getTags('param') as $tag)
90
    {
91 1
      $lookup[$tag['arguments'][0]] = $tag['description'];
92
    }
93
94 1
    $parameters = [];
95 1
    foreach ($this->parameters as $parameter)
96
    {
97 1
      $parameters[] = ['parameter_name' => $parameter['parameter_name'],
98 1
                       'php_type'       => DataTypeHelper::columnTypeToPhpTypeHinting($parameter).'|null',
99 1
                       'dtd_identifier' => $parameter['dtd_identifier'],
100 1
                       'description'    => $lookup[($parameter['parameter_name'])] ?? []];
101
    }
102
103 1
    return $parameters;
104
  }
105
106
  //--------------------------------------------------------------------------------------------------------------------
107
  /**
108
   * Extracts info about the parameters of the stored routine.
109
   *
110
   * @throws RoutineLoaderException
111
   * @throws MySqlQueryErrorException
112
   */
113 1
  public function extractRoutineParameters(): void
114
  {
115 1
    $this->parameters = $this->dl->routineParameters($this->routineName);
116 1
    $this->enhanceTypeOfParameters();
117 1
    $this->enhanceCharacterSet();
118 1
    $this->extractParametersAddendum();
119 1
    $this->enhanceParametersWithAddendum();
120 1
    $this->validateParameterLists();
121 1
  }
122
123
  //--------------------------------------------------------------------------------------------------------------------
124
  /**
125
   * Returns the metadata of of all parameters.
126
   *
127
   * @return array[]
128
   */
129 1
  public function getParameters(): array
130
  {
131 1
    return $this->parameters;
132
  }
133
134
  //--------------------------------------------------------------------------------------------------------------------
135
  /**
136
   * Enhances parameters with character data with their character set.
137
   */
138 1
  private function enhanceCharacterSet(): void
139
  {
140 1
    foreach ($this->parameters as $key => $parameter)
141
    {
142 1
      if ($parameter['parameter_name'])
143
      {
144 1
        $dataTypeDescriptor = $parameter['dtd_identifier'];
145 1
        if (isset($parameter['character_set_name']))
146
        {
147 1
          $dataTypeDescriptor .= ' character set '.$parameter['character_set_name'];
148
        }
149 1
        if (isset($parameter['collation_name']))
150
        {
151 1
          $dataTypeDescriptor .= ' collation '.$parameter['collation_name'];
152
        }
153
154 1
        $parameter['dtd_identifier'] = $dataTypeDescriptor;
155
156 1
        $this->parameters[$key] = $parameter;
157
      }
158
    }
159 1
  }
160
161
  //--------------------------------------------------------------------------------------------------------------------
162
  /**
163
   * Updates parameters with data from parameter addendum tags.
164
   *
165
   * @throws RoutineLoaderException
166
   */
167 1
  private function enhanceParametersWithAddendum(): void
168
  {
169 1
    foreach ($this->parametersAddendum as $parameterName => $addendum)
170
    {
171 1
      $exists = false;
172 1
      foreach ($this->parameters as $key => $parameter)
173
      {
174 1
        if ($parameter['parameter_name']===$parameterName)
175
        {
176 1
          $this->parameters[$key] = array_merge($this->parameters[$key], $addendum);
177 1
          $exists                 = true;
178 1
          break;
179
        }
180
      }
181 1
      if (!$exists)
182
      {
183
        throw new RoutineLoaderException("Specific parameter '%s' does not exist", $parameterName);
184
      }
185
    }
186 1
  }
187
188
  //--------------------------------------------------------------------------------------------------------------------
189
  /**
190
   * @throws RoutineLoaderException
191
   * @throws MySqlQueryErrorException
192
   */
193 1
  private function enhanceTypeOfParameters()
194
  {
195 1
    foreach ($this->parameters as $key => $parameter)
196
    {
197 1
      if ($parameter['data_type']==='TYPE OF')
198
      {
199
        $n = preg_match('/^("(?<schema>[a-zA-Z0-9_]+)"\.)?("(?<table>[a-zA-Z0-9_]+)")\.("(?<column>[a-zA-Z0-9_]+)")%TYPE$/',
200
                        $parameter['dtd_identifier'], $matches);
201
        if ($n!==1)
202
        {
203
          throw new RoutineLoaderException('Unable to parse data type description %s of parameter %s.',
204
                                           $parameter['dtd_identifier'],
205
                                           $parameter['parameter_name']);
206
        }
207
208
        $schemaName = $matches['schema'] ?? null;
209
        $tableName  = $matches['table'];
210
        $columnName = $matches['column'];
211
212
        $column = $this->dl->tableColumn($schemaName, $tableName, $columnName);
213
214
        $this->parameters[$key]['data_type']          = $column['data_type'];
215
        $this->parameters[$key]['numeric_precision']  = $column['numeric_precision'];
216
        $this->parameters[$key]['numeric_scale']      = $column['numeric_scale'];
217
        $this->parameters[$key]['character_set_name'] = $column['character_set_name'];
218
        $this->parameters[$key]['collation_name']     = $column['collation_name'];
219
        $this->parameters[$key]['dtd_identifier']     = $column['column_type'];
220
      }
221
    }
222 1
  }
223
224
  //--------------------------------------------------------------------------------------------------------------------
225
  /**
226
   * Extracts parameter addendum of the routine parameters.
227
   *
228
   * @throws RoutineLoaderException
229
   */
230 1
  private function extractParametersAddendum(): void
231
  {
232 1
    $tags = $this->docBlockReflection->getTags('paramAddendum');
233 1
    foreach ($tags as $tag)
234
    {
235 1
      $parameterName = $tag['arguments'][0];
236 1
      $dataType      = $tag['arguments'][1];
237 1
      $delimiter     = $tag['arguments'][2];
238 1
      $enclosure     = $tag['arguments'][3];
239 1
      $escape        = $tag['arguments'][4];
240
241 1
      if ($parameterName==='' || $dataType=='' || $delimiter==='' || $enclosure==='' || $escape==='')
242
      {
243
        throw new RoutineLoaderException('Expected: @paramAddendum <field_name> <type_of_list> <delimiter> <enclosure> <escape>.');
244
      }
245
246 1
      if (isset($this->parametersAddendum[$parameterName]))
247
      {
248
        throw new RoutineLoaderException("Duplicate @paramAddendum tag for parameter '%s'", $parameterName);
249
      }
250
251 1
      $this->parametersAddendum[$parameterName] = ['name'      => $parameterName,
252 1
                                                   'data_type' => $dataType,
253 1
                                                   'delimiter' => $delimiter,
254 1
                                                   'enclosure' => $enclosure,
255 1
                                                   'escape'    => $escape];
256
    }
257 1
  }
258
259
  //--------------------------------------------------------------------------------------------------------------------
260
  /**
261
   * Validates the parameters found the DocBlock in the source of the stored routine against the parameters from the
262
   * metadata of MySQL and reports missing and unknown parameters names.
263
   */
264 1
  private function validateParameterLists(): void
265
  {
266
    // Make list with names of parameters used in database.
267 1
    $databaseParametersNames = [];
268 1
    foreach ($this->parameters as $parameter)
269
    {
270 1
      $databaseParametersNames[] = $parameter['parameter_name'];
271
    }
272
273
    // Make list with names of parameters used in dock block of routine.
274 1
    $docBlockParametersNames = [];
275 1
    foreach ($this->docBlockReflection->getTags('param') as $tag)
276
    {
277 1
      $docBlockParametersNames[] = $tag['arguments'][0];
278
    }
279
280
    // Check and show warning if any parameters is missing in doc block.
281 1
    $tmp = array_diff($databaseParametersNames, $docBlockParametersNames);
282 1
    foreach ($tmp as $name)
283
    {
284
      $this->io->logNote('Parameter <dbo>%s</dbo> is missing from doc block', $name);
285
    }
286
287
    // Check and show warning if find unknown parameters in doc block.
288 1
    $tmp = array_diff($docBlockParametersNames, $databaseParametersNames);
289 1
    foreach ($tmp as $name)
290
    {
291
      $this->io->logNote('Unknown parameter <dbo>%s</dbo> found in doc block', $name);
292
    }
293 1
  }
294
295
  //--------------------------------------------------------------------------------------------------------------------
296
}
297
298
//----------------------------------------------------------------------------------------------------------------------
299