├── CHANGELOG.md ├── MigrateController.php ├── README.md └── composer.json /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | CHANGELOG 2 | ========= 3 | 4 | **dmstr/yii2-migrate-command** 5 | 6 | ### 0.4.0-beta1 7 | 8 | - updated Yii constraint (experimental), see also https://github.com/yiisoft/yii2/issues/13356 9 | 10 | ### 0.3.0 11 | 12 | - colorized output, code-formatting (@schmunk42) 13 | - sort history by apply_time first, then by version (@sblaut) 14 | - fix error opendir (@arswarog) 15 | - Fixed output of 'new' action (correct display of the migrations) (@omnilight) 16 | 17 | ### 0.2.0 18 | 19 | - fix param description 20 | - update docs 21 | - updated description 22 | - add option disableLookup 23 | - Fix for aliases in mark command (@omnilight) 24 | - addd migration lookup example 25 | 26 | ### 0.1.0 27 | 28 | - always display lookup directories, added bootstrapping instructions 29 | - fixed #2 30 | - fixed database connection display 31 | - display full path, not only alias 32 | - updated file checks 33 | - updated docs 34 | - initital commit, copied code from https://github.com/yiisoft/yii2/pull/3273/files -------------------------------------------------------------------------------- /MigrateController.php: -------------------------------------------------------------------------------- 1 | 63 | * @author Tobias Munk 64 | * @since 1.0 65 | */ 66 | class MigrateController extends Controller 67 | { 68 | /** 69 | * The name of the dummy migration that marks the beginning of the whole migration history. 70 | */ 71 | const BASE_MIGRATION = 'm000000_000000_base'; 72 | 73 | /** 74 | * @var string the default command action. 75 | */ 76 | public $defaultAction = 'up'; 77 | /** 78 | * @var string the directory storing the migration classes. This can be either 79 | * a path alias or a directory. 80 | */ 81 | public $migrationPath = '@app/migrations'; 82 | /** 83 | * @var array additional aliases of migration directories 84 | */ 85 | public $migrationLookup = []; 86 | /** 87 | * @var boolean lookup all application migration paths 88 | */ 89 | public $disableLookup = false; 90 | /** 91 | * @var string the name of the table for keeping applied migration information. 92 | */ 93 | public $migrationTable = '{{%migration}}'; 94 | /** 95 | * @var string the template file for generating new migrations. 96 | * This can be either a path alias (e.g. "@app/migrations/template.php") 97 | * or a file path. 98 | */ 99 | public $templateFile = '@yii/views/migration.php'; 100 | /** 101 | * @var boolean whether to execute the migration in an interactive mode. 102 | */ 103 | public $interactive = true; 104 | /** 105 | * @var Connection|string the DB connection object or the application 106 | * component ID of the DB connection. 107 | */ 108 | public $db = 'db'; 109 | 110 | /** 111 | * @inheritdoc 112 | */ 113 | public function options($actionId) 114 | { 115 | return array_merge( 116 | parent::options($actionId), 117 | ['migrationPath', 'migrationLookup', 'disableLookup', 'migrationTable', 'db'], // global for all actions 118 | ($actionId == 'create') ? ['templateFile'] : [] // action create 119 | ); 120 | } 121 | 122 | /** 123 | * This method is invoked right before an action is to be executed (after all possible filters.) 124 | * It checks the existence of the [[migrationPath]]. 125 | * 126 | * @param \yii\base\Action $action the action to be executed. 127 | * 128 | * @throws Exception if db component isn't configured 129 | * @return boolean whether the action should continue to be executed. 130 | */ 131 | public function beforeAction($action) 132 | { 133 | if (parent::beforeAction($action)) { 134 | $path = Yii::getAlias($this->migrationPath); 135 | 136 | if ($action->id !== 'create') { 137 | if (is_string($this->db)) { 138 | $this->db = Yii::$app->get($this->db); 139 | } 140 | if (!$this->db instanceof Connection) { 141 | throw new Exception( 142 | "The 'db' option must refer to the application component ID of a DB connection." 143 | ); 144 | } 145 | } else { 146 | if (!is_dir($path)) { 147 | $this->stdout("\n$path does not exist, creating..."); 148 | FileHelper::createDirectory($path); 149 | } 150 | } 151 | 152 | $version = Yii::getVersion(); 153 | $this->stdout("Yii Migration Tool (based on Yii v{$version})\n\n", Console::BOLD); 154 | if (isset($this->db->dsn)) { 155 | $this->stdout("Database Connection: " . $this->db->dsn . "\n", Console::FG_BLUE); 156 | } 157 | return true; 158 | } else { 159 | return false; 160 | } 161 | } 162 | 163 | /** 164 | * Upgrades the application by applying new migrations. 165 | * For example, 166 | * 167 | * ~~~ 168 | * yii migrate # apply all new migrations 169 | * yii migrate 3 # apply the first 3 new migrations 170 | * ~~~ 171 | * 172 | * @param integer $limit the number of new migrations to be applied. If 0, it means 173 | * applying all available new migrations. 174 | */ 175 | public function actionUp($limit = 0) 176 | { 177 | $migrations = $this->getNewMigrations(); 178 | if (empty($migrations)) { 179 | $this->stdout("No new migration found. Your system is up-to-date.\n"); 180 | 181 | return self::EXIT_CODE_NORMAL; 182 | } 183 | 184 | $total = count($migrations); 185 | $limit = (int)$limit; 186 | if ($limit > 0) { 187 | $migrations = array_slice($migrations, 0, $limit); 188 | } 189 | 190 | $n = count($migrations); 191 | if ($n === $total) { 192 | echo "Total $n new " . ($n === 1 ? 'migration' : 'migrations') . " to be applied:\n"; 193 | } else { 194 | echo "Total $n out of $total new " . ($total === 1 ? 'migration' : 'migrations') . " to be applied:\n"; 195 | } 196 | 197 | echo "\nMigrations:\n"; 198 | foreach ($migrations as $migration => $alias) { 199 | echo " " . $migration . " (" . $alias . ")\n"; 200 | } 201 | 202 | if ($this->confirm('Apply the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) { 203 | foreach ($migrations as $migration => $alias) { 204 | if (!$this->migrateUp($migration, $alias)) { 205 | echo "\nMigration failed. The rest of the migrations are canceled.\n"; 206 | 207 | return self::EXIT_CODE_ERROR; 208 | } 209 | } 210 | $this->stdout("\nMigrated up successfully.\n", Console::FG_GREEN); 211 | } 212 | } 213 | 214 | /** 215 | * Downgrades the application by reverting old migrations. 216 | * For example, 217 | * 218 | * ~~~ 219 | * yii migrate/down # revert the last migration 220 | * yii migrate/down 3 # revert the last 3 migrations 221 | * ~~~ 222 | * 223 | * @param integer $limit the number of migrations to be reverted. Defaults to 1, 224 | * meaning the last applied migration will be reverted. 225 | * 226 | * @throws Exception if the number of the steps specified is less than 1. 227 | */ 228 | public function actionDown($limit = 1) 229 | { 230 | $limit = (int)$limit; 231 | if ($limit < 1) { 232 | throw new Exception("The step argument must be greater than 0."); 233 | } 234 | 235 | $migrations = $this->getMigrationHistory($limit); 236 | if (empty($migrations)) { 237 | echo "No migration has been done before.\n"; 238 | 239 | return self::EXIT_CODE_NORMAL; 240 | } 241 | 242 | $n = count($migrations); 243 | echo "Total $n " . ($n === 1 ? 'migration' : 'migrations') . " to be reverted:\n"; 244 | foreach ($migrations as $migration => $info) { 245 | echo " $migration (" . $info['alias'] . ")\n"; 246 | } 247 | echo "\n"; 248 | 249 | if ($this->confirm('Revert the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) { 250 | foreach ($migrations as $migration => $info) { 251 | if (!$this->migrateDown($migration, $info['alias'])) { 252 | echo "\nMigration failed. The rest of the migrations are canceled.\n"; 253 | 254 | return self::EXIT_CODE_ERROR; 255 | } 256 | } 257 | echo "\nMigrated down successfully.\n"; 258 | } 259 | } 260 | 261 | /** 262 | * Redoes the last few migrations. 263 | * 264 | * This command will first revert the specified migrations, and then apply 265 | * them again. For example, 266 | * 267 | * ~~~ 268 | * yii migrate/redo # redo the last applied migration 269 | * yii migrate/redo 3 # redo the last 3 applied migrations 270 | * ~~~ 271 | * 272 | * @param integer $limit the number of migrations to be redone. Defaults to 1, 273 | * meaning the last applied migration will be redone. 274 | * 275 | * @throws Exception if the number of the steps specified is less than 1. 276 | */ 277 | public function actionRedo($limit = 1) 278 | { 279 | $limit = (int)$limit; 280 | if ($limit < 1) { 281 | throw new Exception("The step argument must be greater than 0."); 282 | } 283 | 284 | $migrations = $this->getMigrationHistory($limit); 285 | if (empty($migrations)) { 286 | echo "No migration has been done before.\n"; 287 | 288 | return self::EXIT_CODE_NORMAL; 289 | } 290 | 291 | $n = count($migrations); 292 | echo "Total $n " . ($n === 1 ? 'migration' : 'migrations') . " to be redone:\n"; 293 | foreach ($migrations as $migration => $info) { 294 | echo " $migration\n"; 295 | } 296 | echo "\n"; 297 | 298 | if ($this->confirm('Redo the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) { 299 | foreach ($migrations as $migration => $info) { 300 | if (!$this->migrateDown($migration, $info['alias'])) { 301 | echo "\nMigration failed. The rest of the migrations are canceled.\n"; 302 | 303 | return self::EXIT_CODE_ERROR; 304 | } 305 | } 306 | foreach (array_reverse($migrations) as $migration => $info) { 307 | if (!$this->migrateUp($migration, $info['alias'])) { 308 | echo "\nMigration failed. The rest of the migrations migrations are canceled.\n"; 309 | 310 | return self::EXIT_CODE_ERROR; 311 | } 312 | } 313 | echo "\nMigration redone successfully.\n"; 314 | } 315 | } 316 | 317 | /** 318 | * Upgrades or downgrades till the specified version. 319 | * 320 | * Can also downgrade versions to the certain apply time in the past by providing 321 | * a UNIX timestamp or a string parseable by the strtotime() function. This means 322 | * that all the versions applied after the specified certain time would be reverted. 323 | * 324 | * This command will first revert the specified migrations, and then apply 325 | * them again. For example, 326 | * 327 | * ~~~ 328 | * yii migrate/to 101129_185401 # using timestamp 329 | * yii migrate/to m101129_185401_create_user_table # using full name 330 | * yii migrate/to 1392853618 # using UNIX timestamp 331 | * yii migrate/to "2014-02-15 13:00:50" # using strtotime() parseable string 332 | * ~~~ 333 | * 334 | * @param string $version either the version name or the certain time value in the past 335 | * that the application should be migrated to. This can be either the timestamp, 336 | * the full name of the migration, the UNIX timestamp, or the parseable datetime 337 | * string. 338 | * 339 | * @throws Exception if the version argument is invalid. 340 | */ 341 | public function actionTo($version) 342 | { 343 | if (preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/', $version, $matches)) { 344 | $this->migrateToVersion('m' . $matches[1]); 345 | } elseif ((string)(int)$version == $version) { 346 | $this->migrateToTime($version); 347 | } elseif (($time = strtotime($version)) !== false) { 348 | $this->migrateToTime($time); 349 | } else { 350 | throw new Exception( 351 | "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)." 352 | ); 353 | } 354 | } 355 | 356 | /** 357 | * Modifies the migration history to the specified version. 358 | * 359 | * No actual migration will be performed. 360 | * 361 | * ~~~ 362 | * yii migrate/mark 101129_185401 # using timestamp 363 | * yii migrate/mark m101129_185401_create_user_table # using full name 364 | * ~~~ 365 | * 366 | * @param string $version the version at which the migration history should be marked. 367 | * This can be either the timestamp or the full name of the migration. 368 | * 369 | * @throws Exception if the version argument is invalid or the version cannot be found. 370 | */ 371 | public function actionMark($version) 372 | { 373 | $originalVersion = $version; 374 | if (preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/', $version, $matches)) { 375 | $version = 'm' . $matches[1]; 376 | } else { 377 | throw new Exception( 378 | "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)." 379 | ); 380 | } 381 | 382 | // try mark up 383 | $migrations = $this->getNewMigrations(); 384 | $i = 0; 385 | foreach ($migrations as $migration => $alias) { 386 | $stack[$migration] = $alias; 387 | if (strpos($migration, $version . '_') === 0) { 388 | if ($this->confirm("Set migration history at $originalVersion?")) { 389 | $command = $this->db->createCommand(); 390 | foreach ($stack AS $applyMigration => $applyAlias) { 391 | $command->insert( 392 | $this->migrationTable, 393 | [ 394 | 'version' => $applyMigration, 395 | 'alias' => $applyAlias, 396 | 'apply_time' => time(), 397 | ] 398 | )->execute(); 399 | } 400 | echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n"; 401 | } 402 | 403 | return self::EXIT_CODE_NORMAL; 404 | } 405 | $i++; 406 | } 407 | 408 | // try mark down 409 | $migrations = array_keys($this->getMigrationHistory(-1)); 410 | foreach ($migrations as $i => $migration) { 411 | if (strpos($migration, $version . '_') === 0) { 412 | if ($i === 0) { 413 | echo "Already at '$originalVersion'. Nothing needs to be done.\n"; 414 | } else { 415 | if ($this->confirm("Set migration history at $originalVersion?")) { 416 | $command = $this->db->createCommand(); 417 | for ($j = 0; $j < $i; ++$j) { 418 | $command->delete( 419 | $this->migrationTable, 420 | [ 421 | 'version' => $migrations[$j], 422 | ] 423 | )->execute(); 424 | } 425 | echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n"; 426 | } 427 | } 428 | 429 | return self::EXIT_CODE_NORMAL; 430 | } 431 | } 432 | 433 | throw new Exception("Unable to find the version '$originalVersion'."); 434 | } 435 | 436 | /** 437 | * Displays the migration history. 438 | * 439 | * This command will show the list of migrations that have been applied 440 | * so far. For example, 441 | * 442 | * ~~~ 443 | * yii migrate/history # showing the last 10 migrations 444 | * yii migrate/history 5 # showing the last 5 migrations 445 | * yii migrate/history 0 # showing the whole history 446 | * ~~~ 447 | * 448 | * @param integer $limit the maximum number of migrations to be displayed. 449 | * If it is 0, the whole migration history will be displayed. 450 | */ 451 | public function actionHistory($limit = 10) 452 | { 453 | $limit = (int)$limit; 454 | $migrations = $this->getMigrationHistory($limit); 455 | if (empty($migrations)) { 456 | echo "No migration has been done before.\n"; 457 | } else { 458 | $n = count($migrations); 459 | if ($limit > 0) { 460 | echo "Showing the last $n applied " . ($n === 1 ? 'migration' : 'migrations') . ":\n"; 461 | } else { 462 | echo "Total $n " . ($n === 1 ? 'migration has' : 'migrations have') . " been applied before:\n"; 463 | } 464 | foreach ($migrations as $version => $info) { 465 | echo " (" . date('Y-m-d H:i:s', $info['apply_time']) . ') ' . $version . "\n"; 466 | } 467 | } 468 | return self::EXIT_CODE_NORMAL; 469 | } 470 | 471 | /** 472 | * Displays the un-applied new migrations. 473 | * 474 | * This command will show the new migrations that have not been applied. 475 | * For example, 476 | * 477 | * ~~~ 478 | * yii migrate/new # showing the first 10 new migrations 479 | * yii migrate/new 5 # showing the first 5 new migrations 480 | * yii migrate/new 0 # showing all new migrations 481 | * ~~~ 482 | * 483 | * @param integer $limit the maximum number of new migrations to be displayed. 484 | * If it is 0, all available new migrations will be displayed. 485 | */ 486 | public function actionNew($limit = 10) 487 | { 488 | $limit = (int)$limit; 489 | $migrations = $this->getNewMigrations(); 490 | if (empty($migrations)) { 491 | echo "No new migrations found. Your system is up-to-date.\n"; 492 | } else { 493 | $n = count($migrations); 494 | if ($limit > 0 && $n > $limit) { 495 | $migrations = array_slice($migrations, 0, $limit); 496 | echo "Showing $limit out of $n new " . ($n === 1 ? 'migration' : 'migrations') . ":\n"; 497 | } else { 498 | echo "Found $n new " . ($n === 1 ? 'migration' : 'migrations') . ":\n"; 499 | } 500 | 501 | foreach ($migrations as $migration => $alias) { 502 | echo " " . $migration . " (" . $alias . ")" . "\n"; 503 | } 504 | } 505 | return self::EXIT_CODE_NORMAL; 506 | } 507 | 508 | /** 509 | * Creates a new migration. 510 | * 511 | * This command creates a new migration using the available migration template. 512 | * After using this command, developers should modify the created migration 513 | * skeleton by filling up the actual migration logic. 514 | * 515 | * ~~~ 516 | * yii migrate/create create_user_table 517 | * ~~~ 518 | * 519 | * @param string $name the name of the new migration. This should only contain 520 | * letters, digits and/or underscores. 521 | * 522 | * @throws Exception if the name argument is invalid. 523 | */ 524 | public function actionCreate($name) 525 | { 526 | if (!preg_match('/^\w+$/', $name)) { 527 | throw new Exception("The migration name should contain letters, digits and/or underscore characters only."); 528 | } 529 | 530 | $name = 'm' . gmdate('ymd_His') . '_' . $name; 531 | $file = Yii::getAlias($this->migrationPath) . DIRECTORY_SEPARATOR . $name . '.php'; 532 | 533 | if ($this->confirm("Create new migration '$file'?")) { 534 | $content = $this->renderFile(Yii::getAlias($this->templateFile), ['className' => $name]); 535 | file_put_contents(Yii::getAlias($file), $content); 536 | echo "New migration created successfully.\n"; 537 | } 538 | return self::EXIT_CODE_NORMAL; 539 | } 540 | 541 | /** 542 | * Upgrades with the specified migration class. 543 | * 544 | * @param string $class the migration class name 545 | * 546 | * @return boolean whether the migration is successful 547 | */ 548 | protected function migrateUp($class, $alias) 549 | { 550 | if ($class === self::BASE_MIGRATION) { 551 | return true; 552 | } 553 | 554 | echo "*** applying $class\n"; 555 | $start = microtime(true); 556 | $migration = $this->createMigration($class, $alias); 557 | if ($migration->up() !== false) { 558 | $this->db->createCommand()->insert( 559 | $this->migrationTable, 560 | [ 561 | 'version' => $class, 562 | 'alias' => $alias, 563 | 'apply_time' => time(), 564 | ] 565 | )->execute(); 566 | $time = microtime(true) - $start; 567 | echo "*** applied $class (time: " . sprintf("%.3f", $time) . "s)\n\n"; 568 | 569 | return true; 570 | } else { 571 | $time = microtime(true) - $start; 572 | echo "*** failed to apply $class (time: " . sprintf("%.3f", $time) . "s)\n\n"; 573 | 574 | return false; 575 | } 576 | } 577 | 578 | /** 579 | * Downgrades with the specified migration class. 580 | * 581 | * @param string $class the migration class name 582 | * 583 | * @return boolean whether the migration is successful 584 | */ 585 | protected function migrateDown($class, $alias) 586 | { 587 | if ($class === self::BASE_MIGRATION) { 588 | return true; 589 | } 590 | 591 | echo "*** reverting $class\n"; 592 | $start = microtime(true); 593 | $migration = $this->createMigration($class, $alias); 594 | if ($migration->down() !== false) { 595 | $this->db->createCommand()->delete( 596 | $this->migrationTable, 597 | [ 598 | 'version' => $class, 599 | ] 600 | )->execute(); 601 | $time = microtime(true) - $start; 602 | echo "*** reverted $class (time: " . sprintf("%.3f", $time) . "s)\n\n"; 603 | 604 | return true; 605 | } else { 606 | $time = microtime(true) - $start; 607 | echo "*** failed to revert $class (time: " . sprintf("%.3f", $time) . "s)\n\n"; 608 | 609 | return false; 610 | } 611 | } 612 | 613 | /** 614 | * Creates a new migration instance. 615 | * 616 | * @param string $class the migration class name 617 | * 618 | * @return \yii\db\Migration the migration instance 619 | */ 620 | protected function createMigration($class, $alias) 621 | { 622 | $file = $class . '.php'; 623 | require_once(\Yii::getAlias($alias) . '/' . $file); 624 | 625 | return new $class(['db' => $this->db]); 626 | } 627 | 628 | /** 629 | * Migrates to the specified apply time in the past. 630 | * 631 | * @param integer $time UNIX timestamp value. 632 | */ 633 | protected function migrateToTime($time) 634 | { 635 | $count = 0; 636 | $migrations = array_values($this->getMigrationHistory(-1)); 637 | while ($count < count($migrations) && $migrations[$count] > $time) { 638 | ++$count; 639 | } 640 | if ($count === 0) { 641 | echo "Nothing needs to be done.\n"; 642 | } else { 643 | $this->actionDown($count); 644 | } 645 | } 646 | 647 | /** 648 | * Migrates to the certain version. 649 | * 650 | * @param string $version name in the full format. 651 | * 652 | * @throws Exception if the provided version cannot be found. 653 | */ 654 | protected function migrateToVersion($version) 655 | { 656 | $originalVersion = $version; 657 | 658 | // try migrate up 659 | $migrations = $this->getNewMigrations(); 660 | $i = 0; 661 | foreach ($migrations as $migration => $alias) { 662 | if (strpos($migration, $version . '_') === 0) { 663 | $this->actionUp($i + 1); 664 | 665 | return; 666 | } 667 | $i++; 668 | } 669 | 670 | // try migrate down 671 | $migrations = array_keys($this->getMigrationHistory(-1)); 672 | foreach ($migrations as $i => $migration) { 673 | if (strpos($migration, $version . '_') === 0) { 674 | if ($i === 0) { 675 | echo "Already at '$originalVersion'. Nothing needs to be done.\n"; 676 | } else { 677 | $this->actionDown($i); 678 | } 679 | 680 | return; 681 | } 682 | } 683 | 684 | throw new Exception("Unable to find the version '$originalVersion'."); 685 | } 686 | 687 | /** 688 | * Returns the migration history. 689 | * 690 | * @param integer $limit the maximum number of records in the history to be returned 691 | * 692 | * @return array the migration history 693 | */ 694 | protected function getMigrationHistory($limit) 695 | { 696 | if ($this->db->schema->getTableSchema($this->migrationTable, true) === null) { 697 | $this->createMigrationHistoryTable(); 698 | } 699 | $query = new Query; 700 | if ($this->disableLookup === true) { 701 | $query->where(['alias' => $this->migrationPath]); 702 | } 703 | $rows = $query->select(['version', 'alias', 'apply_time']) 704 | ->from($this->migrationTable) 705 | ->orderBy('apply_time DESC, version DESC') 706 | ->limit($limit) 707 | ->createCommand($this->db) 708 | ->queryAll(); 709 | $history = ArrayHelper::map($rows, 'version', 'apply_time'); 710 | foreach ($rows AS $row) { 711 | $history[$row['version']] = ['apply_time' => $row['apply_time'], 'alias' => $row['alias']]; 712 | } 713 | 714 | unset($history[self::BASE_MIGRATION]); 715 | return $history; 716 | } 717 | 718 | /** 719 | * Creates the migration history table. 720 | */ 721 | protected function createMigrationHistoryTable() 722 | { 723 | $tableName = $this->db->schema->getRawTableName($this->migrationTable); 724 | echo "Creating migration history table \"$tableName\"..."; 725 | $this->db->createCommand()->createTable( 726 | $this->migrationTable, 727 | [ 728 | 'version' => 'varchar(180) NOT NULL PRIMARY KEY', 729 | 'alias' => 'varchar(180) NOT NULL', 730 | 'apply_time' => 'integer', 731 | ] 732 | )->execute(); 733 | $this->db->createCommand()->insert( 734 | $this->migrationTable, 735 | [ 736 | 'version' => self::BASE_MIGRATION, 737 | 'alias' => $this->migrationPath, 738 | 'apply_time' => time(), 739 | ] 740 | )->execute(); 741 | echo "done.\n"; 742 | } 743 | 744 | /** 745 | * Returns the migrations that are not applied. 746 | * @return array list of new migrations, (key: migration version; value: alias) 747 | */ 748 | protected function getNewMigrations() 749 | { 750 | $applied = []; 751 | foreach ($this->getMigrationHistory(-1) as $version => $info) { 752 | $applied[substr($version, 1, 13)] = true; 753 | } 754 | 755 | if (isset(\Yii::$app->params['yii.migrations'])) { 756 | $this->migrationLookup = ArrayHelper::merge($this->migrationLookup, \Yii::$app->params['yii.migrations']); 757 | } 758 | 759 | if ($this->migrationPath && $this->disableLookup) { 760 | $directories = [$this->migrationPath]; 761 | } else { 762 | $directories = ArrayHelper::merge([$this->migrationPath], $this->migrationLookup); 763 | } 764 | 765 | $migrations = []; 766 | 767 | echo "\nLookup:\n"; 768 | 769 | foreach ($directories AS $alias) { 770 | $dir = Yii::getAlias($alias); 771 | if (!is_dir($dir)) { 772 | $label = $this->ansiFormat('[warn]', Console::BG_YELLOW); 773 | $this->stdout(" {$label} " . $alias . " (" . \Yii::getAlias($alias) . ")\n"); 774 | Yii::warning("Migration lookup directory '{$alias}' not found", __METHOD__); 775 | continue; 776 | } 777 | $handle = opendir($dir); 778 | while (($file = readdir($handle)) !== false) { 779 | if ($file === '.' || $file === '..') { 780 | continue; 781 | } 782 | $path = $dir . DIRECTORY_SEPARATOR . $file; 783 | if (preg_match('/^(m(\d{6}_\d{6})_.*?)\.php$/', $file, $matches) && is_file( 784 | $path 785 | ) && !isset($applied[$matches[2]]) 786 | ) { 787 | $migrations[$matches[1]] = $alias; 788 | } 789 | } 790 | closedir($handle); 791 | $label = $this->ansiFormat('[ok]', Console::FG_GREEN); 792 | $this->stdout(" {$label} " . $alias . " (" . \Yii::getAlias($alias) . ")\n"); 793 | } 794 | ksort($migrations); 795 | 796 | $this->stdout("\n"); 797 | 798 | return $migrations; 799 | } 800 | } 801 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Extended Migration Command 2 | ========================== 3 | 4 | **DEPRECATION NOTICE - since Yii 2.0.12 this extension is obsolete, its functionality is now supported by the framework core.** For details see also [issue](https://github.com/dmstr/yii2-migrate-command/issues/23#issuecomment-326950039). 5 | 6 | --- 7 | 8 | [![Latest Stable Version](https://poser.pugx.org/dmstr/yii2-migrate-command/v/stable.svg)](https://packagist.org/packages/dmstr/yii2-migrate-command) 9 | [![Total Downloads](https://poser.pugx.org/dmstr/yii2-migrate-command/downloads.svg)](https://packagist.org/packages/dmstr/yii2-migrate-command) 10 | [![License](https://poser.pugx.org/dmstr/yii2-migrate-command/license.svg)](https://packagist.org/packages/dmstr/yii2-migrate-command) 11 | 12 | Console Migration Command with multiple paths/aliases support 13 | 14 | This extension was created from this [Pull Request](https://github.com/yiisoft/yii2/pull/3273) on GitHub, which became unmergeable. 15 | Until this feature will be reimplemented into the core, you can use this extension if you need to handle multiple migration paths. 16 | 17 | > Note! If using `dmstr/yii2-migrate-command` in an existing project, you may have to remove your *migration* table, due to a schema change. 18 | 19 | Installation 20 | ------------ 21 | 22 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/). 23 | 24 | ``` 25 | composer require dmstr/yii2-migrate-command 26 | ``` 27 | 28 | Usage 29 | ----- 30 | 31 | Configure the command in your `main` application configuration: 32 | 33 | ``` 34 | 'controllerMap' => [ 35 | 'migrate' => [ 36 | 'class' => 'dmstr\console\controllers\MigrateController' 37 | ], 38 | ], 39 | ``` 40 | 41 | Once the extension is installed and configured, simply use it on your command line 42 | 43 | ``` 44 | ./yii migrate 45 | ``` 46 | 47 | 48 | ### Adding migrations via application configuration 49 | 50 | Add additional migration paths via application `params`: 51 | 52 | ``` 53 | "yii.migrations"=> [ 54 | "@dektrium/user/migrations", 55 | ], 56 | ``` 57 | 58 | ### Adding migrations via extension `bootstrap()` 59 | 60 | You can also add migrations in your module bootstrap process: 61 | 62 | ``` 63 | public function bootstrap($app) 64 | { 65 | $app->params['yii.migrations'][] = '@vendorname/packagename/migrations'; 66 | } 67 | ``` 68 | 69 | ### Adding migrations via command parameter 70 | 71 | If you want to specify an additional path directly with the command, use 72 | 73 | ``` 74 | ./yii migrate --migrationLookup=@somewhere/migrations/data1,@somewhere/migrations/data2 75 | ``` 76 | 77 | > Note! Please make sure to **not use spaces** in the comma separated list. 78 | 79 | --- 80 | 81 | Built by [dmstr](http://diemeisterei.de), Stuttgart 82 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dmstr/yii2-migrate-command", 3 | "description": "Console Migration Command with multiple paths/aliases support", 4 | "type": "yii2-extension", 5 | "keywords": ["yii2","extension","migration","command","console"], 6 | "license": "BSD-3-Clause", 7 | "authors": [ 8 | { 9 | "name": "Tobias Munk", 10 | "email": "tobias@diemeisterei.de" 11 | } 12 | ], 13 | "autoload": { 14 | "psr-4": { 15 | "dmstr\\console\\controllers\\": "" 16 | } 17 | }, 18 | "require": { 19 | "yiisoft/yii2": "~2.0.10" 20 | } 21 | } 22 | --------------------------------------------------------------------------------