Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 59 additions & 92 deletions system/Commands/Generators/MigrationGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 <name> [options]';

/**
* The Command's Arguments
*
* @var array<string, string>
*/
protected $arguments = [
'name' => 'The migration class name.',
];

/**
* The Command's Options
*
* @var array<string, string>
*/
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);
Expand Down
2 changes: 1 addition & 1 deletion system/Commands/Generators/ScaffoldGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
84 changes: 73 additions & 11 deletions tests/system/Commands/Generators/MigrationGeneratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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'));
}
}
3 changes: 3 additions & 0 deletions user_guide_src/source/changelogs/v4.8.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
13 changes: 7 additions & 6 deletions user_guide_src/source/cli/cli_generators.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
---------------
Expand Down
10 changes: 5 additions & 5 deletions user_guide_src/source/dbmgmt/migration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading