Completed
Pull Request — master (#41)
by Dima
03:20
created

DiffCommand::resolveCanonicalAuditColumns()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 30
Code Lines 16

Duplication

Lines 30
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 30
loc 30
ccs 0
cts 15
cp 0
rs 8.439
cc 5
eloc 16
nc 6
nop 0
crap 30
1
<?php
2
//----------------------------------------------------------------------------------------------------------------------
3
namespace SetBased\Audit\MySql\Command;
4
5
use SetBased\Audit\MySql\AuditDataLayer;
6
use SetBased\Audit\MySql\Helper\ColumnTypesExtended;
7
use SetBased\Audit\MySql\Helper\ColumnTypesHelper;
8
use SetBased\Audit\MySql\Helper\TableHelper;
9
use SetBased\Audit\MySql\Metadata\TableColumnsMetadata;
10
use SetBased\Audit\MySql\Table\Columns;
11
use SetBased\Stratum\MySql\StaticDataLayer;
12
use SetBased\Stratum\Style\StratumStyle;
13
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
14
use Symfony\Component\Console\Helper\Table;
15
use Symfony\Component\Console\Input\InputArgument;
16
use Symfony\Component\Console\Input\InputInterface;
17
use Symfony\Component\Console\Input\InputOption;
18
use Symfony\Component\Console\Output\OutputInterface;
19
20
//----------------------------------------------------------------------------------------------------------------------
21
/**
22
 * Command for comparing data tables with audit tables.
23
 */
24
class DiffCommand extends AuditCommand
25
{
26
  //--------------------------------------------------------------------------------------------------------------------
27
  /**
28
   * The metadata (additional) audit columns (as stored in the config file).
29
   *
30
   * @var TableColumnsMetadata
31
   */
32
  private $auditColumnsMetadata;
33
34
  /**
35
   * The names of all tables in audit schema.
36
   *
37
   * @var array
38
   */
39
  private $auditSchemaTables;
40
41
  /**
42
   * The names of all tables in data schema.
43
   *
44
   * @var array
45
   */
46
  private $dataSchemaTables;
47
48
  /**
49
   * Array with columns for each table.
50
   * array [
51
   *    table_name [
52
   *            column [
53
   *                    data table type,
54
   *                    audit table type
55
   *                    ],
56
   *                      ...
57
   *               ]
58
   *       ]
59
   *
60
   * @var array[]
61
   */
62
  private $diffColumns;
63
64
  /**
65
   * If set all tables and columns are shown.
66
   *
67
   * @var boolean
68
   */
69
  private $full;
70
71
  //--------------------------------------------------------------------------------------------------------------------
72
  /**
73
   * Check full full and return array without new or obsolete columns if full not set.
74
   *
75
   * @param array[] $columns The metadata of the columns of a table.
76
   *
77
   * @return array[]
78
   */
79
  private static function removeMatchingColumns($columns)
80
  {
81
    $cleaned = [];
82
    /** @var ColumnTypesHelper $column */
83
    foreach ($columns as $column)
84
    {
85
      $columnsArray = $column->getTypes();
0 ignored issues
show
Bug introduced by
The method getTypes cannot be called on $column (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
86
      if (!isset($columnsArray['data_column_type']))
87
      {
88
        if ($columnsArray['audit_column_type']!=$columnsArray['config_column_type'])
89
        {
90
          $cleaned[] = $column;
91
        }
92
      }
93 View Code Duplication
      elseif (!isset($columnsArray['config_column_type']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
94 1
      {
95
        if (($columnsArray['audit_column_type']!=$columnsArray['data_column_type']) || ($columnsArray['audit_character_set_name']!=$columnsArray['data_character_set_name'] || $columnsArray['audit_collation_name']!=$columnsArray['data_collation_name']))
96 1
        {
97 1
          $cleaned[] = $column;
98 1
        }
99 1
      }
100 1 View Code Duplication
      else
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
101
      {
102
        if (($columnsArray['data_column_type']!=$columnsArray['audit_column_type'] && $columnsArray['audit_column_type']!=$columnsArray['config_column_type']) || ($columnsArray['audit_column_type']!=$columnsArray['config_column_type'] && !empty($columnsArray['config_column_type'])))
103
        {
104
          $cleaned[] = $column;
105
        }
106
      }
107
    }
108
109
    return $cleaned;
110
  }
111
112
  //--------------------------------------------------------------------------------------------------------------------
113
  /**
114
   * Getting list of all tables from information_schema of database from config file.
115
   */
116
  public function listOfTables()
117
  {
118
    $this->dataSchemaTables = AuditDataLayer::getTablesNames($this->config['database']['data_schema']);
119
120
    $this->auditSchemaTables = AuditDataLayer::getTablesNames($this->config['database']['audit_schema']);
121
  }
122
123
  //--------------------------------------------------------------------------------------------------------------------
124
  /**
125
   * {@inheritdoc}
126
   */
127
  protected function configure()
128
  {
129
    $this->setName('diff')
130
         ->setDescription('Compares data tables and audit tables')
131
         ->addArgument('config file', InputArgument::OPTIONAL, 'The audit configuration file', 'etc/audit.json')
132
         ->addOption('full', 'f', InputOption::VALUE_NONE, 'Show all columns');
133
  }
134
135
  //--------------------------------------------------------------------------------------------------------------------
136
  /**
137
   * {@inheritdoc}
138
   */
139
  protected function execute(InputInterface $input, OutputInterface $output)
140
  {
141
    $this->io = new StratumStyle($input, $output);
142
    // Style for column names with miss matched column types.
143
    $style = new OutputFormatterStyle(null, 'red');
144
    $output->getFormatter()->setStyle('mm_column', $style);
145
146
    // Style for column types of columns with miss matched column types.
147
    $style = new OutputFormatterStyle('yellow');
148
    $output->getFormatter()->setStyle('mm_type', $style);
149
150
    // Style for obsolete tables.
151
    $style = new OutputFormatterStyle('yellow');
152
    $output->getFormatter()->setStyle('obsolete_table', $style);
153
154
    // Style for missing tables.
155
    $style = new OutputFormatterStyle('red');
156
    $output->getFormatter()->setStyle('miss_table', $style);
157
158
    $this->configFileName = $input->getArgument('config file');
159
    $this->readConfigFile();
160
161
    $this->readMetadata();
162
163
    $this->full = $input->getOption('full');
164
165
    $this->connect($this->config);
166
167
    $this->resolveCanonicalAuditColumns();
168
169
    $this->listOfTables();
170
171
    $this->getDiff();
172
    $this->printDiff($output);
173
  }
174
175
  //--------------------------------------------------------------------------------------------------------------------
176
  /**
177
   * Resolves the canonical column types of the audit table columns.
178
   */
179 View Code Duplication
  protected function resolveCanonicalAuditColumns()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
180
  {
181
    if (empty($this->config['audit_columns']))
182
    {
183
      $this->auditColumnsMetadata = new TableColumnsMetadata();
184
    }
185
    else
186
    {
187
      $schema    = $this->config['database']['audit_schema'];
188
      $tableName = '_TMP_'.uniqid();
189
      AuditDataLayer::createTemporaryTable($schema, $tableName, $this->config['audit_columns']);
190
      $columns = AuditDataLayer::getTableColumns($schema, $tableName);
191
      AuditDataLayer::dropTemporaryTable($schema, $tableName);
192
193
      foreach ($this->config['audit_columns'] as $audit_column)
194
      {
195
        $key = StaticDataLayer::searchInRowSet('column_name', $audit_column['column_name'], $columns);
196
        if (isset($audit_column['value_type']))
197
        {
198
          $columns[$key]['value_type'] = $audit_column['value_type'];
199
        }
200
        if (isset($audit_column['expression']))
201
        {
202
          $columns[$key]['expression'] = $audit_column['expression'];
203
        }
204
      }
205
206
      $this->auditColumnsMetadata = new TableColumnsMetadata($columns, 'AuditColumnMetadata');
207
    }
208
  }
209
210
  //--------------------------------------------------------------------------------------------------------------------
211
  /**
212
   * Add not null to audit columns if it not nullable.
213
   *
214
   * @param array $theColumns Audit columns.
215
   *
216
   * @return array
217
   */
218
  private function addNotNull($theColumns)
219
  {
220
    $modifiedColumns = [];
221
    foreach ($theColumns as $column)
222
    {
223
      $modifiedColumn = $column;
224
      $auditColumn    = StaticDataLayer::searchInRowSet('column_name', $modifiedColumn['column_name'], $this->config['audit_columns']);
225
      if (isset($auditColumn))
226
      {
227
        if ($modifiedColumn['is_nullable']==='NO')
228
        {
229
          $modifiedColumn['column_type'] = sprintf('%s not null', $modifiedColumn['column_type']);
230
        }
231
      }
232
      $modifiedColumns[] = $modifiedColumn;
233
    }
234
235
    return $modifiedColumns;
236
  }
237
238
  //--------------------------------------------------------------------------------------------------------------------
239
  /**
240
   * Get the difference between data and audit tables.
241
   *
242
   * @param TableColumnsMetadata $dataColumns  The table columns from data schema.
243
   * @param TableColumnsMetadata $auditColumns The table columns from audit schema.
244
   *
245
   * @return \array[]
0 ignored issues
show
Documentation introduced by
Should the return type not be ColumnTypesExtended?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
246
   */
247
  private function createDiffArray($dataColumns, $auditColumns)
248
  {
249
    $diff = new ColumnTypesExtended($this->config['audit_columns'], $auditColumns, $dataColumns);
0 ignored issues
show
Documentation introduced by
$auditColumns is of type object<SetBased\Audit\My...a\TableColumnsMetadata>, but the function expects a object<SetBased\Audit\MySql\Table\Columns>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
$dataColumns is of type object<SetBased\Audit\My...a\TableColumnsMetadata>, but the function expects a object<SetBased\Audit\MySql\Table\Columns>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
250
251
    return $diff;
252
  }
253
254
  //--------------------------------------------------------------------------------------------------------------------
255
  /**
256
   * Writes the difference between the audit tables and metadata tables to the output.
257
   *
258
   * @param OutputInterface $output The output.
259
   */
260
  private function diffTables($output)
261
  {
262
    foreach ($this->config['tables'] as $tableName => $table)
263
    {
264
      $res = StaticDataLayer::searchInRowSet('table_name', $tableName, $this->auditSchemaTables);
265
      if ($table['audit'] && !isset($res))
266
      {
267
        $output->writeln(sprintf('<miss_table>%s</>', $tableName));
268
      }
269
      else if (!$table['audit'] && isset($res))
270
      {
271
        $output->writeln(sprintf('<obsolete_table>%s</>', $tableName));
272
      }
273
    }
274
  }
275
276
  //--------------------------------------------------------------------------------------------------------------------
277
  /**
278
   * Computes the difference between data and audit tables.
279
   */
280
  private function getDiff()
281
  {
282
    foreach ($this->dataSchemaTables as $table)
283
    {
284
      if ($this->configMetadata['tables'][$table['table_name']]['audit'])
285
      {
286
        $res = StaticDataLayer::searchInRowSet('table_name', $table['table_name'], $this->auditSchemaTables);
287
        if (isset($res))
288
        {
289
          $dataColumns  = new TableColumnsMetadata(AuditDataLayer::getTableColumns($this->config['database']['data_schema'], $table['table_name']));
290
          $auditColumns = AuditDataLayer::getTableColumns($this->config['database']['audit_schema'], $table['table_name']);
291
          $auditColumns = $this->addNotNull($auditColumns);
292
          $auditColumns = new TableColumnsMetadata($auditColumns);
293
294
          $this->diffColumns[$table['table_name']] = $this->createDiffArray($dataColumns, $auditColumns);
295
        }
296
      }
297
    }
298
  }
299
300
  //--------------------------------------------------------------------------------------------------------------------
301
  /**
302
   * Writes the difference between the audit and data tables to the output.
303
   *
304
   * @param OutputInterface $output The output.
305
   */
306
  private function printDiff($output)
307
  {
308
    $first = true;
309
    if (isset($this->diffColumns))
310
    {
311
      /**
312
       * @var ColumnTypesExtended $columns
313
       */
314
      foreach ($this->diffColumns as $tableName => $columns)
315
      {
316
        $columns = $columns->getTypes();
0 ignored issues
show
Bug introduced by
The method getTypes cannot be called on $columns (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
317
        // Remove matching columns unless the full option is used.
318
        if (!$this->full)
319
        {
320
          /** @var array[] $columns */
321
          $columns = self::removeMatchingColumns($columns);
322
        }
323
324
        if (!empty($columns))
325
        {
326
          // Add an empty line between tables.
327
          if ($first)
328
          {
329
            $first = false;
330
          }
331
          else
332
          {
333
            $output->writeln('');
334
          }
335
336
          // Write table name.
337
          $output->writeln($tableName);
338
339
          // Write table with columns.
340
          $rows = new TableHelper($this->config['database']['data_schema'], $this->config['database']['audit_schema'], $tableName, $this->config['audit_columns'], $this->full);
341
          $rows->appendRows($columns);
342
          $rows->addHighlighting();
343
          $table = new Table($output);
344
          $table->setHeaders(['column', 'data table', 'audit table', 'config'])
345
                ->setRows($rows->getRows());
346
          $table->render();
347
        }
348
      }
349
    }
350
    $this->diffTables($output);
351
  }
352
353
  //--------------------------------------------------------------------------------------------------------------------
354
}
355
356
//----------------------------------------------------------------------------------------------------------------------
357