Complex classes like BaseMigrateController 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
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 BaseMigrateController, and based on these observations, apply Extract Interface, too.
| 1 | <?php  | 
            ||
| 25 | abstract class BaseMigrateController extends Controller  | 
            ||
| 26 | { | 
            ||
| 27 | /**  | 
            ||
| 28 | * The name of the dummy migration that marks the beginning of the whole migration history.  | 
            ||
| 29 | */  | 
            ||
| 30 | const BASE_MIGRATION = 'm000000_000000_base';  | 
            ||
| 31 | |||
| 32 | /**  | 
            ||
| 33 | * @var string the default command action.  | 
            ||
| 34 | */  | 
            ||
| 35 | public $defaultAction = 'up';  | 
            ||
| 36 | /**  | 
            ||
| 37 | * @var string|array the directory containing the migration classes. This can be either  | 
            ||
| 38 | * a [path alias](guide:concept-aliases) or a directory path.  | 
            ||
| 39 | *  | 
            ||
| 40 | * Migration classes located at this path should be declared without a namespace.  | 
            ||
| 41 | * Use [[migrationNamespaces]] property in case you are using namespaced migrations.  | 
            ||
| 42 | *  | 
            ||
| 43 | * If you have set up [[migrationNamespaces]], you may set this field to `null` in order  | 
            ||
| 44 | * to disable usage of migrations that are not namespaced.  | 
            ||
| 45 | *  | 
            ||
| 46 | * Since version 2.0.12 you may also specify an array of migration paths that should be searched for  | 
            ||
| 47 | * migrations to load. This is mainly useful to support old extensions that provide migrations  | 
            ||
| 48 | * without namespace and to adopt the new feature of namespaced migrations while keeping existing migrations.  | 
            ||
| 49 | *  | 
            ||
| 50 | * In general, to load migrations from different locations, [[migrationNamespaces]] is the preferable solution  | 
            ||
| 51 | * as the migration name contains the origin of the migration in the history, which is not the case when  | 
            ||
| 52 | * using multiple migration paths.  | 
            ||
| 53 | *  | 
            ||
| 54 | * @see $migrationNamespaces  | 
            ||
| 55 | */  | 
            ||
| 56 | public $migrationPath = ['@app/migrations'];  | 
            ||
| 57 | /**  | 
            ||
| 58 | * @var array list of namespaces containing the migration classes.  | 
            ||
| 59 | *  | 
            ||
| 60 | * Migration namespaces should be resolvable as a [path alias](guide:concept-aliases) if prefixed with `@`, e.g. if you specify  | 
            ||
| 61 |      * the namespace `app\migrations`, the code `Yii::getAlias('@app/migrations')` should be able to return | 
            ||
| 62 | * the file path to the directory this namespace refers to.  | 
            ||
| 63 | * This corresponds with the [autoloading conventions](guide:concept-autoloading) of Yii.  | 
            ||
| 64 | *  | 
            ||
| 65 | * For example:  | 
            ||
| 66 | *  | 
            ||
| 67 | * ```php  | 
            ||
| 68 | * [  | 
            ||
| 69 | * 'app\migrations',  | 
            ||
| 70 | * 'some\extension\migrations',  | 
            ||
| 71 | * ]  | 
            ||
| 72 | * ```  | 
            ||
| 73 | *  | 
            ||
| 74 | * @since 2.0.10  | 
            ||
| 75 | * @see $migrationPath  | 
            ||
| 76 | */  | 
            ||
| 77 | public $migrationNamespaces = [];  | 
            ||
| 78 | /**  | 
            ||
| 79 | * @var string the template file for generating new migrations.  | 
            ||
| 80 | * This can be either a [path alias](guide:concept-aliases) (e.g. "@app/migrations/template.php")  | 
            ||
| 81 | * or a file path.  | 
            ||
| 82 | */  | 
            ||
| 83 | public $templateFile;  | 
            ||
| 84 | |||
| 85 | |||
| 86 | /**  | 
            ||
| 87 | * @inheritdoc  | 
            ||
| 88 | */  | 
            ||
| 89 | 24 | public function options($actionID)  | 
            |
| 90 |     { | 
            ||
| 91 | 24 | return array_merge(  | 
            |
| 92 | 24 | parent::options($actionID),  | 
            |
| 93 | 24 | ['migrationPath', 'migrationNamespaces'], // global for all actions  | 
            |
| 94 | 24 | $actionID === 'create' ? ['templateFile'] : [] // action create  | 
            |
| 95 | );  | 
            ||
| 96 | }  | 
            ||
| 97 | |||
| 98 | /**  | 
            ||
| 99 | * This method is invoked right before an action is to be executed (after all possible filters.)  | 
            ||
| 100 | * It checks the existence of the [[migrationPath]].  | 
            ||
| 101 | * @param \yii\base\Action $action the action to be executed.  | 
            ||
| 102 | * @throws InvalidConfigException if directory specified in migrationPath doesn't exist and action isn't "create".  | 
            ||
| 103 | * @return bool whether the action should continue to be executed.  | 
            ||
| 104 | */  | 
            ||
| 105 | 33 | public function beforeAction($action)  | 
            |
| 106 |     { | 
            ||
| 107 | 33 |         if (parent::beforeAction($action)) { | 
            |
| 108 | 33 |             if (empty($this->migrationNamespaces) && empty($this->migrationPath)) { | 
            |
| 109 |                 throw new InvalidConfigException('At least one of `migrationPath` or `migrationNamespaces` should be specified.'); | 
            ||
| 110 | }  | 
            ||
| 111 | |||
| 112 | 33 |             foreach ($this->migrationNamespaces as $key => $value) { | 
            |
| 113 | 8 | $this->migrationNamespaces[$key] = trim($value, '\\');  | 
            |
| 114 | }  | 
            ||
| 115 | |||
| 116 | 33 |             if (is_array($this->migrationPath)) { | 
            |
| 117 | 7 |                 foreach ($this->migrationPath as $i => $path) { | 
            |
| 118 | 7 | $this->migrationPath[$i] = Yii::getAlias($path);  | 
            |
| 119 | }  | 
            ||
| 120 | 26 |             } elseif ($this->migrationPath !== null) { | 
            |
| 121 | 20 | $path = Yii::getAlias($this->migrationPath);  | 
            |
| 122 | 20 |                 if (!is_dir($path)) { | 
            |
| 123 | 5 |                     if ($action->id !== 'create') { | 
            |
| 124 |                         throw new InvalidConfigException("Migration failed. Directory specified in migrationPath doesn't exist: {$this->migrationPath}"); | 
            ||
| 125 | }  | 
            ||
| 126 | 5 | FileHelper::createDirectory($path);  | 
            |
| 
                                                                                                    
                        
                         | 
                |||
| 127 | }  | 
            ||
| 128 | 20 | $this->migrationPath = $path;  | 
            |
| 129 | }  | 
            ||
| 130 | |||
| 131 | 33 | $version = Yii::getVersion();  | 
            |
| 132 | 33 |             $this->stdout("Yii Migration Tool (based on Yii v{$version})\n\n"); | 
            |
| 133 | |||
| 134 | 33 | return true;  | 
            |
| 135 | }  | 
            ||
| 136 | |||
| 137 | return false;  | 
            ||
| 138 | }  | 
            ||
| 139 | |||
| 140 | /**  | 
            ||
| 141 | * Upgrades the application by applying new migrations.  | 
            ||
| 142 | *  | 
            ||
| 143 | * For example,  | 
            ||
| 144 | *  | 
            ||
| 145 | * ```  | 
            ||
| 146 | * yii migrate # apply all new migrations  | 
            ||
| 147 | * yii migrate 3 # apply the first 3 new migrations  | 
            ||
| 148 | * ```  | 
            ||
| 149 | *  | 
            ||
| 150 | * @param int $limit the number of new migrations to be applied. If 0, it means  | 
            ||
| 151 | * applying all available new migrations.  | 
            ||
| 152 | *  | 
            ||
| 153 | * @return int the status of the action execution. 0 means normal, other values mean abnormal.  | 
            ||
| 154 | */  | 
            ||
| 155 | 22 | public function actionUp($limit = 0)  | 
            |
| 156 |     { | 
            ||
| 157 | 22 | $migrations = $this->getNewMigrations();  | 
            |
| 158 | 22 |         if (empty($migrations)) { | 
            |
| 159 | 1 |             $this->stdout("No new migrations found. Your system is up-to-date.\n", Console::FG_GREEN); | 
            |
| 160 | |||
| 161 | 1 | return ExitCode::OK;  | 
            |
| 162 | }  | 
            ||
| 163 | |||
| 164 | 21 | $total = count($migrations);  | 
            |
| 165 | 21 | $limit = (int) $limit;  | 
            |
| 166 | 21 |         if ($limit > 0) { | 
            |
| 167 | 4 | $migrations = array_slice($migrations, 0, $limit);  | 
            |
| 168 | }  | 
            ||
| 169 | |||
| 170 | 21 | $n = count($migrations);  | 
            |
| 171 | 21 |         if ($n === $total) { | 
            |
| 172 | 20 |             $this->stdout("Total $n new " . ($n === 1 ? 'migration' : 'migrations') . " to be applied:\n", Console::FG_YELLOW); | 
            |
| 173 |         } else { | 
            ||
| 174 | 2 |             $this->stdout("Total $n out of $total new " . ($total === 1 ? 'migration' : 'migrations') . " to be applied:\n", Console::FG_YELLOW); | 
            |
| 175 | }  | 
            ||
| 176 | |||
| 177 | 21 |         foreach ($migrations as $migration) { | 
            |
| 178 | 21 |             $this->stdout("\t$migration\n"); | 
            |
| 179 | }  | 
            ||
| 180 | 21 |         $this->stdout("\n"); | 
            |
| 181 | |||
| 182 | 21 | $applied = 0;  | 
            |
| 183 | 21 |         if ($this->confirm('Apply the above ' . ($n === 1 ? 'migration' : 'migrations') . '?')) { | 
            |
| 184 | 21 |             foreach ($migrations as $migration) { | 
            |
| 185 | 21 |                 if (!$this->migrateUp($migration)) { | 
            |
| 186 |                     $this->stdout("\n$applied from $n " . ($applied === 1 ? 'migration was' : 'migrations were') . " applied.\n", Console::FG_RED); | 
            ||
| 187 |                     $this->stdout("\nMigration failed. The rest of the migrations are canceled.\n", Console::FG_RED); | 
            ||
| 188 | |||
| 189 | return ExitCode::UNSPECIFIED_ERROR;  | 
            ||
| 190 | }  | 
            ||
| 191 | 21 | $applied++;  | 
            |
| 192 | }  | 
            ||
| 193 | |||
| 194 | 21 |             $this->stdout("\n$n " . ($n === 1 ? 'migration was' : 'migrations were') . " applied.\n", Console::FG_GREEN); | 
            |
| 195 | 21 |             $this->stdout("\nMigrated up successfully.\n", Console::FG_GREEN); | 
            |
| 196 | }  | 
            ||
| 197 | 21 | }  | 
            |
| 198 | |||
| 199 | /**  | 
            ||
| 200 | * Downgrades the application by reverting old migrations.  | 
            ||
| 201 | *  | 
            ||
| 202 | * For example,  | 
            ||
| 203 | *  | 
            ||
| 204 | * ```  | 
            ||
| 205 | * yii migrate/down # revert the last migration  | 
            ||
| 206 | * yii migrate/down 3 # revert the last 3 migrations  | 
            ||
| 207 | * yii migrate/down all # revert all migrations  | 
            ||
| 208 | * ```  | 
            ||
| 209 | *  | 
            ||
| 210 | * @param int|string $limit the number of migrations to be reverted. Defaults to 1,  | 
            ||
| 211 | * meaning the last applied migration will be reverted. When value is "all", all migrations will be reverted.  | 
            ||
| 212 | * @throws Exception if the number of the steps specified is less than 1.  | 
            ||
| 213 | *  | 
            ||
| 214 | * @return int the status of the action execution. 0 means normal, other values mean abnormal.  | 
            ||
| 215 | */  | 
            ||
| 216 | 11 | public function actionDown($limit = 1)  | 
            |
| 217 |     { | 
            ||
| 218 | 11 |         if ($limit === 'all') { | 
            |
| 219 | 1 | $limit = null;  | 
            |
| 220 |         } else { | 
            ||
| 221 | 10 | $limit = (int) $limit;  | 
            |
| 222 | 10 |             if ($limit < 1) { | 
            |
| 223 |                 throw new Exception('The step argument must be greater than 0.'); | 
            ||
| 224 | }  | 
            ||
| 225 | }  | 
            ||
| 226 | |||
| 227 | 11 | $migrations = $this->getMigrationHistory($limit);  | 
            |
| 228 | |||
| 229 | 11 |         if (empty($migrations)) { | 
            |
| 230 |             $this->stdout("No migration has been done before.\n", Console::FG_YELLOW); | 
            ||
| 231 | |||
| 232 | return ExitCode::OK;  | 
            ||
| 233 | }  | 
            ||
| 234 | |||
| 235 | 11 | $migrations = array_keys($migrations);  | 
            |
| 236 | |||
| 237 | 11 | $n = count($migrations);  | 
            |
| 238 | 11 |         $this->stdout("Total $n " . ($n === 1 ? 'migration' : 'migrations') . " to be reverted:\n", Console::FG_YELLOW); | 
            |
| 239 | 11 |         foreach ($migrations as $migration) { | 
            |
| 240 | 11 |             $this->stdout("\t$migration\n"); | 
            |
| 241 | }  | 
            ||
| 242 | 11 |         $this->stdout("\n"); | 
            |
| 243 | |||
| 244 | 11 | $reverted = 0;  | 
            |
| 245 | 11 |         if ($this->confirm('Revert the above ' . ($n === 1 ? 'migration' : 'migrations') . '?')) { | 
            |
| 246 | 11 |             foreach ($migrations as $migration) { | 
            |
| 247 | 11 |                 if (!$this->migrateDown($migration)) { | 
            |
| 248 |                     $this->stdout("\n$reverted from $n " . ($reverted === 1 ? 'migration was' : 'migrations were') . " reverted.\n", Console::FG_RED); | 
            ||
| 249 |                     $this->stdout("\nMigration failed. The rest of the migrations are canceled.\n", Console::FG_RED); | 
            ||
| 250 | |||
| 251 | return ExitCode::UNSPECIFIED_ERROR;  | 
            ||
| 252 | }  | 
            ||
| 253 | 11 | $reverted++;  | 
            |
| 254 | }  | 
            ||
| 255 | 11 |             $this->stdout("\n$n " . ($n === 1 ? 'migration was' : 'migrations were') . " reverted.\n", Console::FG_GREEN); | 
            |
| 256 | 11 |             $this->stdout("\nMigrated down successfully.\n", Console::FG_GREEN); | 
            |
| 257 | }  | 
            ||
| 258 | 11 | }  | 
            |
| 259 | |||
| 260 | /**  | 
            ||
| 261 | * Redoes the last few migrations.  | 
            ||
| 262 | *  | 
            ||
| 263 | * This command will first revert the specified migrations, and then apply  | 
            ||
| 264 | * them again. For example,  | 
            ||
| 265 | *  | 
            ||
| 266 | * ```  | 
            ||
| 267 | * yii migrate/redo # redo the last applied migration  | 
            ||
| 268 | * yii migrate/redo 3 # redo the last 3 applied migrations  | 
            ||
| 269 | * yii migrate/redo all # redo all migrations  | 
            ||
| 270 | * ```  | 
            ||
| 271 | *  | 
            ||
| 272 | * @param int|string $limit the number of migrations to be redone. Defaults to 1,  | 
            ||
| 273 | * meaning the last applied migration will be redone. When equals "all", all migrations will be redone.  | 
            ||
| 274 | * @throws Exception if the number of the steps specified is less than 1.  | 
            ||
| 275 | *  | 
            ||
| 276 | * @return int the status of the action execution. 0 means normal, other values mean abnormal.  | 
            ||
| 277 | */  | 
            ||
| 278 | 2 | public function actionRedo($limit = 1)  | 
            |
| 279 |     { | 
            ||
| 280 | 2 |         if ($limit === 'all') { | 
            |
| 281 | $limit = null;  | 
            ||
| 282 |         } else { | 
            ||
| 283 | 2 | $limit = (int) $limit;  | 
            |
| 284 | 2 |             if ($limit < 1) { | 
            |
| 285 |                 throw new Exception('The step argument must be greater than 0.'); | 
            ||
| 286 | }  | 
            ||
| 287 | }  | 
            ||
| 288 | |||
| 289 | 2 | $migrations = $this->getMigrationHistory($limit);  | 
            |
| 290 | |||
| 291 | 2 |         if (empty($migrations)) { | 
            |
| 292 |             $this->stdout("No migration has been done before.\n", Console::FG_YELLOW); | 
            ||
| 293 | |||
| 294 | return ExitCode::OK;  | 
            ||
| 295 | }  | 
            ||
| 296 | |||
| 297 | 2 | $migrations = array_keys($migrations);  | 
            |
| 298 | |||
| 299 | 2 | $n = count($migrations);  | 
            |
| 300 | 2 |         $this->stdout("Total $n " . ($n === 1 ? 'migration' : 'migrations') . " to be redone:\n", Console::FG_YELLOW); | 
            |
| 301 | 2 |         foreach ($migrations as $migration) { | 
            |
| 302 | 2 |             $this->stdout("\t$migration\n"); | 
            |
| 303 | }  | 
            ||
| 304 | 2 |         $this->stdout("\n"); | 
            |
| 305 | |||
| 306 | 2 |         if ($this->confirm('Redo the above ' . ($n === 1 ? 'migration' : 'migrations') . '?')) { | 
            |
| 307 | 2 |             foreach ($migrations as $migration) { | 
            |
| 308 | 2 |                 if (!$this->migrateDown($migration)) { | 
            |
| 309 |                     $this->stdout("\nMigration failed. The rest of the migrations are canceled.\n", Console::FG_RED); | 
            ||
| 310 | |||
| 311 | 2 | return ExitCode::UNSPECIFIED_ERROR;  | 
            |
| 312 | }  | 
            ||
| 313 | }  | 
            ||
| 314 | 2 |             foreach (array_reverse($migrations) as $migration) { | 
            |
| 315 | 2 |                 if (!$this->migrateUp($migration)) { | 
            |
| 316 |                     $this->stdout("\nMigration failed. The rest of the migrations are canceled.\n", Console::FG_RED); | 
            ||
| 317 | |||
| 318 | 2 | return ExitCode::UNSPECIFIED_ERROR;  | 
            |
| 319 | }  | 
            ||
| 320 | }  | 
            ||
| 321 | 2 |             $this->stdout("\n$n " . ($n === 1 ? 'migration was' : 'migrations were') . " redone.\n", Console::FG_GREEN); | 
            |
| 322 | 2 |             $this->stdout("\nMigration redone successfully.\n", Console::FG_GREEN); | 
            |
| 323 | }  | 
            ||
| 324 | 2 | }  | 
            |
| 325 | |||
| 326 | /**  | 
            ||
| 327 | * Upgrades or downgrades till the specified version.  | 
            ||
| 328 | *  | 
            ||
| 329 | * Can also downgrade versions to the certain apply time in the past by providing  | 
            ||
| 330 | * a UNIX timestamp or a string parseable by the strtotime() function. This means  | 
            ||
| 331 | * that all the versions applied after the specified certain time would be reverted.  | 
            ||
| 332 | *  | 
            ||
| 333 | * This command will first revert the specified migrations, and then apply  | 
            ||
| 334 | * them again. For example,  | 
            ||
| 335 | *  | 
            ||
| 336 | * ```  | 
            ||
| 337 | * yii migrate/to 101129_185401 # using timestamp  | 
            ||
| 338 | * yii migrate/to m101129_185401_create_user_table # using full name  | 
            ||
| 339 | * yii migrate/to 1392853618 # using UNIX timestamp  | 
            ||
| 340 | * yii migrate/to "2014-02-15 13:00:50" # using strtotime() parseable string  | 
            ||
| 341 | * yii migrate/to app\migrations\M101129185401CreateUser # using full namespace name  | 
            ||
| 342 | * ```  | 
            ||
| 343 | *  | 
            ||
| 344 | * @param string $version either the version name or the certain time value in the past  | 
            ||
| 345 | * that the application should be migrated to. This can be either the timestamp,  | 
            ||
| 346 | * the full name of the migration, the UNIX timestamp, or the parseable datetime  | 
            ||
| 347 | * string.  | 
            ||
| 348 | * @throws Exception if the version argument is invalid.  | 
            ||
| 349 | */  | 
            ||
| 350 | 3 | public function actionTo($version)  | 
            |
| 351 |     { | 
            ||
| 352 | 3 |         if (($namespaceVersion = $this->extractNamespaceMigrationVersion($version)) !== false) { | 
            |
| 353 | 1 | $this->migrateToVersion($namespaceVersion);  | 
            |
| 354 | 2 |         } elseif (($migrationName = $this->extractMigrationVersion($version)) !== false) { | 
            |
| 355 | 2 | $this->migrateToVersion($migrationName);  | 
            |
| 356 |         } elseif ((string) (int) $version == $version) { | 
            ||
| 357 | $this->migrateToTime($version);  | 
            ||
| 358 |         } elseif (($time = strtotime($version)) !== false) { | 
            ||
| 359 | $this->migrateToTime($time);  | 
            ||
| 360 |         } else { | 
            ||
| 361 |             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 the full namespaced name of a migration (e.g. app\\migrations\\M101129185401CreateUserTable),\n a UNIX timestamp (e.g. 1392853000), or a datetime string parseable\nby the strtotime() function (e.g. 2014-02-15 13:00:50)."); | 
            ||
| 362 | }  | 
            ||
| 363 | 3 | }  | 
            |
| 364 | |||
| 365 | /**  | 
            ||
| 366 | * Modifies the migration history to the specified version.  | 
            ||
| 367 | *  | 
            ||
| 368 | * No actual migration will be performed.  | 
            ||
| 369 | *  | 
            ||
| 370 | * ```  | 
            ||
| 371 | * yii migrate/mark 101129_185401 # using timestamp  | 
            ||
| 372 | * yii migrate/mark m101129_185401_create_user_table # using full name  | 
            ||
| 373 | * yii migrate/mark app\migrations\M101129185401CreateUser # using full namespace name  | 
            ||
| 374 | * yii migrate/mark m000000_000000_base # reset the complete migration history  | 
            ||
| 375 | * ```  | 
            ||
| 376 | *  | 
            ||
| 377 | * @param string $version the version at which the migration history should be marked.  | 
            ||
| 378 | * This can be either the timestamp or the full name of the migration.  | 
            ||
| 379 | * You may specify the name `m000000_000000_base` to set the migration history to a  | 
            ||
| 380 | * state where no migration has been applied.  | 
            ||
| 381 | * @return int CLI exit code  | 
            ||
| 382 | * @throws Exception if the version argument is invalid or the version cannot be found.  | 
            ||
| 383 | */  | 
            ||
| 384 | 4 | public function actionMark($version)  | 
            |
| 385 |     { | 
            ||
| 386 | 4 | $originalVersion = $version;  | 
            |
| 387 | 4 |         if (($namespaceVersion = $this->extractNamespaceMigrationVersion($version)) !== false) { | 
            |
| 388 | 1 | $version = $namespaceVersion;  | 
            |
| 389 | 3 |         } elseif (($migrationName = $this->extractMigrationVersion($version)) !== false) { | 
            |
| 390 | 3 | $version = $migrationName;  | 
            |
| 391 |         } elseif ($version !== static::BASE_MIGRATION) { | 
            ||
| 392 |             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)\nor the full name of a namespaced migration (e.g. app\\migrations\\M101129185401CreateUserTable)."); | 
            ||
| 393 | }  | 
            ||
| 394 | |||
| 395 | // try mark up  | 
            ||
| 396 | 4 | $migrations = $this->getNewMigrations();  | 
            |
| 397 | 4 |         foreach ($migrations as $i => $migration) { | 
            |
| 398 | 3 |             if (strpos($migration, $version) === 0) { | 
            |
| 399 | 3 |                 if ($this->confirm("Set migration history at $originalVersion?")) { | 
            |
| 400 | 3 |                     for ($j = 0; $j <= $i; ++$j) { | 
            |
| 401 | 3 | $this->addMigrationHistory($migrations[$j]);  | 
            |
| 402 | }  | 
            ||
| 403 | 3 |                     $this->stdout("The migration history is set at $originalVersion.\nNo actual migration was performed.\n", Console::FG_GREEN); | 
            |
| 404 | }  | 
            ||
| 405 | |||
| 406 | 3 | return ExitCode::OK;  | 
            |
| 407 | }  | 
            ||
| 408 | }  | 
            ||
| 409 | |||
| 410 | // try mark down  | 
            ||
| 411 | 1 | $migrations = array_keys($this->getMigrationHistory(null));  | 
            |
| 412 | 1 | $migrations[] = static::BASE_MIGRATION;  | 
            |
| 413 | 1 |         foreach ($migrations as $i => $migration) { | 
            |
| 414 | 1 |             if (strpos($migration, $version) === 0) { | 
            |
| 415 | 1 |                 if ($i === 0) { | 
            |
| 416 |                     $this->stdout("Already at '$originalVersion'. Nothing needs to be done.\n", Console::FG_YELLOW); | 
            ||
| 417 |                 } else { | 
            ||
| 418 | 1 |                     if ($this->confirm("Set migration history at $originalVersion?")) { | 
            |
| 419 | 1 |                         for ($j = 0; $j < $i; ++$j) { | 
            |
| 420 | 1 | $this->removeMigrationHistory($migrations[$j]);  | 
            |
| 421 | }  | 
            ||
| 422 | 1 |                         $this->stdout("The migration history is set at $originalVersion.\nNo actual migration was performed.\n", Console::FG_GREEN); | 
            |
| 423 | }  | 
            ||
| 424 | }  | 
            ||
| 425 | |||
| 426 | 1 | return ExitCode::OK;  | 
            |
| 427 | }  | 
            ||
| 428 | }  | 
            ||
| 429 | |||
| 430 |         throw new Exception("Unable to find the version '$originalVersion'."); | 
            ||
| 431 | }  | 
            ||
