Importer   F
last analyzed

Complexity

Total Complexity 170

Size/Duplication

Total Lines 859
Duplicated Lines 0 %

Test Coverage

Coverage 12.98%

Importance

Changes 0
Metric Value
wmc 170
eloc 461
dl 0
loc 859
ccs 46
cts 354
cp 0.1298
rs 2
c 0
b 0
f 0

26 Methods

Rating   Name   Duplication   Size   Complexity  
A importScheme() 0 4 2
A dropForeignKeys() 0 29 5
F addData() 0 69 18
A postImport() 0 4 2
A importData() 0 4 2
B loadFiles() 0 15 7
A dropTable() 0 26 5
A logs() 0 7 2
A getOptions() 0 10 3
A dropIndexes() 0 26 5
A updateScheme() 0 5 2
A addForeignKey() 0 24 5
A postUpdate() 0 4 2
A refreshSchema() 0 3 1
A __construct() 0 4 1
A getIndexes() 0 18 6
B renameColumns() 0 26 6
A renameTables() 0 26 5
C addTables() 0 55 14
A getColumns() 0 13 4
A dropColumns() 0 26 5
A drop() 0 10 4
F updateTables() 0 156 48
B updateForeignKey() 0 35 9
A checkIntegrity() 0 4 2
A compareColumns() 0 7 5

How to fix   Complexity   

Complex Class

Complex classes like Importer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Importer, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * File that imports structure and data to database.
4
 *
5
 * @package App
6
 *
7
 * @copyright YetiForce S.A.
8
 * @license   YetiForce Public License 6.5 (licenses/LicenseEN.txt or yetiforce.com)
9
 * @author    Mariusz Krzaczkowski <[email protected]>
10
 * @author    Radosław Skrzypczak <[email protected]>
11
 */
12
13
namespace App\Db;
14
15
use App\Db\Importers\Base;
16
17
/**
18
 * Class that imports structure and data to database.
19
 */
