Skip to content
Merged
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
178 changes: 82 additions & 96 deletions system/Commands/Generators/MigrationGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,86 +13,71 @@

namespace CodeIgniter\Commands\Generators;

use CodeIgniter\CLI\BaseCommand;
use CodeIgniter\CLI\AbstractGeneratorCommand;
use CodeIgniter\CLI\Attributes\Command;
use CodeIgniter\CLI\Attributes\GeneratorCommand;
use CodeIgniter\CLI\CLI;
use CodeIgniter\CLI\GeneratorTrait;
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).',
];

/**
* Actually execute a command.
*/
public function run(array $params)
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',
));
}

protected function provideGeneratorOptions(): void
{
$this->addNamespaceOption()->addSuffixOption();
}

protected function initialize(array &$arguments, array &$options): void
{
$this->component = 'Migration';
$this->directory = 'Database\Migrations';
$this->template = 'migration.tpl.php';
if (! $this->hasUnboundOption('session', $options)) {
return;
}

if (array_key_exists('session', $params) || CLI::getOption('session')) {
$table = $params['table'] ?? CLI::getOption('table') ?? 'ci_sessions';
$params[0] = "_create_{$table}_table";
$table = $this->getUnboundOption('table', $options);

$group = $params['dbgroup'] ?? CLI::getOption('dbgroup');
$group = is_string($group) ? $group : 'default';
$driver = config(Database::class)->{$group}['DBDriver'] ?? null;
$arguments[0] = sprintf('_create_%s_table', is_string($table) ? $table : 'ci_sessions');
}

protected function execute(array $arguments, array $options): int
{
if ($this->getValidatedOption('session') === true) {
$group = $this->getDatabaseGroup();
$driver = $this->getDatabaseDriver($group);

if ($driver === null) {
CLI::error(lang('CLI.generator.undefinedDatabaseGroup', [$group]));
Expand All @@ -107,40 +92,41 @@ public function run(array $params)
}
}

$this->classNameLang = 'CLI.generator.className.migration';
$this->generateClass($params);

return EXIT_SUCCESS;
return $this->generateClass();
}

/**
* 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->getDatabaseGroup();

return [
'session' => true,
'table' => $this->getValidatedOption('table'),
'DBGroup' => $group,
'DBDriver' => $this->getDatabaseDriver($group),
'matchIP' => config(SessionConfig::class)->matchIP,
];
}

/**
* Change file basename before saving.
*/
protected function basename(string $filename): string
{
return gmdate(config(Migrations::class)->timestampFormat) . basename($filename);
}

private function getDatabaseGroup(): string
{
$group = $this->getValidatedOption('dbgroup');
assert(is_string($group));

return $group;
}

private function getDatabaseDriver(string $group): ?string
{
return config(Database::class)->{$group}['DBDriver'] ?? null;
}
}
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
117 changes: 97 additions & 20 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\Config\Factories;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\StreamFilterTrait;
Expand All @@ -27,61 +28,137 @@ final class MigrationGeneratorTest extends CIUnitTestCase
{
use StreamFilterTrait;

protected function setUp(): void
{
parent::setUp();

CLI::reset();
}

protected function tearDown(): void
{
parent::tearDown();

CLI::reset();
Factories::reset('config');

$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)) {
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;
}

private function injectSessionsGroup(string $driver): void
{
$config = new class () extends Database {
/**
* @var array<string, string>
*/
public array $sessions = [];
};
$config->sessions['DBDriver'] = $driver;

Factories::injectMock('config', 'Database', $config);
}

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
{
$this->injectSessionsGroup('Postgre');

command('make:migration --session --table logger --dbgroup sessions');

$contents = $this->getContents('CreateLoggerTable');
$this->assertStringContainsString("protected \$DBGroup = 'sessions';", $contents);
$this->assertStringContainsString("'ip_address inet NOT NULL',", $contents);
$this->assertStringContainsString("\$this->forge->createTable('logger', true);", $contents);
$this->assertStringContainsString("\$this->forge->dropTable('logger', true);", $contents);
}

public function testGenerateMigrationWithShortcuts(): void
{
$this->injectSessionsGroup('MySQLi');

command('make:migration --session -t logger -g sessions');

$contents = $this->getContents('CreateLoggerTable');
$this->assertStringContainsString("protected \$DBGroup = 'sessions';", $contents);
$this->assertStringContainsString("\$this->forge->createTable('logger', true);", $contents);
}

public function testSessionRejectsUnsupportedDriver(): void
{
$config = new Database();
$config->default['DBDriver'] = 'SQLite3';
Factories::injectMock('config', 'Database', $config);
$this->injectSessionsGroup('SQLite3');

command('make:migration -session');
command('make:migration --session --dbgroup sessions');

$this->assertStringContainsString(
'Database sessions are only supported on MySQLi and Postgre. The "default" database group uses the "SQLite3" driver.',
$this->getStreamFilterBuffer(),
$this->assertSame(
"\nDatabase sessions are only supported on MySQLi and Postgre. The \"sessions\" database group uses the \"SQLite3\" driver.\n",
$this->getUndecoratedBuffer(),
);
$this->assertSame([], glob(APPPATH . 'Database/Migrations/*_CreateCiSessionsTable.php'));
}

public function testSessionRejectsUndefinedGroup(): void
{
command('make:migration -session -dbgroup bogus');
command('make:migration --session --dbgroup bogus');

$this->assertStringContainsString('The "bogus" database group is not defined.', $this->getStreamFilterBuffer());
$this->assertSame("\nThe \"bogus\" database group is not defined.\n", $this->getUndecoratedBuffer());
$this->assertSame([], glob(APPPATH . 'Database/Migrations/*_CreateCiSessionsTable.php'));
}

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'));
}
}
Loading
Loading