297 lines
8.8 KiB
PHP
Executable File
297 lines
8.8 KiB
PHP
Executable File
<?php
|
||
|
||
namespace common\components\Migration;
|
||
|
||
use Exception;
|
||
use Throwable;
|
||
use Yii;
|
||
use yii\db\Query;
|
||
use yii\db\TableSchema;
|
||
use yii\helpers\Console;
|
||
|
||
class MigrationWithChangeFkRelations extends MigrationWithDefaultOptions
|
||
{
|
||
public function changeAllForeignKeys($deleteAction = null, $allowNamePatternsForReftable = [])
|
||
{
|
||
$success = true;
|
||
$dbSchema = Yii::$app->db->schema;
|
||
$tables = $dbSchema->getTableSchemas();
|
||
$tableCount = count($tables);
|
||
foreach ($tables as $i => $table) {
|
||
$success = $this->changeForeignKeyInTable($table, $deleteAction, $allowNamePatternsForReftable, $tableCount, $i);
|
||
if (!$success) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return $success;
|
||
}
|
||
|
||
public function changeForeignKeyInTable(TableSchema $table, $deleteAction = null, $allowNamePatternsForReftable = [], $tableCount = 0, $i = 0)
|
||
{
|
||
if (!$table->foreignKeys) {
|
||
return true;
|
||
}
|
||
|
||
echo $this->printTransformationTableInfo($table->fullName, $tableCount, ++$i);
|
||
|
||
$this_table_name = "{{%{$table->fullName}}}";
|
||
|
||
|
||
|
||
$thisPrimaryKey = array_shift($table->primaryKey);
|
||
|
||
$foreignKeys = $table->foreignKeys;
|
||
foreach ($foreignKeys as $fkName => $fkStructure) {
|
||
|
||
$that_table_name = "{{%{$fkStructure[0]}}}";
|
||
unset($fkStructure[0]);
|
||
|
||
if (
|
||
$allowNamePatternsForReftable &&
|
||
!$this->refTableIsAllowed($allowNamePatternsForReftable, $that_table_name)
|
||
) {
|
||
continue;
|
||
}
|
||
|
||
foreach ($fkStructure as $thisColumnName => $thatColumnName) {
|
||
if ($deleteAction == 'SET NULL' && !$table->columns[$thisColumnName]->allowNull) {
|
||
|
||
|
||
$this->setColumnAllowNull(
|
||
$this_table_name,
|
||
$thisColumnName,
|
||
$table->columns[$thisColumnName]->type,
|
||
$table->columns[$thisColumnName]->size
|
||
);
|
||
}
|
||
|
||
$this->changeForeignKeyRoutine(
|
||
$fkName,
|
||
$this_table_name,
|
||
$thisColumnName,
|
||
$thisPrimaryKey,
|
||
$that_table_name,
|
||
$thatColumnName,
|
||
$deleteAction
|
||
);
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
public function deleteUnRelatedRows(
|
||
$this_table_name,
|
||
$thisColumnName,
|
||
$thisPrimaryKey,
|
||
$that_table_name,
|
||
$thatColumnName,
|
||
$deleteAction = null
|
||
) {
|
||
$leftJoinString = "{{%that_table_name}}.{$thatColumnName} = {$this_table_name}.{$thisColumnName}";
|
||
if ($this->db->driverName === 'pgsql') {
|
||
$leftJoinString = "{{%that_table_name}}.\"{$thatColumnName}\" = {$this_table_name}.\"{$thisColumnName}\"";
|
||
}
|
||
$unRelatedColumns = (new Query())
|
||
->select("{$this_table_name}.{$thisPrimaryKey}")
|
||
->from($this_table_name)
|
||
->leftJoin(['that_table_name' => $that_table_name], $leftJoinString)
|
||
->where([
|
||
'and',
|
||
['IS', "{{%that_table_name}}.{$thatColumnName}", null],
|
||
['IS NOT', "{$this_table_name}.{$thisColumnName}", null],
|
||
])
|
||
->all();
|
||
if (!$unRelatedColumns) {
|
||
return true;
|
||
}
|
||
|
||
$deleteRowCount = count($unRelatedColumns);
|
||
echo $this->printUnRelatedRowInfo($this_table_name, $that_table_name, $deleteRowCount);
|
||
|
||
try {
|
||
return $this->delete(
|
||
$this_table_name,
|
||
[$thisPrimaryKey => array_column($unRelatedColumns, $thisPrimaryKey)]
|
||
);
|
||
} catch (Throwable $th) {
|
||
return $this->getTableFromErrorMessage($th->getMessage(), $deleteAction);
|
||
}
|
||
}
|
||
|
||
public function getTableFromErrorMessage($error, $deleteAction = null)
|
||
{
|
||
$pattern = "/\(`\w+`.`(\w+)`,/";
|
||
if (preg_match($pattern, $error, $matches)) {
|
||
$dbSchema = Yii::$app->db->schema;
|
||
$tables = $dbSchema->getTableSchema($matches[1], true);
|
||
|
||
return $this->changeForeignKeyInTable($tables, $deleteAction);
|
||
}
|
||
|
||
throw new Exception("Не удалось выделить таблицу из: {$error}");
|
||
}
|
||
|
||
public function printTransformationTableInfo($tableFullName, $tableCount, $i)
|
||
{
|
||
$result = '';
|
||
|
||
$result .= Console::ansiFormat(
|
||
"\nКонвертирование 'Внешних ключей' для таблицы ",
|
||
[Console::BG_BLACK, Console::FG_GREEN]
|
||
);
|
||
$result .= Console::ansiFormat(
|
||
$tableFullName,
|
||
[Console::BG_GREEN, Console::FG_BLACK]
|
||
);
|
||
if ($tableCount) {
|
||
$result .= Console::ansiFormat(
|
||
" ( {$i} / {$tableCount} )",
|
||
[Console::BG_BLACK, Console::FG_GREEN]
|
||
);
|
||
}
|
||
$result .= "\n";
|
||
|
||
return $result;
|
||
}
|
||
|
||
public function printUnRelatedRowInfo($this_table_name, $that_table_name, $deleteRowCount)
|
||
{
|
||
$result = '';
|
||
|
||
$result .= Console::ansiFormat(
|
||
'Удаление битых связей из таблицы ',
|
||
[Console::BG_BLACK, Console::FG_YELLOW]
|
||
);
|
||
$result .= Console::ansiFormat(
|
||
$this_table_name,
|
||
[Console::BG_YELLOW, Console::FG_BLACK]
|
||
);
|
||
$result .= Console::ansiFormat(
|
||
' к таблице ',
|
||
[Console::BG_BLACK, Console::FG_YELLOW]
|
||
);
|
||
$result .= Console::ansiFormat(
|
||
$that_table_name,
|
||
[Console::BG_YELLOW, Console::FG_BLACK]
|
||
);
|
||
$result .= Console::ansiFormat(
|
||
" ( {$deleteRowCount} )\n",
|
||
[Console::BG_BLACK, Console::FG_YELLOW]
|
||
);
|
||
|
||
return $result;
|
||
}
|
||
|
||
public function setColumnAllowNull(
|
||
$table,
|
||
$name,
|
||
$type,
|
||
$size
|
||
) {
|
||
$summaryType = $this->{$type}($size)->defaultValue(null);
|
||
$this->alterColumn($table, $name, $summaryType);
|
||
|
||
echo $this->printSetColumnAllowNull($table, $name);
|
||
}
|
||
|
||
public function printSetColumnAllowNull($table, $name)
|
||
{
|
||
$result = '';
|
||
|
||
$result .= Console::ansiFormat(
|
||
'Установка значение по умолчания как ',
|
||
[Console::BG_BLACK, Console::FG_CYAN]
|
||
);
|
||
$result .= Console::ansiFormat(
|
||
'NULL',
|
||
[Console::BG_PURPLE, Console::FG_BLACK]
|
||
);
|
||
$result .= Console::ansiFormat(
|
||
' для колонки ',
|
||
[Console::BG_BLACK, Console::FG_CYAN]
|
||
);
|
||
$result .= Console::ansiFormat(
|
||
$name,
|
||
[Console::BG_CYAN, Console::FG_BLACK]
|
||
);
|
||
$result .= Console::ansiFormat(
|
||
' в таблице ',
|
||
[Console::BG_BLACK, Console::FG_CYAN]
|
||
);
|
||
$result .= Console::ansiFormat(
|
||
$table,
|
||
[Console::BG_CYAN, Console::FG_BLACK]
|
||
);
|
||
|
||
return $result;
|
||
}
|
||
|
||
public function changeForeignKeyRoutine(
|
||
$fkName,
|
||
$this_table_name,
|
||
$thisColumnName,
|
||
$thisPrimaryKey,
|
||
$that_table_name,
|
||
$thatColumnName,
|
||
$deleteAction
|
||
) {
|
||
$success = $this->deleteUnRelatedRows(
|
||
$this_table_name,
|
||
$thisColumnName,
|
||
$thisPrimaryKey,
|
||
$that_table_name,
|
||
$thatColumnName,
|
||
);
|
||
if (!$success) {
|
||
return;
|
||
}
|
||
|
||
$this->dropForeignKey($fkName, $this_table_name);
|
||
|
||
Yii::$app->db->schema->refresh();
|
||
|
||
try {
|
||
$this->addForeignKey(
|
||
$fkName,
|
||
$this_table_name,
|
||
$thisColumnName,
|
||
$that_table_name,
|
||
$thatColumnName,
|
||
$deleteAction
|
||
);
|
||
} catch (Throwable $th) {
|
||
echo Console::ansiFormat(
|
||
"\n\nОшибка конвертирования 'Внешнего ключа': {$th->getMessage()}\n\n",
|
||
[Console::BG_BLACK, Console::FG_RED]
|
||
);
|
||
|
||
$this->addForeignKey(
|
||
$fkName,
|
||
$this_table_name,
|
||
$thisColumnName,
|
||
$that_table_name,
|
||
$thatColumnName
|
||
);
|
||
|
||
throw $th;
|
||
}
|
||
}
|
||
|
||
public function refTableIsAllowed(
|
||
$allowNamePatternsForReftable,
|
||
$that_table_name
|
||
) {
|
||
$isMatch = false;
|
||
foreach ($allowNamePatternsForReftable as $pattern) {
|
||
if (preg_match($pattern, $that_table_name)) {
|
||
$isMatch = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
return $isMatch;
|
||
}
|
||
}
|