20
class Importer
21
{
22
	/**
23
	 * End of line character.
24
	 *
25
	 * @var string
26
	 */
27
	public $logs;
28
	/**
29
	 * Start time.
30
	 *
31
	 * @var string|float
32
	 */
33
	private $startTime;
34
	/**
35
	 * Path to the directory with files to import.
36
	 *
37
	 * @var string
38
	 */
39
	public $path = 'install/install_schema';
40
41
	/**
42
	 * Stop import if an error occurs.
43
	 *
44
	 * @var bool
45
	 */
46
	public $dieOnError = false;
47
48
	/**
49
	 * Check redundant tables.
50
	 *
51
	 * @var bool
52
	 */
53
	public $redundantTables = false;
54
55
	/**
56
	 * Array with objects to import.
57
	 *
58
	 * @var Base[]
59
	 */
60
	private $importers = [];
61
62
	/**
63
	 * Construct.
64
	 */
65
	public function __construct()
66
	{
67
		$this->logs = '-------------   ' . date('Y-m-d H:i:s') . "   -------------\n";
68
		$this->startTime = microtime(true);
69
	}
70
71
	/**
72
	 * Load all files for import.
73
	 *
74
	 * @param bool|string $path
75
	 */
76
	public function loadFiles($path = false)
77
	{
78
		$dir = new \DirectoryIterator($path ?: $this->path);
79
		foreach ($dir as $fileinfo) {
80
			if ('dir' !== $fileinfo->getType() && 'php' === $fileinfo->getExtension()) {
81
				require $fileinfo->getPath() . \DIRECTORY_SEPARATOR . $fileinfo->getFilename();
82
				$className = 'Importers\\' . $fileinfo->getBasename('.php');
83
				$instance = new $className();
84
				if (method_exists($instance, 'scheme')) {
85
					$instance->scheme();
86
				}
87
				if (method_exists($instance, 'data')) {
88
					$instance->data();
89
				}
90
				$this->importers[] = $instance;
91
			}
92
		}
93
	}
94
95
	/**
96
	 * Refresh db schema.
97
	 */
98
	public function refreshSchema()
99
	{
100
		\App\Db::getInstance()->getSchema()->getTableSchemas('', true);
101
	}
102
103
	/**
104
	 * Show or save logs.
105
	 *
106
	 * @param bool $show
107
	 */
108
	public function logs($show = true)
109
	{
110
		$time = round((microtime(true) - $this->startTime) / 60, 2);
111 2
		if ($show) {
112
			echo $this->logs . '---------  ' . date('Y-m-d H:i:s') . "  ($time min)  -------------\n";
113 2
		} else {
114 2
			file_put_contents('cache/logs/Importer.log', $this->logs . '-------------  ' . date('Y-m-d H:i:s') . " ($time min)   -------------\n", LOCK_EX);
115 2
		}
116
	}
117 2
118 2
	/**
119
	 * Import database structure.
120
	 */
121
	public function importScheme()
122
	{
123
		foreach ($this->importers as &$importer) {
124
			$this->addTables($importer);
125 2
		}
126 2
	}
127 2
128
	/**
129 2
	 * Import database rows.
130 2
	 */
131
	public function importData()
132
	{
133
		foreach ($this->importers as &$importer) {
134
			$this->addData($importer);
135
		}
136
	}
137
138
	/**
139 2
	 * Post Process action.
140 2
	 */
141 2
	public function postImport()
142
	{
143 2
		foreach ($this->importers as &$importer) {
144 2
			$this->addForeignKey($importer);
145
		}
146
	}
147
148
	/**
149
	 * Update db scheme.
150
	 */
151
	public function updateScheme()
152
	{
153
		foreach ($this->importers as &$importer) {
154 2
			$this->updateTables($importer);
155 2
			$this->drop($importer);
156
		}
157
	}
158
159
	/**
160
	 * Post Process action.
161
	 */
162
	public function postUpdate()
163
	{
164
		foreach ($this->importers as &$importer) {
165 2
			$this->updateForeignKey($importer);
166
		}
167 2
	}
168 2
169 2
	/**
170 2
	 * Creating tables.
171
	 *
172
	 * @param Base $importer
173
	 */
174 2
	public function addTables(Base $importer)
175
	{
176
		$this->logs .= "> start add tables ({$importer->dbType})\n";
177
		$startMain = microtime(true);
178
		foreach ($importer->tables as $tableName => $table) {
179
			$this->logs .= "  > add table: $tableName ... ";
180
			$start = microtime(true);
181
			try {
182
				$importer->db->createCommand()->createTable($tableName, $this->getColumns($importer, $table), $this->getOptions($importer, $table))->execute();
183
				$time = round((microtime(true) - $start), 1);
184
				$this->logs .= "done    ({$time}s)\n";
185 2
			} catch (\Throwable $e) {
186
				$time = round((microtime(true) - $start), 1);
187 2
				$this->logs .= "    ({$time}s) | Error(1) [{$e->getMessage()}] in  \n{$e->getTraceAsString()} !!!\n";
188
				if ($this->dieOnError) {
189
					throw new \App\Exceptions\AppException('Importer error: ' . $e->getMessage(), (int) $e->getCode(), $e);
190 2
				}
191 2
			}
192 2
			if ($indexes = $this->getIndexes($importer, $table)) {
193
				foreach ($indexes as $index) {
194
					$this->logs .= "  > create index: {$index[0]} ... ";
195
					$start = microtime(true);
196
					try {
197 2
						$importer->db->createCommand()->createIndex($index[0], $tableName, $index[1], (isset($index[2]) && $index[2]) ? true : false)->execute();
198
						$time = round((microtime(true) - $start), 1);
199
						$this->logs .= "done    ({$time}s)\n";
200
					} catch (\Throwable $e) {
201
						$time = round((microtime(true) - $start), 1);
202
						$this->logs .= "    ({$time}s) | Error(2) [{$e->getMessage()}] in  \n{$e->getTraceAsString()} !!!\n";
203
						if ($this->dieOnError) {
204
							throw new \App\Exceptions\AppException('Importer error: ' . $e->getMessage(), (int) $e->getCode(), $e);
205
						}
206
					}
207
				}
208 2
			}
209
			if (isset($table['primaryKeys'])) {
210 2
				foreach ($table['primaryKeys'] as $primaryKey) {
211 2
					$this->logs .= "  > add primary key: {$primaryKey[0]} ... ";
212
					$start = microtime(true);
213 2
					try {
214 2
						$importer->db->createCommand()->addPrimaryKey($primaryKey[0], $tableName, $primaryKey[1])->execute();
215 2
						$time = round((microtime(true) - $start), 1);
216
						$this->logs .= "done    ({$time}s)\n";
217
					} catch (\Throwable $e) {
218
						$time = round((microtime(true) - $start), 1);
219
						$this->logs .= "    ({$time}s) | Error(3) [{$e->getMessage()}] in  \n{$e->getTraceAsString()} !!!\n";
220
						if ($this->dieOnError) {
221
							throw new \App\Exceptions\AppException('Importer error: ' . $e->getMessage(), (int) $e->getCode(), $e);
222
						}
223
					}
224
				}
225 2
			}
226
		}
227
		$time = round((microtime(true) - $startMain) / 60, 2);
228
		$this->logs .= "# end add tables ($time min)\n";
229
	}
230
231
	/**
232
	 * Get additional SQL fragment that will be appended to the generated SQL.
233 2
	 *
234
	 * @param Base  $importer
235 2
	 * @param array $table
236
	 *
237
	 * @return string
238 2
	 */
239 2
	public function getOptions(Base $importer, $table)
240 2
	{
241
		$options = null;
242 2
		if ('mysql' === $importer->db->getDriverName()) {
243 2
			$options = "ENGINE={$table['engine']} DEFAULT CHARSET={$table['charset']}";
244
			if (isset($table['collate'])) {
245
				$options .= " COLLATE={$table['collate']}";
246
			}
247
		}
248
		return $options;
249
	}
250
251 2
	/**
252 2
	 * Get columns to create.
253
	 *
254
	 * @param Base  $importer
255
	 * @param array $table
256
	 *
257
	 * @return array
258
	 */
259
	public function getColumns(Base $importer, $table)
260
	{
261
		if (empty($table['columns'])) {
262
			return [];
263
		}
264
		$type = $importer->db->getDriverName();
265
		$columns = $table['columns'];
266
		if (isset($table['columns_' . $type])) {
267
			foreach ($table['columns_' . $type] as $column => $customType) {
268
				$columns[$column] = $customType;
269
			}
270
		}
271
		return $columns;
272
	}
273
274
	/**
275
	 * Get index to create.
276
	 *
277
	 * @param Base  $importer
278
	 * @param array $table
279
	 *
280
	 * @return array
281
	 */
282
	public function getIndexes(Base $importer, $table)
283
	{
284
		if (!isset($table['index'])) {
285
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array.
Loading history...
286
		}
287
		$type = $importer->db->getDriverName();
288
		$indexes = $table['index'];
289
		if (isset($table['index_' . $type])) {
290
			foreach ($table['index_' . $type] as $customIndex) {
291
				foreach ($indexes as $key => $index) {
292
					if ($customIndex[0] === $index[0]) {
293
						$this->logs .= "    > custom index, driver: $type, type: {$customIndex['0']} \n";
294
						$indexes[$key] = $customIndex;
295
					}
296
				}
297
			}
298
		}
299
		return $indexes;
300
	}
301
302
	/**
303
	 * Creates a SQL command for adding a foreign key constraint to an existing table.
304
	 *
305
	 * @param Base $importer
306
	 */
307
	public function addForeignKey(Base $importer)
308
	{
309
		if (!isset($importer->foreignKey)) {
310
			return;
311
		}
312
		$this->logs .= "> start add foreign key ({$importer->dbType})\n";
313
		$startMain = microtime(true);
314
		foreach ($importer->foreignKey as $key) {
315
			$this->logs .= "  > add: {$key[0]}, {$key[1]} ... ";
316
			$start = microtime(true);
317
			try {
318
				$importer->db->createCommand()->addForeignKey($key[0], $key[1], $key[2], $key[3], $key[4], $key[5] ?? null, $key[6] ?? null)->execute();
319
				$time = round((microtime(true) - $start), 1);
320
				$this->logs .= "done    ({$time}s)\n";
321
			} catch (\Throwable $e) {
322
				$time = round((microtime(true) - $start), 1);
323
				$this->logs .= "    ({$time}s) | Error(4) [{$e->getMessage()}] in  \n{$e->getTraceAsString()} !!!\n";
324
				if ($this->dieOnError) {
325
					throw new \App\Exceptions\AppException('Importer error: ' . $e->getMessage(), (int) $e->getCode(), $e);
326
				}
327
			}
328
		}
329
		$time = round((microtime(true) - $startMain) / 60, 2);
330
		$this->logs .= "# end add foreign key ($time min)\n";
331
	}
332
333
	/**
334
	 * Creating rows.
335
	 *
336
	 * @param Base $importer
337
	 */
338
	public function addData(Base $importer)
339
	{
340
		if (!isset($importer->data)) {
341
			return;
342
		}
343
		$this->logs .= "> start add data rows ({$importer->dbType})\n";
344
		$startMain = microtime(true);
345
		foreach ($importer->data as $tableName => $table) {
346
			$this->logs .= "  > add data to table: $tableName ... ";
347
			try {
348
				$keys = $table['columns'];
349
				if (\is_array($table['values']) && isset($table['values'][0])) {
350
					if ((new \App\Db\Query())->from($tableName)->where(array_combine($keys, $table['values'][0]))->exists($importer->db)) {
351
						$this->logs .= "| Info: skipped because it exist first row\n";
352
					} else {
353
						$start = microtime(true);
354
						foreach ($table['values'] as $values) {
355
							$importer->db->createCommand()->insert($tableName, array_combine($keys, $values))->execute();
356
						}
357
						$time = round((microtime(true) - $start), 1);
358
						$this->logs .= "done    ({$time}s)\n";
359
					}
360
				} else {
361
					$this->logs .= "| Error: No values\n";
362
				}
363
			} catch (\Throwable $e) {
364
				$this->logs .= " | Error(5) [{$e->getMessage()}] in  \n{$e->getTraceAsString()} !!!\n";
365
				if ($this->dieOnError) {
366
					throw new \App\Exceptions\AppException('Importer error: ' . $e->getMessage(), (int) $e->getCode(), $e);
367
				}
368
			}
369
		}
370
		$time = round((microtime(true) - $startMain) / 60, 2);
371
		$this->logs .= "# end add data rows ($time min)\n";
372
		$this->logs .= "> start reset sequence\n";
373
		$startMain = microtime(true);
374
		foreach ($importer->data as $tableName => $table) {
375
			$tableSchema = $importer->db->getTableSchema($tableName);
376
			$isAutoIncrement = false;
377
			foreach ($tableSchema->columns as $column) {
378
				if ($column->autoIncrement) {
379
					$isAutoIncrement = true;
380
					break;
381
				}
382
			}
383
			if ($isAutoIncrement) {
384
				$this->logs .= "  > reset sequence: $tableName ... ";
385
				$start = microtime(true);
386
				try {
387
					$importer->db->createCommand()->resetSequence($tableName)->execute();
388
					$time = round((microtime(true) - $start), 1);
389
					$this->logs .= "done    ({$time}s)\n";
390
				} catch (\Throwable $e) {
391
					$time = round((microtime(true) - $start), 1);
392
					$this->logs .= "    ({$time}s) | Error(6) [{$e->getMessage()}] in  \n{$e->getTraceAsString()} !!!\n";
393
					if ($this->dieOnError) {
394
						throw new \App\Exceptions\AppException('Importer error: ' . $e->getMessage(), (int) $e->getCode(), $e);
395
					}
396
				}
397
				if ($this->redundantTables && isset($importer->data[$tableName . '_seq'])) {
398
					$this->logs .= "   > Error: redundant table {$tableName}_seq !!!\n";
399
					if ($this->dieOnError) {
400
						throw new \App\Exceptions\AppException('Importer error: ' . $e->getMessage(), (int) $e->getCode(), $e);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $e does not seem to be defined for all execution paths leading up to this point.
Loading history...
401
					}
402
				}
403
			}
404
		}
405
		$time = round((microtime(true) - $startMain) / 60, 2);
406
		$this->logs .= "# end reset sequence ($time min)\n";
407
	}
408
409
	/**
410
	 * Rename tables.
411
	 *
412
	 * $tables = [
413
	 *        ['oldName', 'newName']
414
	 *        ['u_#__mail_address_boock', 'u_#__mail_address_book']
415
	 * ];
416
	 *
417
	 * @param array $tables
418
	 */
419
	public function renameTables($tables)
420
	{
421
		$this->logs .= "> start rename tables\n";
422
		$startMain = microtime(true);
423
		$db = \App\Db::getInstance();
424
		$dbCommand = $db->createCommand();
425
		foreach ($tables as $table) {
426
			$this->logs .= "  > rename table, {$table[0]} ... ";
427
			if ($db->isTableExists($table[0])) {
428
				$start = microtime(true);
429
				try {
430
					$dbCommand->renameTable($table[0], $table[1])->execute();
431
					$time = round((microtime(true) - $start), 1);
432
					$this->logs .= "done    ({$time}s)\n";
433
				} catch (\Throwable $e) {
434
					$time = round((microtime(true) - $start), 1);
435
					$this->logs .= "    ({$time}s) | Error(11) [{$e->getMessage()}] in \n{$e->getTraceAsString()} !!!\n";
436
				}
437
			} elseif ($db->isTableExists($table[1])) {
438
				$this->logs .= " | Info - table {$table[1]} is exists\n";
439
			} else {
440
				$this->logs .= " | Error - table does not exist\n";
441
			}
442
		}
443
		$time = round((microtime(true) - $startMain) / 60, 2);
444
		$this->logs .= "# end rename tables ($time min)\n";
445
	}
446
447
	/**
448
	 * Drop table.
449
	 *
450
	 * @param array|string $tables
451
	 */
452
	public function dropTable($tables)
453
	{
454
		$this->logs .= "> start drop tables\n";
455
		$startMain = microtime(true);
456
		$db = \App\Db::getInstance();
457
		if (\is_string($tables)) {
458
			$tables = [$tables];
459
		}
460
		foreach ($tables as $tableName) {
461
			$this->logs .= "  > drop table, {$tableName} ... ";
462
			if ($db->isTableExists($tableName)) {
463
				$start = microtime(true);
464
				try {
465
					$db->createCommand()->dropTable($tableName)->execute();
466
					$time = round((microtime(true) - $start), 1);
467
					$this->logs .= "done    ({$time}s)\n";
468
				} catch (\Throwable $e) {
469
					$time = round((microtime(true) - $start), 1);
470
					$this->logs .= "    ({$time}s) | Error(12) [{$e->getMessage()}] in \n{$e->getTraceAsString()} !!!\n";
471
				}
472
			} else {
473
				$this->logs .= " | Info - table does not exist\n";
474
			}
475
		}
476
		$time = round((microtime(true) - $startMain) / 60, 2);
477
		$this->logs .= "# end drop tables ($time min)\n";
478
	}
479
480
	/**
481
	 * Drop indexes.
482
	 *
483
	 * @param array $tables [$table=>[$index,...],...]
484
	 */
485
	public function dropIndexes(array $tables)
486
	{
487
		$this->logs .= "> start drop indexes\n";
488
		$startMain = microtime(true);
489
		$db = \App\Db::getInstance();
490
		foreach ($tables as $tableName => $indexes) {
491
			$dbIndexes = $db->getTableKeys($tableName);
492
			foreach ($indexes as $index) {
493
				$this->logs .= "  > drop index, {$tableName}:{$index} ... ";
494
				if (isset($dbIndexes[$index])) {
495
					$start = microtime(true);
496
					try {
497
						$db->createCommand()->dropIndex($index, $tableName)->execute();
498
						$time = round((microtime(true) - $start), 1);
499
						$this->logs .= "done    ({$time}s)\n";
500
					} catch (\Throwable $e) {
501
						$time = round((microtime(true) - $start), 1);
502
						$this->logs .= "    ({$time}s) | Error(12) [{$e->getMessage()}] in \n{$e->getTraceAsString()} !!!\n";
503
					}
504
				} else {
505
					$this->logs .= " | Info - index not exists\n";
506
				}
507
			}
508
		}
509
		$time = round((microtime(true) - $startMain) / 60, 2);
510
		$this->logs .= "# end drop indexes ($time min)\n";
511
	}
512
513
	/**
514
	 * Drop foreign keys.
515
	 *
516
	 * @param array $foreignKeys [$foreignKey=>table,...]
517
	 */
518
	public function dropForeignKeys(array $foreignKeys)
519
	{
520
		$this->logs .= "> start drop foreign keys\n";
521
		$startMain = microtime(true);
522
		$db = \App\Db::getInstance();
523
		foreach ($foreignKeys as $keyName => $tableName) {
524
			$this->logs .= "  > drop foreign key, {$tableName}:{$keyName} ... ";
525
			$tableSchema = $db->getTableSchema($tableName, true);
526
			if ($tableSchema) {
527
				$keyName = str_replace('#__', $db->tablePrefix, $keyName);
528
				if (isset($tableSchema->foreignKeys[$keyName])) {
529
					$start = microtime(true);
530
					try {
531
						$db->createCommand()->dropForeignKey($keyName, $tableName)->execute();
532
						$time = round((microtime(true) - $start), 1);
533
						$this->logs .= "done    ({$time}s)\n";
534
					} catch (\Throwable $e) {
535
						$time = round((microtime(true) - $start), 1);
536
						$this->logs .= "    ({$time}s) | Error [{$e->getMessage()}] in \n{$e->getTraceAsString()} !!!\n";
537
					}
538
				} else {
539
					$this->logs .= " | Info - foreign key not exists\n";
540
				}
541
			} else {
542
				$this->logs .= " | Error - table does not exists\n";
543
			}
544
		}
545
		$time = round((microtime(true) - $startMain) / 60, 2);
546
		$this->logs .= "# end drop foreign keys ($time min)\n";
547
	}
548
549
	/**
550
	 * Rename columns.
551
	 *
552
	 * $columns = [
553
	 *        ['TableName', 'oldName', 'newName'],
554
	 *        ['vtiger_smsnotifier', 'status', 'smsnotifier_status'],
555
	 * ];
556
	 *
557
	 * @param array $columns
558
	 */
559
	public function renameColumns($columns)
560
	{
561
		$this->logs .= "> start rename columns\n";
562
		$startMain = microtime(true);
563
		$db = \App\Db::getInstance();
564
		$dbCommand = $db->createCommand();
565
		$schema = $db->getSchema();
566
		foreach ($columns as $column) {
567
			$tableSchema = $schema->getTableSchema($column[0]);
568
			$this->logs .= "  > rename column: {$column[0]}:{$column[1]} ... ";
569
			if ($tableSchema && isset($tableSchema->columns[$column[1]]) && !isset($tableSchema->columns[$column[2]])) {
570
				$start = microtime(true);
571
				try {
572
					$dbCommand->renameColumn($column[0], $column[1], $column[2])->execute();
573
					$time = round((microtime(true) - $start), 1);
574
					$this->logs .= "done    ({$time}s)\n";
575
				} catch (\Throwable $e) {
576
					$time = round((microtime(true) - $start), 1);
577
					$this->logs .= "    ({$time}s) | Error(13) [{$e->getMessage()}] in \n{$e->getTraceAsString()} !!!\n";
578
				}
579
			} else {
580
				$this->logs .= " | Warning - table or column does not exists\n";
581
			}
582
		}
583
		$time = round((microtime(true) - $startMain) / 60, 2);
584
		$this->logs .= "# end rename columns ($time min)\n";
585
	}
586
587
	/**
588
	 * Drop tables and columns.
589
	 *
590
	 * @param Base $importer
591
	 */
592
	public function drop(Base $importer)
593
	{
594
		if (isset($importer->dropTables)) {
595
			$this->dropTable($importer->dropTables);
596
		}
597
		if (isset($importer->dropColumns)) {
598
			$this->dropColumns($importer->dropColumns);
599
		}
600
		if (isset($importer->dropIndexes)) {
601
			$this->dropIndexes($importer->dropIndexes);
602
		}
603
	}
604
605
	/**
606
	 * Drop columns.
607
	 *
608
	 * $columns = [
609
	 *        ['TableName', 'columnName'],
610
	 *        ['vtiger_smsnotifier', 'status'],
611
	 * ];
612
	 *
613
	 * @param array $columns
614
	 */
615
	public function dropColumns($columns)
616
	{
617
		$this->logs .= "> start drop columns\n";
618
		$startMain = microtime(true);
619
		$db = \App\Db::getInstance();
620
		$dbCommand = $db->createCommand();
621
		$schema = $db->getSchema();
622
		foreach ($columns as $column) {
623
			$tableSchema = $schema->getTableSchema($column[0]);
624
			$this->logs .= "  > drop column: {$column[0]}:{$column[1]} ... ";
625
			if ($tableSchema && isset($tableSchema->columns[$column[1]])) {
626
				$start = microtime(true);
627
				try {
628
					$dbCommand->dropColumn($column[0], $column[1])->execute();
629
					$time = round((microtime(true) - $start), 1);
630
					$this->logs .= "done    ({$time}s)\n";
631
				} catch (\Throwable $e) {
632
					$time = round((microtime(true) - $start), 1);
633
					$this->logs .= "    ({$time}s) | Error(14) [{$e->getMessage()}] in \n{$e->getTraceAsString()} !!!\n";
634
				}
635
			} else {
636
				$this->logs .= " | Info - table or column does not exist\n";
637
			}
638
		}
639
		$time = round((microtime(true) - $startMain) / 60, 2);
640
		$this->logs .= "# end drop columns ($time min)\n";
641
	}
642
643
	/**
644
	 * Update tables structure.
645
	 *
646
	 * @param Base $importer
647
	 *
648
	 * @throws \App\Exceptions\AppException
649
	 */
650
	public function updateTables(Base $importer)
651
	{
652
		$this->logs .= "> start update tables ({$importer->dbType})\n";
653
		$startMain = microtime(true);
654
		$schema = $importer->db->getSchema();
655
		$queryBuilder = $schema->getQueryBuilder();
656
		$dbCommand = $importer->db->createCommand();
657
		foreach ($importer->tables as $tableName => $table) {
658
			try {
659
				if (!$importer->db->isTableExists($tableName)) {
660
					$this->logs .= "  > add table: $tableName ... ";
661
					$start = microtime(true);
662
					$dbCommand->createTable($tableName, $this->getColumns($importer, $table), $this->getOptions($importer, $table))->execute();
663
					$time = round((microtime(true) - $start), 1);
664
					$this->logs .= "done    ({$time}s)\n";
665
				} else {
666
					$tableSchema = $schema->getTableSchema($tableName);
667
					foreach ($this->getColumns($importer, $table) as $columnName => $column) {
668
						$renameFrom = $mode = null;
669
						if (\is_array($column)) {
670
							$renameFrom = $column['renameFrom'] ?? '';
671
							$mode = $column['mode'] ?? $mode; // 0,null - create/update, 1 - update only
672
							$column = $column['type'] ?? '';
673
						}
674
						$columnExists = isset($tableSchema->columns[$columnName]);
675
						if ($renameFrom && !$columnExists && isset($tableSchema->columns[$renameFrom])) {
676
							$this->logs .= "  > rename column: {$tableName}:{$renameFrom} -> {$columnName}... ";
677
							$start = microtime(true);
678
							$dbCommand->renameColumn($tableName, $renameFrom, $columnName)->execute();
679
							$time = round((microtime(true) - $start), 1);
680
							$this->logs .= "done    ({$time}s)\n";
681
							$tableSchema = $schema->getTableSchema($tableName, true);
682
							$columnExists = isset($tableSchema->columns[$columnName]);
683
						}elseif (!$columnExists && 1 !== $mode) {
684
							$this->logs .= "  > add column: $tableName:$columnName ... ";
685
							$start = microtime(true);
686
							$dbCommand->addColumn($tableName, $columnName, $column)->execute();
687
							$time = round((microtime(true) - $start), 1);
688
							$this->logs .= "done    ({$time}s)\n";
689
						}
690
						if ($columnExists && $column instanceof \yii\db\ColumnSchemaBuilder && $this->compareColumns($queryBuilder, $tableSchema->columns[$columnName], $column)) {
691
							$primaryKey = false;
692
							if ($column instanceof \yii\db\ColumnSchemaBuilder && (\in_array($column->get('type'), ['upk', 'pk', 'ubigpk', 'bigpk']))) {
693
								$primaryKey = true;
694
								$column->set('type', \in_array($column->get('type'), ['ubigpk', 'bigpk']) ? \yii\db\Schema::TYPE_BIGINT : \yii\db\Schema::TYPE_INTEGER);
695
							}
696
							if ($tableSchema->foreignKeys) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $tableSchema->foreignKeys of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
697
								foreach ($tableSchema->foreignKeys as $keyName => $value) {
698
									if (isset($value[$columnName])) {
699
										$this->logs .= "  > foreign key must be removed and added in postUpdate: $tableName:$columnName <> {$value[0]}:{$value[$columnName]} FK:{$keyName}\n";
700
										$importer->foreignKey[] = [$keyName, $tableName, $columnName, $value[0], $value[$columnName], 'CASCADE', null];
701
										$dbCommand->dropForeignKey($keyName, $tableName)->execute();
702
									}
703
								}
704
							}
705
							foreach ($schema->findForeignKeyToColumn($tableName, $columnName) as $sourceTableName => $fks) {
706
								foreach ($fks as $keyName => $fk) {
707
									$this->logs .= "  > foreign key must be removed and added in postUpdate: $tableName:$columnName <> $sourceTableName:{$fk['sourceColumn']} FK:{$keyName}\n";
708
									$importer->foreignKey[] = [$keyName, $sourceTableName, $fk['sourceColumn'], $tableName, $columnName, 'CASCADE', null];
709
									$dbCommand->dropForeignKey($keyName, $sourceTableName)->execute();
710
								}
711
							}
712
							$this->logs .= "  > alter column: $tableName:$columnName ... ";
713
							$start = microtime(true);
714
							$dbCommand->alterColumn($tableName, $columnName, $column)->execute();
715
							$time = round((microtime(true) - $start), 1);
716
							$this->logs .= "done    ({$time}s)\n";
717
							if ($primaryKey) {
718
								if (!isset($table['primaryKeys'])) {
719
									$table['primaryKeys'] = [];
720
								}
721
								$table['primaryKeys'][] = [$tableSchema->fullName . '_pk', [$columnName]];
722
							}
723
						} elseif (!($column instanceof \yii\db\ColumnSchemaBuilder)) {
724
							$this->logs .= "  > Warning: column ({$tableName}:{$columnName}) is not verified\n";
725
						}
726
					}
727
				}
728
			} catch (\Throwable $e) {
729
				$this->logs .= " | Error(7) {$tableName} [{$e->getMessage()}] in  \n{$e->getTraceAsString()} !!!\n";
730
				if ($this->dieOnError) {
731
					throw new \App\Exceptions\AppException('Importer error: ' . $e->getMessage(), (int) $e->getCode(), $e);
732
				}
733
			}
734
			if ($indexes = $this->getIndexes($importer, $table)) {
735
				$dbIndexes = $importer->db->getTableKeys($tableName);
736
				foreach ($indexes as $index) {
737
					try {
738
						if (isset($dbIndexes[$index[0]])) {
739
							$update = false;
740
							if (\is_string($index[1]) ? !isset($dbIndexes[$index[0]][$index[1]]) : array_diff($index[1], array_keys($dbIndexes[$index[0]]))) {
741
								$update = true;
742
							} else {
743
								foreach ($dbIndexes[$index[0]] as $dbIndex) {
744
									if (empty($index[2]) !== empty($dbIndex['unique'])) {
745
										$update = true;
746
									}
747
								}
748
							}
749
							if ($update) {
750
								$this->logs .= "  > update index: {$index[0]} ... ";
751
								$start = microtime(true);
752
								$dbCommand->dropIndex($index[0], $tableName)->execute();
753
								$dbCommand->createIndex($index[0], $tableName, $index[1], !empty($index[2]))->execute();
754
								$time = round((microtime(true) - $start), 1);
755
								$this->logs .= "done    ({$time}s)\n";
756
							}
757
						} else {
758
							$this->logs .= "  > create index: {$index[0]} ... ";
759
							$start = microtime(true);
760
							$dbCommand->createIndex($index[0], $tableName, $index[1], !empty($index[2]))->execute();
761
							$time = round((microtime(true) - $start), 1);
762
							$this->logs .= "done    ({$time}s)\n";
763
						}
764
					} catch (\Throwable $e) {
765
						$this->logs .= " | Error(8) [{$e->getMessage()}] in  \n{$e->getTraceAsString()} !!!\n";
766
						if ($this->dieOnError) {
767
							throw new \App\Exceptions\AppException('Importer error: ' . $e->getMessage(), (int) $e->getCode(), $e);
768
						}
769
					}
770
				}
771
			}
772
			if (isset($table['primaryKeys'])) {
773
				$dbPrimaryKeys = $importer->db->getPrimaryKey($tableName);
774
				foreach ($table['primaryKeys'] as $primaryKey) {
775
					$status = true;
776
					foreach ($dbPrimaryKeys as $dbPrimaryKey) {
777
						if (\is_string($primaryKey[1]) ? !(1 !== \count($dbPrimaryKey) && $primaryKey[1] !== $dbPrimaryKey[0]) : !array_diff($primaryKey[1], $dbPrimaryKey)) {
778
							$status = false;
779
						}
780
					}
781
					if ($status) {
782
						$this->logs .= "  > update primary key: {$primaryKey[0]} , table: $tableName , column: " . (\is_array($primaryKey[1]) ? implode(',', $primaryKey[1]) : $primaryKey[1]) . ' ... ';
783
						$start = microtime(true);
784
						try {
785
							if (isset($dbPrimaryKeys[$primaryKey[0]])) {
786
								$dbCommand->dropPrimaryKey($primaryKey[0], $tableName)->execute();
787
							} elseif ($dbPrimaryKeys) {
788
								$dbCommand->dropPrimaryKey(key($dbPrimaryKeys), $tableName)->execute();
789
							}
790
							$dbCommand->addPrimaryKey($primaryKey[0], $tableName, $primaryKey[1])->execute();
791
							$time = round((microtime(true) - $start), 1);
792
							$this->logs .= "done    ({$time}s)\n";
793
						} catch (\Throwable $e) {
794
							$time = round((microtime(true) - $start), 1);
795
							$this->logs .= "    ({$time}s) | Error(10) [{$e->getMessage()}] in \n{$e->getTraceAsString()} !!!\n";
796
							if ($this->dieOnError) {
797
								throw new \App\Exceptions\AppException('Importer error: ' . $e->getMessage(), (int) $e->getCode(), $e);
798
							}
799
						}
800
					}
801
				}
802
			}
803
		}
804
		$time = round((microtime(true) - $startMain) / 60, 2);
805
		$this->logs .= "# end update tables    ({$time}s)\n";
806
	}
