Completed
Push — master ( 8e94f8...744b49 )
by P.R.
04:26
created

AuditTable::getTableColumnsFromConfig()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.0261

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 11
ccs 6
cts 7
cp 0.8571
rs 9.4285
cc 3
eloc 6
nc 2
nop 2
crap 3.0261
1
<?php
2
//----------------------------------------------------------------------------------------------------------------------
3
namespace SetBased\Audit\MySql;
4
5
use SetBased\Audit\MySql\Metadata\AlterColumnMetadata;
6
use SetBased\Audit\MySql\Metadata\ColumnMetadata;
7
use SetBased\Audit\MySql\Metadata\TableColumnsMetadata;
8
use SetBased\Audit\MySql\Metadata\TableMetadata;
9
use SetBased\Stratum\Style\StratumStyle;
10
11
//--------------------------------------------------------------------------------------------------------------------
12
/**
13
 * Class for work on table with all column like audit,data and config.
14
 */
15
class AuditTable
16
{
17
  //--------------------------------------------------------------------------------------------------------------------
18
  /**
19
   * The unique alias for this data table.
20
   *
21
   * @var string
22
   */
23
  private $alias;
24
25
  /**
26
   * The metadata (additional) audit columns (as stored in the config file).
27
   *
28
   * @var TableColumnsMetadata
29
   */
30
  private $auditColumns;
31
32
  /**
33
   * The name of the schema with the audit tables.
34
   *
35
   * @var string
36
   */
37
  private $auditSchemaName;
38
39
  /**
40
   * The table metadata.
41
   *
42
   * @var TableMetadata
43
   */
44
  private $configTable;
45
46
  /**
47
   * The metadata of the columns of the data table retrieved from information_schema.
48
   *
49
   * @var TableColumnsMetadata
50
   */
51
  private $dataTableColumnsDatabase;
52
53
  /**
54
   * The output decorator
55
   *
56
   * @var StratumStyle
57
   */
58
  private $io;
59
60
  /**
61
   * The skip variable for triggers.
62
   *
63
   * @var string
64
   */
65
  private $skipVariable;
66
67
  //--------------------------------------------------------------------------------------------------------------------
68
  /**
69
   * Object constructor.
70
   *
71
   * @param StratumStyle         $io                   The output for log messages.
72
   * @param TableMetadata        $configTable          The table meta data.
73
   * @param string               $auditSchema          The name of the schema with audit tables.
74
   * @param TableColumnsMetadata $auditColumnsMetadata The columns of the audit table as stored in the config file.
75
   * @param string               $alias                An unique alias for this table.
76
   * @param string               $skipVariable         The skip variable
77
   */
78 8
  public function __construct($io,
79
                              $configTable,
80
                              $auditSchema,
81
                              $auditColumnsMetadata,
82
                              $alias,
83
                              $skipVariable)
84
  {
85 8
    $this->io                       = $io;
86 8
    $this->configTable              = $configTable;
87 8
    $this->auditSchemaName          = $auditSchema;
88 8
    $this->dataTableColumnsDatabase = new TableColumnsMetadata($this->getColumnsFromInformationSchema());
89 8
    $this->auditColumns             = $auditColumnsMetadata;
90 8
    $this->alias                    = $alias;
91 8
    $this->skipVariable             = $skipVariable;
92 8
  }
93
94
  //--------------------------------------------------------------------------------------------------------------------
95
  /**
96
   * Returns a random alias for this table.
97
   *
98
   * @return string
99
   */
100
  public static function getRandomAlias()
101
  {
102
    return uniqid();
103
  }
104
105
  //--------------------------------------------------------------------------------------------------------------------
106
  /**
107
   * Creates missing audit table for this table.
108
   */
109 5
  public function createMissingAuditTable()
110
  {
111 5
    $this->io->logInfo('Creating audit table <dbo>%s.%s<dbo>',
112 5
                       $this->auditSchemaName,
113 5
                       $this->configTable->getTableName());
114
115
    // In the audit table all columns from the data table must be nullable.
116 5
    $dataTableColumnsDatabase = clone($this->dataTableColumnsDatabase);
117 5
    $dataTableColumnsDatabase->makeNullable();
118
119 5
    $columns = TableColumnsMetadata::combine($this->auditColumns, $dataTableColumnsDatabase);
120 5
    AuditDataLayer::createAuditTable($this->configTable->getSchemaName(),
121 5
                                     $this->auditSchemaName,
122 5
                                     $this->configTable->getTableName(),
123 5
                                     $columns);
124 5
  }
125
126
  //--------------------------------------------------------------------------------------------------------------------
127
  /**
128
   * Creates audit triggers on this table.
129
   *
130
   * @param string[] $additionalSql Additional SQL statements to be include in triggers.
131
   */
132 8
  public function createTriggers($additionalSql)
133
  {
134
    // Lock the table to prevent insert, updates, or deletes between dropping and creating triggers.
135 8
    $this->lockTable($this->configTable->getTableName());
136
137
    // Drop all triggers, if any.
138 8
    $this->dropTriggers();
139
140
    // Create or recreate the audit triggers.
141 8
    $this->createTableTrigger('INSERT', $this->skipVariable, $additionalSql);
142 8
    $this->createTableTrigger('UPDATE', $this->skipVariable, $additionalSql);
143 8
    $this->createTableTrigger('DELETE', $this->skipVariable, $additionalSql);
144
145
    // Insert, updates, and deletes are no audited again. So, release lock on the table.
146 8
    $this->unlockTables();
147 8
  }
148
149
  //--------------------------------------------------------------------------------------------------------------------
150
  /**
151
   * Returns the table name.
152
   *
153
   * @return string
154
   */
155 8
  public function getTableName()
156
  {
157 8
    return $this->configTable->getTableName();
158
  }
159
160
  //--------------------------------------------------------------------------------------------------------------------
161
  /**
162
   * Main function for work with table.
163
   *
164
   * @param string[] $additionalSql Additional SQL statements to be include in triggers.
165
   *
166
   * @return TableColumnsMetadata[]
167
   */
168 8
  public function main($additionalSql)
169
  {
170 8
    $comparedColumns = [];
171 8
    $columns         = $this->configTable->getColumns();
172 8
    if (isset($columns))
173 8
    {
174 8
      $comparedColumns = $this->getTableColumnInfo();
175 8
    }
176
177 8
    $newColumns      = $comparedColumns['new_columns']->getColumns();
178 8
    $obsoleteColumns = $comparedColumns['obsolete_columns']->getColumns();
179 8
    if (empty($newColumns) && empty($obsoleteColumns))
180 8
    {
181 8
      $alteredColumns = $comparedColumns['altered_columns']->getColumns();
182 8
      if (empty($alteredColumns))
183 8
      {
184 8
        $this->createTriggers($additionalSql);
185 8
      }
186 8
    }
187
188 8
    return $comparedColumns;
189
  }
190
191
  //--------------------------------------------------------------------------------------------------------------------
192
  /**
193
   * Adds new columns to audit table.
194
   *
195
   * @param TableColumnsMetadata $columns TableColumnsMetadata array
196
   */
197 8
  private function addNewColumns($columns)
198
  {
199 8
    AuditDataLayer::addNewColumns($this->auditSchemaName, $this->configTable->getTableName(), $columns);
200 8
  }
201
202
  //--------------------------------------------------------------------------------------------------------------------
203
  /**
204
   * Returns metadata of new table columns that can be used in a 'alter table .. add column' statement.
205
   *
206
   * @param TableColumnsMetadata $newColumns The metadata new table columns.
207
   *
208
   * @return TableColumnsMetadata
209
   */
210 8
  private function alterNewColumns($newColumns)
211
  {
212 8
    $alterNewColumns = new TableColumnsMetadata();
213 8
    foreach ($newColumns->getColumns() as $newColumn)
214
    {
215
      $properties          = $newColumn->getProperties();
216
      $properties['after'] = $this->dataTableColumnsDatabase->getPreviousColumn($properties['column_name']);
217
218
      $alterNewColumns->appendTableColumn(new AlterColumnMetadata($properties));
0 ignored issues
show
Documentation introduced by
$properties is of type array<string,integer|nul...after":"integer|null"}>, but the function expects a array<integer,array>.

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...
219 8
    }
220
221 8
    return $alterNewColumns;
222
  }
223
224
  //--------------------------------------------------------------------------------------------------------------------
225
  /**
226
   * Creates a triggers for this table.
227
   *
228
   * @param string      $action      The trigger action (INSERT, DELETE, or UPDATE).
229
   * @param string|null $skipVariable
230
   * @param string[]    $additionSql The additional SQL statements to be included in triggers.
231
   */
232 8
  private function createTableTrigger($action, $skipVariable, $additionSql)
233
  {
234 8
    $triggerName = $this->getTriggerName($action);
235
236 8
    $this->io->logVerbose('Creating trigger <dbo>%s.%s</dbo> on table <dbo>%s.%s</dbo>',
237 8
                          $this->configTable->getSchemaName(),
238 8
                          $triggerName,
239 8
                          $this->configTable->getSchemaName(),
240 8
                          $this->configTable->getTableName());
241
242 8
    AuditDataLayer::createAuditTrigger($this->configTable->getSchemaName(),
243 8
                                       $this->auditSchemaName,
244 8
                                       $this->configTable->getTableName(),
245 8
                                       $triggerName,
246 8
                                       $action,
247 8
                                       $this->auditColumns,
248 8
                                       $this->dataTableColumnsDatabase,
249 8
                                       $skipVariable,
250 8
                                       $additionSql);
251 8
  }
252
253
  //--------------------------------------------------------------------------------------------------------------------
254
  /**
255
   * Drops all triggers from this table.
256
   */
257 8
  private function dropTriggers()
258
  {
259 8
    $triggers = AuditDataLayer::getTableTriggers($this->configTable->getSchemaName(), $this->configTable->getTableName());
260 8
    foreach ($triggers as $trigger)
261
    {
262 3
      $this->io->logVerbose('Dropping trigger <dbo>%s</dbo> on <dbo>%s.%s</dbo>',
263 3
                            $trigger['trigger_name'],
264 3
                            $this->configTable->getSchemaName(),
265 3
                            $this->configTable->getTableName());
266
267 3
      AuditDataLayer::dropTrigger($this->configTable->getSchemaName(), $trigger['trigger_name']);
268 8
    }
269 8
  }
270
271
  //--------------------------------------------------------------------------------------------------------------------
272
  /**
273
   * Compares columns types from table in data_schema with columns in config file.
274
   *
275
   * @return TableColumnsMetadata
276
   */
277 8
  private function getAlteredColumns()
278
  {
279 8
    $alteredColumnsTypes = TableColumnsMetadata::differentColumnTypes($this->dataTableColumnsDatabase,
280 8
                                                                      $this->configTable->getColumns());
281
282 8
    return $alteredColumnsTypes;
283
  }
284
285
  //--------------------------------------------------------------------------------------------------------------------
286
  /**
287
   * Selects and returns the metadata of the columns of this table from information_schema.
288
   *
289
   * @return array[]
0 ignored issues
show
Documentation introduced by
Should the return type not be \array[]?

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...
290
   */
291 8
  private function getColumnsFromInformationSchema()
292
  {
293 8
    $result = AuditDataLayer::getTableColumns($this->configTable->getSchemaName(), $this->configTable->getTableName());
294
295 8
    return $result;
296
  }
297
298
  //--------------------------------------------------------------------------------------------------------------------
299
  /**
300
   * Compare columns from table in data_schema with columns in config file.
301
   *
302
   * @return \array[]
0 ignored issues
show
Documentation introduced by
Should the return type not be array<string,TableColumnsMetadata>?

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...
303
   */
304 8
  private function getTableColumnInfo()
305
  {
306 8
    $columnActual  = new TableColumnsMetadata(AuditDataLayer::getTableColumns($this->auditSchemaName,
307 8
                                                                              $this->configTable->getTableName()));
308 8
    $columnsConfig = TableColumnsMetadata::combine($this->auditColumns, $this->configTable->getColumns());
309 8
    $columnsTarget = TableColumnsMetadata::combine($this->auditColumns, $this->dataTableColumnsDatabase);
310
311 8
    $newColumns      = TableColumnsMetadata::notInOtherSet($columnsTarget, $columnActual);
312 8
    $obsoleteColumns = TableColumnsMetadata::notInOtherSet($columnsConfig, $columnsTarget);
313 8
    $alteredColumns  = $this->getAlteredColumns();
314
315 8
    $this->loggingColumnInfo($newColumns, $obsoleteColumns, $alteredColumns);
316
317 8
    $alterNewColumns = $this->alterNewColumns($newColumns);
318 8
    $this->addNewColumns($alterNewColumns);
319
320 8
    return ['full_columns'     => $this->getTableColumnsFromConfig($newColumns, $obsoleteColumns),
321 8
            'new_columns'      => $newColumns,
322 8
            'obsolete_columns' => $obsoleteColumns,
323 8
            'altered_columns'  => $alteredColumns];
324
  }
325
326
  //--------------------------------------------------------------------------------------------------------------------
327
  /**
328
   * Check for know what columns array returns.
329
   *
330
   * @param TableColumnsMetadata $newColumns
331
   * @param TableColumnsMetadata $obsoleteColumns
332
   *
333
   * @return TableColumnsMetadata
334
   */
335 8
  private function getTableColumnsFromConfig($newColumns, $obsoleteColumns)
336
  {
337 8
    $new      = $newColumns->getColumns();
338 8
    $obsolete = $obsoleteColumns->getColumns();
339 8
    if (!empty($new) && !empty($obsolete))
340 8
    {
341
      return $this->configTable->getColumns();
342
    }
343
344 8
    return $this->dataTableColumnsDatabase;
345
  }
346
347
  //--------------------------------------------------------------------------------------------------------------------
348
  /**
349
   * Create and return trigger name.
350
   *
351
   * @param string $action Trigger on action (Insert, Update, Delete)
352
   *
353
   * @return string
354
   */
355 8
  private function getTriggerName($action)
356
  {
357 8
    return strtolower(sprintf('trg_%s_%s', $this->alias, $action));
358
  }
359
360
  //--------------------------------------------------------------------------------------------------------------------
361
  /**
362
   * Lock the table to prevent insert, updates, or deletes between dropping and creating triggers.
363
   *
364
   * @param string $tableName Name of table
365
   */
366 8
  private function lockTable($tableName)
367
  {
368 8
    AuditDataLayer::lockTable($tableName);
369 8
  }
370
371
  //--------------------------------------------------------------------------------------------------------------------
372
  /**
373
   * Logging new and obsolete columns.
374
   *
375
   * @param TableColumnsMetadata $newColumns
376
   * @param TableColumnsMetadata $obsoleteColumns
377
   * @param TableColumnsMetadata $alteredColumns
378
   */
379 8
  private function loggingColumnInfo($newColumns, $obsoleteColumns, $alteredColumns)
380
  {
381 8
    $new      = $newColumns->getColumns();
382 8
    $obsolete = $obsoleteColumns->getColumns();
383 8
    if (!empty($new) && !empty($obsolete))
384 8
    {
385
      $this->io->logInfo('Found both new and obsolete columns for table %s', $this->configTable->getTableName());
386
      $this->io->logInfo('No action taken');
387
388
      /** @var ColumnMetadata $column */
389
      foreach ($newColumns->getColumns() as $column)
390
      {
391
        $this->io->logInfo('New column %s', $column->getProperty('column_name'));
392
      }
393
      foreach ($obsoleteColumns->getColumns() as $column)
394
      {
395
        $this->io->logInfo('Obsolete column %s', $column->getProperty('column_name'));
396
      }
397
    }
398
399
    /** @var ColumnMetadata $column */
400 8
    foreach ($obsoleteColumns->getColumns() as $column)
401
    {
402
      $this->io->logInfo('Obsolete column %s.%s', $this->configTable->getTableName(), $column->getProperty('column_name'));
403 8
    }
404
405
    /** @var ColumnMetadata $column */
406 8
    foreach ($newColumns->getColumns() as $column)
407
    {
408
      $this->io->logInfo('New column %s.%s', $this->configTable->getTableName(), $column->getProperty('column_name'));
409 8
    }
410
411 8
    foreach ($alteredColumns->getColumns() as $column)
412
    {
413
      $this->io->logInfo('Type of <dbo>%s.%s</dbo> has been altered to <dbo>%s</dbo>',
414
                         $this->configTable->getTableName(),
415
                         $column['column_name'],
416
                         $column['column_type']);
417 8
    }
418 8
  }
419
420
  //--------------------------------------------------------------------------------------------------------------------
421
  /**
422
   * Releases all table locks.
423
   */
424 8
  private function unlockTables()
425
  {
426 8
    AuditDataLayer::unlockTables();
427 8
  }
428
429
  //--------------------------------------------------------------------------------------------------------------------
430
}
431
432
//----------------------------------------------------------------------------------------------------------------------
433