| 432 | |||
| 433 | /**  | 
            ||
| 434 | * Truncates the whole database and starts the migration from the beginning.  | 
            ||
| 435 | *  | 
            ||
| 436 | * ```  | 
            ||
| 437 | * yii migrate/fresh  | 
            ||
| 438 | * ```  | 
            ||
| 439 | *  | 
            ||
| 440 | * @since 2.0.13  | 
            ||
| 441 | */  | 
            ||
| 442 | 1 | public function actionFresh()  | 
            |
| 443 |     { | 
            ||
| 444 | 1 |         if (YII_ENV_PROD) { | 
            |
| 445 |             $this->stdout("YII_ENV is set to 'prod'.\nRefreshing migrations is not possible on production systems.\n"); | 
            ||
| 446 | return ExitCode::OK;  | 
            ||
| 447 | }  | 
            ||
| 448 | |||
| 449 | 1 | if ($this->confirm(  | 
            |
| 450 | 1 |             "Are you sure you want to reset the database and start the migration from the beginning?\nAll data will be lost irreversibly!")) { | 
            |
| 451 | 1 | $this->truncateDatabase();  | 
            |
| 452 | 1 | $this->actionUp();  | 
            |
| 453 |         } else { | 
            ||
| 454 |             $this->stdout('Action was cancelled by user. Nothing has been performed.'); | 
            ||
| 455 | }  | 
            ||
| 456 | 1 | }  | 
            |
