Completed
Push — master ( b08ae0...b7efbe )
by P.R.
04:19
created

AuditTable::main()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 3

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 0
loc 20
ccs 12
cts 12
cp 1
rs 9.4285
cc 3
eloc 11
nc 2
nop 1
crap 3
1
<?php
2
//----------------------------------------------------------------------------------------------------------------------
3
namespace SetBased\Audit\MySql;
4
5
use SetBased\Audit\MySql\Metadata\AlterColumnMetadata;
6
use SetBased\Audit\MySql\Metadata\TableColumnsMetadata;
7
use SetBased\Audit\MySql\Metadata\TableMetadata;
8
use SetBased\Stratum\Style\StratumStyle;
9
10
//--------------------------------------------------------------------------------------------------------------------
11
/**
12
 * Class for work on table with all column like audit,data and config.
13
 */
14
class AuditTable
15
{
16
  //--------------------------------------------------------------------------------------------------------------------
17
  /**
18
   * The unique alias for this data table.
19
   *
20
   * @var string
21
   */
22
  private $alias;
23
24
  /**
25
   * The metadata (additional) audit columns (as stored in the config file).
26
   *
27
   * @var TableColumnsMetadata
28
   */
29
  private $auditColumns;
30
31
  /**
32
   * The name of the schema with the audit tables.
33
   *
34
   * @var string
35
   */
36
  private $auditSchemaName;
37
38
  /**
39
   * The table metadata.
40
   *
41
   * @var TableMetadata
42
   */
43
  private $configTable;
44
45
  /**
46
   * The metadata of the columns of the data table retrieved from information_schema.
47
   *
48
   * @var TableColumnsMetadata
49
   */
50
  private $dataTableColumnsDatabase;
51
52
  /**
53
   * The output decorator
54
   *
55
   * @var StratumStyle
56
   */
57
  private $io;
58
59
  /**
60
   * The skip variable for triggers.
61
   *
62
   * @var string
63
   */
64
  private $skipVariable;
65
66
  //--------------------------------------------------------------------------------------------------------------------
67
  /**
68
   * Object constructor.
69
   *
70
   * @param StratumStyle         $io                   The output for log messages.
71
   * @param TableMetadata        $configTable          The table meta data.
72
   * @param string               $auditSchema          The name of the schema with audit tables.
73
   * @param TableColumnsMetadata $auditColumnsMetadata The columns of the audit table as stored in the config file.
74
   * @param string               $alias                An unique alias for this table.
75
   * @param string               $skipVariable         The skip variable
76
   */
77 12
  public function __construct($io,
78
                              $configTable,
79
                              $auditSchema,
80
                              $auditColumnsMetadata,
81
                              $alias,
82 1
                              $skipVariable)
83
  {
84 12
    $this->io                       = $io;
85 12
    $this->configTable              = $configTable;
86 12
    $this->auditSchemaName          = $auditSchema;
87 12
    $this->dataTableColumnsDatabase = new TableColumnsMetadata($this->getColumnsFromInformationSchema());
88 12
    $this->auditColumns             = $auditColumnsMetadata;
89 12
    $this->alias                    = $alias;
90 12
    $this->skipVariable             = $skipVariable;
91 12
  }
92
93
  //--------------------------------------------------------------------------------------------------------------------
94
  /**
95
   * Returns a random alias for this table.
96
   *
97
   * @return string
98
   */
99
  public static function getRandomAlias()
100
  {
101
    return uniqid();
102
  }
103
104
  //--------------------------------------------------------------------------------------------------------------------
105
  /**
106
   * Creates an audit table for this table.
107
   */
108 9
  public function createAuditTable()
109
  {
110 9
    $this->io->logInfo('Creating audit table <dbo>%s.%s<dbo>',
111 9
                       $this->auditSchemaName,
112 9
                       $this->configTable->getTableName());
113
114
    // In the audit table all columns from the data table must be nullable.
115 9
    $dataTableColumnsDatabase = clone($this->dataTableColumnsDatabase);
116 9
    $dataTableColumnsDatabase->makeNullable();
117
118 9
    $columns = TableColumnsMetadata::combine($this->auditColumns, $dataTableColumnsDatabase);
119 9
    AuditDataLayer::createAuditTable($this->configTable->getSchemaName(),
120 9
                                     $this->auditSchemaName,
121 9
                                     $this->configTable->getTableName(),
122 9
                                     $columns);
123 9
  }
124
125
  //--------------------------------------------------------------------------------------------------------------------
126
  /**
127
   * Creates audit triggers on this table.
128
   *
129
   * @param string[] $additionalSql Additional SQL statements to be include in triggers.
130
   */
131 12
  public function createTriggers($additionalSql)
132
  {
133
    // Lock the table to prevent insert, updates, or deletes between dropping and creating triggers.
134 12
    $this->lockTable($this->configTable->getTableName());
135
136
    // Drop all triggers, if any.
137 12
    $this->dropTriggers();
138
139
    // Create or recreate the audit triggers.
140 12
    $this->createTableTrigger('INSERT', $this->skipVariable, $additionalSql);
141 12
    $this->createTableTrigger('UPDATE', $this->skipVariable, $additionalSql);
142 12
    $this->createTableTrigger('DELETE', $this->skipVariable, $additionalSql);
143
144
    // Insert, updates, and deletes are no audited again. So, release lock on the table.
145 12
    $this->unlockTables();
146 12
  }
147
148
  //--------------------------------------------------------------------------------------------------------------------
149
  /**
150
   * Returns the table name.
151
   *
152
   * @return string
153
   */
154 1
  public function getTableName()
155
  {
156 1
    return $this->configTable->getTableName();
157
  }
158
159
  //--------------------------------------------------------------------------------------------------------------------
160
  /**
161
   * Main function for work with table.
162
   *
163
   * @param string[] $additionalSql Additional SQL statements to be include in triggers.
164
   *
165
   * @return bool
166
   */
167 12
  public function main($additionalSql)
168
  {
169 12
    $comparedColumns = $this->getTableColumnInfo();
170 12
    $newColumns      = $comparedColumns['new_columns'];
171 12
    $obsoleteColumns = $comparedColumns['obsolete_columns'];
172 12
    $alteredColumns  = $comparedColumns['altered_columns'];
173
174 12
    if ($newColumns->getNumberOfColumns()==0 || $obsoleteColumns->getNumberOfColumns()==0)
175 12
    {
176 12
      $this->addNewColumns($newColumns);
177
178 12
      $this->createTriggers($additionalSql);
179
180 12
      return ($alteredColumns->getNumberOfColumns()==0);
181
    }
182
183 1
    $this->io->warning(sprintf('Skipping table %s', $this->getTableName()));
184
185 1
    return false;
186
  }
187
188
  //--------------------------------------------------------------------------------------------------------------------
189
  /**
190
   * Adds new columns to audit table.
191
   *
192
   * @param TableColumnsMetadata $columns TableColumnsMetadata array
193
   */
194 12
  private function addNewColumns($columns)
195
  {
196
    // Return immediately if there are no columns to add.
197 12
    if ($columns->getNumberOfColumns()==0) return;
198
199 1
    $alterColumns = $this->alterNewColumns($columns);
200
201 1
    AuditDataLayer::addNewColumns($this->auditSchemaName, $this->configTable->getTableName(), $alterColumns);
202 1
  }
203
204
  //--------------------------------------------------------------------------------------------------------------------
205
  /**
206
   * Returns metadata of new table columns that can be used in a 'alter table .. add column' statement.
207
   *
208
   * @param TableColumnsMetadata $newColumns The metadata new table columns.
209
   *
210
   * @return TableColumnsMetadata
211
   */
212 1
  private function alterNewColumns($newColumns)
213
  {
214 1
    $alterNewColumns = new TableColumnsMetadata();
215 1
    foreach ($newColumns->getColumns() as $newColumn)
216
    {
217 1
      $properties          = $newColumn->getProperties();
218 1
      $properties['after'] = $this->dataTableColumnsDatabase->getPreviousColumn($properties['column_name']);
219
220 1
      $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...
221 1
    }
222
223 1
    return $alterNewColumns;
224
  }
225
226
  //--------------------------------------------------------------------------------------------------------------------
227
  /**
228
   * Creates a triggers for this table.
229
   *
230
   * @param string      $action      The trigger action (INSERT, DELETE, or UPDATE).
231
   * @param string|null $skipVariable
232
   * @param string[]    $additionSql The additional SQL statements to be included in triggers.
233
   */
234 12
  private function createTableTrigger($action, $skipVariable, $additionSql)
235
  {
236 12
    $triggerName = $this->getTriggerName($action);
237
238 12
    $this->io->logVerbose('Creating trigger <dbo>%s.%s</dbo> on table <dbo>%s.%s</dbo>',
239 12
                          $this->configTable->getSchemaName(),
240 12
                          $triggerName,
241 12
                          $this->configTable->getSchemaName(),
242 12
                          $this->configTable->getTableName());
243
244 12
    AuditDataLayer::createAuditTrigger($this->configTable->getSchemaName(),
245 12
                                       $this->auditSchemaName,
246 12
                                       $this->configTable->getTableName(),
247 12
                                       $triggerName,
248 12
                                       $action,
249 12
                                       $this->auditColumns,
250 12
                                       $this->dataTableColumnsDatabase,
251 12
                                       $skipVariable,
252 12
                                       $additionSql);
253 12
  }
254
255
  //--------------------------------------------------------------------------------------------------------------------
256
  /**
257
   * Drops all triggers from this table.
258
   */
259 12
  private function dropTriggers()
260
  {
261 12
    $triggers = AuditDataLayer::getTableTriggers($this->configTable->getSchemaName(), $this->configTable->getTableName());
262 12
    foreach ($triggers as $trigger)
263
    {
264 6
      $this->io->logVerbose('Dropping trigger <dbo>%s</dbo> on <dbo>%s.%s</dbo>',
265 6
                            $trigger['trigger_name'],
266 6
                            $this->configTable->getSchemaName(),
267 6
                            $this->configTable->getTableName());
268
269 6
      AuditDataLayer::dropTrigger($this->configTable->getSchemaName(), $trigger['trigger_name']);
270 12
    }
271 12
  }
272
273
  //--------------------------------------------------------------------------------------------------------------------
274
  /**
275
   * Compares columns types from table in data_schema with columns in config file.
276
   *
277
   * @return TableColumnsMetadata
278
   */
279 12
  private function getAlteredColumns()
280
  {
281 12
    if ($this->configTable->getColumns()->getNumberOfColumns())
282 12
    {
283 1
      $alteredColumnsTypes = TableColumnsMetadata::differentColumnTypes($this->dataTableColumnsDatabase,
284 1
                                                                        $this->configTable->getColumns(),
285 1
                                                                        ['is_nullable']);
286 1
    }
287
    else
288
    {
289 11
      $alteredColumnsTypes = new TableColumnsMetadata();
290
    }
291
292 12
    return $alteredColumnsTypes;
293
  }
294
295
  //--------------------------------------------------------------------------------------------------------------------
296
  /**
297
   * Selects and returns the metadata of the columns of this table from information_schema.
298
   *
299
   * @return \array[]
300
   */
301 12
  private function getColumnsFromInformationSchema()
302
  {
303 12
    $result = AuditDataLayer::getTableColumns($this->configTable->getSchemaName(), $this->configTable->getTableName());
304
305 12
    return $result;
306
  }
307
308
  //--------------------------------------------------------------------------------------------------------------------
309
  /**
310
   * Compare columns from table in data_schema with columns in config file.
311
   *
312
   * @return array<string,TableColumnsMetadata>
313
   */
314 12
  private function getTableColumnInfo()
315
  {
316 12
    $columnActual  = new TableColumnsMetadata(AuditDataLayer::getTableColumns($this->auditSchemaName,
317 12
                                                                              $this->configTable->getTableName()));
318 12
    $columnsConfig = TableColumnsMetadata::combine($this->auditColumns, $this->configTable->getColumns());
319 12
    $columnsTarget = TableColumnsMetadata::combine($this->auditColumns, $this->dataTableColumnsDatabase);
320
321 12
    $newColumns      = TableColumnsMetadata::notInOtherSet($columnsTarget, $columnActual);
322 12
    $obsoleteColumns = TableColumnsMetadata::notInOtherSet($columnsConfig, $columnsTarget);
323 12
    $alteredColumns  = $this->getAlteredColumns();
324
325 12
    $this->logColumnInfo($newColumns, $obsoleteColumns, $alteredColumns);
326
327 12
    return ['new_columns'      => $newColumns,
328 12
            'obsolete_columns' => $obsoleteColumns,
329 12
            'altered_columns'  => $alteredColumns];
330
  }
331
332
  //--------------------------------------------------------------------------------------------------------------------
333
  /**
334
   * Returns the trigger name for a trigger action.
335
   *
336
   * @param string $action Trigger on action (Insert, Update, Delete)
337
   *
338
   * @return string
339
   */
340 12
  private function getTriggerName($action)
341
  {
342 12
    return strtolower(sprintf('trg_%s_%s', $this->alias, $action));
343
  }
344
345
  //--------------------------------------------------------------------------------------------------------------------
346
  /**
347
   * Lock the table to prevent insert, updates, or deletes between dropping and creating triggers.
348
   *
349
   * @param string $tableName Name of table
350
   */
351 12
  private function lockTable($tableName)
352
  {
353 12
    AuditDataLayer::lockTable($tableName);
354 12
  }
355
356
  //--------------------------------------------------------------------------------------------------------------------
357
  /**
358
   * Logs info about new, obsolete, and altered columns.
359
   *
360
   * @param TableColumnsMetadata $newColumns      The metadata of the new columns.
361
   * @param TableColumnsMetadata $obsoleteColumns The metadata of the obsolete columns.
362
   * @param TableColumnsMetadata $alteredColumns  The metadata of the altered columns.
363
   */
364 12
  private function logColumnInfo($newColumns, $obsoleteColumns, $alteredColumns)
365
  {
366 12
    foreach ($newColumns->getColumns() as $column)
367
    {
368 2
      $this->io->logInfo('New column <dbo>%s.%s</dbo>',
369 2
                         $this->configTable->getTableName(),
370 2
                         $column->getProperty('column_name'));
371 12
    }
372
373 12
    foreach ($obsoleteColumns->getColumns() as $column)
374
    {
375 1
      $this->io->logInfo('Obsolete column <dbo>%s.%s</dbo>',
376 1
                         $this->configTable->getTableName(),
377 1
                         $column->getProperty('column_name'));
378 12
    }
379
380 12
    foreach ($alteredColumns->getColumns() as $column)
381
    {
382
      $this->io->logInfo('Type of <dbo>%s.%s</dbo> has been altered to <dbo>%s</dbo>',
383
                         $this->configTable->getTableName(),
384
                         $column->getProperty('column_name'),
385
                         $column->getProperty('column_type'));
386 12
    }
387 12
  }
388
389
  //--------------------------------------------------------------------------------------------------------------------
390
  /**
391
   * Releases all table locks.
392
   */
393 12
  private function unlockTables()
394
  {
395 12
    AuditDataLayer::unlockTables();
396 12
  }
397
398
  //--------------------------------------------------------------------------------------------------------------------
399
}
400
401
//----------------------------------------------------------------------------------------------------------------------
402