diff --git a/src/Console/Commands/Migration.php b/src/Console/Commands/Migration.php index 154f31f..d79e303 100644 --- a/src/Console/Commands/Migration.php +++ b/src/Console/Commands/Migration.php @@ -14,14 +14,12 @@ namespace Phalcon\Migrations\Console\Commands; use Phalcon\Config; -use Phalcon\Cop\Parser; -use Phalcon\Migrations\Console\Color; -use Phalcon\Migrations\Migrations; -use Phalcon\Migrations\Script\ScriptException; -use Phalcon\Mvc\Model\Exception; use Phalcon\Config\Adapter\Ini as IniConfig; use Phalcon\Config\Adapter\Json as JsonConfig; use Phalcon\Config\Adapter\Yaml as YamlConfig; +use Phalcon\Cop\Parser; +use Phalcon\Migrations\Console\Color; +use Phalcon\Migrations\Migrations; /** * Migration Command @@ -69,8 +67,8 @@ public function getPossibleParams(): array /** * @throws CommandsException - * @throws ScriptException - * @throws Exception + * @throws \Phalcon\Db\Exception + * @throws \Exception */ public function run(): void { @@ -146,15 +144,16 @@ public function run(): void break; case 'run': Migrations::run([ - 'directory' => $path, - 'tableName' => $tableName, - 'migrationsDir' => $migrationsDir, - 'force' => $this->parser->has('force'), - 'tsBased' => $migrationsTsBased, - 'config' => $config, - 'version' => $this->parser->get('version'), - 'migrationsInDb' => $migrationsInDb, - 'verbose' => $this->parser->has('verbose'), + 'directory' => $path, + 'tableName' => $tableName, + 'migrationsDir' => $migrationsDir, + 'force' => $this->parser->has('force'), + 'tsBased' => $migrationsTsBased, + 'config' => $config, + 'version' => $this->parser->get('version'), + 'migrationsInDb' => $migrationsInDb, + 'verbose' => $this->parser->has('verbose'), + 'skip-foreign-checks' => $this->parser->has('skip-foreign-checks'), ]); break; case 'list': diff --git a/src/Migrations.php b/src/Migrations.php index 2ac2260..83daed7 100644 --- a/src/Migrations.php +++ b/src/Migrations.php @@ -28,6 +28,7 @@ use Phalcon\Migrations\Version\IncrementalItem; use Phalcon\Migrations\Version\ItemCollection as VersionCollection; use Phalcon\Migrations\Version\TimestampedItem; +use SplFileInfo; class Migrations { @@ -191,6 +192,7 @@ public static function generate(array $options) * @param array $options * @throws DbException * @throws RuntimeException + * @throws Exception */ public static function run(array $options) { @@ -198,6 +200,7 @@ public static function run(array $options) $listTables = new ListTablesIterator(); $optionStack->setOptions($options); $optionStack->setDefaultOption('verbose', false); + $optionStack->setDefaultOption('skip-foreign-checks', false); // Define versioning type to be used if (!empty($options['tsBased']) || $optionStack->getOption('tsBased')) { @@ -355,12 +358,18 @@ public static function run(array $options) $migrationStartTime = date('Y-m-d H:i:s'); if ($optionStack->getOption('tableName') === '@') { + /** @var SplFileInfo $fileInfo */ foreach ($iterator as $fileInfo) { if (!$fileInfo->isFile() || 0 !== strcasecmp($fileInfo->getExtension(), 'php')) { continue; } - ModelMigration::migrate($fileInfo->getBasename('.php'), $initialVersion, $versionItem); + ModelMigration::migrate( + $fileInfo->getBasename('.php'), + $initialVersion, + $versionItem, + $optionStack->getOption('skip-foreign-checks') + ); } } else { if (!empty($prefix)) { @@ -369,7 +378,12 @@ public static function run(array $options) $tables = explode(',', $optionStack->getOption('tableName')); foreach ($tables as $tableName) { - ModelMigration::migrate($tableName, $initialVersion, $versionItem); + ModelMigration::migrate( + $tableName, + $initialVersion, + $versionItem, + $optionStack->getOption('skip-foreign-checks') + ); } } diff --git a/src/Mvc/Model/Migration.php b/src/Mvc/Model/Migration.php index c7564d1..f969cdf 100644 --- a/src/Mvc/Model/Migration.php +++ b/src/Mvc/Model/Migration.php @@ -328,12 +328,14 @@ public static function shouldExportDataFromTable(string $table, array $exportTab * @param string $tableName * @param ItemInterface|null $fromVersion * @param ItemInterface|null $toVersion + * @param bool $skipForeignChecks * @throws Exception */ public static function migrate( string $tableName, ItemInterface $fromVersion = null, - ItemInterface $toVersion = null + ItemInterface $toVersion = null, + bool $skipForeignChecks = false ): void { $fromVersion = $fromVersion ?: VersionCollection::createItem($fromVersion); $toVersion = $toVersion ?: VersionCollection::createItem($toVersion); @@ -342,6 +344,10 @@ public static function migrate( return; // nothing to do } + if ($skipForeignChecks === true) { + self::$connection->execute('SET FOREIGN_KEY_CHECKS=0'); + } + if ($fromVersion->getStamp() < $toVersion->getStamp()) { $toMigration = self::createClass($toVersion, $tableName); @@ -378,6 +384,10 @@ public static function migrate( $toMigration->morph(); } } + + if ($skipForeignChecks === true) { + self::$connection->execute('SET FOREIGN_KEY_CHECKS=1'); + } } /** diff --git a/tests/_support/Helper/Mysql.php b/tests/_support/Helper/Mysql.php index 03b9cb9..71d8fc9 100644 --- a/tests/_support/Helper/Mysql.php +++ b/tests/_support/Helper/Mysql.php @@ -31,17 +31,23 @@ public function _initialize() public function _before(TestInterface $test) { + $this->setForeignKeys(); foreach ($this->getPhalconDb()->listTables() as $table) { $this->getPhalconDb()->dropTable($table); } + + $this->setForeignKeys(true); } public function _after(TestInterface $test) { + $this->setForeignKeys(); foreach ($this->getPhalconDb()->listTables() as $table) { $this->getPhalconDb()->dropTable($table); } + $this->setForeignKeys(true); + /** * Reset filename or DB connection */ @@ -123,4 +129,14 @@ public function batchInsert(string $table, array $columns, array $rows) $this->getPhalconDb()->execute($query); } + + /** + * Executes 'SET FOREIGN_KEY_CHECKS' query + * + * @param bool $enabled + */ + protected function setForeignKeys(bool $enabled = false): void + { + $this->getPhalconDb()->execute('SET FOREIGN_KEY_CHECKS=' . intval($enabled)); + } } diff --git a/tests/cli/RunCest.php b/tests/cli/RunCest.php index 7499966..11ec3f0 100644 --- a/tests/cli/RunCest.php +++ b/tests/cli/RunCest.php @@ -15,9 +15,15 @@ use CliTester; use Phalcon\Db\Column; +use Phalcon\Db\Reference; final class RunCest { + /** + * @var string + */ + protected $configPath = 'tests/_data/cli/migrations.php'; + /** * @param CliTester $I */ @@ -46,14 +52,113 @@ public function generateAndRun(CliTester $I): void ], ]); - $configPath = 'tests/_data/cli/migrations.php'; + $I->runShellCommand('php phalcon-migrations generate --config=' . $this->configPath); + $I->seeInShellOutput('Success: Version 1.0.0 was successfully generated'); + $I->seeResultCodeIs(0); + + $I->runShellCommand('php phalcon-migrations run --config=' . $this->configPath); + $I->seeInShellOutput('Success: Version 1.0.0 was successfully migrated'); + $I->seeResultCodeIs(0); + } + + /** + * @param CliTester $I + */ + public function skipForeignKeys(CliTester $I): void + { + $table1 = 'client'; + $table2 = 'x-skip-foreign-keys'; + + $this->createTablesWithForeignKey($I); - $I->runShellCommand('php phalcon-migrations generate --config=' . $configPath); + $I->runShellCommand('php phalcon-migrations generate --config=' . $this->configPath); $I->seeInShellOutput('Success: Version 1.0.0 was successfully generated'); $I->seeResultCodeIs(0); - $I->runShellCommand('php phalcon-migrations run --config=' . $configPath); + $migrationContent = file_get_contents(codecept_output_dir('1.0.0/' . $table2 . '.php')); + $I->assertNotFalse(strpos($migrationContent, "'referencedTable' => 'client',")); + + $I->getPhalconDb()->dropTable($table2); + $I->getPhalconDb()->dropTable($table1); + + $I->runShellCommand('php phalcon-migrations run --skip-foreign-checks --config=' . $this->configPath); $I->seeInShellOutput('Success: Version 1.0.0 was successfully migrated'); $I->seeResultCodeIs(0); } + + /** + * @param CliTester $I + */ + public function expectForeignKeyDbError(CliTester $I): void + { + $table1 = 'client'; + $table2 = 'x-skip-foreign-keys'; + + $this->createTablesWithForeignKey($I); + + $I->runShellCommand('php phalcon-migrations generate --config=' . $this->configPath); + $I->seeInShellOutput('Success: Version 1.0.0 was successfully generated'); + $I->seeResultCodeIs(0); + + $migrationContent = file_get_contents(codecept_output_dir('1.0.0/' . $table2 . '.php')); + $I->assertNotFalse(strpos($migrationContent, "'referencedTable' => 'client',")); + + $I->getPhalconDb()->dropTable($table2); + $I->getPhalconDb()->dropTable($table1); + + $I->runShellCommand('php phalcon-migrations run --config=' . $this->configPath, false); + $I->seeInShellOutput('Fatal Error: SQLSTATE[HY000]: General error: 1215 Cannot add foreign key constraint'); + $I->seeResultCodeIs(1); + } + + /** + * DRY! + * + * @param CliTester $I + */ + protected function createTablesWithForeignKey(CliTester $I): void + { + $schema = getenv('MYSQL_TEST_DB_DATABASE'); + $table1 = 'client'; + $table2 = 'x-skip-foreign-keys'; + + $I->getPhalconDb()->createTable($table1, $schema, [ + 'columns' => [ + new Column('id', [ + 'type' => Column::TYPE_INTEGER, + 'size' => 11, + 'notNull' => true, + 'primary' => true, + ]), + ], + ]); + + $I->getPhalconDb()->createTable($table2, $schema, [ + 'columns' => [ + new Column('id', [ + 'type' => Column::TYPE_INTEGER, + 'size' => 10, + 'notNull' => true, + ]), + new Column('clientId', [ + 'type' => Column::TYPE_INTEGER, + 'size' => 11, + 'notNull' => true, + ]), + ], + 'references' => [ + new Reference( + 'fk_client_1', + [ + 'referencedSchema' => $schema, + 'referencedTable' => $table1, + 'columns' => ['clientId'], + 'referencedColumns' => ['id'], + 'onUpdate' => 'NO ACTION', + 'onDelete' => 'NO ACTION', + ] + ), + ], + ]); + } }