| 457 | |||
| 458 | /**  | 
            ||
| 459 | * Checks if given migration version specification matches namespaced migration name.  | 
            ||
| 460 | * @param string $rawVersion raw version specification received from user input.  | 
            ||
| 461 | * @return string|false actual migration version, `false` - if not match.  | 
            ||
| 462 | * @since 2.0.10  | 
            ||
| 463 | */  | 
            ||
| 464 | 6 | private function extractNamespaceMigrationVersion($rawVersion)  | 
            |
| 465 |     { | 
            ||
| 466 | 6 |         if (preg_match('/^\\\\?([\w_]+\\\\)+m(\d{6}_?\d{6})(\D.*)?$/is', $rawVersion, $matches)) { | 
            |
| 467 | 2 | return trim($rawVersion, '\\');  | 
            |
| 468 | }  | 
            ||
| 469 | |||
| 470 | 4 | return false;  | 
            |
| 471 | }  | 
            ||
| 472 | |||
| 473 | /**  | 
            ||
| 474 | * Checks if given migration version specification matches migration base name.  | 
            ||
| 475 | * @param string $rawVersion raw version specification received from user input.  | 
            ||
| 476 | * @return string|false actual migration version, `false` - if not match.  | 
            ||
| 477 | * @since 2.0.10  | 
            ||
| 478 | */  | 
            ||
