diff --git a/docs/en/reference/configuration.rst b/docs/en/reference/configuration.rst index 7b92c7520..71eb4b793 100644 --- a/docs/en/reference/configuration.rst +++ b/docs/en/reference/configuration.rst @@ -243,18 +243,23 @@ Setting ``transactional`` to ``false`` will disable that. From the Command Line ~~~~~~~~~~~~~~~~~~~~~ -You can also set this option from the command line with the ``migrate`` command and the ``--all-or-nothing`` option: +To override the configuration and explicitly enable All or Nothing Transaction from the command line, +use the ``--all-or-nothing`` option: .. code-block:: sh $ ./vendor/bin/doctrine-migrations migrate --all-or-nothing -If you have it enabled at the configuration level and want to change it for an individual migration you can -pass a value of ``0`` or ``1`` to ``--all-or-nothing``. +.. note:: + + Passing options to --all-or-nothing is deprecated from 3.7.x, and will not be allowed in 4.x + +To override the configuration and explicitly disable All or Nothing Transaction from the command line, +use the ``--no-all-or-nothing`` option: .. code-block:: sh - $ ./vendor/bin/doctrine-migrations migrate --all-or-nothing=0 + $ ./vendor/bin/doctrine-migrations migrate --no-all-or-nothing Connection Configuration ------------------------ diff --git a/src/Tools/Console/Command/MigrateCommand.php b/src/Tools/Console/Command/MigrateCommand.php index 4394ac026..956a2fb4a 100644 --- a/src/Tools/Console/Command/MigrateCommand.php +++ b/src/Tools/Console/Command/MigrateCommand.php @@ -81,6 +81,12 @@ protected function configure(): void 'Wrap the entire migration in a transaction.', ConsoleInputMigratorConfigurationFactory::ABSENT_CONFIG_VALUE, ) + ->addOption( + 'no-all-or-nothing', + null, + InputOption::VALUE_NONE, + 'Disable wrapping the entire migration in a transaction.', + ) ->setHelp(<<<'EOT' The %command.name% command executes a migration to a specified version or the latest available version: diff --git a/src/Tools/Console/ConsoleInputMigratorConfigurationFactory.php b/src/Tools/Console/ConsoleInputMigratorConfigurationFactory.php index fffc450ba..a9be446af 100644 --- a/src/Tools/Console/ConsoleInputMigratorConfigurationFactory.php +++ b/src/Tools/Console/ConsoleInputMigratorConfigurationFactory.php @@ -31,30 +31,55 @@ public function getMigratorConfiguration(InputInterface $input): MigratorConfigu private function determineAllOrNothingValueFrom(InputInterface $input): bool|null { - $allOrNothingOption = null; + $enableAllOrNothingOption = self::ABSENT_CONFIG_VALUE; + $disableAllOrNothingOption = null; + + if ($input->hasOption('no-all-or-nothing')) { + $disableAllOrNothingOption = $input->getOption('no-all-or-nothing'); + } + $wasOptionExplicitlyPassed = $input->hasOption('all-or-nothing'); if ($wasOptionExplicitlyPassed) { - $allOrNothingOption = $input->getOption('all-or-nothing'); + /** + * Due to this option being able to receive optional values, its behavior is tricky: + * - when `--all-or-nothing` option is not provided, the default is set to self::ABSENT_CONFIG_VALUE + * - when `--all-or-nothing` option is provided without values, this will be `null` + * - when `--all-or-nothing` option is provided with a value, we get the provided value + */ + $enableAllOrNothingOption = $input->getOption('all-or-nothing'); + } + + $enableAllOrNothingDeprecation = match ($enableAllOrNothingOption) { + self::ABSENT_CONFIG_VALUE, null => false, + default => true, + }; + + if ($enableAllOrNothingOption !== self::ABSENT_CONFIG_VALUE && $disableAllOrNothingOption === true) { + throw InvalidAllOrNothingConfiguration::new(); + } + + if ($disableAllOrNothingOption === true) { + return false; } - if ($wasOptionExplicitlyPassed && ($allOrNothingOption !== null && $allOrNothingOption !== self::ABSENT_CONFIG_VALUE)) { + if ($enableAllOrNothingDeprecation) { Deprecation::trigger( 'doctrine/migrations', 'https://github.com/doctrine/migrations/issues/1304', <<<'DEPRECATION' Context: Passing values to option `--all-or-nothing` Problem: Passing values is deprecated - Solution: If you need to disable the behavior, omit the option, + Solution: If you need to disable the behavior, use --no-all-or-nothing, otherwise, pass the option without a value DEPRECATION, ); } - return match ($allOrNothingOption) { + return match ($enableAllOrNothingOption) { self::ABSENT_CONFIG_VALUE => null, - null => false, - default => (bool) $allOrNothingOption, + null => true, + default => (bool) $enableAllOrNothingOption, }; } } diff --git a/src/Tools/Console/InvalidAllOrNothingConfiguration.php b/src/Tools/Console/InvalidAllOrNothingConfiguration.php new file mode 100644 index 000000000..742b6e4d1 --- /dev/null +++ b/src/Tools/Console/InvalidAllOrNothingConfiguration.php @@ -0,0 +1,16 @@ +createMock(DbalMigrator::class); $this->dependencyFactory->setService(Migrator::class, $migrator); - $this->configuration->setAllOrNothing($default); + if ($default !== null) { + $this->configuration->setAllOrNothing($default); + } $migrator->expects(self::once()) ->method('migrate') @@ -371,7 +374,7 @@ public function testExecuteMigrateAllOrNothing(bool $default, array $input, bool self::assertSame(0, $this->migrateCommandTester->getStatusCode()); } - /** @psalm-return Generator, 2: bool, 3?: bool}> */ + /** @psalm-return Generator, 2: bool, 3?: bool}> */ public static function allOrNothing(): Generator { yield [false, ['--all-or-nothing' => false], false]; @@ -381,7 +384,8 @@ public static function allOrNothing(): Generator yield [false, ['--all-or-nothing' => true], true]; yield [false, ['--all-or-nothing' => 1], true]; yield [false, ['--all-or-nothing' => '1'], true]; - yield [false, ['--all-or-nothing' => null], false, false]; + yield [false, ['--all-or-nothing' => null], true, false]; + yield [true, ['--no-all-or-nothing' => null], false, false]; yield [true, ['--all-or-nothing' => false], false]; yield [true, ['--all-or-nothing' => 0], false]; @@ -389,6 +393,20 @@ public static function allOrNothing(): Generator yield [true, [], true, false]; yield [false, [], false, false]; + yield [null, [], false, false]; + } + + public function testThrowsOnContradictoryAllOrNothingOptions(): void + { + $this->expectException(InvalidAllOrNothingConfiguration::class); + + $this->migrateCommandTester->execute( + [ + '--all-or-nothing' => null, + '--no-all-or-nothing' => null, + ], + ['interactive' => false], + ); } public function testExecuteMigrateCancelExecutedUnavailableMigrations(): void