Completed
Pull Request — master (#31)
by
unknown
02:42
created

Table::lockTable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 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
  public function __construct($io,
92
                              $tableName,
93
                              $dataSchema,
94
                              $auditSchema,
95
                              $configColumnsMetadata,
96
                              $auditColumnsMetadata,
97
                              $alias,
98
                              $skipVariable)
99
  {
100
    $this->io                       = $io;
101
    $this->tableName                = $tableName;
102
    $this->dataTableColumnsConfig   = new Columns($configColumnsMetadata);
103
    $this->dataSchemaName           = $dataSchema;
104
    $this->auditSchemaName          = $auditSchema;
105
    $this->dataTableColumnsDatabase = new Columns($this->getColumnsFromInformationSchema());
106
    $this->auditColumns             = new Columns($auditColumnsMetadata);
107
    $this->alias                    = $alias;
108
    $this->skipVariable             = $skipVariable;
109
  }
110
111
  //--------------------------------------------------------------------------------------------------------------------
112
  /**
113
   * Returns a random alias for this table.
114
   *
115
   * @return string
116
   */
117
  public static function getRandomAlias()
118
  {
119
    return uniqid();
120
  }
121
122
  //--------------------------------------------------------------------------------------------------------------------
123
  /**
124
   * Creates missing audit table for this table.
125
   */
126
  public function createMissingAuditTable()
127
  {
128
    $this->io->logInfo('Creating audit table <dbo>%s.%s<dbo>', $this->auditSchemaName, $this->tableName);
129
130
    $columns = Columns::combine($this->auditColumns, $this->dataTableColumnsDatabase);
131
    DataLayer::createAuditTable($this->dataSchemaName, $this->auditSchemaName, $this->tableName, $columns);
132
  }
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
  public function createTriggers($additionalSql)
141
  {
142
    // Lock the table to prevent insert, updates, or deletes between dropping and creating triggers.
143
    $this->lockTable($this->tableName);
144
145
    // Drop all triggers, if any.
146
    $this->dropTriggers();
147
148
    // Create or recreate the audit triggers.
149
    $this->createTableTrigger('INSERT', $this->skipVariable, $additionalSql);
150
    $this->createTableTrigger('UPDATE', $this->skipVariable, $additionalSql);
151
    $this->createTableTrigger('DELETE', $this->skipVariable, $additionalSql);
152
153
    // Insert, updates, and deletes are no audited again. So, release lock on the table.
154
    $this->unlockTables();
155
  }
156
157
  //--------------------------------------------------------------------------------------------------------------------
158
  /**
159
   * Returns the name of this table.
160
   *
161
   * @return string
162
   */
163
  public function getTableName()
164
  {
165
    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
  public function main($additionalSql)
177
  {
178
    $comparedColumns = null;
179
    if (isset($this->dataTableColumnsConfig))
180
    {
181
      $comparedColumns = $this->getTableColumnInfo();
182
    }
183
184
    $newColumns      = $comparedColumns['new_columns']->getColumns();
185
    $obsoleteColumns = $comparedColumns['obsolete_columns']->getColumns();
186
    if (empty($newColumns) && empty($obsoleteColumns))
187
    {
188
      $alteredColumns = $comparedColumns['altered_columns']->getColumns();
189
      if (empty($alteredColumns))
190
      {
191
        $this->createTriggers($additionalSql);
192
      }
193
    }
194
195
    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
  private function createTableTrigger($action, $skipVariable, $additionSql)
218
  {
219
    $triggerName = $this->getTriggerName($action);
220
221
    $this->io->logVerbose('Creating trigger <dbo>%s.%s</dbo> on table <dbo>%s.%s</dbo>',
222
                          $this->dataSchemaName,
223
                          $triggerName,
224
                          $this->dataSchemaName,
225
                          $this->tableName);
226
227
    DataLayer::createAuditTrigger($this->dataSchemaName,
228
                                  $this->auditSchemaName,
229
                                  $this->tableName,
230
                                  $triggerName,
231
                                  $action,
232
                                  $this->auditColumns,
233
                                  $this->dataTableColumnsDatabase,
234
                                  $skipVariable,
235
                                  $additionSql);
236
  }
237
238
  //--------------------------------------------------------------------------------------------------------------------
239
  /**
240
   * Drops all triggers from this table.
241
   */
242
  private function dropTriggers()
243
  {
244
    $triggers = DataLayer::getTableTriggers($this->dataSchemaName, $this->tableName);
245
    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
    }
254
  }
255
256
  //--------------------------------------------------------------------------------------------------------------------
257
  /**
258
   * Compares columns types from table in data_schema with columns in config file.
259
   *
260
   * @return Columns
261
   */
262
  private function getAlteredColumns()
263
  {
264
    $alteredColumnsTypes = Columns::differentColumnTypes($this->dataTableColumnsDatabase,
265
                                                         $this->dataTableColumnsConfig);
266
267
    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
  private function getColumnsFromInformationSchema()
277
  {
278
    $result = DataLayer::getTableColumns($this->dataSchemaName, $this->tableName);
279
280
    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
  private function getTableColumnInfo()
290
  {
291
    $columnActual  = new Columns(DataLayer::getTableColumns($this->auditSchemaName, $this->tableName));
292
    $columnsConfig = Columns::combine($this->auditColumns, $this->dataTableColumnsConfig);
293
    $columnsTarget = Columns::combine($this->auditColumns, $this->dataTableColumnsDatabase);
294
295
    $newColumns      = Columns::notInOtherSet($columnsTarget, $columnActual);
296
    $obsoleteColumns = Columns::notInOtherSet($columnsConfig, $columnsTarget);
297
    $alteredColumns  = $this->getAlteredColumns();
298
299
    $this->loggingColumnInfo($newColumns, $obsoleteColumns, $alteredColumns);
300
    $this->addNewColumns($newColumns);
301
302
    return ['full_columns'     => $this->getTableColumnsFromConfig($newColumns, $obsoleteColumns),
303
            'new_columns'      => $newColumns,
304
            'obsolete_columns' => $obsoleteColumns,
305
            'altered_columns'  => $alteredColumns];
306
  }
307
308
  //--------------------------------------------------------------------------------------------------------------------
309
  /**
310
   * Check for know what columns array returns.
311
   *
312
   * @param Columns $newColumns
313
   * @param Columns $obsoleteColumns
314
   *
315
   * @return Columns
316
   */
317
  private function getTableColumnsFromConfig($newColumns, $obsoleteColumns)
318
  {
319
    $new      = $newColumns->getColumns();
320
    $obsolete = $obsoleteColumns->getColumns();
321
    if (!empty($new) && !empty($obsolete))
322
    {
323
      return $this->dataTableColumnsConfig;
324
    }
325
326
    return $this->dataTableColumnsDatabase;
327
  }
328
329
  //--------------------------------------------------------------------------------------------------------------------
330
  /**
331
   * Create and return trigger name.
332
   *
333
   * @param string $action Trigger on action (Insert, Update, Delete)
334
   *
335
   * @return string
336
   */
337
  private function getTriggerName($action)
338
  {
339
    return strtolower(sprintf('trg_%s_%s', $this->alias, $action));
340
  }
341
342
  //--------------------------------------------------------------------------------------------------------------------
343
  /**
344
   * Lock the table to prevent insert, updates, or deletes between dropping and creating triggers.
345
   *
346
   * @param string $tableName Name of table
347
   */
348
  private function lockTable($tableName)
349
  {
350
    DataLayer::lockTable($tableName);
351
  }
352
353
  //--------------------------------------------------------------------------------------------------------------------
354
  /**
355
   * Logging new and obsolete columns.
356
   *
357
   * @param Columns $newColumns
358
   * @param Columns $obsoleteColumns
359
   * @param Columns $alteredColumns
360
   */
361
  private function loggingColumnInfo($newColumns, $obsoleteColumns, $alteredColumns)
362
  {
363
    $new      = $newColumns->getColumns();
364
    $obsolete = $obsoleteColumns->getColumns();
365
    if (!empty($new) && !empty($obsolete))
366
    {
367
      $this->io->logInfo('Found both new and obsolete columns for table %s', $this->tableName);
368
      $this->io->logInfo('No action taken');
369
370
      /** @var ColumnType $column */
371
      foreach ($newColumns->getColumns() as $column)
372
      {
373
        $this->io->logInfo('New column %s', $column->getProperty('column_name'));
374
      }
375
      foreach ($obsoleteColumns->getColumns() as $column)
376
      {
377
        $this->io->logInfo('Obsolete column %s', $column->getProperty('column_name'));
378
      }
379
    }
380
381
    /** @var ColumnType $column */
382
    foreach ($obsoleteColumns->getColumns() as $column)
383
    {
384
      $this->io->logInfo('Obsolete column %s.%s', $this->tableName, $column->getProperty('column_name'));
385
    }
386
387
    /** @var ColumnType $column */
388
    foreach ($newColumns->getColumns() as $column)
389
    {
390
      $this->io->logInfo('New column %s.%s', $this->tableName, $column->getProperty('column_name'));
391
    }
392
393
    foreach ($alteredColumns->getColumns() as $column)
394
    {
395
      $this->io->logInfo('Type of <dbo>%s.%s</dbo> has been altered to <dbo>%s</dbo>',
396
                         $this->tableName,
397
                         $column['column_name'],
398
                         $column['column_type']);
399
    }
400
  }
401
402
  //--------------------------------------------------------------------------------------------------------------------
403
  /**
404
   * Releases all table locks.
405
   */
406
  private function unlockTables()
407
  {
408
    DataLayer::unlockTables();
409
  }
410
411
  //--------------------------------------------------------------------------------------------------------------------
412
}
413
414
//----------------------------------------------------------------------------------------------------------------------
415