diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 5d5d940fb8..a76a492b03 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -117,6 +117,7 @@ Yii Framework 2 Change Log - Enh #2436: Label of the attribute, which looks like `relatedModel.attribute`, will be received from the related model if it available (djagya) - Enh #2415: Added support for inverse relations (qiangxue) - Enh #2490: `yii\db\Query::count()` and other query scalar methods now properly handle queries with GROUP BY clause (qiangxue) +- Enh #2499: Added ability to downgrade migrations by their absolute apply time (resurtm, gorcer) - Enh: Added support for using arrays as option values for console commands (qiangxue) - Enh: Added `favicon.ico` and `robots.txt` to default application templates (samdark) - Enh: Added `Widget::autoIdPrefix` to support prefixing automatically generated widget IDs (qiangxue) diff --git a/framework/console/controllers/MigrateController.php b/framework/console/controllers/MigrateController.php index b72e9cce29..ab521d682e 100644 --- a/framework/console/controllers/MigrateController.php +++ b/framework/console/controllers/MigrateController.php @@ -286,50 +286,37 @@ class MigrateController extends Controller /** * Upgrades or downgrades till the specified version. * + * Can also downgrade versions to the certain apply time in the past by providing + * a UNIX timestamp or a string parseable by the strtotime() function. This means + * that all the versions applied after the specified certain time would be reverted. + * * This command will first revert the specified migrations, and then apply * them again. For example, * * ~~~ * yii migrate/to 101129_185401 # using timestamp * yii migrate/to m101129_185401_create_user_table # using full name + * yii migrate/to 1392853618 # using UNIX timestamp + * yii migrate/to "2014-02-15 13:00:50" # using strtotime() parseable string * ~~~ * - * @param string $version the version name that the application should be migrated to. - * This can be either the timestamp or the full name of the migration. - * @throws Exception if the version argument is invalid + * @param string $version either the version name or the certain time value in the past + * that the application should be migrated to. This can be either the timestamp, + * the full name of the migration, the UNIX timestamp, or the parseable datetime + * string. + * @throws Exception if the version argument is invalid. */ public function actionTo($version) { - $originalVersion = $version; if (preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/', $version, $matches)) { - $version = 'm' . $matches[1]; + $this->migrateToVersion('m' . $matches[1]); + } elseif ((string)(int)$version == $version) { + $this->migrateToTime($version); + } elseif (($time = strtotime($version)) !== false) { + $this->migrateToTime($time); } else { - throw new Exception("The version argument must be either a timestamp (e.g. 101129_185401)\nor the full name of a migration (e.g. m101129_185401_create_user_table)."); + throw new Exception("The version argument must be either a timestamp (e.g. 101129_185401),\n the full name of a migration (e.g. m101129_185401_create_user_table),\n a UNIX timestamp (e.g. 1392853000), or a datetime string parseable\nby the strtotime() function (e.g. 2014-02-15 13:00:50)."); } - - // try migrate up - $migrations = $this->getNewMigrations(); - foreach ($migrations as $i => $migration) { - if (strpos($migration, $version . '_') === 0) { - $this->actionUp($i + 1); - return; - } - } - - // try migrate down - $migrations = array_keys($this->getMigrationHistory(-1)); - foreach ($migrations as $i => $migration) { - if (strpos($migration, $version . '_') === 0) { - if ($i === 0) { - echo "Already at '$originalVersion'. Nothing needs to be done.\n"; - } else { - $this->actionDown($i); - } - return; - } - } - - throw new Exception("Unable to find the version '$originalVersion'."); } /** @@ -567,6 +554,58 @@ class MigrateController extends Controller return new $class(['db' => $this->db]); } + /** + * Migrates to the specified apply time in the past. + * @param integer $time UNIX timestamp value. + */ + protected function migrateToTime($time) + { + $count = 0; + $migrations = array_values($this->getMigrationHistory(-1)); + while ($count < count($migrations) && $migrations[$count] > $time) { + ++$count; + } + if ($count === 0) { + echo "Nothing needs to be done.\n"; + } else { + $this->actionDown($count); + } + } + + /** + * Migrates to the certain version. + * @param string $version name in the full format. + * @throws Exception if the provided version cannot be found. + */ + protected function migrateToVersion($version) + { + $originalVersion = $version; + + // try migrate up + $migrations = $this->getNewMigrations(); + foreach ($migrations as $i => $migration) { + if (strpos($migration, $version . '_') === 0) { + $this->actionUp($i + 1); + return; + } + } + + // try migrate down + $migrations = array_keys($this->getMigrationHistory(-1)); + foreach ($migrations as $i => $migration) { + if (strpos($migration, $version . '_') === 0) { + if ($i === 0) { + echo "Already at '$originalVersion'. Nothing needs to be done.\n"; + } else { + $this->actionDown($i); + } + return; + } + } + + throw new Exception("Unable to find the version '$originalVersion'."); + } + /** * Returns the migration history. * @param integer $limit the maximum number of records in the history to be returned