| 479 | 4 | private function extractMigrationVersion($rawVersion)  | 
            |
| 480 |     { | 
            ||
| 481 | 4 |         if (preg_match('/^m?(\d{6}_?\d{6})(\D.*)?$/is', $rawVersion, $matches)) { | 
            |
| 482 | 4 | return 'm' . $matches[1];  | 
            |
| 483 | }  | 
            ||
| 484 | |||
| 485 | return false;  | 
            ||
| 486 | }  | 
            ||
| 487 | |||
| 488 | /**  | 
            ||
| 489 | * Displays the migration history.  | 
            ||
| 490 | *  | 
            ||
| 491 | * This command will show the list of migrations that have been applied  | 
            ||
| 492 | * so far. For example,  | 
            ||
| 493 | *  | 
            ||
| 494 | * ```  | 
            ||
| 495 | * yii migrate/history # showing the last 10 migrations  | 
            ||
| 496 | * yii migrate/history 5 # showing the last 5 migrations  | 
            ||
| 497 | * yii migrate/history all # showing the whole history  | 
            ||
| 498 | * ```  | 
            ||
| 499 | *  | 
            ||
| 500 | * @param int|string $limit the maximum number of migrations to be displayed.  | 
            ||
| 501 | * If it is "all", the whole migration history will be displayed.  | 
            ||
| 502 | * @throws \yii\console\Exception if invalid limit value passed  | 
            ||
| 503 | */  | 
            ||
