diff --git a/UPGRADE-6.4.md b/UPGRADE-6.4.md index 4950151722ad8..438654dde8384 100644 --- a/UPGRADE-6.4.md +++ b/UPGRADE-6.4.md @@ -11,6 +11,12 @@ Cache * [BC break] `EarlyExpirationHandler` no longer implements `MessageHandlerInterface`, rely on `AsMessageHandler` instead +Console +----- + + * Deprecate passing both `InputArgument::REQUIRED` and `InputArgument::OPTIONAL` modes to `InputArgument` constructor + * Deprecate passing more than one out of `InputOption::VALUE_NONE`, `InputOption::VALUE_REQUIRED` and `InputOption::VALUE_OPTIONAL` modes to `InputOption` constructor + DependencyInjection ------------------- diff --git a/src/Symfony/Component/Console/CHANGELOG.md b/src/Symfony/Component/Console/CHANGELOG.md index 9ccb41d945792..f5ec3c048f6bd 100644 --- a/src/Symfony/Component/Console/CHANGELOG.md +++ b/src/Symfony/Component/Console/CHANGELOG.md @@ -9,6 +9,8 @@ CHANGELOG * The application can also catch errors with `Application::setCatchErrors(true)` * Add `RunCommandMessage` and `RunCommandMessageHandler` * Dispatch `ConsoleTerminateEvent` after an exit on signal handling and add `ConsoleTerminateEvent::getInterruptingSignal()` + * Deprecate passing both `InputArgument::REQUIRED` and `InputArgument::OPTIONAL` modes to `InputArgument` constructor + * Deprecate passing more than one out of `InputOption::VALUE_NONE`, `InputOption::VALUE_REQUIRED` and `InputOption::VALUE_OPTIONAL` modes to `InputOption` constructor 6.3 --- diff --git a/src/Symfony/Component/Console/Input/InputArgument.php b/src/Symfony/Component/Console/Input/InputArgument.php index 5cb151488dc56..7342afd6628ff 100644 --- a/src/Symfony/Component/Console/Input/InputArgument.php +++ b/src/Symfony/Component/Console/Input/InputArgument.php @@ -46,12 +46,16 @@ class InputArgument */ public function __construct(string $name, int $mode = null, string $description = '', string|bool|int|float|array $default = null, \Closure|array $suggestedValues = []) { - if (null === $mode) { - $mode = self::OPTIONAL; - } elseif ($mode > 7 || $mode < 1) { + // If not explicitly marked as required, we assume the value to be optional + $mode = self::REQUIRED === (self::REQUIRED & $mode) ? $mode : (self::OPTIONAL | $mode); + if ($mode > 7 || $mode < 1) { throw new InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode)); } + if ((self::REQUIRED | self::OPTIONAL) === ((self::REQUIRED | self::OPTIONAL) & $mode)) { + trigger_deprecation('symfony/console', '6.4', 'InputArgument mode should specify either required or optional.'); + } + $this->name = $name; $this->mode = $mode; $this->description = $description; diff --git a/src/Symfony/Component/Console/Input/InputOption.php b/src/Symfony/Component/Console/Input/InputOption.php index fdf88dcc27490..59076608c3b35 100644 --- a/src/Symfony/Component/Console/Input/InputOption.php +++ b/src/Symfony/Component/Console/Input/InputOption.php @@ -92,12 +92,16 @@ public function __construct(string $name, string|array $shortcut = null, int $mo } } - if (null === $mode) { - $mode = self::VALUE_NONE; - } elseif ($mode >= (self::VALUE_NEGATABLE << 1) || $mode < 1) { + // If not explicitly marked as required or optional, we assume the value accepts no input + $mode = self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $mode) || self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $mode) ? $mode : (self::VALUE_NONE | $mode); + if ($mode >= (self::VALUE_NEGATABLE << 1) || $mode < 1) { throw new InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); } + if (!in_array($mode & (self::VALUE_NONE | self::VALUE_REQUIRED | self::VALUE_OPTIONAL), [self::VALUE_NONE, self::VALUE_REQUIRED, self::VALUE_OPTIONAL], true)) { + trigger_deprecation('symfony/console', '6.4', 'InputOption mode should be either none, required or optional.'); + } + $this->name = $name; $this->shortcut = $shortcut; $this->mode = $mode; diff --git a/src/Symfony/Component/Console/Tests/Input/InputArgumentTest.php b/src/Symfony/Component/Console/Tests/Input/InputArgumentTest.php index 398048cbc592d..d7ab67295300c 100644 --- a/src/Symfony/Component/Console/Tests/Input/InputArgumentTest.php +++ b/src/Symfony/Component/Console/Tests/Input/InputArgumentTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Console\Tests\Input; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Completion\Suggestion; @@ -20,6 +21,8 @@ class InputArgumentTest extends TestCase { + use ExpectDeprecationTrait; + public function testConstructor() { $argument = new InputArgument('foo'); @@ -32,7 +35,7 @@ public function testModes() $this->assertFalse($argument->isRequired(), '__construct() gives a "InputArgument::OPTIONAL" mode by default'); $argument = new InputArgument('foo', null); - $this->assertFalse($argument->isRequired(), '__construct() can take "InputArgument::OPTIONAL" as its mode'); + $this->assertFalse($argument->isRequired(), '__construct() gives a "InputArgument::OPTIONAL" mode by default'); $argument = new InputArgument('foo', InputArgument::OPTIONAL); $this->assertFalse($argument->isRequired(), '__construct() can take "InputArgument::OPTIONAL" as its mode'); @@ -49,6 +52,16 @@ public function testInvalidModes() new InputArgument('foo', '-1'); } + /** + * @group legacy + */ + public function testAmbigiousRequirementSpecifierMode() + { + $this->expectDeprecation('Since symfony/console 6.4: InputArgument mode should specify either required or optional.'); + + new InputArgument('foo', InputArgument::OPTIONAL | InputArgument::REQUIRED); + } + public function testIsArray() { $argument = new InputArgument('foo', InputArgument::IS_ARRAY); diff --git a/src/Symfony/Component/Console/Tests/Input/InputOptionTest.php b/src/Symfony/Component/Console/Tests/Input/InputOptionTest.php index 0b5271b324aea..311bdbf043fe1 100644 --- a/src/Symfony/Component/Console/Tests/Input/InputOptionTest.php +++ b/src/Symfony/Component/Console/Tests/Input/InputOptionTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Console\Tests\Input; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Completion\Suggestion; @@ -20,6 +21,8 @@ class InputOptionTest extends TestCase { + use ExpectDeprecationTrait; + public function testConstructor() { $option = new InputOption('foo'); @@ -69,9 +72,9 @@ public function testModes() $this->assertFalse($option->isValueOptional(), '__construct() gives a "InputOption::VALUE_NONE" mode by default'); $option = new InputOption('foo', 'f', null); - $this->assertFalse($option->acceptValue(), '__construct() can take "InputOption::VALUE_NONE" as its mode'); - $this->assertFalse($option->isValueRequired(), '__construct() can take "InputOption::VALUE_NONE" as its mode'); - $this->assertFalse($option->isValueOptional(), '__construct() can take "InputOption::VALUE_NONE" as its mode'); + $this->assertFalse($option->acceptValue(), '__construct() gives a "InputOption::VALUE_NONE" mode by default'); + $this->assertFalse($option->isValueRequired(), '__construct() gives a "InputOption::VALUE_NONE" mode by default'); + $this->assertFalse($option->isValueOptional(), '__construct() gives a "InputOption::VALUE_NONE" mode by default'); $option = new InputOption('foo', 'f', InputOption::VALUE_NONE); $this->assertFalse($option->acceptValue(), '__construct() can take "InputOption::VALUE_NONE" as its mode'); @@ -97,6 +100,46 @@ public function testInvalidModes() new InputOption('foo', 'f', '-1'); } + /** + * @group legacy + */ + public function testCombiningNoneAndRequiredModeIsInvalid() + { + $this->expectDeprecation('Since symfony/console 6.4: InputOption mode should be either none, required or optional.'); + + new InputOption('foo', 'f', InputOption::VALUE_NONE | InputOption::VALUE_REQUIRED); + } + + /** + * @group legacy + */ + public function testCombiningNoneAndOptionalModeIsInvalid() + { + $this->expectDeprecation('Since symfony/console 6.4: InputOption mode should be either none, required or optional.'); + + new InputOption('foo', 'f', InputOption::VALUE_NONE | InputOption::VALUE_OPTIONAL); + } + + /** + * @group legacy + */ + public function testCombiningRequiredAndOptionalModeIsInvalid() + { + $this->expectDeprecation('Since symfony/console 6.4: InputOption mode should be either none, required or optional.'); + + new InputOption('foo', 'f', InputOption::VALUE_REQUIRED | InputOption::VALUE_OPTIONAL); + } + + /** + * @group legacy + */ + public function testCombiningNoneRequiredAndOptionalModeIsInvalid() + { + $this->expectDeprecation('Since symfony/console 6.4: InputOption mode should be either none, required or optional.'); + + new InputOption('foo', 'f', InputOption::VALUE_NONE | InputOption::VALUE_REQUIRED | InputOption::VALUE_OPTIONAL); + } + public function testEmptyNameIsInvalid() { $this->expectException(\InvalidArgumentException::class);