diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 5e89992336..c66dde5eb7 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -26,6 +26,7 @@ Yii Framework 2 Change Log - Bug #11188: Fixed wrong index usage in `CaptchaAction` when calling `imagefilledrectangle` (alsopub) - Bug #11221: Boolean validator generates incorrect error message (azaikin, githubjeka) - Bug #11223: Fixed returning an empty array when DbManager::getRolesByUser() was called on a user with user id 0 (VirtualRJ) +- Bug #11280: Descendants of `yii\console\controllers\BaseMigrateController`, like the one for MongoDB, unable to create new migration (klimov-paul) - Bug: SQlite querybuilder did not create primary key with bigint for `TYPE_BIGPK` (cebe) - Enh #5469: Add mimetype validation by mask in FileValidator (kirsenn, samdark, silverfire) - Enh #8145, #8139, #10234 #11153: `yii\validators\Validator::$attributes` property now supports `!attribute` notation to validate attribute, but do not mark it as safe (mdmunir) diff --git a/framework/console/controllers/BaseMigrateController.php b/framework/console/controllers/BaseMigrateController.php index 8e64f67bd8..36eda056bc 100644 --- a/framework/console/controllers/BaseMigrateController.php +++ b/framework/console/controllers/BaseMigrateController.php @@ -41,32 +41,7 @@ abstract class BaseMigrateController extends Controller * or a file path. */ public $templateFile; - /** - * @var array a set of template paths for generating migration code automatically. - * - * The key is the template type, the value is a path or the alias. Supported types are: - * - `create_table`: table creating template - * - `drop_table`: table dropping template - * - `add_column`: adding new column template - * - `drop_column`: dropping column template - * - `create_junction`: create junction template - * - * @since 2.0.7 - */ - public $generatorTemplateFiles; - /** - * @var array column definition strings used for creating migration code. - * The format of each definition is `COLUMN_NAME:COLUMN_TYPE:COLUMN_DECORATOR`. - * For example, `--fields=name:string(12):notNull` produces a string column of size 12 which is not null. - * @since 2.0.7 - */ - public $fields = []; - /** - * @var array columns which have a foreign key and their related table. - * @since 2.0.8 - */ - protected $foreignKeys = []; /** * @inheritdoc @@ -76,7 +51,7 @@ abstract class BaseMigrateController extends Controller return array_merge( parent::options($actionID), ['migrationPath'], // global for all actions - $actionID === 'create' ? ['templateFile', 'fields'] : [] // action create + $actionID === 'create' ? ['templateFile'] : [] // action create ); } @@ -98,7 +73,6 @@ abstract class BaseMigrateController extends Controller FileHelper::createDirectory($path); } $this->migrationPath = $path; - $this->parseFields(); $version = Yii::getVersion(); $this->stdout("Yii Migration Tool (based on Yii v{$version})\n\n"); @@ -504,59 +478,11 @@ abstract class BaseMigrateController extends Controller $className = 'm' . gmdate('ymd_His') . '_' . $name; $file = $this->migrationPath . DIRECTORY_SEPARATOR . $className . '.php'; if ($this->confirm("Create new migration '$file'?")) { - $table = null; - if (preg_match('/^create_junction_(.+)_and_(.+)$/', $name, $matches)) { - $this->templateFile = $this->generatorTemplateFiles['create_junction']; - $firstTable = mb_strtolower($matches[1], Yii::$app->charset); - $secondTable = mb_strtolower($matches[2], Yii::$app->charset); - - $this->fields = array_merge( - [ - [ - 'property' => $firstTable . '_id', - 'decorators' => 'integer()', - ], - [ - 'property' => $secondTable . '_id', - 'decorators' => 'integer()', - ], - ], - $this->fields, - [ - [ - 'property' => 'PRIMARY KEY(' . - $firstTable . '_id, ' . - $secondTable . '_id)', - ], - ] - ); - - $this->foreignKeys[$firstTable . '_id'] = $firstTable; - $this->foreignKeys[$secondTable . '_id'] = $secondTable; - $table = $firstTable . '_' . $secondTable; - } elseif (preg_match('/^add_(.+)_to_(.+)$/', $name, $matches)) { - $this->templateFile = $this->generatorTemplateFiles['add_column']; - $table = mb_strtolower($matches[2], Yii::$app->charset); - } elseif (preg_match('/^drop_(.+)_from_(.+)$/', $name, $matches)) { - $this->templateFile = $this->generatorTemplateFiles['drop_column']; - $table = mb_strtolower($matches[2], Yii::$app->charset); - } elseif (preg_match('/^create_(.+)$/', $name, $matches)) { - $this->addDefaultPrimaryKey(); - $this->templateFile = $this->generatorTemplateFiles['create_table']; - $table = mb_strtolower($matches[1], Yii::$app->charset); - } elseif (preg_match('/^drop_(.+)$/', $name, $matches)) { - $this->addDefaultPrimaryKey(); - $this->templateFile = $this->generatorTemplateFiles['drop_table']; - $table = mb_strtolower($matches[1], Yii::$app->charset); - } - - file_put_contents($file, $this->renderFile($this->templateFile, [ - 'className' => $className, + $content = $this->generateMigrationSourceCode([ 'name' => $name, - 'table' => $table, - 'fields' => $this->fields, - 'foreignKeys' => $this->foreignKeys, - ])); + 'className' => $className, + ]); + file_put_contents($file, $content); $this->stdout("New migration created successfully.\n", Console::FG_GREEN); } } @@ -715,49 +641,19 @@ abstract class BaseMigrateController extends Controller } /** - * Parse the command line migration fields - * @since 2.0.7 + * Generates new migration source PHP code. + * Child class may override this method, adding extra logic or variation to the process. + * @param array $params generation parameters, usually following parameters are present: + * + * - name: string migration base name + * - className: string migration class name + * + * @return string generated PHP code. + * @since 2.0.8 */ - protected function parseFields() + protected function generateMigrationSourceCode($params) { - foreach ($this->fields as $index => $field) { - $chunks = preg_split('/\s?:\s?/', $field, null); - $property = array_shift($chunks); - - foreach ($chunks as $i => &$chunk) { - if (strpos($chunk, 'foreignKey') === 0) { - preg_match('/foreignKey\((\w*)\)/', $chunk, $matches); - $this->foreignKeys[$property] = isset($matches[1]) - ? $matches[1] - : preg_replace('/_id$/', '', $property); - - unset($chunks[$i]); - continue; - } - - if (!preg_match('/^(.+?)\(([^)]+)\)$/', $chunk)) { - $chunk .= '()'; - } - } - $this->fields[$index] = [ - 'property' => $property, - 'decorators' => implode('->', $chunks), - ]; - } - } - - /** - * Adds default primary key to fields list if there's no primary key specified - * @since 2.0.7 - */ - protected function addDefaultPrimaryKey() - { - foreach ($this->fields as $field) { - if ($field['decorators'] === 'primaryKey()') { - return; - } - } - array_unshift($this->fields, ['property' => 'id', 'decorators' => 'primaryKey()']); + return $this->renderFile(Yii::getAlias($this->templateFile), $params); } /** diff --git a/framework/console/controllers/MigrateController.php b/framework/console/controllers/MigrateController.php index a8a35f87bc..98b2d1c746 100644 --- a/framework/console/controllers/MigrateController.php +++ b/framework/console/controllers/MigrateController.php @@ -64,7 +64,15 @@ class MigrateController extends BaseMigrateController */ public $templateFile = '@yii/views/migration.php'; /** - * @inheritdoc + * @var array a set of template paths for generating migration code automatically. + * + * The key is the template type, the value is a path or the alias. Supported types are: + * - `create_table`: table creating template + * - `drop_table`: table dropping template + * - `add_column`: adding new column template + * - `drop_column`: dropping column template + * - `create_junction`: create junction template + * * @since 2.0.7 */ public $generatorTemplateFiles = [ @@ -74,6 +82,13 @@ class MigrateController extends BaseMigrateController 'drop_column' => '@yii/views/dropColumnMigration.php', 'create_junction' => '@yii/views/createTableMigration.php' ]; + /** + * @var array column definition strings used for creating migration code. + * The format of each definition is `COLUMN_NAME:COLUMN_TYPE:COLUMN_DECORATOR`. + * For example, `--fields=name:string(12):notNull` produces a string column of size 12 which is not null. + * @since 2.0.7 + */ + public $fields = []; /** * @var Connection|array|string the DB connection object or the application component ID of the DB connection to use * when applying migrations. Starting from version 2.0.3, this can also be a configuration array @@ -81,6 +96,12 @@ class MigrateController extends BaseMigrateController */ public $db = 'db'; + /** + * @var array columns which have a foreign key and their related table. + * @since 2.0.8 + */ + protected $foreignKeys = []; + /** * @inheritdoc @@ -89,7 +110,8 @@ class MigrateController extends BaseMigrateController { return array_merge( parent::options($actionID), - ['migrationTable', 'db'] // global for all actions + ['migrationTable', 'db'], // global for all actions + $actionID === 'create' ? ['fields'] : [] // action create ); } @@ -198,4 +220,113 @@ class MigrateController extends BaseMigrateController 'version' => $version, ])->execute(); } + + /** + * @inheritdoc + */ + protected function generateMigrationSourceCode($params) + { + $this->parseFields(); + + $name = $params['name']; + + $templateFile = $this->templateFile; + $table = null; + if (preg_match('/^create_junction_(.+)_and_(.+)$/', $name, $matches)) { + $templateFile = $this->generatorTemplateFiles['create_junction']; + $firstTable = mb_strtolower($matches[1], Yii::$app->charset); + $secondTable = mb_strtolower($matches[2], Yii::$app->charset); + + $this->fields = array_merge( + [ + [ + 'property' => $firstTable . '_id', + 'decorators' => 'integer()', + ], + [ + 'property' => $secondTable . '_id', + 'decorators' => 'integer()', + ], + ], + $this->fields, + [ + [ + 'property' => 'PRIMARY KEY(' . + $firstTable . '_id, ' . + $secondTable . '_id)', + ], + ] + ); + + $this->foreignKeys[$firstTable . '_id'] = $firstTable; + $this->foreignKeys[$secondTable . '_id'] = $secondTable; + $table = $firstTable . '_' . $secondTable; + } elseif (preg_match('/^add_(.+)_to_(.+)$/', $name, $matches)) { + $templateFile = $this->generatorTemplateFiles['add_column']; + $table = mb_strtolower($matches[2], Yii::$app->charset); + } elseif (preg_match('/^drop_(.+)_from_(.+)$/', $name, $matches)) { + $templateFile = $this->generatorTemplateFiles['drop_column']; + $table = mb_strtolower($matches[2], Yii::$app->charset); + } elseif (preg_match('/^create_(.+)$/', $name, $matches)) { + $this->addDefaultPrimaryKey(); + $templateFile = $this->generatorTemplateFiles['create_table']; + $table = mb_strtolower($matches[1], Yii::$app->charset); + } elseif (preg_match('/^drop_(.+)$/', $name, $matches)) { + $this->addDefaultPrimaryKey(); + $templateFile = $this->generatorTemplateFiles['drop_table']; + $table = mb_strtolower($matches[1], Yii::$app->charset); + } + + return $this->renderFile(Yii::getAlias($templateFile), array_merge($params, [ + 'table' => $table, + 'fields' => $this->fields, + 'foreignKeys' => $this->foreignKeys, + ])); + } + + /** + * Parse the command line migration fields + * @since 2.0.7 + */ + protected function parseFields() + { + foreach ($this->fields as $index => $field) { + $chunks = preg_split('/\s?:\s?/', $field, null); + $property = array_shift($chunks); + + foreach ($chunks as $i => &$chunk) { + if (strpos($chunk, 'foreignKey') === 0) { + preg_match('/foreignKey\((\w*)\)/', $chunk, $matches); + $this->foreignKeys[$property] = isset($matches[1]) + ? $matches[1] + : preg_replace('/_id$/', '', $property); + + unset($chunks[$i]); + continue; + } + + if (!preg_match('/^(.+?)\(([^)]+)\)$/', $chunk)) { + $chunk .= '()'; + } + } + $this->fields[$index] = [ + 'property' => $property, + 'decorators' => implode('->', $chunks), + ]; + } + } + + /** + * Adds default primary key to fields list if there's no primary key specified + * @since 2.0.7 + */ + protected function addDefaultPrimaryKey() + { + foreach ($this->fields as $field) { + if ($field['decorators'] === 'primaryKey()') { + return; + } + } + array_unshift($this->fields, ['property' => 'id', 'decorators' => 'primaryKey()']); + } }