Completed
Pull Request — master (#36)
by
unknown
05:26
created

Table   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 405
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 81.56%

Importance

Changes 2
Bugs 1 Features 0
Metric Value
wmc 32
c 2
b 1
f 0
lcom 1
cbo 4
dl 0
loc 405
ccs 115
cts 141
cp 0.8156
rs 9.6

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 19 1
A getRandomAlias() 0 4 1
A createMissingAuditTable() 0 7 1
A createTriggers() 0 16 1
A getTableName() 0 4 1
B main() 0 21 5
A createTableTrigger() 0 20 1
A dropTriggers() 0 13 2
A getAlteredColumns() 0 7 1
A getColumnsFromInformationSchema() 0 6 1
A addNewColumns() 0 4 1
A getTableColumnInfo() 0 22 2
A getTableColumnsFromConfig() 0 11 3
A getTriggerName() 0 4 1
A lockTable() 0 4 1
C loggingColumnInfo() 0 40 8
A unlockTables() 0 4 1
1
<?php
2
//----------------------------------------------------------------------------------------------------------------------
3
namespace SetBased\Audit\MySql\Table;
4
5
use SetBased\Audit\MySql\DataLayer;
6
use SetBased\Stratum\Style\StratumStyle;
7
8
//--------------------------------------------------------------------------------------------------------------------
9
/**
10
 * Class for metadata of tables.
11
 */
12
class Table
13
{
14
  //--------------------------------------------------------------------------------------------------------------------
15
  /**
16
   * The unique alias for this data table.
17
   *
18
   * @var string
19
   */
20
  private $alias;
21
22
  /**
23
   * The metadata (additional) audit columns (as stored in the config file).
24
   *
25
   * @var Columns
26
   */
27
  private $auditColumns;
28
29
  /**
30
   * The name of the schema with the audit tables.
31
   *
32
   * @var string
33
   */
34
  private $auditSchemaName;
35
36
  /**
37
   * The name of the schema with the data tables.
38
   *
39
   * @var string
40
   */
41
  private $dataSchemaName;
42
43
  /**
44
   * The metadata of the columns of the data table as stored in the config file.
45
   *
46
   * @var Columns
47
   */
48
  private $dataTableColumnsConfig;
49
50
  /**
51
   * The metadata of the columns of the data table retrieved from information_schema.
52
   *
53
   * @var Columns
54
   */
55
  private $dataTableColumnsDatabase;
56
57
  /**
58
   * The output decorator
59
   *
60
   * @var StratumStyle
61
   */
62
  private $io;
63
64
  /**
65
   * The skip variable for triggers.
66
   *
67
   * @var string
68
   */
69
  private $skipVariable;
70
71
  /**
72
   * The name of this data table.
73
   *
74
   * @var string
75
   */
76
  private $tableName;
77
78
  //--------------------------------------------------------------------------------------------------------------------
79
  /**
80
   * Object constructor.
81
   *
82
   * @param StratumStyle $io                    The output for log messages.
83
   * @param string       $tableName             The table name.
84
   * @param string       $dataSchema            The name of the schema with data tables.
85
   * @param string       $auditSchema           The name of the schema with audit tables.
86
   * @param array[]      $configColumnsMetadata The columns of the data table as stored in the config file.
87
   * @param array[]      $auditColumnsMetadata  The columns of the audit table as stored in the config file.
88
   * @param string       $alias                 An unique alias for this table.
89
   * @param string       $skipVariable          The skip variable
90
   */
91 2
  public function __construct($io,
92
                              $tableName,
93
                              $dataSchema,
94
                              $auditSchema,
95
                              $configColumnsMetadata,
96
                              $auditColumnsMetadata,
97
                              $alias,
98
                              $skipVariable)
99
  {
100 2
    $this->io                       = $io;
101 2
    $this->tableName                = $tableName;
102 2
    $this->dataTableColumnsConfig   = new Columns($configColumnsMetadata);
103 2
    $this->dataSchemaName           = $dataSchema;
104 2
    $this->auditSchemaName          = $auditSchema;
105 2
    $this->dataTableColumnsDatabase = new Columns($this->getColumnsFromInformationSchema());
106 2
    $this->auditColumns             = new Columns($auditColumnsMetadata);
107 2
    $this->alias                    = $alias;
108 2
    $this->skipVariable             = $skipVariable;
109 2
  }
110
111
  //--------------------------------------------------------------------------------------------------------------------
112
  /**
113
   * Returns a random alias for this table.
114
   *
115
   * @return string
116
   */
117 1
  public static function getRandomAlias()
118
  {
119 1
    return uniqid();
120
  }
121
122
  //--------------------------------------------------------------------------------------------------------------------
123
  /**
124
   * Creates missing audit table for this table.
125
   */
126 2
  public function createMissingAuditTable()
127
  {
128 2
    $this->io->logInfo('Creating audit table <dbo>%s.%s<dbo>', $this->auditSchemaName, $this->tableName);
129
130 2
    $columns = Columns::combine($this->auditColumns, $this->dataTableColumnsDatabase);
131 2
    DataLayer::createAuditTable($this->dataSchemaName, $this->auditSchemaName, $this->tableName, $columns);
132 2
  }
133
134
  //--------------------------------------------------------------------------------------------------------------------
135
  /**
136
   * Creates audit triggers on this table.
137
   *
138
   * @param string[] $additionalSql Additional SQL statements to be include in triggers.
139
   */
140 2
  public function createTriggers($additionalSql)
141
  {
142
    // Lock the table to prevent insert, updates, or deletes between dropping and creating triggers.
143 2
    $this->lockTable($this->tableName);
144
145
    // Drop all triggers, if any.
146 2
    $this->dropTriggers();
147
148
    // Create or recreate the audit triggers.
149 2
    $this->createTableTrigger('INSERT', $this->skipVariable, $additionalSql);
150 2
    $this->createTableTrigger('UPDATE', $this->skipVariable, $additionalSql);
151 2
    $this->createTableTrigger('DELETE', $this->skipVariable, $additionalSql);
152
153
    // Insert, updates, and deletes are no audited again. So, release lock on the table.
154 2
    $this->unlockTables();
155 2
  }
156
157
  //--------------------------------------------------------------------------------------------------------------------
158
  /**
159
   * Returns the name of this table.
160
   *
161
   * @return string
162
   */
163 2
  public function getTableName()
164
  {
165 2
    return $this->tableName;
166
  }
167
168
  //--------------------------------------------------------------------------------------------------------------------
169
  /**
170
   * Main function for work with table.
171
   *
172
   * @param string[] $additionalSql Additional SQL statements to be include in triggers.
173
   *
174
   * @return \array[] Columns for config file
0 ignored issues
show
Documentation introduced by
Should the return type not be array<string,Columns>?

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...
175
   */
176 2
  public function main($additionalSql)
177
  {
178 2
    $comparedColumns = null;
179 2
    if (isset($this->dataTableColumnsConfig))
180 2
    {
181 2
      $comparedColumns = $this->getTableColumnInfo();
182 2
    }
183
184 2
    $newColumns      = $comparedColumns['new_columns']->getColumns();
185 2
    $obsoleteColumns = $comparedColumns['obsolete_columns']->getColumns();
186 2
    if (empty($newColumns) && empty($obsoleteColumns))
187 2
    {
188 2
      $alteredColumns = $comparedColumns['altered_columns']->getColumns();
189 2
      if (empty($alteredColumns))
190 2
      {
191 2
        $this->createTriggers($additionalSql);
192 2
      }
193 2
    }
194
195 2
    return $comparedColumns;
196
  }
197
198
  //--------------------------------------------------------------------------------------------------------------------
199
  /**
200
   * Adds new columns to audit table.
201
   *
202
   * @param Columns $columns Columns array
203
   */
204
  private function addNewColumns($columns)
205
  {
206
    DataLayer::addNewColumns($this->auditSchemaName, $this->tableName, $columns);
207
  }
208
209
  //--------------------------------------------------------------------------------------------------------------------
210
  /**
211
   * Creates a triggers for this table.
212
   *
213
   * @param string      $action      The trigger action (INSERT, DELETE, or UPDATE).
214
   * @param string|null $skipVariable
215
   * @param string[]    $additionSql The additional SQL statements to be included in triggers.
216
   */
217 2
  private function createTableTrigger($action, $skipVariable, $additionSql)
218
  {
219 2
    $triggerName = $this->getTriggerName($action);
220
221 2
    $this->io->logVerbose('Creating trigger <dbo>%s.%s</dbo> on table <dbo>%s.%s</dbo>',
222 2
                          $this->dataSchemaName,
223 2
                          $triggerName,
224 2
                          $this->dataSchemaName,
225 2
                          $this->tableName);
226
227 2
    DataLayer::createAuditTrigger($this->dataSchemaName,
228 2
                                  $this->auditSchemaName,
229 2
                                  $this->tableName,
230 2
                                  $triggerName,
231 2
                                  $action,
232 2
                                  $this->auditColumns,
233 2
                                  $this->dataTableColumnsDatabase,
234 2
                                  $skipVariable,
235 2
                                  $additionSql);
236 2
  }
237
238
  //--------------------------------------------------------------------------------------------------------------------
239
  /**
240
   * Drops all triggers from this table.
241
   */
242 2
  private function dropTriggers()
243
  {
244 2
    $triggers = DataLayer::getTableTriggers($this->dataSchemaName, $this->tableName);
245 2
    foreach ($triggers as $trigger)
246
    {
247
      $this->io->logVerbose('Dropping trigger <dbo>%s</dbo> on <dbo>%s.%s</dbo>',
248
                            $trigger['trigger_name'],
249
                            $this->dataSchemaName,
250
                            $this->tableName);
251
252
      DataLayer::dropTrigger($this->dataSchemaName, $trigger['trigger_name']);
253 2
    }
254 2
  }
255
256
  //--------------------------------------------------------------------------------------------------------------------
257
  /**
258
   * Compares columns types from table in data_schema with columns in config file.
259
   *
260
   * @return Columns
261
   */
262 2
  private function getAlteredColumns()
263
  {
264 2
    $alteredColumnsTypes = Columns::differentColumnTypes($this->dataTableColumnsDatabase,
265 2
                                                         $this->dataTableColumnsConfig);
266
267 2
    return $alteredColumnsTypes;
268
  }
269
270
  //--------------------------------------------------------------------------------------------------------------------
271
  /**
272
   * Selects and returns the metadata of the columns of this table from information_schema.
273
   *
274
   * @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...
275
   */
276 2
  private function getColumnsFromInformationSchema()
277
  {
278 2
    $result = DataLayer::getTableColumns($this->dataSchemaName, $this->tableName);
279
280 2
    return $result;
281
  }
282
283
  //--------------------------------------------------------------------------------------------------------------------
284
  /**
285
   * Compare columns from table in data_schema with columns in config file.
286
   *
287
   * @return \array[]
0 ignored issues
show
Documentation introduced by
Should the return type not be array<string,Columns>?

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