diff --git a/system/Commands/Database/Migrate.php b/system/Commands/Database/Migrate.php index 50d120c1b6f6..e91aa972ef9f 100644 --- a/system/Commands/Database/Migrate.php +++ b/system/Commands/Database/Migrate.php @@ -13,81 +13,75 @@ namespace CodeIgniter\Commands\Database; -use CodeIgniter\CLI\BaseCommand; +use CodeIgniter\CLI\AbstractCommand; +use CodeIgniter\CLI\Attributes\Command; use CodeIgniter\CLI\CLI; +use CodeIgniter\CLI\Input\Option; use CodeIgniter\CLI\SignalTrait; +use CodeIgniter\Database\MigrationRunner; use Throwable; /** - * Runs all new migrations. + * Locates and runs all new migrations against the database. */ -class Migrate extends BaseCommand +#[Command( + name: 'migrate', + description: 'Locates and runs all new migrations against the database.', + group: 'Database', +)] +class Migrate extends AbstractCommand { use SignalTrait; - /** - * The group the command is lumped under - * when listing commands. - * - * @var string - */ - protected $group = 'Database'; - - /** - * The Command's name - * - * @var string - */ - protected $name = 'migrate'; - - /** - * the Command's short description - * - * @var string - */ - protected $description = 'Locates and runs all new migrations against the database.'; - - /** - * the Command's usage - * - * @var string - */ - protected $usage = 'migrate [options]'; - - /** - * the Command's Options - * - * @var array - */ - protected $options = [ - '-n' => 'Set migration namespace', - '-g' => 'Set database group', - '--all' => 'Set for all namespaces, will ignore (-n) option', - ]; - - /** - * Ensures that all migrations have been run. - */ - public function run(array $params) + protected function configure(): void { + $this + ->addOption(new Option( + name: 'namespace', + shortcut: 'n', + description: 'Set migration namespace.', + requiresValue: true, + default: '', + )) + ->addOption(new Option( + name: 'group', + shortcut: 'g', + description: 'Set database group.', + requiresValue: true, + default: '', + )) + ->addOption(new Option( + name: 'all', + description: 'Set for all namespaces. This will ignore the `--namespace` option.', + )); + } + + protected function execute(array $arguments, array $options): int + { + /** @var MigrationRunner $runner */ $runner = service('migrations'); $runner->clearCliMessages(); CLI::write(lang('Migrations.latest'), 'yellow'); - $namespace = $params['n'] ?? CLI::getOption('n'); - $group = $params['g'] ?? CLI::getOption('g'); + $namespace = $options['namespace']; + assert(is_string($namespace)); + + $group = $options['group']; + assert(is_string($group)); + + $group = $group !== '' ? $group : null; try { - if (array_key_exists('all', $params) || CLI::getOption('all')) { + if ($options['all'] === true) { $runner->setNamespace(null); - } elseif ($namespace) { + } elseif ($namespace !== '') { $runner->setNamespace($namespace); } $this->withSignalsBlocked(static function () use ($runner, $group): void { if (! $runner->latest($group)) { - CLI::error(lang('Migrations.generalFault'), 'light_gray', 'red'); // @codeCoverageIgnore + CLI::error(lang('Migrations.generalFault'), 'light_gray', 'red'); } }); @@ -100,12 +94,10 @@ public function run(array $params) CLI::write(lang('Migrations.migrated'), 'green'); return EXIT_SUCCESS; - // @codeCoverageIgnoreStart } catch (Throwable $e) { - $this->showError($e); + $this->renderThrowable($e); return EXIT_ERROR; - // @codeCoverageIgnoreEnd } } } diff --git a/system/Commands/Database/MigrateRefresh.php b/system/Commands/Database/MigrateRefresh.php index ee36a67f70e0..c8d867059108 100644 --- a/system/Commands/Database/MigrateRefresh.php +++ b/system/Commands/Database/MigrateRefresh.php @@ -13,81 +13,102 @@ namespace CodeIgniter\Commands\Database; -use CodeIgniter\CLI\BaseCommand; +use CodeIgniter\CLI\AbstractCommand; +use CodeIgniter\CLI\Attributes\Command; use CodeIgniter\CLI\CLI; +use CodeIgniter\CLI\Input\Option; use CodeIgniter\CLI\SignalTrait; /** - * Does a rollback followed by a latest to refresh the current state - * of the database. + * Does a rollback followed by a latest to refresh the current state of the database. */ -class MigrateRefresh extends BaseCommand +#[Command( + name: 'migrate:refresh', + description: 'Does a rollback followed by a latest to refresh the current state of the database.', + group: 'Database', +)] +class MigrateRefresh extends AbstractCommand { use SignalTrait; - /** - * The group the command is lumped under - * when listing commands. - * - * @var string - */ - protected $group = 'Database'; - - /** - * The Command's name - * - * @var string - */ - protected $name = 'migrate:refresh'; - - /** - * the Command's short description - * - * @var string - */ - protected $description = 'Does a rollback followed by a latest to refresh the current state of the database.'; - - /** - * the Command's usage - * - * @var string - */ - protected $usage = 'migrate:refresh [options]'; - - /** - * the Command's Options - * - * @var array - */ - protected $options = [ - '-n' => 'Set migration namespace', - '-g' => 'Set database group', - '--all' => 'Set latest for all namespace, will ignore (-n) option', - '-f' => 'Force command - this option allows you to bypass the confirmation question when running this command in a production environment', - ]; - - /** - * Does a rollback followed by a latest to refresh the current state - * of the database. - */ - public function run(array $params) + protected function configure(): void { - $params['b'] = 0; + $this + ->addOption(new Option( + name: 'namespace', + shortcut: 'n', + description: 'Set migration namespace.', + requiresValue: true, + default: '', + )) + ->addOption(new Option( + name: 'group', + shortcut: 'g', + description: 'Set database group.', + requiresValue: true, + default: '', + )) + ->addOption(new Option( + name: 'all', + description: 'Set latest for all namespaces. This will ignore the `--namespace` option.', + )) + ->addOption(new Option( + name: 'force', + shortcut: 'f', + description: 'Bypass the confirmation question when running this command in a production environment.', + )); + } + + protected function interact(array &$arguments, array &$options): void + { + if (! service('environment')->isProduction()) { + return; + } + + if ($this->hasUnboundOption('force', $options)) { + return; + } - if (service('environment')->isProduction()) { - // @codeCoverageIgnoreStart - $force = array_key_exists('f', $params) || CLI::getOption('f'); + if (CLI::prompt(lang('Migrations.refreshConfirm'), ['y', 'n']) === 'y') { + $options['force'] = null; // simulate the presence of the --force option + } + } - if (! $force && CLI::prompt(lang('Migrations.refreshConfirm'), ['y', 'n']) === 'n') { - return EXIT_ERROR; - } + protected function execute(array $arguments, array $options): int + { + if (service('environment')->isProduction() && $options['force'] === false) { + return EXIT_ERROR; + } + + $namespace = $options['namespace']; + assert(is_string($namespace)); + + $group = $options['group']; + assert(is_string($group)); + + // A target batch of 0 rolls everything back before re-applying. + $rollbackOptions = ['batch' => '0']; + $migrateOptions = []; + + if ($options['force'] === true) { + $rollbackOptions['force'] = null; + } + + if ($namespace !== '') { + $migrateOptions['namespace'] = $namespace; + } + + if ($group !== '') { + $migrateOptions['group'] = $group; + } - $params['f'] = null; - // @codeCoverageIgnoreEnd + if ($options['all'] === true) { + $migrateOptions['all'] = null; } return $this->withSignalsBlocked( - fn (): int => $this->call('migrate:rollback', $params) | $this->call('migrate', $params), + fn (): int => $this->call('migrate:rollback', options: $rollbackOptions) + | $this->call('migrate', options: $migrateOptions), ); } } diff --git a/system/Commands/Database/MigrateRollback.php b/system/Commands/Database/MigrateRollback.php index 888b60819a1b..f11e3a30e6a4 100644 --- a/system/Commands/Database/MigrateRollback.php +++ b/system/Commands/Database/MigrateRollback.php @@ -13,89 +13,78 @@ namespace CodeIgniter\Commands\Database; -use CodeIgniter\CLI\BaseCommand; +use CodeIgniter\CLI\AbstractCommand; +use CodeIgniter\CLI\Attributes\Command; use CodeIgniter\CLI\CLI; +use CodeIgniter\CLI\Input\Option; use CodeIgniter\CLI\SignalTrait; use CodeIgniter\Database\MigrationRunner; use Throwable; /** - * Runs all of the migrations in reverse order, until they have - * all been unapplied. + * Runs the "down" method for all migrations in the last batch. */ -class MigrateRollback extends BaseCommand +#[Command( + name: 'migrate:rollback', + description: 'Runs the "down" method for all migrations in the last batch.', + group: 'Database', +)] +class MigrateRollback extends AbstractCommand { use SignalTrait; - /** - * The group the command is lumped under - * when listing commands. - * - * @var string - */ - protected $group = 'Database'; - - /** - * The Command's name - * - * @var string - */ - protected $name = 'migrate:rollback'; - - /** - * the Command's short description - * - * @var string - */ - protected $description = 'Runs the "down" method for all migrations in the last batch.'; - - /** - * the Command's usage - * - * @var string - */ - protected $usage = 'migrate:rollback [options]'; - - /** - * the Command's Options - * - * @var array - */ - protected $options = [ - '-b' => 'Specify a batch to roll back to; e.g. "3" to return to batch #3', - '-f' => 'Force command - this option allows you to bypass the confirmation question when running this command in a production environment', - ]; - - /** - * Runs all of the migrations in reverse order, until they have - * all been unapplied. - */ - public function run(array $params) + protected function configure(): void { - if (service('environment')->isProduction()) { - // @codeCoverageIgnoreStart - $force = array_key_exists('f', $params) || CLI::getOption('f'); + $this + ->addOption(new Option( + name: 'batch', + shortcut: 'b', + description: 'Specify a batch to roll back to.', + requiresValue: true, + default: '', + )) + ->addOption(new Option( + name: 'force', + shortcut: 'f', + description: 'Bypass the confirmation question when running this command in a production environment.', + )); + } - if (! $force && CLI::prompt(lang('Migrations.rollBackConfirm'), ['y', 'n']) === 'n') { - return EXIT_ERROR; - } - // @codeCoverageIgnoreEnd + protected function interact(array &$arguments, array &$options): void + { + if (! service('environment')->isProduction()) { + return; + } + + if ($this->hasUnboundOption('force', $options)) { + return; + } + + if (CLI::prompt(lang('Migrations.rollBackConfirm'), ['y', 'n']) === 'y') { + $options['force'] = null; // simulate the presence of the --force option + } + } + + protected function execute(array $arguments, array $options): int + { + if (service('environment')->isProduction() && $options['force'] === false) { + return EXIT_ERROR; } /** @var MigrationRunner $runner */ $runner = service('migrations'); try { - $batch = $params['b'] ?? CLI::getOption('b') ?? $runner->getLastBatch() - 1; + $batch = $options['batch']; + assert(is_string($batch)); - if (is_string($batch)) { - if (! ctype_digit($batch)) { - CLI::error('Invalid batch number: ' . $batch, 'light_gray', 'red'); - CLI::newLine(); - - return EXIT_ERROR; - } + if ($batch === '') { + $batch = $runner->getLastBatch() - 1; + } elseif (! ctype_digit($batch)) { + CLI::error('Invalid batch number: ' . $batch, 'light_gray', 'red'); + return EXIT_ERROR; + } else { $batch = (int) $batch; } @@ -103,7 +92,7 @@ public function run(array $params) $exit = $this->withSignalsBlocked(static function () use ($runner, $batch): int { if (! $runner->regress($batch)) { - CLI::error(lang('Migrations.generalFault'), 'light_gray', 'red'); // @codeCoverageIgnore + CLI::error(lang('Migrations.generalFault'), 'light_gray', 'red'); return EXIT_ERROR; } @@ -124,12 +113,10 @@ public function run(array $params) CLI::write('Done rolling back migrations.', 'green'); return EXIT_SUCCESS; - // @codeCoverageIgnoreStart } catch (Throwable $e) { - $this->showError($e); + $this->renderThrowable($e); return EXIT_ERROR; - // @codeCoverageIgnoreEnd } } } diff --git a/system/Commands/Database/MigrateStatus.php b/system/Commands/Database/MigrateStatus.php index e070dc71dbc7..adcc17ac40d7 100644 --- a/system/Commands/Database/MigrateStatus.php +++ b/system/Commands/Database/MigrateStatus.php @@ -13,60 +13,28 @@ namespace CodeIgniter\Commands\Database; -use CodeIgniter\CLI\BaseCommand; +use CodeIgniter\CLI\AbstractCommand; +use CodeIgniter\CLI\Attributes\Command; use CodeIgniter\CLI\CLI; +use CodeIgniter\CLI\Input\Option; +use CodeIgniter\Database\MigrationRunner; /** * Displays a list of all migrations and whether they've been run or not. - * - * @see \CodeIgniter\Commands\Database\MigrateStatusTest */ -class MigrateStatus extends BaseCommand +#[Command( + name: 'migrate:status', + description: 'Displays a list of all migrations and whether they\'ve been run or not.', + group: 'Database', +)] +class MigrateStatus extends AbstractCommand { - /** - * The group the command is lumped under - * when listing commands. - * - * @var string - */ - protected $group = 'Database'; - - /** - * The Command's name - * - * @var string - */ - protected $name = 'migrate:status'; - - /** - * the Command's short description - * - * @var string - */ - protected $description = 'Displays a list of all migrations and whether they\'ve been run or not.'; - - /** - * the Command's usage - * - * @var string - */ - protected $usage = 'migrate:status [options]'; - - /** - * the Command's Options - * - * @var array - */ - protected $options = [ - '-g' => 'Set database group', - ]; - /** * Namespaces to ignore when looking for migrations. * * @var list */ - protected $ignoredNamespaces = [ + protected array $ignoredNamespaces = [ 'CodeIgniter', 'Config', 'Kint', @@ -75,26 +43,33 @@ class MigrateStatus extends BaseCommand 'Psr\Log', ]; - /** - * Displays a list of all migrations and whether they've been run or not. - * - * @param array $params - */ - public function run(array $params) + protected function configure(): void { - $runner = service('migrations'); - $paramGroup = $params['g'] ?? CLI::getOption('g'); + $this->addOption(new Option( + name: 'group', + shortcut: 'g', + description: 'Set database group.', + requiresValue: true, + default: '', + )); + } + + protected function execute(array $arguments, array $options): int + { + /** @var MigrationRunner $runner */ + $runner = service('migrations'); + + $group = $options['group']; + assert(is_string($group)); - // Get all namespaces $namespaces = service('autoloader')->getNamespace(); - // Collection of migration status $status = []; foreach (array_keys($namespaces) as $namespace) { if (! service('environment')->isTesting()) { // Make Tests\\Support discoverable for testing - $this->ignoredNamespaces[] = 'Tests\Support'; // @codeCoverageIgnore + $this->ignoredNamespaces[] = 'Tests\Support'; } if (in_array($namespace, $this->ignoredNamespaces, true)) { @@ -107,12 +82,12 @@ public function run(array $params) $migrations = $runner->findNamespaceMigrations($namespace); - if (empty($migrations)) { + if ($migrations === []) { continue; } $runner->setNamespace($namespace); - $history = $runner->getHistory((string) $paramGroup); + $history = $runner->getHistory($group); ksort($migrations); foreach ($migrations as $uid => $migration) { @@ -123,7 +98,6 @@ public function run(array $params) $batch = '---'; foreach ($history as $row) { - // @codeCoverageIgnoreStart if ($runner->getObjectUid($row) !== $migration->uid) { continue; } @@ -131,7 +105,6 @@ public function run(array $params) $date = date('Y-m-d H:i:s', (int) $row->time); $group = $row->group; $batch = $row->batch; - // @codeCoverageIgnoreEnd } $status[] = [ @@ -146,12 +119,9 @@ public function run(array $params) } if ($status === []) { - // @codeCoverageIgnoreStart CLI::error(lang('Migrations.noneFound'), 'light_gray', 'red'); - CLI::newLine(); return EXIT_ERROR; - // @codeCoverageIgnoreEnd } $headers = [ diff --git a/tests/system/Commands/DatabaseCommandsTest.php b/tests/system/Commands/DatabaseCommandsTest.php index b8894e49f965..a3d09dd9870d 100644 --- a/tests/system/Commands/DatabaseCommandsTest.php +++ b/tests/system/Commands/DatabaseCommandsTest.php @@ -13,9 +13,12 @@ namespace CodeIgniter\Commands; +use CodeIgniter\Database\MigrationRunner; +use CodeIgniter\EnvironmentDetector; use CodeIgniter\Test\CIUnitTestCase; use CodeIgniter\Test\DatabaseTestTrait; use CodeIgniter\Test\StreamFilterTrait; +use Config\Services; use PHPUnit\Framework\Attributes\Group; /** @@ -36,85 +39,180 @@ protected function tearDown(): void parent::tearDown(); } - protected function getBuffer(): string - { - return $this->getStreamFilterBuffer(); - } - - protected function clearBuffer(): void - { - $this->resetStreamFilterBuffer(); - } - public function testMigrate(): void { command('migrate --all'); - $this->assertStringContainsString('Migrations complete.', $this->getBuffer()); + $this->assertStringContainsString('Migrations complete.', $this->getStreamFilterBuffer()); command('migrate:rollback'); - $this->clearBuffer(); + $this->resetStreamFilterBuffer(); command('migrate -n Tests\\\\Support'); - $this->assertStringContainsString('Migrations complete.', $this->getBuffer()); + $this->assertStringContainsString('Migrations complete.', $this->getStreamFilterBuffer()); } public function testMigrateRollbackValidBatchNumber(): void { command('migrate --all'); - $this->clearBuffer(); + $this->resetStreamFilterBuffer(); command('migrate:rollback -b 1'); - $this->assertStringContainsString('Done rolling back migrations.', $this->getBuffer()); + $this->assertStringContainsString('Done rolling back migrations.', $this->getStreamFilterBuffer()); } public function testMigrateRollbackInvalidBatchNumber(): void { command('migrate --all'); - $this->clearBuffer(); + $this->resetStreamFilterBuffer(); command('migrate:rollback -b x'); - $this->assertStringContainsString('Invalid batch number: x', $this->getBuffer()); + $this->assertStringContainsString('Invalid batch number: x', $this->getStreamFilterBuffer()); } public function testMigrateRollback(): void { command('migrate --all -g tests'); - $this->clearBuffer(); + $this->resetStreamFilterBuffer(); - command('migrate:rollback -g tests'); - $this->assertStringContainsString('Done rolling back migrations.', $this->getBuffer()); + command('migrate:rollback'); + $this->assertStringContainsString('Done rolling back migrations.', $this->getStreamFilterBuffer()); } public function testMigrateRefresh(): void { command('migrate --all'); - $this->clearBuffer(); + $this->resetStreamFilterBuffer(); command('migrate:refresh'); - $this->assertStringContainsString('Migrations complete.', $this->getBuffer()); + $this->assertStringContainsString('Migrations complete.', $this->getStreamFilterBuffer()); } public function testMigrateStatus(): void { command('migrate --all'); - $this->clearBuffer(); + $this->resetStreamFilterBuffer(); command('migrate:status -g tests'); - $this->assertStringContainsString('Namespace', $this->getBuffer()); - $this->assertStringContainsString('Version', $this->getBuffer()); - $this->assertStringContainsString('Filename', $this->getBuffer()); + $this->assertStringContainsString('Namespace', $this->getStreamFilterBuffer()); + $this->assertStringContainsString('Version', $this->getStreamFilterBuffer()); + $this->assertStringContainsString('Filename', $this->getStreamFilterBuffer()); + } + + public function testMigrateAbortsWhenMigrationsDisabled(): void + { + config('Migrations')->enabled = false; + Services::resetSingle('migrations'); + + command('migrate'); + $this->assertStringContainsString('Migrations have been loaded but are disabled', $this->getStreamFilterBuffer()); + + config('Migrations')->enabled = true; + Services::resetSingle('migrations'); + } + + public function testMigrateRollbackAbortsWhenMigrationsDisabled(): void + { + config('Migrations')->enabled = false; + Services::resetSingle('migrations'); + + command('migrate:rollback'); + $this->assertStringContainsString('Migrations have been loaded but are disabled', $this->getStreamFilterBuffer()); + + config('Migrations')->enabled = true; + Services::resetSingle('migrations'); + } + + public function testMigrateRollbackAbortsInProductionWithoutForce(): void + { + Services::injectMock('environment', new EnvironmentDetector('production')); + + $exitCode = service('commands')->runCommand('migrate:rollback', [], ['no-interaction' => null]); + $this->assertSame(EXIT_ERROR, $exitCode); + + Services::resetSingle('environment'); + } + + public function testMigrateRefreshAbortsInProductionWithoutForce(): void + { + Services::injectMock('environment', new EnvironmentDetector('production')); + + $exitCode = service('commands')->runCommand('migrate:refresh', [], ['no-interaction' => null]); + $this->assertSame(EXIT_ERROR, $exitCode); + + Services::resetSingle('environment'); + } + + public function testMigrateRefreshForwardsNamespaceGroupAndForceOptions(): void + { + command('migrate --all'); + $this->resetStreamFilterBuffer(); + + command('migrate:refresh -n Tests\\\\Support -g tests -f'); + $this->assertStringContainsString('Migrations complete.', $this->getStreamFilterBuffer()); + } + + public function testMigrateRefreshForwardsAllNamespacesOption(): void + { + command('migrate --all'); + $this->resetStreamFilterBuffer(); + + command('migrate:refresh --all'); + $this->assertStringContainsString('Migrations complete.', $this->getStreamFilterBuffer()); + } + + public function testMigrateReportsGeneralFaultWhenLatestFails(): void + { + $runner = $this->createMock(MigrationRunner::class); + $runner->method('latest')->willReturn(false); + $runner->method('getCliMessages')->willReturn([]); + Services::injectMock('migrations', $runner); + + command('migrate'); + $this->assertStringContainsString(lang('Migrations.generalFault'), $this->getStreamFilterBuffer()); + + Services::resetSingle('migrations'); + } + + public function testMigrateRollbackReportsGeneralFaultWhenRegressFails(): void + { + $runner = $this->createMock(MigrationRunner::class); + $runner->method('getLastBatch')->willReturn(1); + $runner->method('regress')->willReturn(false); + $runner->method('getCliMessages')->willReturn([]); + Services::injectMock('migrations', $runner); + + command('migrate:rollback'); + $this->assertStringContainsString(lang('Migrations.generalFault'), $this->getStreamFilterBuffer()); + + Services::resetSingle('migrations'); + } + + public function testMigrateStatusReportsNoneFoundWhenNoMigrationsExist(): void + { + // A non-testing environment makes the command ignore the Tests\Support namespace. + Services::injectMock('environment', new EnvironmentDetector('production')); + + $runner = $this->createMock(MigrationRunner::class); + $runner->method('findNamespaceMigrations')->willReturn([]); + Services::injectMock('migrations', $runner); + + command('migrate:status'); + $this->assertStringContainsString(lang('Migrations.noneFound'), $this->getStreamFilterBuffer()); + + Services::resetSingle('migrations'); + Services::resetSingle('environment'); } public function testSeed(): void { command('migrate --all'); - $this->clearBuffer(); + $this->resetStreamFilterBuffer(); // use '\\\\' to prevent escaping command('db:seed Tests\\\\Support\\\\Database\\\\Seeds\\\\CITestSeeder'); - $this->assertStringContainsString('Seeded', $this->getBuffer()); - $this->clearBuffer(); + $this->assertStringContainsString('Seeded', $this->getStreamFilterBuffer()); + $this->resetStreamFilterBuffer(); command('db:seed Foobar.php'); - $this->assertStringContainsString('The specified seeder is not a valid file:', $this->getBuffer()); + $this->assertStringContainsString('The specified seeder is not a valid file:', $this->getStreamFilterBuffer()); } } diff --git a/user_guide_src/source/changelogs/v4.8.0.rst b/user_guide_src/source/changelogs/v4.8.0.rst index 359448aa83b5..6e1d2ff5da47 100644 --- a/user_guide_src/source/changelogs/v4.8.0.rst +++ b/user_guide_src/source/changelogs/v4.8.0.rst @@ -32,6 +32,7 @@ Behavior Changes behaviour may need to re-implement against the modern API (``configure()`` + ``execute()`` and the ``#[Command]`` attribute) once the class it extends is migrated, or, preferably, compose instead of extending. Invocations on the command line are unaffected. - **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. - **Commands:** The ``logs:clear`` command now returns ``EXIT_SUCCESS`` (previously ``EXIT_ERROR``) when the user declines the interactive confirmation prompt, since user-initiated cancellation is not a failure. Output messages have also been reworded to distinguish cancellation (interactive ``n``) from abort (non-interactive without ``--force``), and the resolved log directory path is now included in the prompt, success, and failure messages. - **Database:** The Postgre driver's ``$db->error()['code']`` previously always returned ``''``. It now returns the 5-character SQLSTATE string for query and transaction failures (e.g., ``'42P01'``), or ``'08006'`` for connection-level failures. Code that relied on ``$db->error()['code'] === ''`` will need updating. - **Filters:** HTTP method matching for method-based filters is now case-sensitive. The keys in ``Config\Filters::$methods`` must exactly match the request method @@ -200,6 +201,7 @@ Commands - Added ``make:request`` generator command to scaffold :ref:`Form Request ` classes. - Added ``key:rotate`` command to demote the current ``encryption.key`` to ``encryption.previousKeys`` in **.env** and generate a new key. See :ref:`spark-key-rotate`. - Added ``AbstractCommand::callSilently()`` to invoke another command with its output discarded, restoring the prior IO afterwards. See :ref:`modern-commands-call-silently`. +- The ``migrate``, ``migrate:rollback``, ``migrate:refresh``, and ``migrate:status`` commands now accept long option names (``--namespace``, ``--group``, ``--batch``, ``--force``) alongside their existing short forms (``-n``, ``-g``, ``-b``, ``-f``). - Added :php:class:`NullInputOutput `, an :php:class:`InputOutput ` sink that discards all writes and returns an empty string from ``input()``. Testing diff --git a/user_guide_src/source/dbmgmt/migration.rst b/user_guide_src/source/dbmgmt/migration.rst index a9baed5b9c62..384a0bdd23cb 100644 --- a/user_guide_src/source/dbmgmt/migration.rst +++ b/user_guide_src/source/dbmgmt/migration.rst @@ -132,8 +132,8 @@ Migrates a database group with all available migrations: You can use ``migrate`` with the following options: -- ``-g`` - to specify database group. If specified, only migrations for the specified database group will be run. If not specified, all migrations will be run. -- ``-n`` - to choose namespace, otherwise ``App`` namespace will be used. +- ``--group`` (``-g``) - to specify database group. If specified, only migrations for the specified database group will be run. If not specified, all migrations will be run. +- ``--namespace`` (``-n``) - to choose namespace, otherwise ``App`` namespace will be used. - ``--all`` - to migrate all namespaces to the latest migration. This example will migrate ``Acme\Blog`` namespace with any new migrations on the test database group: @@ -165,8 +165,8 @@ Rolls back all migrations to a blank slate, effectively migration 0: You can use ``migrate:rollback`` with the following options: -- ``-b`` - to choose a batch: natural numbers specify the batch. -- ``-f`` - to force a bypass confirmation question, it is only asked in a production environment. +- ``--batch`` (``-b``) - to choose a batch: natural numbers specify the batch. +- ``--force`` (``-f``) - to force a bypass confirmation question, it is only asked in a production environment. migrate:refresh =============== @@ -179,10 +179,10 @@ Refreshes the database state by first rolling back all migrations, and then migr You can use ``migrate:refresh`` with the following options: -- ``-g`` - to specify database group. If specified, only migrations for the specified database group will be run. If not specified, all migrations will be run. -- ``-n`` - to choose namespace, otherwise ``App`` namespace will be used. +- ``--group`` (``-g``) - to specify database group. If specified, only migrations for the specified database group will be run. If not specified, all migrations will be run. +- ``--namespace`` (``-n``) - to choose namespace, otherwise ``App`` namespace will be used. - ``--all`` - to refresh all namespaces. -- ``-f`` - to force a bypass confirmation question, it is only asked in a production environment. +- ``--force`` (``-f``) - to force a bypass confirmation question, it is only asked in a production environment. migrate:status ============== @@ -205,7 +205,7 @@ Displays a list of all migrations and the date and time they ran, or '--' if the You can use ``migrate:status`` with the following options: -- ``-g`` - to specify database group. If specified, only migrations for the specified database group will be checked. If not specified, all migrations will be checked. +- ``--group`` (``-g``) - to specify database group. If specified, only migrations for the specified database group will be checked. If not specified, all migrations will be checked. make:migration ============== diff --git a/utils/phpstan-baseline/argument.type.neon b/utils/phpstan-baseline/argument.type.neon index cd9dad9ee180..98e43f64d549 100644 --- a/utils/phpstan-baseline/argument.type.neon +++ b/utils/phpstan-baseline/argument.type.neon @@ -1,12 +1,7 @@ -# total 74 errors +# total 72 errors parameters: ignoreErrors: - - - message: '#^Parameter \#2 \$params of method CodeIgniter\\CLI\\BaseCommand\:\:call\(\) expects array\, array\ given\.$#' - count: 2 - path: ../../system/Commands/Database/MigrateRefresh.php - - message: '#^Parameter \#3 \.\.\.\$arrays of function array_map expects array, int\|string given\.$#' count: 1 diff --git a/utils/phpstan-baseline/empty.notAllowed.neon b/utils/phpstan-baseline/empty.notAllowed.neon index 97d0b83db012..45d134476fb5 100644 --- a/utils/phpstan-baseline/empty.notAllowed.neon +++ b/utils/phpstan-baseline/empty.notAllowed.neon @@ -1,4 +1,4 @@ -# total 210 errors +# total 209 errors parameters: ignoreErrors: @@ -7,11 +7,6 @@ parameters: count: 1 path: ../../system/Commands/Database/CreateDatabase.php - - - message: '#^Construct empty\(\) is not allowed\. Use more strict comparison\.$#' - count: 1 - path: ../../system/Commands/Database/MigrateStatus.php - - message: '#^Construct empty\(\) is not allowed\. Use more strict comparison\.$#' count: 1 diff --git a/utils/phpstan-baseline/loader.neon b/utils/phpstan-baseline/loader.neon index b80f3ba890ff..0c082cf4f8a9 100644 --- a/utils/phpstan-baseline/loader.neon +++ b/utils/phpstan-baseline/loader.neon @@ -1,4 +1,4 @@ -# total 1985 errors +# total 1981 errors includes: - argument.type.neon diff --git a/utils/phpstan-baseline/method.childParameterType.neon b/utils/phpstan-baseline/method.childParameterType.neon index d5b6014b15a6..19eacd00e502 100644 --- a/utils/phpstan-baseline/method.childParameterType.neon +++ b/utils/phpstan-baseline/method.childParameterType.neon @@ -1,12 +1,7 @@ -# total 12 errors +# total 11 errors parameters: ignoreErrors: - - - message: '#^Parameter \#1 \$params \(array\\) of method CodeIgniter\\Commands\\Database\\MigrateStatus\:\:run\(\) should be contravariant with parameter \$params \(array\\) of method CodeIgniter\\CLI\\BaseCommand\:\:run\(\)$#' - count: 1 - path: ../../system/Commands/Database/MigrateStatus.php - - message: '#^Parameter \#1 \$offset \(string\) of method CodeIgniter\\Cookie\\Cookie\:\:offsetSet\(\) should be contravariant with parameter \$offset \(string\|null\) of method ArrayAccess\\:\:offsetSet\(\)$#' count: 1