| 504 | 4 | public function actionHistory($limit = 10)  | 
            |
| 505 |     { | 
            ||
| 506 | 4 |         if ($limit === 'all') { | 
            |
| 507 | $limit = null;  | 
            ||
| 508 |         } else { | 
            ||
| 509 | 4 | $limit = (int) $limit;  | 
            |
| 510 | 4 |             if ($limit < 1) { | 
            |
| 511 |                 throw new Exception('The limit must be greater than 0.'); | 
            ||
| 512 | }  | 
            ||
| 513 | }  | 
            ||
| 514 | |||
| 515 | 4 | $migrations = $this->getMigrationHistory($limit);  | 
            |
| 516 | |||
| 517 | 4 |         if (empty($migrations)) { | 
            |
| 518 | 4 |             $this->stdout("No migration has been done before.\n", Console::FG_YELLOW); | 
            |
| 519 |         } else { | 
            ||
| 520 | 2 | $n = count($migrations);  | 
            |
| 521 | 2 |             if ($limit > 0) { | 
            |
| 522 | 2 |                 $this->stdout("Showing the last $n applied " . ($n === 1 ? 'migration' : 'migrations') . ":\n", Console::FG_YELLOW); | 
            |
| 523 |             } else { | 
            ||
| 524 |                 $this->stdout("Total $n " . ($n === 1 ? 'migration has' : 'migrations have') . " been applied before:\n", Console::FG_YELLOW); | 
            ||
| 525 | }  | 
            ||
| 526 | 2 |             foreach ($migrations as $version => $time) { | 
            |
| 527 | 2 |                 $this->stdout("\t(" . date('Y-m-d H:i:s', $time) . ') ' . $version . "\n"); | 
            |
| 528 | }  | 
            ||
| 529 | }  | 
            ||
| 530 | 4 | }  | 
            |
| 531 | |||
| 532 | /**  | 
            ||
| 533 | * Displays the un-applied new migrations.  | 
            ||
| 534 | *  | 
            ||
| 535 | * This command will show the new migrations that have not been applied.  | 
            ||
| 536 | * For example,  | 
            ||
| 537 | *  | 
            ||
| 538 | * ```  | 
            ||
| 539 | * yii migrate/new # showing the first 10 new migrations  | 
            ||
| 540 | * yii migrate/new 5 # showing the first 5 new migrations  | 
            ||
| 541 | * yii migrate/new all # showing all new migrations  | 
            ||
| 542 | * ```  | 
            ||
| 543 | *  | 
            ||
| 544 | * @param int|string $limit the maximum number of new migrations to be displayed.  | 
            ||
| 545 | * If it is `all`, all available new migrations will be displayed.  | 
            ||
| 546 | * @throws \yii\console\Exception if invalid limit value passed  | 
            ||
| 547 | */  | 
            ||
| 548 | 1 | public function actionNew($limit = 10)  | 
            |
| 549 |     { | 
            ||
| 550 | 1 |         if ($limit === 'all') { | 
            |
| 551 | $limit = null;  | 
            ||
| 552 |         } else { | 
            ||
| 553 | 1 | $limit = (int) $limit;  | 
            |
| 554 | 1 |             if ($limit < 1) { | 
            |
| 555 |                 throw new Exception('The limit must be greater than 0.'); | 
            ||
| 556 | }  | 
            ||
| 557 | }  | 
            ||
| 558 | |||
| 559 | 1 | $migrations = $this->getNewMigrations();  | 
            |
| 560 | |||
| 561 | 1 |         if (empty($migrations)) { | 
            |
| 562 | 1 |             $this->stdout("No new migrations found. Your system is up-to-date.\n", Console::FG_GREEN); | 
            |
| 563 |         } else { | 
            ||
| 564 | 1 | $n = count($migrations);  | 
            |
| 565 | 1 |             if ($limit && $n > $limit) { | 
            |
| 566 | $migrations = array_slice($migrations, 0, $limit);  | 
            ||
| 567 |                 $this->stdout("Showing $limit out of $n new " . ($n === 1 ? 'migration' : 'migrations') . ":\n", Console::FG_YELLOW); | 
            ||
| 568 |             } else { | 
            ||
| 569 | 1 |                 $this->stdout("Found $n new " . ($n === 1 ? 'migration' : 'migrations') . ":\n", Console::FG_YELLOW); | 
            |
| 570 | }  | 
            ||
| 571 | |||
| 572 | 1 |             foreach ($migrations as $migration) { | 
            |
| 573 | 1 |                 $this->stdout("\t" . $migration . "\n"); | 
            |
| 574 | }  | 
            ||
| 575 | }  | 
            ||
| 576 | 1 | }  | 
            |
