diff --git a/src/Generator.php b/src/Generator.php index 54e5aa6..901ef98 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -25,6 +25,7 @@ use function count; use function get_class; use function in_array; +use function is_array; /** * Class Generator @@ -73,7 +74,8 @@ class Generator extends Component public $templateFileUpdate; /** - * @var string Migration namespace. + * @var string|array Migration namespace. + * Since 3.5.0 this can be array of namespaces. */ public $namespace; @@ -111,6 +113,10 @@ public function init(): void if (!($this->db instanceof Connection)) { throw new InvalidConfigException("Parameter 'db' must be an instance of yii\\db\\Connection!"); } + + if ($this->namespace !== null && !is_array($this->namespace)) { + $this->namespace = [$this->namespace]; + } } protected $_tableSchema; @@ -306,7 +312,7 @@ public function getTable(): TableStructure */ public function getNormalizedNamespace(): ?string { - return !empty($this->namespace) ? FileHelper::normalizePath($this->namespace, '\\') : null; + return !empty($this->namespace) ? FileHelper::normalizePath(reset($this->namespace), '\\') : null; } /** diff --git a/src/Updater.php b/src/Updater.php index 0b76f69..7a46193 100644 --- a/src/Updater.php +++ b/src/Updater.php @@ -50,7 +50,8 @@ class Updater extends Generator public $migrationTable = '{{%migration}}'; /** - * @var string Directory storing the migration classes. This can be either a path alias or a directory. + * @var string|array Directory storing the migration classes. This can be either a path alias or a directory. + * Since 3.5.0 this can be array of directories. */ public $migrationPath = '@app/migrations'; @@ -76,6 +77,14 @@ public function init(): void { parent::init(); + if (empty($this->migrationPath)) { + throw new InvalidConfigException('You must provide "migrationPath" value.'); + } + + if (!is_array($this->migrationPath)) { + $this->migrationPath = [$this->migrationPath]; + } + $this->_currentTable = $this->tableName; foreach ($this->skipMigrations as $index => $migration) { @@ -190,10 +199,17 @@ protected function gatherChanges(array $changes): bool protected function extract(string $migration): array { if (strpos($migration, '\\') === false) { - $file = Yii::getAlias($this->migrationPath . DIRECTORY_SEPARATOR . $migration . '.php'); + $fileFound = false; + foreach ($this->migrationPath as $path) { + $file = Yii::getAlias($path . DIRECTORY_SEPARATOR . $migration . '.php'); + if (file_exists($file)) { + $fileFound = true; + break; + } + } - if (!file_exists($file)) { - throw new ErrorException("File '{$file}' can not be found! Check migration history table."); + if (!$fileFound) { + throw new ErrorException("File '{$migration}.php' can not be found! Check migration history table."); } require_once $file; diff --git a/src/controllers/MigrationController.php b/src/controllers/MigrationController.php index ead35bb..28e2f32 100644 --- a/src/controllers/MigrationController.php +++ b/src/controllers/MigrationController.php @@ -29,6 +29,7 @@ use function gmdate; use function implode; use function in_array; +use function is_array; use function is_dir; use function strlen; use function strpos; @@ -39,7 +40,7 @@ * Generates migration file based on the existing database table and previous migrations. * * @author Paweł Bizley Brzozowski - * @version 3.4.0 + * @version 3.5.0 * @license Apache 2.0 * https://github.com/bizley/yii2-migration */ @@ -48,7 +49,7 @@ class MigrationController extends Controller /** * @var string */ - protected $version = '3.4.0'; + protected $version = '3.5.0'; /** * @var string Default command action. @@ -56,18 +57,23 @@ class MigrationController extends Controller public $defaultAction = 'list'; /** - * @var string Directory storing the migration classes. This can be either a path alias or a directory. + * @var string|array Directory storing the migration classes. This can be either a path alias or a directory. + * Since 3.5.0 this can be array of directories. In this case the first element will be used for generator and + * only first one will be created if it doesn't exist yet. * Alias -p */ public $migrationPath = '@app/migrations'; /** - * @var string Full migration namespace. If given it's used instead of $migrationPath. Note that backslash (\) + * @var string|array Full migration namespace. If given it's used instead of $migrationPath. Note that backslash (\) * symbol is usually considered a special character in the shell, so you need to escape it properly to avoid shell * errors or incorrect behavior. * Migration namespace should be resolvable as a path alias if prefixed with @, e.g. if you specify the namespace * 'app\migrations', the code Yii::getAlias('@app/migrations') should be able to return the file path to * the directory this namespace refers to. + * When this property is given $migrationPath is ignored. + * Since 3.5.0 this can be array of namespaces. In this case the first element will be used for generator and + * only first one will be checked for corresponding directory to exist and be created if needed. * Alias -n * @since 1.1 */ @@ -290,18 +296,34 @@ public function beforeAction($action): bool // BC declaration return false; } - if (!$this->showOnly && in_array($action->id, ['create', 'create-all', 'update', 'update-all'], true)) { - if ($this->migrationPath !== null) { - $this->migrationPath = $this->preparePathDirectory($this->migrationPath); - } - + if (in_array($action->id, ['create', 'create-all', 'update', 'update-all'], true)) { if ($this->migrationNamespace !== null) { - $this->migrationNamespace = FileHelper::normalizePath($this->migrationNamespace, '\\'); - $this->workingPath = $this->preparePathDirectory( - FileHelper::normalizePath('@' . $this->migrationNamespace, '/') - ); + if (!is_array($this->migrationNamespace)) { + $this->migrationNamespace = [$this->migrationNamespace]; + } + foreach ($this->migrationNamespace as &$namespace) { + $namespace = FileHelper::normalizePath($this->migrationNamespace, '\\'); + + if ($this->workingPath === null && !$this->showOnly) { + $this->workingPath = $this->preparePathDirectory( + '@' . FileHelper::normalizePath($namespace, '/') + ); + } + } + } elseif ($this->migrationPath !== null) { + if (!is_array($this->migrationPath)) { + $this->migrationPath = [$this->migrationPath]; + } + foreach ($this->migrationPath as $path) { + if ($this->workingPath === null && !$this->showOnly) { + $this->workingPath = $this->preparePathDirectory($path); + break; + } + } } else { - $this->workingPath = $this->migrationPath; + throw new InvalidConfigException( + 'You must provide either "migrationPath" or "migrationNamespace" for this action.' + ); } } @@ -473,7 +495,7 @@ public function actionCreate(string $table): int $postponedForeignKeys = []; - $counterSize = strlen((string)$countTables) + 1; + $counterSize = strlen((string) $countTables) + 1; $migrationsGenerated = 0; foreach ($tables as $name) { $this->stdout(" > Generating create migration for table '{$name}' ...", Console::FG_YELLOW); @@ -488,7 +510,7 @@ public function actionCreate(string $table): int } else { $className = sprintf('m%s_create_table_%s', gmdate('ymd_His'), $name); } - $file = Yii::getAlias($this->workingPath . DIRECTORY_SEPARATOR . $className . '.php'); + $file = $this->workingPath . DIRECTORY_SEPARATOR . $className . '.php'; $generator = new Generator([ 'db' => $this->db, @@ -534,7 +556,10 @@ public function actionCreate(string $table): int $this->stdout("\n"); - $postponedForeignKeys = array_merge($postponedForeignKeys, $generator->getSuppressedForeignKeys()); + $supressedKeys = $generator->getSuppressedForeignKeys(); + foreach ($supressedKeys as $supressedKey) { + $postponedForeignKeys[] = $supressedKey; + } } if ($postponedForeignKeys) { @@ -545,7 +570,7 @@ public function actionCreate(string $table): int gmdate('ymd_His'), ++$migrationsGenerated ); - $file = Yii::getAlias($this->workingPath . DIRECTORY_SEPARATOR . $className . '.php'); + $file = $this->workingPath . DIRECTORY_SEPARATOR . $className . '.php'; if ($this->generateFile($file, $this->view->renderFile(Yii::getAlias($this->templateFileForeignKey), [ 'fks' => $postponedForeignKeys, @@ -625,7 +650,7 @@ public function actionUpdate(string $table): int $this->stdout(" > Generating update migration for table '{$name}' ...", Console::FG_YELLOW); $className = 'm' . gmdate('ymd_His') . '_update_table_' . $name; - $file = Yii::getAlias($this->workingPath . DIRECTORY_SEPARATOR . $className . '.php'); + $file = $this->workingPath . DIRECTORY_SEPARATOR . $className . '.php'; $updater = new Updater([ 'db' => $this->db, diff --git a/tests/cases/DbTestCase.php b/tests/cases/DbTestCase.php index 9c7285f..a317618 100644 --- a/tests/cases/DbTestCase.php +++ b/tests/cases/DbTestCase.php @@ -53,8 +53,6 @@ public static function setUpBeforeClass(): void 'controllerMap' => [ 'migration' => [ 'class' => MigrationController::class, - 'migrationPath' => null, - 'migrationNamespace' => null, ], 'migrate' => [ 'class' => EchoMigrateController::class, @@ -97,7 +95,9 @@ protected static function runSilentMigration(string $route, array $params = []): */ public static function tearDownAfterClass(): void { - static::runSilentMigration('migrate/down', ['all']); + if (static::$runMigrations) { + static::runSilentMigration('migrate/down', ['all']); + } if (static::$db) { static::$db->close(); diff --git a/tests/cases/MigrationControllerTestCase.php b/tests/cases/MigrationControllerTestCase.php index bfab728..19ec246 100644 --- a/tests/cases/MigrationControllerTestCase.php +++ b/tests/cases/MigrationControllerTestCase.php @@ -196,7 +196,7 @@ public function testCreateInProperOrder(): void $output = str_replace(["\r", "\n"], '', $mock->flushStdOutBuffer()); $file = Yii::getAlias( - $mock->migrationPath + reset($mock->migrationPath) . DIRECTORY_SEPARATOR . 'm' . gmdate('ymd_His') . '_01_create_table_test_pk.php' @@ -233,4 +233,19 @@ public function testCreatePostponedFK(): void $this->assertContains('> Generating create migration for foreign keys ...DONE!', $output); $this->assertContains(' Generated 3 file(s).', $output); } + + /** + * @throws Exception + * @throws InvalidRouteException + */ + public function testInvalidConfig(): void + { + $controller = new MockMigrationController('migration', Yii::$app); + $controller->migrationPath = null; + + $this->expectExceptionMessage( + 'You must provide either "migrationPath" or "migrationNamespace" for this action.' + ); + $controller->runAction('create', ['table']); + } }