807
808
	/**
809
	 * Compare two columns if they are identical.
810
	 *
811
	 * @param \yii\db\QueryBuilder        $queryBuilder
812
	 * @param \yii\db\ColumnSchema        $baseColumn
813
	 * @param \yii\db\ColumnSchemaBuilder $targetColumn
814
	 *
815
	 * @return bool
816
	 */
817
	protected function compareColumns(\yii\db\QueryBuilder $queryBuilder, \yii\db\ColumnSchema $baseColumn, \yii\db\ColumnSchemaBuilder $targetColumn)
818
	{
819
		return strtok($baseColumn->dbType, ' ') !== strtok($queryBuilder->getColumnType($targetColumn), ' ')
820
		|| ($baseColumn->allowNull !== (null === $targetColumn->isNotNull))
821
		|| ($baseColumn->defaultValue !== $targetColumn->default)
822
		|| ($baseColumn->unsigned !== $targetColumn->isUnsigned)
823
		|| ($baseColumn->autoIncrement !== $targetColumn->autoIncrement);
824
	}
825
826
	/**
827
	 * Update a foreign key constraint to an existing table.
828
	 *
829
	 * @param Base $importer
830
	 */
831
	public function updateForeignKey(Base $importer)
832
	{
833
		if (!isset($importer->foreignKey)) {
834
			return;
835
		}
836
		$this->logs .= "> start update foreign key ({$importer->dbType})\n";
837
		$startMain = microtime(true);
838
		$dbCommand = $importer->db->createCommand();
839
		$schema = $importer->db->getSchema();
840
		foreach ($importer->foreignKey as $key) {
841
			$add = true;
842
			$keyName = $importer->db->quoteSql($key[0]);
843
			$sourceTableName = $importer->db->quoteSql($key[1]);
844
			$destTableName = $importer->db->quoteSql($key[3]);
845
			$tableSchema = $schema->getTableSchema($sourceTableName);
846
			foreach ($tableSchema->foreignKeys as $dbForeignKey) {
847
				if ($destTableName === $dbForeignKey[0] && isset($dbForeignKey[$key[2]]) && $key[4] === $dbForeignKey[$key[2]]) {
848
					$add = false;
849
				}
850
			}
851
			if ($add) {
852
				$this->logs .= "  > add: $keyName, $sourceTableName ... ";
853
				$start = microtime(true);
854
				try {
855
					$dbCommand->addForeignKey($keyName, $sourceTableName, $key[2], $destTableName, $key[4], $key[5], $key[6])->execute();
856
					$time = round((microtime(true) - $start), 1);
857
					$this->logs .= "done    ({$time}s)\n";
858
				} catch (\Throwable $e) {
859
					$time = round((microtime(true) - $start), 1);
860
					$this->logs .= "     ({$time}s) | Error(10) [{$e->getMessage()}] in \n{$e->getTraceAsString()} !!!\n";
861
				}
862
			}
863
		}
864
		$time = round((microtime(true) - $startMain) / 60, 2);
865
		$this->logs .= "# end update foreign key    ({$time}s)\n";
866
	}
867
868
	/**
869
	 * Builds a SQL command for enabling or disabling integrity check.
870
	 *
871
	 * @param bool $check whether to turn on or off the integrity check.
872
	 *
873
	 * @return void
874
	 */
875
	public function checkIntegrity($check)
876
	{
877
		foreach ($this->importers as &$importer) {
878
			$importer->db->createCommand()->checkIntegrity($check)->execute();
879
		}
880
	}
881
}
882