| 577 | |||
| 578 | /**  | 
            ||
| 579 | * Creates a new migration.  | 
            ||
| 580 | *  | 
            ||
| 581 | * This command creates a new migration using the available migration template.  | 
            ||
| 582 | * After using this command, developers should modify the created migration  | 
            ||
| 583 | * skeleton by filling up the actual migration logic.  | 
            ||
| 584 | *  | 
            ||
| 585 | * ```  | 
            ||
| 586 | * yii migrate/create create_user_table  | 
            ||
| 587 | * ```  | 
            ||
| 588 | *  | 
            ||
| 589 | * In order to generate a namespaced migration, you should specify a namespace before the migration's name.  | 
            ||
| 590 | * Note that backslash (`\`) is usually considered a special character in the shell, so you need to escape it  | 
            ||
| 591 | * properly to avoid shell errors or incorrect behavior.  | 
            ||
| 592 | * For example:  | 
            ||
| 593 | *  | 
            ||
| 594 | * ```  | 
            ||
| 595 | * yii migrate/create 'app\\migrations\\createUserTable'  | 
            ||
| 596 | * ```  | 
            ||
| 597 | *  | 
            ||
| 598 | * In case [[migrationPath]] is not set and no namespace is provided, the first entry of [[migrationNamespaces]] will be used.  | 
            ||
| 599 | *  | 
            ||
| 600 | * @param string $name the name of the new migration. This should only contain  | 
            ||
| 601 | * letters, digits, underscores and/or backslashes.  | 
            ||
| 602 | *  | 
            ||
| 603 | * Note: If the migration name is of a special form, for example create_xxx or  | 
            ||
| 604 | * drop_xxx, then the generated migration file will contain extra code,  | 
            ||
| 605 | * in this case for creating/dropping tables.  | 
            ||
| 606 | *  | 
            ||
| 607 | * @throws Exception if the name argument is invalid.  | 
            ||
| 608 | */  | 
            ||
| 609 | 9 | public function actionCreate($name)  | 
            |
| 610 |     { | 
            ||
| 611 | 9 |         if (!preg_match('/^[\w\\\\]+$/', $name)) { | 
            |
| 612 |             throw new Exception('The migration name should contain letters, digits, underscore and/or backslash characters only.'); | 
            ||
| 613 | }  | 
            ||
| 614 | |||
| 615 | 9 | list($namespace, $className) = $this->generateClassName($name);  | 
            |
| 616 | 9 | $migrationPath = $this->findMigrationPath($namespace);  | 
            |
| 617 | |||
| 618 | 9 | $file = $migrationPath . DIRECTORY_SEPARATOR . $className . '.php';  | 
            |
| 619 | 9 |         if ($this->confirm("Create new migration '$file'?")) { | 
            |
| 620 | 9 | $content = $this->generateMigrationSourceCode([  | 
            |
| 621 | 9 | 'name' => $name,  | 
            |
| 622 | 9 | 'className' => $className,  | 
            |
| 623 | 9 | 'namespace' => $namespace,  | 
            |
| 624 | ]);  | 
            ||
| 625 | 9 | FileHelper::createDirectory($migrationPath);  | 
            |
| 626 | 9 | file_put_contents($file, $content);  | 
            |
| 627 | 9 |             $this->stdout("New migration created successfully.\n", Console::FG_GREEN); | 
            |
| 628 | }  | 
            ||
| 629 | 9 | }  | 
            |
| 630 | |||
| 631 | /**  | 
            ||
| 632 | * Generates class base name and namespace from migration name from user input.  | 
            ||
| 633 | * @param string $name migration name from user input.  | 
            ||
| 634 | * @return array list of 2 elements: 'namespace' and 'class base name'  | 
            ||
| 635 | * @since 2.0.10  | 
            ||
| 636 | */  | 
            ||
| 637 | 9 | private function generateClassName($name)  | 
            |
| 638 |     { | 
            ||
| 639 | 9 | $namespace = null;  | 
            |
| 640 | 9 | $name = trim($name, '\\');  | 
            |
| 641 | 9 |         if (strpos($name, '\\') !== false) { | 
            |
| 642 | 1 | $namespace = substr($name, 0, strrpos($name, '\\'));  | 
            |
| 643 | 1 | $name = substr($name, strrpos($name, '\\') + 1);  | 
            |
| 644 |         } else { | 
            ||
| 645 | 9 |             if ($this->migrationPath === null) { | 
            |
| 646 | 1 | $migrationNamespaces = $this->migrationNamespaces;  | 
            |
| 647 | 1 | $namespace = array_shift($migrationNamespaces);  | 
            |
| 648 | }  | 
            ||
| 649 | }  | 
            ||
| 650 | |||
| 651 | 9 |         if ($namespace === null) { | 
            |
| 652 | 9 |             $class = 'm' . gmdate('ymd_His') . '_' . $name; | 
            |
| 653 |         } else { | 
            ||
| 654 | 1 |             $class = 'M' . gmdate('ymdHis') . ucfirst($name); | 
            |
| 655 | }  | 
            ||
| 656 | |||
| 657 | 9 | return [$namespace, $class];  | 
            |
| 658 | }  | 
            ||
| 659 | |||
| 660 | /**  | 
            ||
| 661 | * Finds the file path for the specified migration namespace.  | 
            ||
| 662 | * @param string|null $namespace migration namespace.  | 
            ||
| 663 | * @return string migration file path.  | 
            ||
| 664 | * @throws Exception on failure.  | 
            ||
| 665 | * @since 2.0.10  | 
            ||
| 666 | */  | 
            ||
| 667 | 9 | private function findMigrationPath($namespace)  | 
            |
| 668 |     { | 
            ||
| 669 | 9 |         if (empty($namespace)) { | 
            |
| 670 | 9 | return is_array($this->migrationPath) ? reset($this->migrationPath) : $this->migrationPath;  | 
            |
| 671 | }  | 
            ||
| 672 | |||
| 673 | 1 |         if (!in_array($namespace, $this->migrationNamespaces, true)) { | 
            |
| 674 |             throw new Exception("Namespace '{$namespace}' not found in `migrationNamespaces`"); | 
            ||
| 675 | }  | 
            ||
| 676 | |||
| 677 | 1 | return $this->getNamespacePath($namespace);  | 
            |
| 678 | }  | 
            ||
| 679 | |||
| 680 | /**  | 
            ||
| 681 | * Returns the file path matching the give namespace.  | 
            ||
| 682 | * @param string $namespace namespace.  | 
            ||
| 683 | * @return string file path.  | 
            ||
| 684 | * @since 2.0.10  | 
            ||
| 685 | */  | 
            ||
| 686 | 7 | private function getNamespacePath($namespace)  | 
            |
| 687 |     { | 
            ||
| 688 | 7 |         return str_replace('/', DIRECTORY_SEPARATOR, Yii::getAlias('@' . str_replace('\\', '/', $namespace))); | 
            |
| 689 | }  | 
            ||
