diff --git a/system/Commands/Generators/MigrationGenerator.php b/system/Commands/Generators/MigrationGenerator.php index f2caf53d93ed..8df4a154d1e2 100644 --- a/system/Commands/Generators/MigrationGenerator.php +++ b/system/Commands/Generators/MigrationGenerator.php @@ -13,116 +13,83 @@ namespace CodeIgniter\Commands\Generators; -use CodeIgniter\CLI\BaseCommand; -use CodeIgniter\CLI\CLI; -use CodeIgniter\CLI\GeneratorTrait; +use CodeIgniter\CLI\AbstractGeneratorCommand; +use CodeIgniter\CLI\Attributes\Command; +use CodeIgniter\CLI\Attributes\GeneratorCommand; +use CodeIgniter\CLI\Input\Option; use Config\Database; use Config\Migrations; use Config\Session as SessionConfig; -/** - * Generates a skeleton migration file. - */ -class MigrationGenerator extends BaseCommand +#[Command(name: 'make:migration', description: 'Generates a new migration file.', group: 'Generators')] +#[GeneratorCommand( + component: 'Migration', + template: 'migration.tpl.php', + directory: 'Database\Migrations', + classNameLang: 'CLI.generator.className.migration', +)] +class MigrationGenerator extends AbstractGeneratorCommand { - use GeneratorTrait; - - /** - * The Command's Group - * - * @var string - */ - protected $group = 'Generators'; - - /** - * The Command's Name - * - * @var string - */ - protected $name = 'make:migration'; - - /** - * The Command's Description - * - * @var string - */ - protected $description = 'Generates a new migration file.'; - - /** - * The Command's Usage - * - * @var string - */ - protected $usage = 'make:migration [options]'; - - /** - * The Command's Arguments - * - * @var array - */ - protected $arguments = [ - 'name' => 'The migration class name.', - ]; - - /** - * The Command's Options - * - * @var array - */ - protected $options = [ - '--session' => 'Generates the migration file for database sessions.', - '--table' => 'Table name to use for database sessions. Default: "ci_sessions".', - '--dbgroup' => 'Database group to use for database sessions. Default: "default".', - '--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".', - '--suffix' => 'Append the component title to the class name (e.g. User => UserMigration).', - ]; + protected function configure(): void + { + parent::configure(); + + $this + ->addOption(new Option( + name: 'session', + description: 'Generate the migration file for database sessions.', + )) + ->addOption(new Option( + name: 'table', + shortcut: 't', + description: 'Table name to use for database sessions.', + requiresValue: true, + default: 'ci_sessions', + )) + ->addOption(new Option( + name: 'dbgroup', + shortcut: 'g', + description: 'Database group to use for database sessions.', + requiresValue: true, + valueLabel: 'group', + default: 'default', + )); + } - /** - * Actually execute a command. - */ - public function run(array $params) + protected function provideGeneratorOptions(): void { - $this->component = 'Migration'; - $this->directory = 'Database\Migrations'; - $this->template = 'migration.tpl.php'; + $this->addNamespaceOption()->addSuffixOption(); + } - if (array_key_exists('session', $params) || CLI::getOption('session')) { - $table = $params['table'] ?? CLI::getOption('table') ?? 'ci_sessions'; - $params[0] = "_create_{$table}_table"; + protected function initialize(array &$arguments, array &$options): void + { + if (! $this->hasUnboundOption('session', $options)) { + return; } - $this->classNameLang = 'CLI.generator.className.migration'; - $this->generateClass($params); + $table = $this->getUnboundOption('table', $options); - return EXIT_SUCCESS; + $arguments[0] = sprintf('_create_%s_table', is_string($table) ? $table : 'ci_sessions'); } - /** - * Prepare options and do the necessary replacements. - */ - protected function prepare(string $class): string + protected function getTemplateData(string $class): array { - $data = []; - $data['session'] = false; - - if ($this->getOption('session')) { - $table = $this->getOption('table'); - $DBGroup = $this->getOption('dbgroup'); - - $data['session'] = true; - $data['table'] = is_string($table) ? $table : 'ci_sessions'; - $data['DBGroup'] = is_string($DBGroup) ? $DBGroup : 'default'; - $data['DBDriver'] = config(Database::class)->{$data['DBGroup']}['DBDriver']; - - $data['matchIP'] = config(SessionConfig::class)->matchIP; + if ($this->getValidatedOption('session') !== true) { + return ['session' => false]; } - return $this->parseTemplate($class, [], [], $data); + $group = $this->getValidatedOption('dbgroup'); + assert(is_string($group)); + + return [ + 'session' => true, + 'table' => $this->getValidatedOption('table'), + 'DBGroup' => $group, + 'DBDriver' => config(Database::class)->{$group}['DBDriver'], + 'matchIP' => config(SessionConfig::class)->matchIP, + ]; } - /** - * Change file basename before saving. - */ protected function basename(string $filename): string { return gmdate(config(Migrations::class)->timestampFormat) . basename($filename); diff --git a/system/Commands/Generators/ScaffoldGenerator.php b/system/Commands/Generators/ScaffoldGenerator.php index a5d3d62632e1..9009b0effc8c 100644 --- a/system/Commands/Generators/ScaffoldGenerator.php +++ b/system/Commands/Generators/ScaffoldGenerator.php @@ -119,7 +119,7 @@ public function run(array $params) // Call those commands! $exit1 = $this->call('make:controller', array_merge([$class], $controllerOpts, $options)); $exit2 = $this->call('make:model', array_merge([$class], $modelOpts, $options)); - $exit3 = $this->call('make:migration', array_merge([$class], $options)); + $exit3 = $this->call('make:migration', array_merge([$class], array_diff_key($options, ['force' => null]))); $exit4 = $this->call('make:seeder', array_merge([$class], $options)); assert(is_int($exit1) && is_int($exit2) && is_int($exit3) && is_int($exit4)); diff --git a/tests/system/Commands/Generators/MigrationGeneratorTest.php b/tests/system/Commands/Generators/MigrationGeneratorTest.php index c4670019a422..f058b3a89027 100644 --- a/tests/system/Commands/Generators/MigrationGeneratorTest.php +++ b/tests/system/Commands/Generators/MigrationGeneratorTest.php @@ -13,6 +13,7 @@ namespace CodeIgniter\Commands\Generators; +use CodeIgniter\CLI\CLI; use CodeIgniter\Test\CIUnitTestCase; use CodeIgniter\Test\StreamFilterTrait; use PHPUnit\Framework\Attributes\Group; @@ -25,36 +26,97 @@ final class MigrationGeneratorTest extends CIUnitTestCase { use StreamFilterTrait; + protected function setUp(): void + { + parent::setUp(); + + CLI::reset(); + } + protected function tearDown(): void { - $result = str_replace(["\033[0;32m", "\033[0m", "\n"], '', $this->getStreamFilterBuffer()); - $file = str_replace('APPPATH' . DIRECTORY_SEPARATOR, APPPATH, trim(substr($result, 14))); - if (is_file($file)) { + parent::tearDown(); + + CLI::reset(); + + foreach (glob(APPPATH . 'Database/Migrations/*_*.php') as $file) { unlink($file); } } + private function getUndecoratedBuffer(): string + { + return preg_replace('/\e\[[^m]+m/', '', $this->getStreamFilterBuffer()) ?? ''; + } + + private function getContents(string $basename): string + { + $files = glob(APPPATH . 'Database/Migrations/*_' . $basename . '.php'); + $this->assertCount(1, $files); + + $contents = file_get_contents($files[0]); + $this->assertIsString($contents); + + return $contents; + } + public function testGenerateMigration(): void { command('make:migration database'); - $this->assertStringContainsString('_Database.php', $this->getStreamFilterBuffer()); + + $this->assertMatchesRegularExpression( + '#^\nFile created: APPPATH/Database/Migrations/\d{4}-\d{2}-\d{2}-\d{6}_Database\.php\n$#', + $this->getUndecoratedBuffer(), + ); + + $contents = $this->getContents('Database'); + $this->assertStringContainsString('namespace App\Database\Migrations;', $contents); + $this->assertStringContainsString('class Database extends Migration', $contents); + $this->assertStringNotContainsString('$DBGroup', $contents); } public function testGenerateMigrationWithOptionSession(): void { - command('make:migration -session'); - $this->assertStringContainsString('_CreateCiSessionsTable.php', $this->getStreamFilterBuffer()); + command('make:migration --session'); + + $contents = $this->getContents('CreateCiSessionsTable'); + $this->assertStringContainsString('class CreateCiSessionsTable extends Migration', $contents); + $this->assertStringContainsString("protected \$DBGroup = 'default';", $contents); + $this->assertStringContainsString("\$this->forge->addKey('id', true);", $contents); + $this->assertStringContainsString("\$this->forge->createTable('ci_sessions', true);", $contents); } - public function testGenerateMigrationWithOptionTable(): void + public function testSessionIgnoresNameArgument(): void { - command('make:migration -session -table logger'); - $this->assertStringContainsString('_CreateLoggerTable.php', $this->getStreamFilterBuffer()); + command('make:migration database --session'); + + $this->assertStringContainsString('_CreateCiSessionsTable.php', $this->getUndecoratedBuffer()); + $this->assertFileDoesNotExist(APPPATH . 'Database/Migrations/Database.php'); + } + + public function testGenerateMigrationWithOptionTableAndDbGroup(): void + { + command('make:migration --session --table logger --dbgroup tests'); + + $contents = $this->getContents('CreateLoggerTable'); + $this->assertStringContainsString("protected \$DBGroup = 'tests';", $contents); + $this->assertStringContainsString("\$this->forge->createTable('logger', true);", $contents); + $this->assertStringContainsString("\$this->forge->dropTable('logger', true);", $contents); + } + + public function testGenerateMigrationWithShortcuts(): void + { + command('make:migration --session -t logger -g tests'); + + $contents = $this->getContents('CreateLoggerTable'); + $this->assertStringContainsString("protected \$DBGroup = 'tests';", $contents); + $this->assertStringContainsString("\$this->forge->createTable('logger', true);", $contents); } public function testGenerateMigrationWithOptionSuffix(): void { - command('make:migration database -suffix'); - $this->assertStringContainsString('_DatabaseMigration.php', $this->getStreamFilterBuffer()); + command('make:migration database --suffix'); + + $this->assertStringContainsString('class DatabaseMigration extends Migration', $this->getContents('DatabaseMigration')); } } diff --git a/user_guide_src/source/changelogs/v4.8.0.rst b/user_guide_src/source/changelogs/v4.8.0.rst index bb597e684bc4..40203e1fb14f 100644 --- a/user_guide_src/source/changelogs/v4.8.0.rst +++ b/user_guide_src/source/changelogs/v4.8.0.rst @@ -33,6 +33,8 @@ Behavior Changes - **Commands:** Generator commands migrated to ``AbstractGeneratorCommand`` now return ``EXIT_ERROR`` (previously ``EXIT_SUCCESS``) when the target file exists without ``--force``, when the namespace is not defined, or when the name does not form a valid class name. The ``CodeIgniter`` namespace confirmation is no longer prompted on non-interactive runs. +- **Commands:** The ``make:migration`` command now rejects ``--force`` as an unknown option. It was previously accepted but ignored, since the + timestamped file name never collides with an existing file. - **Commands:** The success and error messages from ``debugbar:clear``, ``cache:clear``, and ``cache:info`` now include the affected path or cache driver/handler so the user can see which resource was acted on (or rejected). Scripts asserting on the prior literal text will need to be updated. - **Commands:** Declining the ``key:generate`` overwrite prompt interactively now returns ``EXIT_SUCCESS`` instead of ``EXIT_ERROR``. Output messages were also reworded; CI/automation that branches on the exit code or greps the previous wording will need updating. - **Commands:** The ``migrate:rollback`` command no longer accepts the undocumented ``-g`` (database group) option. It never had any effect, since ``MigrationRunner::regress()`` ignores the group, and the modern command pipeline now rejects unknown options. Remove ``-g`` from any ``migrate:rollback`` invocation. @@ -238,6 +240,7 @@ Commands options, which the target command validates like any other input. - The ``make:controller`` command now accepts ``-b`` and ``-r`` as shortcuts for ``--bare`` and ``--restful``, and rejects an invalid ``--restful`` value on non-interactive runs instead of prompting. +- The ``make:migration`` command now accepts ``-t`` and ``-g`` as shortcuts for ``--table`` and ``--dbgroup``. - Modern commands can now opt in to prompting for missing required arguments on interactive runs by implementing the new ``PromptsForMissingInputInterface`` marker interface. Prompt labels can be customized via ``getArgumentPromptLabels()``, and an ``afterPrompting()`` hook runs when prompting occurred. Non-interactive runs keep failing fast with the missing-arguments error. diff --git a/user_guide_src/source/cli/cli_generators.rst b/user_guide_src/source/cli/cli_generators.rst index a5b1283fa02f..fc039b3b74b5 100644 --- a/user_guide_src/source/cli/cli_generators.rst +++ b/user_guide_src/source/cli/cli_generators.rst @@ -313,12 +313,13 @@ Argument: Options: ======== -* ``--session``: Generate a migration file for database sessions. -* ``--table``: Set the table name to use for database sessions. Defaults to ``ci_sessions``. -* ``--dbgroup``: Set the database group for database sessions. Defaults to ``default`` group. -* ``--namespace``: Set the root namespace. Defaults to value of ``APP_NAMESPACE``. -* ``--suffix``: Append the component suffix to the generated class name. -* ``--force``: Set this flag to overwrite existing files on destination. +* ``--session``: Generate a migration file for database sessions. The ``name`` argument is ignored. +* ``--table`` (``-t``): Set the table name to use for database sessions. Defaults to ``ci_sessions``. +* ``--dbgroup`` (``-g``): Set the database group for database sessions. Defaults to ``default`` group. +* ``--namespace`` (``-n``): Set the root namespace. Defaults to value of ``APP_NAMESPACE``. +* ``--suffix`` (``-s``): Append the component suffix to the generated class name. + +.. note:: ``make:migration`` has no ``--force`` option, since the timestamped file name never collides with an existing file. make:validation --------------- diff --git a/user_guide_src/source/dbmgmt/migration.rst b/user_guide_src/source/dbmgmt/migration.rst index 384a0bdd23cb..dfa6021382bc 100644 --- a/user_guide_src/source/dbmgmt/migration.rst +++ b/user_guide_src/source/dbmgmt/migration.rst @@ -220,15 +220,15 @@ creates is the Pascal case version of the filename. You can use ``make:migration`` with the following options: -- ``--namespace`` - Set root namespace. Default: ``APP_NAMESPACE``. -- ``--suffix`` - Append the component title to the class name. +- ``--namespace`` (``-n``) - Set root namespace. Default: ``APP_NAMESPACE``. +- ``--suffix`` (``-s``) - Append the component title to the class name. The following options are also available to generate the migration file for database sessions: -- ``--session`` - Generates the migration file for database sessions. -- ``--table`` - Table name to use for database sessions. Default: ``ci_sessions``. -- ``--dbgroup`` - Database group to use for database sessions. Default: ``default``. +- ``--session`` - Generates the migration file for database sessions. +- ``--table`` (``-t``) - Table name to use for database sessions. Default: ``ci_sessions``. +- ``--dbgroup`` (``-g``) - Database group to use for database sessions. Default: ``default``. ********************* Migration Preferences