Completed
Pull Request — master (#55)
by Dima
03:18
created

AuditTable::getRandomAlias()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 2
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 16
  public function __construct($io,
78
                              $configTable,
79
                              $auditSchema,
80
                              $auditColumnsMetadata,
81
                              $alias,
82 1
                              $skipVariable)
83
  {
84 16
    $this->io                       = $io;
85 16
    $this->configTable              = $configTable;
86 16
    $this->auditSchemaName          = $auditSchema;
87 16
    $this->dataTableColumnsDatabase = new TableColumnsMetadata($this->getColumnsFromInformationSchema());
88 16
    $this->auditColumns             = $auditColumnsMetadata;
89 16
    $this->alias                    = $alias;
90 16
    $this->skipVariable             = $skipVariable;
91 16
  }
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 13
  public function createAuditTable()
109
  {
110 13
    $this->io->logInfo('Creating audit table <dbo>%s.%s<dbo>',
111 13
                       $this->auditSchemaName,
112 13
                       $this->configTable->getTableName());
113
114
    // In the audit table all columns from the data table must be nullable.
115 13
    $dataTableColumnsDatabase = clone($this->dataTableColumnsDatabase);
116 13
    $dataTableColumnsDatabase->makeNullable();
117
118 13
    $columns = TableColumnsMetadata::combine($this->auditColumns, $dataTableColumnsDatabase);
119 13
    AuditDataLayer::createAuditTable($this->configTable->getSchemaName(),
120 13
                                     $this->auditSchemaName,
121 13
                                     $this->configTable->getTableName(),
122 13
                                     $columns);
123 13
  }
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 16
  public function createTriggers($additionalSql)
132
  {
133
    // Lock the table to prevent insert, updates, or deletes between dropping and creating triggers.
134 16
    $this->lockTable($this->configTable->getTableName());
135
136
    // Drop all triggers, if any.
137 16
    $this->dropAuditTriggers($this->configTable->getSchemaName(), $this->configTable->getTableName());
138
139
    // Create or recreate the audit triggers.
140 16
    $this->createTableTrigger('INSERT', $this->skipVariable, $additionalSql);
141 16
    $this->createTableTrigger('UPDATE', $this->skipVariable, $additionalSql);
142 16
    $this->createTableTrigger('DELETE', $this->skipVariable, $additionalSql);
143
144
    // Insert, updates, and deletes are no audited again. So, release lock on the table.
145 16
    $this->unlockTables();
146 16
  }
147
148
  //--------------------------------------------------------------------------------------------------------------------
149
  /**
150
   * Drops all audit triggers from this table.
151
   *
152
   * @param string $schemaName The name of the table schema.
153
   * @param string $tableName  The name of the table.
154
   */
155 16
  public function dropAuditTriggers($schemaName, $tableName)
156
  {
157 16
    $triggers = AuditDataLayer::getTableTriggers($schemaName, $tableName);
158 16
    foreach ($triggers as $trigger)
159
    {
160 6
      if (preg_match('/^trg_audit_.*_(insert|update|delete)$/', $trigger['trigger_name']))
161 6
      {
162 6
        $this->io->logVerbose('Dropping trigger <dbo>%s</dbo> on <dbo>%s.%s</dbo>',
163 6
                              $trigger['trigger_name'],
164 6
                              $schemaName,
165 6
                              $tableName);
166
167 6
        AuditDataLayer::dropTrigger($schemaName, $trigger['trigger_name']);
168 6
      }
169 16
    }
170 16
  }
171
172
  //--------------------------------------------------------------------------------------------------------------------
173
  /**
174
   * Returns the table name.
175
   *
176
   * @return string
177
   */
178 1
  public function getTableName()
179
  {
180 1
    return $this->configTable->getTableName();
181
  }
182
183
  //--------------------------------------------------------------------------------------------------------------------
184
  /**
185
   * Main function for work with table.
186
   *
187
   * @param string[] $additionalSql Additional SQL statements to be include in triggers.
188
   *
189
   * @return bool
190
   */
191 16
  public function main($additionalSql)
192
  {
193 16
    $comparedColumns = $this->getTableColumnInfo();
194 16
    $newColumns      = $comparedColumns['new_columns'];
195 16
    $obsoleteColumns = $comparedColumns['obsolete_columns'];
196 16
    $alteredColumns  = $comparedColumns['altered_columns'];
197
198 16
    if ($newColumns->getNumberOfColumns()==0 || $obsoleteColumns->getNumberOfColumns()==0)
199 16
    {
200 16
      $this->addNewColumns($newColumns);
201
202 16
      $this->createTriggers($additionalSql);
203
204 16
      return ($alteredColumns->getNumberOfColumns()==0);
205
    }
206
207 1
    $this->io->warning(sprintf('Skipping table %s', $this->getTableName()));
208
209 1
    return false;
210
  }
211
212
  //--------------------------------------------------------------------------------------------------------------------
213
  /**
214
   * Adds new columns to audit table.
215
   *
216
   * @param TableColumnsMetadata $columns TableColumnsMetadata array
217
   */
218 16
  private function addNewColumns($columns)
219
  {
220
    // Return immediately if there are no columns to add.
221 16
    if ($columns->getNumberOfColumns()==0) return;
222
223 1
    $alterColumns = $this->alterNewColumns($columns);
224
225 1
    AuditDataLayer::addNewColumns($this->auditSchemaName, $this->configTable->getTableName(), $alterColumns);
226 1
  }
227
228
  //--------------------------------------------------------------------------------------------------------------------
229
  /**
230
   * Returns metadata of new table columns that can be used in a 'alter table .. add column' statement.
231
   *
232
   * @param TableColumnsMetadata $newColumns The metadata new table columns.
233
   *
234
   * @return TableColumnsMetadata
235
   */
236 1
  private function alterNewColumns($newColumns)
237
  {
238 1
    $alterNewColumns = new TableColumnsMetadata();
239 1
    foreach ($newColumns->getColumns() as $newColumn)
240
    {
241 1
      $properties          = $newColumn->getProperties();
242 1
      $properties['after'] = $this->dataTableColumnsDatabase->getPreviousColumn($properties['column_name']);
243
244 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...
245 1
    }
246
247 1
    return $alterNewColumns;
248
  }
249
250
  //--------------------------------------------------------------------------------------------------------------------
251
  /**
252
   * Creates a triggers for this table.
253
   *
254
   * @param string      $action      The trigger action (INSERT, DELETE, or UPDATE).
255
   * @param string|null $skipVariable
256
   * @param string[]    $additionSql The additional SQL statements to be included in triggers.
257
   */
258 16
  private function createTableTrigger($action, $skipVariable, $additionSql)
259
  {
260 16
    $triggerName = $this->getTriggerName($action);
261
262 16
    $this->io->logVerbose('Creating trigger <dbo>%s.%s</dbo> on table <dbo>%s.%s</dbo>',
263 16
                          $this->configTable->getSchemaName(),
264 16
                          $triggerName,
265 16
                          $this->configTable->getSchemaName(),
266 16
                          $this->configTable->getTableName());
267
268 16
    AuditDataLayer::createAuditTrigger($this->configTable->getSchemaName(),
269 16
                                       $this->auditSchemaName,
270 16
                                       $this->configTable->getTableName(),
271 16
                                       $triggerName,
272 16
                                       $action,
273 16
                                       $this->auditColumns,
274 16
                                       $this->dataTableColumnsDatabase,
275 16
                                       $skipVariable,
276 16
                                       $additionSql);
277 16
  }
278
279
  //--------------------------------------------------------------------------------------------------------------------
280
  /**
281
   * Compares columns types from table in data_schema with columns in config file.
282
   *
283
   * @return TableColumnsMetadata
284
   */
285 16
  private function getAlteredColumns()
286
  {
287 16
    if ($this->configTable->getColumns()->getNumberOfColumns())
288 16
    {
289 5
      $alteredColumnsTypes = TableColumnsMetadata::differentColumnTypes($this->dataTableColumnsDatabase,
290 5
                                                                        $this->configTable->getColumns(),
291 5
                                                                        ['is_nullable']);
292 5
    }
293
    else
294
    {
295 11
      $alteredColumnsTypes = new TableColumnsMetadata();
296
    }
297
298 16
    return $alteredColumnsTypes;
299
  }
300
301
  //--------------------------------------------------------------------------------------------------------------------
302
  /**
303
   * Selects and returns the metadata of the columns of this table from information_schema.
304
   *
305
   * @return \array[]
306
   */
307 16
  private function getColumnsFromInformationSchema()
308
  {
309 16
    $result = AuditDataLayer::getTableColumns($this->configTable->getSchemaName(), $this->configTable->getTableName());
310
311 16
    return $result;
312
  }
313
314
  //--------------------------------------------------------------------------------------------------------------------
315
  /**
316
   * Compare columns from table in data_schema with columns in config file.
317
   *
318
   * @return array<string,TableColumnsMetadata>
319
   */
320 16
  private function getTableColumnInfo()
321
  {
322 16
    $columnActual  = new TableColumnsMetadata(AuditDataLayer::getTableColumns($this->auditSchemaName,
323 16
                                                                              $this->configTable->getTableName()));
324 16
    $columnsConfig = TableColumnsMetadata::combine($this->auditColumns, $this->configTable->getColumns());
325 16
    $columnsTarget = TableColumnsMetadata::combine($this->auditColumns, $this->dataTableColumnsDatabase);
326
327 16
    $newColumns      = TableColumnsMetadata::notInOtherSet($columnsTarget, $columnActual);
328 16
    $obsoleteColumns = TableColumnsMetadata::notInOtherSet($columnsConfig, $columnsTarget);
329 16
    $alteredColumns  = $this->getAlteredColumns();
330
331 16
    $this->logColumnInfo($newColumns, $obsoleteColumns, $alteredColumns);
332
333 16
    return ['new_columns'      => $newColumns,
334 16
            'obsolete_columns' => $obsoleteColumns,
335 16
            'altered_columns'  => $alteredColumns];
336
  }
337
338
  //--------------------------------------------------------------------------------------------------------------------
339
  /**
340
   * Returns the trigger name for a trigger action.
341
   *
342
   * @param string $action Trigger on action (Insert, Update, Delete)
343
   *
344
   * @return string
345
   */
346 16
  private function getTriggerName($action)
347
  {
348 16
    return strtolower(sprintf('trg_audit_%s_%s', $this->alias, $action));
349
  }
350
351
  //--------------------------------------------------------------------------------------------------------------------
352
  /**
353
   * Lock the table to prevent insert, updates, or deletes between dropping and creating triggers.
354
   *
355
   * @param string $tableName Name of table
356
   */
357 16
  private function lockTable($tableName)
358
  {
359 16
    AuditDataLayer::lockTable($tableName);
360 16
  }
361
362
  //--------------------------------------------------------------------------------------------------------------------
363
  /**
364
   * Logs info about new, obsolete, and altered columns.
365
   *
366
   * @param TableColumnsMetadata $newColumns      The metadata of the new columns.
367
   * @param TableColumnsMetadata $obsoleteColumns The metadata of the obsolete columns.
368
   * @param TableColumnsMetadata $alteredColumns  The metadata of the altered columns.
369
   */
370 16
  private function logColumnInfo($newColumns, $obsoleteColumns, $alteredColumns)
371
  {
372 16
    foreach ($newColumns->getColumns() as $column)
373
    {
374 2
      $this->io->logInfo('New column <dbo>%s.%s</dbo>',
375 2
                         $this->configTable->getTableName(),
376 2
                         $column->getProperty('column_name'));
377 16
    }
378
379 16
    foreach ($obsoleteColumns->getColumns() as $column)
380
    {
381 1
      $this->io->logInfo('Obsolete column <dbo>%s.%s</dbo>',
382 1
                         $this->configTable->getTableName(),
383 1
                         $column->getProperty('column_name'));
384 16
    }
385
386 16
    foreach ($alteredColumns->getColumns() as $column)
387
    {
388 1
      $this->io->logInfo('Type of <dbo>%s.%s</dbo> has been altered to <dbo>%s</dbo>',
389 1
                         $this->configTable->getTableName(),
390 1
                         $column->getProperty('column_name'),
391 1
                         $column->getProperty('column_type'));
392 16
    }
393 16
  }
394
395
  //--------------------------------------------------------------------------------------------------------------------
396
  /**
397
   * Releases all table locks.
398
   */
399 16
  private function unlockTables()
400
  {
401 16
    AuditDataLayer::unlockTables();
402 16
  }
403
404
  //--------------------------------------------------------------------------------------------------------------------
405
}
406
407
//----------------------------------------------------------------------------------------------------------------------
408