| 690 | |||
| 691 | /**  | 
            ||
| 692 | * Upgrades with the specified migration class.  | 
            ||
| 693 | * @param string $class the migration class name  | 
            ||
| 694 | * @return bool whether the migration is successful  | 
            ||
| 695 | */  | 
            ||
| 696 | 21 | protected function migrateUp($class)  | 
            |
| 697 |     { | 
            ||
| 698 | 21 |         if ($class === self::BASE_MIGRATION) { | 
            |
| 699 | return true;  | 
            ||
| 700 | }  | 
            ||
| 701 | |||
| 702 | 21 |         $this->stdout("*** applying $class\n", Console::FG_YELLOW); | 
            |
| 703 | 21 | $start = microtime(true);  | 
            |
| 704 | 21 | $migration = $this->createMigration($class);  | 
            |
| 705 | 21 |         if ($migration->up() !== false) { | 
            |
| 706 | 21 | $this->addMigrationHistory($class);  | 
            |
| 707 | 21 | $time = microtime(true) - $start;  | 
            |
| 708 | 21 |             $this->stdout("*** applied $class (time: " . sprintf('%.3f', $time) . "s)\n\n", Console::FG_GREEN); | 
            |
| 709 | |||
| 710 | 21 | return true;  | 
            |
| 711 | }  | 
            ||
| 712 | |||
| 713 | $time = microtime(true) - $start;  | 
            ||
| 714 |         $this->stdout("*** failed to apply $class (time: " . sprintf('%.3f', $time) . "s)\n\n", Console::FG_RED); | 
            ||
| 715 | |||
| 716 | return false;  | 
            ||
| 717 | }  | 
            ||
| 718 | |||
| 719 | /**  | 
            ||
| 720 | * Downgrades with the specified migration class.  | 
            ||
| 721 | * @param string $class the migration class name  | 
            ||
| 722 | * @return bool whether the migration is successful  | 
            ||
| 723 | */  | 
            ||
| 724 | 12 | protected function migrateDown($class)  | 
            |
| 725 |     { | 
            ||
| 726 | 12 |         if ($class === self::BASE_MIGRATION) { | 
            |
| 727 | return true;  | 
            ||
| 728 | }  | 
            ||
| 729 | |||
| 730 | 12 |         $this->stdout("*** reverting $class\n", Console::FG_YELLOW); | 
            |
| 731 | 12 | $start = microtime(true);  | 
            |
| 732 | 12 | $migration = $this->createMigration($class);  | 
            |
| 733 | 12 |         if ($migration->down() !== false) { | 
            |
| 734 | 12 | $this->removeMigrationHistory($class);  | 
            |
| 735 | 12 | $time = microtime(true) - $start;  | 
            |
| 736 | 12 |             $this->stdout("*** reverted $class (time: " . sprintf('%.3f', $time) . "s)\n\n", Console::FG_GREEN); | 
            |
| 737 | |||
| 738 | 12 | return true;  | 
            |
| 739 | }  | 
            ||
| 740 | |||
| 741 | $time = microtime(true) - $start;  | 
            ||
| 742 |         $this->stdout("*** failed to revert $class (time: " . sprintf('%.3f', $time) . "s)\n\n", Console::FG_RED); | 
            ||
| 743 | |||
| 744 | return false;  | 
            ||
| 745 | }  | 
            ||
| 746 | |||
| 747 | /**  | 
            ||
| 748 | * Creates a new migration instance.  | 
            ||
| 749 | * @param string $class the migration class name  | 
            ||
| 750 | * @return \yii\db\MigrationInterface the migration instance  | 
            ||
| 751 | */  | 
            ||
| 752 | protected function createMigration($class)  | 
            ||
| 753 |     { | 
            ||
| 754 | $this->includeMigrationFile($class);  | 
            ||
| 755 | return new $class();  | 
            ||
| 756 | }  | 
            ||
| 757 | |||
| 758 | /**  | 
            ||
| 759 | * Includes the migration file for a given migration class name.  | 
            ||
| 760 | *  | 
            ||
| 761 | * This function will do nothing on namespaced migrations, which are loaded by  | 
            ||
| 762 | * autoloading automatically. It will include the migration file, by searching  | 
            ||
| 763 | * [[migrationPath]] for classes without namespace.  | 
            ||
| 764 | * @param string $class the migration class name.  | 
            ||
| 765 | * @since 2.0.12  | 
            ||
| 766 | */  | 
            ||
| 767 | 21 | protected function includeMigrationFile($class)  | 
            |
| 768 |     { | 
            ||
| 769 | 21 | $class = trim($class, '\\');  | 
            |
| 770 | 21 |         if (strpos($class, '\\') === false) { | 
            |
| 771 | 17 |             if (is_array($this->migrationPath)) { | 
            |
| 772 | 7 |                 foreach ($this->migrationPath as $path) { | 
            |
| 773 | 7 | $file = $path . DIRECTORY_SEPARATOR . $class . '.php';  | 
            |
| 774 | 7 |                     if (is_file($file)) { | 
            |
| 775 | 7 | require_once $file;  | 
            |
| 776 | 7 | break;  | 
            |
| 777 | }  | 
            ||
| 778 | }  | 
            ||
| 779 |             } else { | 
            ||
| 780 | 10 | $file = $this->migrationPath . DIRECTORY_SEPARATOR . $class . '.php';  | 
            |
| 781 | 10 | require_once $file;  | 
            |
| 782 | }  | 
            ||
| 783 | }  | 
            ||
| 784 | 21 | }  | 
            |
| 785 | |||
| 786 | /**  | 
            ||
| 787 | * Migrates to the specified apply time in the past.  | 
            ||
| 788 | * @param int $time UNIX timestamp value.  | 
            ||
| 789 | */  | 
            ||
| 790 | protected function migrateToTime($time)  | 
            ||
| 791 |     { | 
            ||
| 792 | $count = 0;  | 
            ||
| 793 | $migrations = array_values($this->getMigrationHistory(null));  | 
            ||
| 794 |         while ($count < count($migrations) && $migrations[$count] > $time) { | 
            ||
| 795 | ++$count;  | 
            ||
| 796 | }  | 
            ||
| 797 |         if ($count === 0) { | 
            ||
| 798 |             $this->stdout("Nothing needs to be done.\n", Console::FG_GREEN); | 
            ||
| 799 |         } else { | 
            ||
| 800 | $this->actionDown($count);  | 
            ||
| 801 | }  | 
            ||
| 802 | }  | 
            ||
| 803 | |||
| 804 | /**  | 
            ||
| 805 | * Migrates to the certain version.  | 
            ||
| 806 | * @param string $version name in the full format.  | 
            ||
| 807 | * @return int CLI exit code  | 
            ||
| 808 | * @throws Exception if the provided version cannot be found.  | 
            ||
| 809 | */  | 
            ||
| 810 | 3 | protected function migrateToVersion($version)  | 
            |
