From b69c2ebdeaa9fb6ac2ff5bb3d69c2a6688255b99 Mon Sep 17 00:00:00 2001 From: Jesper Noordsij Date: Fri, 13 Oct 2023 23:11:35 +0200 Subject: [PATCH 1/4] Disallow setting both REQUIRED and OPTIONAL on InputArgument Also automatically set OPTIONAL whenever REQUIRED is not specified. --- .../Component/Console/Input/InputArgument.php | 10 +++++++--- .../Console/Tests/Input/InputArgumentTest.php | 15 ++++++++++++++- 2 files changed, 21 insertions(+), 4 deletions(-) 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/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); From fbd2554a0b6ff2db12db3aad24b8e36d1f0b69f4 Mon Sep 17 00:00:00 2001 From: Jesper Noordsij Date: Fri, 13 Oct 2023 23:30:05 +0200 Subject: [PATCH 2/4] Disallow setting combinations of NONE, REQUIRED and OPTIONAL on InputOption Also automatically set NONE whenever REQUIRED is not specified. --- .../Component/Console/Input/InputOption.php | 11 +++-- .../Console/Tests/Input/InputOptionTest.php | 49 +++++++++++++++++-- 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Component/Console/Input/InputOption.php b/src/Symfony/Component/Console/Input/InputOption.php index fdf88dcc27490..c6f111e17fb7c 100644 --- a/src/Symfony/Component/Console/Input/InputOption.php +++ b/src/Symfony/Component/Console/Input/InputOption.php @@ -92,12 +92,17 @@ 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)); } + $requirementSpecifier = ($mode & (self::VALUE_NONE | self::VALUE_REQUIRED | self::VALUE_OPTIONAL)); + if (!(self::VALUE_NONE === $requirementSpecifier || self::VALUE_REQUIRED === $requirementSpecifier || self::VALUE_OPTIONAL === $requirementSpecifier)) { + 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/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); From 251b2424bc234ac0676db53f398194da1c822fbb Mon Sep 17 00:00:00 2001 From: Jesper Noordsij Date: Fri, 13 Oct 2023 23:54:40 +0200 Subject: [PATCH 3/4] Add entries to CHANGELOG and UPGRADE-6.4 for deprecated modes on InputArgument and InputOption --- UPGRADE-6.4.md | 6 ++++++ src/Symfony/Component/Console/CHANGELOG.md | 2 ++ 2 files changed, 8 insertions(+) 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 --- From 2262675a61fc84c58f2f8ac318dc5d873de01f36 Mon Sep 17 00:00:00 2001 From: Jesper Noordsij <45041769+jnoordsij@users.noreply.github.com> Date: Sun, 15 Oct 2023 13:45:21 +0200 Subject: [PATCH 4/4] Improve codestyle for mode check in InputOption MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jérôme Tamarelle --- src/Symfony/Component/Console/Input/InputOption.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Symfony/Component/Console/Input/InputOption.php b/src/Symfony/Component/Console/Input/InputOption.php index c6f111e17fb7c..59076608c3b35 100644 --- a/src/Symfony/Component/Console/Input/InputOption.php +++ b/src/Symfony/Component/Console/Input/InputOption.php @@ -98,8 +98,7 @@ public function __construct(string $name, string|array $shortcut = null, int $mo throw new InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); } - $requirementSpecifier = ($mode & (self::VALUE_NONE | self::VALUE_REQUIRED | self::VALUE_OPTIONAL)); - if (!(self::VALUE_NONE === $requirementSpecifier || self::VALUE_REQUIRED === $requirementSpecifier || self::VALUE_OPTIONAL === $requirementSpecifier)) { + 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.'); }