| 811 |     { | 
            ||
| 812 | 3 | $originalVersion = $version;  | 
            |
| 813 | |||
| 814 | // try migrate up  | 
            ||
| 815 | 3 | $migrations = $this->getNewMigrations();  | 
            |
| 816 | 3 |         foreach ($migrations as $i => $migration) { | 
            |
| 817 | 2 |             if (strpos($migration, $version) === 0) { | 
            |
| 818 | 2 | $this->actionUp($i + 1);  | 
            |
| 819 | |||
| 820 | 2 | return ExitCode::OK;  | 
            |
| 821 | }  | 
            ||
| 822 | }  | 
            ||
| 823 | |||
| 824 | // try migrate down  | 
            ||
| 825 | 1 | $migrations = array_keys($this->getMigrationHistory(null));  | 
            |
| 826 | 1 |         foreach ($migrations as $i => $migration) { | 
            |
| 827 | 1 |             if (strpos($migration, $version) === 0) { | 
            |
| 828 | 1 |                 if ($i === 0) { | 
            |
| 829 |                     $this->stdout("Already at '$originalVersion'. Nothing needs to be done.\n", Console::FG_YELLOW); | 
            ||
| 830 |                 } else { | 
            ||
| 831 | 1 | $this->actionDown($i);  | 
            |
| 832 | }  | 
            ||
| 833 | |||
| 834 | 1 | return ExitCode::OK;  | 
            |
| 835 | }  | 
            ||
| 836 | }  | 
            ||
| 837 | |||
| 838 |         throw new Exception("Unable to find the version '$originalVersion'."); | 
            ||
| 839 | }  | 
            ||
| 840 | |||
| 841 | /**  | 
            ||
| 842 | * Returns the migrations that are not applied.  | 
            ||
| 843 | * @return array list of new migrations  | 
            ||
| 844 | */  | 
            ||
| 845 | 24 | protected function getNewMigrations()  | 
            |
| 846 |     { | 
            ||
| 847 | 24 | $applied = [];  | 
            |
| 848 | 24 |         foreach ($this->getMigrationHistory(null) as $class => $time) { | 
            |
| 849 | 3 | $applied[trim($class, '\\')] = true;  | 
            |
| 850 | }  | 
            ||
| 851 | |||
| 852 | 24 | $migrationPaths = [];  | 
            |
| 853 | 24 |         if (is_array($this->migrationPath)) { | 
            |
| 854 | 7 |             foreach ($this->migrationPath as $path) { | 
            |
| 855 | 7 | $migrationPaths[] = [$path, ''];  | 
            |
| 856 | }  | 
            ||
| 857 | 17 |         } elseif (!empty($this->migrationPath)) { | 
            |
| 858 | 12 | $migrationPaths[] = [$this->migrationPath, ''];  | 
            |
| 859 | }  | 
            ||
| 860 | 24 |         foreach ($this->migrationNamespaces as $namespace) { | 
            |
| 861 | 6 | $migrationPaths[] = [$this->getNamespacePath($namespace), $namespace];  | 
            |
| 862 | }  | 
            ||
| 863 | |||
| 864 | 24 | $migrations = [];  | 
            |
| 865 | 24 |         foreach ($migrationPaths as $item) { | 
            |
| 866 | 24 | list($migrationPath, $namespace) = $item;  | 
            |
| 867 | 24 |             if (!file_exists($migrationPath)) { | 
            |
| 868 | continue;  | 
            ||
| 869 | }  | 
            ||
| 870 | 24 | $handle = opendir($migrationPath);  | 
            |
| 871 | 24 |             while (($file = readdir($handle)) !== false) { | 
            |
| 872 | 24 |                 if ($file === '.' || $file === '..') { | 
            |
| 873 | 24 | continue;  | 
            |
| 874 | }  | 
            ||
| 875 | 23 | $path = $migrationPath . DIRECTORY_SEPARATOR . $file;  | 
            |
| 876 | 23 |                 if (preg_match('/^(m(\d{6}_?\d{6})\D.*?)\.php$/is', $file, $matches) && is_file($path)) { | 
            |
| 877 | 23 | $class = $matches[1];  | 
            |
| 878 | 23 |                     if (!empty($namespace)) { | 
            |
| 879 | 6 | $class = $namespace . '\\' . $class;  | 
            |
| 880 | }  | 
            ||
| 881 | 23 |                     $time = str_replace('_', '', $matches[2]); | 
            |
| 882 | 23 |                     if (!isset($applied[$class])) { | 
            |
| 883 | 23 | $migrations[$time . '\\' . $class] = $class;  | 
            |
| 884 | }  | 
            ||
| 885 | }  | 
            ||
| 886 | }  | 
            ||
| 887 | 24 | closedir($handle);  | 
            |
| 888 | }  | 
            ||
| 889 | 24 | ksort($migrations);  | 
            |
| 890 | |||
| 891 | 24 | return array_values($migrations);  | 
            |
| 892 | }  | 
            ||
| 893 | |||
| 894 | /**  | 
            ||
| 895 | * Generates new migration source PHP code.  | 
            ||
| 896 | * Child class may override this method, adding extra logic or variation to the process.  | 
            ||
| 897 | * @param array $params generation parameters, usually following parameters are present:  | 
            ||
| 898 | *  | 
            ||
| 899 | * - name: string migration base name  | 
            ||
| 900 | * - className: string migration class name  | 
            ||
| 901 | *  | 
            ||
| 902 | * @return string generated PHP code.  | 
            ||
| 903 | * @since 2.0.8  | 
            ||
| 904 | */  | 
            ||
| 905 | protected function generateMigrationSourceCode($params)  | 
            ||
| 906 |     { | 
            ||
| 907 | return $this->renderFile(Yii::getAlias($this->templateFile), $params);  | 
            ||
| 908 | }  | 
            ||
| 909 | |||
| 910 | /**  | 
            ||
| 911 | * Truncates the database.  | 
            ||
| 912 | * This method should be overwritten in subclasses to implement the task of clearing the database.  | 
            ||
| 913 | * @throws NotSupportedException if not overridden  | 
            ||
| 914 | * @since 2.0.13  | 
            ||
| 915 | */  | 
            ||
| 916 | protected function truncateDatabase()  | 
            ||
| 917 |     { | 
            ||
| 918 |         throw new NotSupportedException('This command is not implemented in ' . get_class($this)); | 
            ||
| 919 | }  | 
            ||
| 920 | |||
| 921 | /**  | 
            ||
| 922 | * Returns the migration history.  | 
            ||
| 923 | * @param int $limit the maximum number of records in the history to be returned. `null` for "no limit".  | 
            ||
| 924 | * @return array the migration history  | 
            ||
| 925 | */  | 
            ||
| 926 | abstract protected function getMigrationHistory($limit);  | 
            ||
| 927 | |||
| 928 | /**  | 
            ||
| 929 | * Adds new migration entry to the history.  | 
            ||
| 930 | * @param string $version migration version name.  | 
            ||
| 931 | */  | 
            ||
| 932 | abstract protected function addMigrationHistory($version);  | 
            ||
| 933 | |||
| 934 | /**  | 
            ||
| 935 | * Removes existing migration from the history.  | 
            ||
| 936 | * @param string $version migration version name.  | 
            ||
| 937 | */  | 
            ||
| 938 | abstract protected function removeMigrationHistory($version);  | 
            ||
| 939 | }  | 
            ||
| 940 | 
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.