From 235179850a9136f1acc79645038f4ce3f9c886ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Wed, 15 Nov 2023 23:01:24 +0100 Subject: [PATCH 01/19] Fix ProgressBar::iterate on empty iterator Co-authored-by: Florian Reimair --- Helper/ProgressBar.php | 59 +++++++++++++++++++++++--------- Tests/Helper/ProgressBarTest.php | 16 ++++++++- 2 files changed, 58 insertions(+), 17 deletions(-) diff --git a/Helper/ProgressBar.php b/Helper/ProgressBar.php index 64389c4a2..a1f806d5f 100644 --- a/Helper/ProgressBar.php +++ b/Helper/ProgressBar.php @@ -195,7 +195,7 @@ public function getStartTime(): int public function getMaxSteps(): int { - return $this->max; + return $this->max ?? 0; } public function getProgress(): int @@ -215,7 +215,7 @@ public function getProgressPercent(): float public function getBarOffset(): float { - return floor($this->max ? $this->percent * $this->barWidth : (null === $this->redrawFreq ? (int) (min(5, $this->barWidth / 15) * $this->writeCount) : $this->step) % $this->barWidth); + return floor(null !== $this->max ? $this->percent * $this->barWidth : (null === $this->redrawFreq ? (int) (min(5, $this->barWidth / 15) * $this->writeCount) : $this->step) % $this->barWidth); } public function getEstimated(): float @@ -253,7 +253,7 @@ public function setBarCharacter(string $char): void public function getBarCharacter(): string { - return $this->barChar ?? ($this->max ? '=' : $this->emptyBarChar); + return $this->barChar ?? (null !== $this->max ? '=' : $this->emptyBarChar); } public function setEmptyBarCharacter(string $char): void @@ -315,7 +315,21 @@ public function maxSecondsBetweenRedraws(float $seconds): void */ public function iterate(iterable $iterable, int $max = null): iterable { - $this->start($max ?? (is_countable($iterable) ? \count($iterable) : 0)); + if (0 === $max) { + $max = null; + } + + $max ??= is_countable($iterable) ? \count($iterable) : null; + + if (0 === $max) { + $this->max = 0; + $this->stepWidth = 2; + $this->finish(); + + return; + } + + $this->start($max); foreach ($iterable as $key => $value) { yield $key => $value; @@ -373,11 +387,15 @@ public function setProgress(int $step): void $step = 0; } - $redrawFreq = $this->redrawFreq ?? (($this->max ?: 10) / 10); - $prevPeriod = (int) ($this->step / $redrawFreq); - $currPeriod = (int) ($step / $redrawFreq); + $redrawFreq = $this->redrawFreq ?? (($this->max ?? 10) / 10); + $prevPeriod = $redrawFreq ? (int) ($this->step / $redrawFreq) : 0; + $currPeriod = $redrawFreq ? (int) ($step / $redrawFreq) : 0; $this->step = $step; - $this->percent = $this->max ? (float) $this->step / $this->max : 0; + $this->percent = match ($this->max) { + null => 0, + 0 => 1, + default => (float) $this->step / $this->max, + }; $timeInterval = microtime(true) - $this->lastWriteTime; // Draw regardless of other limits @@ -398,11 +416,20 @@ public function setProgress(int $step): void } } - public function setMaxSteps(int $max): void + public function setMaxSteps(?int $max): void { + if (0 === $max) { + $max = null; + } + $this->format = null; - $this->max = max(0, $max); - $this->stepWidth = $this->max ? Helper::width((string) $this->max) : 4; + if (null === $max) { + $this->max = null; + $this->stepWidth = 4; + } else { + $this->max = max(0, $max); + $this->stepWidth = Helper::width((string) $this->max); + } } /** @@ -410,16 +437,16 @@ public function setMaxSteps(int $max): void */ public function finish(): void { - if (!$this->max) { + if (null === $this->max) { $this->max = $this->step; } - if ($this->step === $this->max && !$this->overwrite) { + if (($this->step === $this->max || null === $this->max) && !$this->overwrite) { // prevent double 100% output return; } - $this->setProgress($this->max); + $this->setProgress($this->max ?? $this->step); } /** @@ -542,14 +569,14 @@ private static function initPlaceholderFormatters(): array }, 'elapsed' => fn (self $bar) => Helper::formatTime(time() - $bar->getStartTime(), 2), 'remaining' => function (self $bar) { - if (!$bar->getMaxSteps()) { + if (null === $bar->getMaxSteps()) { throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.'); } return Helper::formatTime($bar->getRemaining(), 2); }, 'estimated' => function (self $bar) { - if (!$bar->getMaxSteps()) { + if (null === $bar->getMaxSteps()) { throw new LogicException('Unable to display the estimated time if the maximum number of steps is not set.'); } diff --git a/Tests/Helper/ProgressBarTest.php b/Tests/Helper/ProgressBarTest.php index 4dff078ae..cc7ed6c88 100644 --- a/Tests/Helper/ProgressBarTest.php +++ b/Tests/Helper/ProgressBarTest.php @@ -1092,6 +1092,20 @@ public function testIterateUncountable() ); } + public function testEmptyInputWithDebugFormat() + { + $bar = new ProgressBar($output = $this->getOutputStream()); + $bar->setFormat('%current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%'); + + $this->assertEquals([], iterator_to_array($bar->iterate([]))); + + rewind($output->getStream()); + $this->assertEquals( + ' 0/0 [============================] 100% < 1 sec/< 1 sec', + stream_get_contents($output->getStream()) + ); + } + protected function getOutputStream($decorated = true, $verbosity = StreamOutput::VERBOSITY_NORMAL) { return new StreamOutput(fopen('php://memory', 'r+', false), $verbosity, $decorated); @@ -1263,7 +1277,7 @@ public function testMultiLineFormatIsFullyCorrectlyWithManuallyCleanup() 'Foo!'.\PHP_EOL. $this->generateOutput('[--->------------------------]'). "\nProcessing \"foobar\"...". - $this->generateOutput("[----->----------------------]\nProcessing \"foobar\"..."), + $this->generateOutput("[============================]\nProcessing \"foobar\"..."), stream_get_contents($output->getStream()) ); } From 531f11d95b6c89815c37d446ff63d630ab9c9977 Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Mon, 20 Nov 2023 19:32:45 +0100 Subject: [PATCH 02/19] [CssSelector][Serializer][Translation] [Command] Clean unused code --- Command/Command.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Command/Command.php b/Command/Command.php index c49891777..ef1e7c31e 100644 --- a/Command/Command.php +++ b/Command/Command.php @@ -277,10 +277,6 @@ public function run(InputInterface $input, OutputInterface $output): int $statusCode = ($this->code)($input, $output); } else { $statusCode = $this->execute($input, $output); - - if (!\is_int($statusCode)) { - throw new \TypeError(sprintf('Return value of "%s::execute()" must be of the type int, "%s" returned.', static::class, get_debug_type($statusCode))); - } } return is_numeric($statusCode) ? (int) $statusCode : 0; From 0b33220c25d174123c4582f23b3d35625ae881a0 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Thu, 14 Dec 2023 11:03:37 +0100 Subject: [PATCH 03/19] Set `strict` parameter of `in_array` to true where possible --- Application.php | 2 +- Descriptor/ReStructuredTextDescriptor.php | 2 +- Helper/TableCellStyle.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Application.php b/Application.php index 07cc6d674..4d1a6d953 100644 --- a/Application.php +++ b/Application.php @@ -716,7 +716,7 @@ public function find(string $name): Command $aliases[$nameOrAlias] = $commandName; - return $commandName === $nameOrAlias || !\in_array($commandName, $commands); + return $commandName === $nameOrAlias || !\in_array($commandName, $commands, true); })); } diff --git a/Descriptor/ReStructuredTextDescriptor.php b/Descriptor/ReStructuredTextDescriptor.php index d4423fd34..f12fecb67 100644 --- a/Descriptor/ReStructuredTextDescriptor.php +++ b/Descriptor/ReStructuredTextDescriptor.php @@ -226,7 +226,7 @@ private function getNonDefaultOptions(InputDefinition $definition): array $nonDefaultOptions = []; foreach ($definition->getOptions() as $option) { // Skip global options. - if (!\in_array($option->getName(), $globalOptions)) { + if (!\in_array($option->getName(), $globalOptions, true)) { $nonDefaultOptions[] = $option; } } diff --git a/Helper/TableCellStyle.php b/Helper/TableCellStyle.php index 9419dcb40..49b97f853 100644 --- a/Helper/TableCellStyle.php +++ b/Helper/TableCellStyle.php @@ -67,7 +67,7 @@ public function getTagOptions(): array { return array_filter( $this->getOptions(), - fn ($key) => \in_array($key, self::TAG_OPTIONS) && isset($this->options[$key]), + fn ($key) => \in_array($key, self::TAG_OPTIONS, true) && isset($this->options[$key]), \ARRAY_FILTER_USE_KEY ); } From e6bafc780aad05f332917caa3f7b7576a1e05ef2 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Mon, 18 Dec 2023 08:46:12 +0100 Subject: [PATCH 04/19] Code updates --- Helper/Table.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Helper/Table.php b/Helper/Table.php index fe2ac87c1..fd2e94d5d 100644 --- a/Helper/Table.php +++ b/Helper/Table.php @@ -717,7 +717,7 @@ private function fillNextRows(array $rows, int $line): array foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { // we need to know if $unmergedRow will be merged or inserted into $rows - if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) { + if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRow) <= $this->numberOfColumns)) { foreach ($unmergedRow as $cellKey => $cell) { // insert cell into row at cellKey position array_splice($rows[$unmergedRowKey], $cellKey, 0, [$cell]); @@ -726,7 +726,7 @@ private function fillNextRows(array $rows, int $line): array $row = $this->copyRow($rows, $unmergedRowKey - 1); foreach ($unmergedRow as $column => $cell) { if (!empty($cell)) { - $row[$column] = $unmergedRow[$column]; + $row[$column] = $cell; } } array_splice($rows, $unmergedRowKey, 0, [$row]); From e0b2ccdd08585175012a8a9999d842153835df78 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Tue, 10 Oct 2023 16:04:32 +0200 Subject: [PATCH 05/19] [Console][EventDispatcher][Security][Serializer][Workflow] Add PHPDoc to attribute classes and properties --- Attribute/AsCommand.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Attribute/AsCommand.php b/Attribute/AsCommand.php index b337f548f..6066d7c53 100644 --- a/Attribute/AsCommand.php +++ b/Attribute/AsCommand.php @@ -17,6 +17,12 @@ #[\Attribute(\Attribute::TARGET_CLASS)] class AsCommand { + /** + * @param string $name The name of the command, used when calling it (i.e. "cache:clear") + * @param string|null $description The description of the command, displayed with the help page + * @param string[] $aliases The list of aliases of the command. The command will be executed when using one of them (i.e. "cache:clean") + * @param bool $hidden If true, the command won't be shown when listing all the available commands, but it can still be run as any other command + */ public function __construct( public string $name, public ?string $description = null, From f92fef1127ac0222ab92dd09122ad6b324610785 Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski Date: Tue, 2 Jan 2024 15:49:33 +0100 Subject: [PATCH 06/19] CS: trailing commas --- Completion/Suggestion.php | 2 +- Helper/OutputWrapper.php | 2 +- Output/AnsiColorMode.php | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Completion/Suggestion.php b/Completion/Suggestion.php index 7392965a2..3251b079f 100644 --- a/Completion/Suggestion.php +++ b/Completion/Suggestion.php @@ -20,7 +20,7 @@ class Suggestion implements \Stringable { public function __construct( private readonly string $value, - private readonly string $description = '' + private readonly string $description = '', ) { } diff --git a/Helper/OutputWrapper.php b/Helper/OutputWrapper.php index 2ec819c74..0ea2b7056 100644 --- a/Helper/OutputWrapper.php +++ b/Helper/OutputWrapper.php @@ -49,7 +49,7 @@ final class OutputWrapper private const URL_PATTERN = 'https?://\S+'; public function __construct( - private bool $allowCutUrls = false + private bool $allowCutUrls = false, ) { } diff --git a/Output/AnsiColorMode.php b/Output/AnsiColorMode.php index 5f9f744fe..ca40ffb79 100644 --- a/Output/AnsiColorMode.php +++ b/Output/AnsiColorMode.php @@ -63,7 +63,7 @@ public function convertFromHexToAnsiColorCode(string $hexColor): string return match ($this) { self::Ansi4 => (string) $this->convertFromRGB($r, $g, $b), self::Ansi8 => '8;5;'.((string) $this->convertFromRGB($r, $g, $b)), - self::Ansi24 => sprintf('8;2;%d;%d;%d', $r, $g, $b) + self::Ansi24 => sprintf('8;2;%d;%d;%d', $r, $g, $b), }; } @@ -72,7 +72,7 @@ private function convertFromRGB(int $r, int $g, int $b): int return match ($this) { self::Ansi4 => $this->degradeHexColorToAnsi4($r, $g, $b), self::Ansi8 => $this->degradeHexColorToAnsi8($r, $g, $b), - default => throw new InvalidArgumentException("RGB cannot be converted to {$this->name}.") + default => throw new InvalidArgumentException("RGB cannot be converted to {$this->name}."), }; } From 7739bb6aef4a98d2a28465de5a5e2a78f66caa7a Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Tue, 2 Jan 2024 14:14:18 +0100 Subject: [PATCH 07/19] [Asset][BrowserKit][Cache][Console][CssSelector] Use CPP --- Application.php | 10 ++++------ Command/LazyCommand.php | 12 ++++++++---- CommandLoader/ContainerCommandLoader.php | 11 ++++------- CommandLoader/FactoryCommandLoader.php | 8 +++----- Cursor.php | 8 ++++---- Descriptor/ApplicationDescription.php | 13 +++++-------- Event/ConsoleErrorEvent.php | 11 ++++++----- Event/ConsoleEvent.php | 15 +++++---------- Event/ConsoleSignalEvent.php | 14 +++++++------- EventListener/ErrorListener.php | 8 +++----- Exception/CommandNotFoundException.php | 12 ++++++------ Helper/Dumper.php | 14 +++++--------- Helper/ProgressIndicator.php | 12 ++++++------ Helper/Table.php | 8 +++----- Helper/TableCell.php | 9 ++++----- Helper/TableRows.php | 8 +++----- Input/ArrayInput.php | 10 ++++------ Input/InputArgument.php | 15 +++++++-------- Input/InputOption.php | 14 ++++++++------ Logger/ConsoleLogger.php | 9 +++++---- Messenger/RunCommandMessageHandler.php | 5 +++-- Question/ChoiceQuestion.php | 9 +++++---- Question/ConfirmationQuestion.php | 10 +++++----- Question/Question.php | 10 ++++------ Style/OutputStyle.php | 8 +++----- Style/SymfonyStyle.php | 11 +++++------ Tester/ApplicationTester.php | 8 +++----- Tester/CommandCompletionTester.php | 8 +++----- Tester/CommandTester.php | 8 +++----- 29 files changed, 134 insertions(+), 164 deletions(-) diff --git a/Application.php b/Application.php index 4d1a6d953..00acef1dd 100644 --- a/Application.php +++ b/Application.php @@ -75,8 +75,6 @@ class Application implements ResetInterface private array $commands = []; private bool $wantHelps = false; private ?Command $runningCommand = null; - private string $name; - private string $version; private ?CommandLoaderInterface $commandLoader = null; private bool $catchExceptions = true; private bool $catchErrors = false; @@ -91,10 +89,10 @@ class Application implements ResetInterface private ?SignalRegistry $signalRegistry = null; private array $signalsToDispatchEvent = []; - public function __construct(string $name = 'UNKNOWN', string $version = 'UNKNOWN') - { - $this->name = $name; - $this->version = $version; + public function __construct( + private string $name = 'UNKNOWN', + private string $version = 'UNKNOWN', + ) { $this->terminal = new Terminal(); $this->defaultCommand = 'list'; if (\defined('SIGINT') && SignalRegistry::isSupported()) { diff --git a/Command/LazyCommand.php b/Command/LazyCommand.php index 7279724a6..df44b3d01 100644 --- a/Command/LazyCommand.php +++ b/Command/LazyCommand.php @@ -27,17 +27,21 @@ final class LazyCommand extends Command { private \Closure|Command $command; - private ?bool $isEnabled; - public function __construct(string $name, array $aliases, string $description, bool $isHidden, \Closure $commandFactory, ?bool $isEnabled = true) - { + public function __construct( + string $name, + array $aliases, + string $description, + bool $isHidden, + \Closure $commandFactory, + private ?bool $isEnabled = true, + ) { $this->setName($name) ->setAliases($aliases) ->setHidden($isHidden) ->setDescription($description); $this->command = $commandFactory; - $this->isEnabled = $isEnabled; } public function ignoreValidationErrors(): void diff --git a/CommandLoader/ContainerCommandLoader.php b/CommandLoader/ContainerCommandLoader.php index bfa0ac467..84e25be76 100644 --- a/CommandLoader/ContainerCommandLoader.php +++ b/CommandLoader/ContainerCommandLoader.php @@ -22,16 +22,13 @@ */ class ContainerCommandLoader implements CommandLoaderInterface { - private ContainerInterface $container; - private array $commandMap; - /** * @param array $commandMap An array with command names as keys and service ids as values */ - public function __construct(ContainerInterface $container, array $commandMap) - { - $this->container = $container; - $this->commandMap = $commandMap; + public function __construct( + private ContainerInterface $container, + private array $commandMap, + ) { } public function get(string $name): Command diff --git a/CommandLoader/FactoryCommandLoader.php b/CommandLoader/FactoryCommandLoader.php index 9ced75aeb..ae16bf6f1 100644 --- a/CommandLoader/FactoryCommandLoader.php +++ b/CommandLoader/FactoryCommandLoader.php @@ -21,14 +21,12 @@ */ class FactoryCommandLoader implements CommandLoaderInterface { - private array $factories; - /** * @param callable[] $factories Indexed by command names */ - public function __construct(array $factories) - { - $this->factories = $factories; + public function __construct( + private array $factories, + ) { } public function has(string $name): bool diff --git a/Cursor.php b/Cursor.php index 69fd3821c..965f996e0 100644 --- a/Cursor.php +++ b/Cursor.php @@ -18,16 +18,16 @@ */ final class Cursor { - private OutputInterface $output; /** @var resource */ private $input; /** * @param resource|null $input */ - public function __construct(OutputInterface $output, $input = null) - { - $this->output = $output; + public function __construct( + private OutputInterface $output, + $input = null, + ) { $this->input = $input ?? (\defined('STDIN') ? \STDIN : fopen('php://input', 'r+')); } diff --git a/Descriptor/ApplicationDescription.php b/Descriptor/ApplicationDescription.php index f8ed18045..5149fde4c 100644 --- a/Descriptor/ApplicationDescription.php +++ b/Descriptor/ApplicationDescription.php @@ -24,9 +24,6 @@ class ApplicationDescription { public const GLOBAL_NAMESPACE = '_global'; - private Application $application; - private ?string $namespace; - private bool $showHidden; private array $namespaces; /** @@ -39,11 +36,11 @@ class ApplicationDescription */ private array $aliases = []; - public function __construct(Application $application, string $namespace = null, bool $showHidden = false) - { - $this->application = $application; - $this->namespace = $namespace; - $this->showHidden = $showHidden; + public function __construct( + private Application $application, + private ?string $namespace = null, + private bool $showHidden = false, + ) { } public function getNamespaces(): array diff --git a/Event/ConsoleErrorEvent.php b/Event/ConsoleErrorEvent.php index d4a691216..f469fbcee 100644 --- a/Event/ConsoleErrorEvent.php +++ b/Event/ConsoleErrorEvent.php @@ -22,14 +22,15 @@ */ final class ConsoleErrorEvent extends ConsoleEvent { - private \Throwable $error; private int $exitCode; - public function __construct(InputInterface $input, OutputInterface $output, \Throwable $error, Command $command = null) - { + public function __construct( + InputInterface $input, + OutputInterface $output, + private \Throwable $error, + Command $command = null, + ) { parent::__construct($command, $input, $output); - - $this->error = $error; } public function getError(): \Throwable diff --git a/Event/ConsoleEvent.php b/Event/ConsoleEvent.php index 437a58e1e..2f9f0778e 100644 --- a/Event/ConsoleEvent.php +++ b/Event/ConsoleEvent.php @@ -23,16 +23,11 @@ */ class ConsoleEvent extends Event { - protected ?Command $command; - - private InputInterface $input; - private OutputInterface $output; - - public function __construct(?Command $command, InputInterface $input, OutputInterface $output) - { - $this->command = $command; - $this->input = $input; - $this->output = $output; + public function __construct( + protected ?Command $command, + private InputInterface $input, + private OutputInterface $output, + ) { } /** diff --git a/Event/ConsoleSignalEvent.php b/Event/ConsoleSignalEvent.php index 95af1f915..b27f08a18 100644 --- a/Event/ConsoleSignalEvent.php +++ b/Event/ConsoleSignalEvent.php @@ -20,14 +20,14 @@ */ final class ConsoleSignalEvent extends ConsoleEvent { - private int $handlingSignal; - private int|false $exitCode; - - public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $handlingSignal, int|false $exitCode = 0) - { + public function __construct( + Command $command, + InputInterface $input, + OutputInterface $output, + private int $handlingSignal, + private int|false $exitCode = 0, + ) { parent::__construct($command, $input, $output); - $this->handlingSignal = $handlingSignal; - $this->exitCode = $exitCode; } public function getHandlingSignal(): int diff --git a/EventListener/ErrorListener.php b/EventListener/ErrorListener.php index 5c38e8ef8..49915a498 100644 --- a/EventListener/ErrorListener.php +++ b/EventListener/ErrorListener.php @@ -24,11 +24,9 @@ */ class ErrorListener implements EventSubscriberInterface { - private ?LoggerInterface $logger; - - public function __construct(LoggerInterface $logger = null) - { - $this->logger = $logger; + public function __construct( + private ?LoggerInterface $logger = null, + ) { } public function onConsoleError(ConsoleErrorEvent $event): void diff --git a/Exception/CommandNotFoundException.php b/Exception/CommandNotFoundException.php index 1e9f1c793..47750d5f9 100644 --- a/Exception/CommandNotFoundException.php +++ b/Exception/CommandNotFoundException.php @@ -18,19 +18,19 @@ */ class CommandNotFoundException extends \InvalidArgumentException implements ExceptionInterface { - private array $alternatives; - /** * @param string $message Exception message to throw * @param string[] $alternatives List of similar defined names * @param int $code Exception code * @param \Throwable|null $previous Previous exception used for the exception chaining */ - public function __construct(string $message, array $alternatives = [], int $code = 0, \Throwable $previous = null) - { + public function __construct( + string $message, + private array $alternatives = [], + int $code = 0, + \Throwable $previous = null, + ) { parent::__construct($message, $code, $previous); - - $this->alternatives = $alternatives; } /** diff --git a/Helper/Dumper.php b/Helper/Dumper.php index 8c6a94d51..0cd01e616 100644 --- a/Helper/Dumper.php +++ b/Helper/Dumper.php @@ -21,17 +21,13 @@ */ final class Dumper { - private OutputInterface $output; - private ?CliDumper $dumper; - private ?ClonerInterface $cloner; private \Closure $handler; - public function __construct(OutputInterface $output, CliDumper $dumper = null, ClonerInterface $cloner = null) - { - $this->output = $output; - $this->dumper = $dumper; - $this->cloner = $cloner; - + public function __construct( + private OutputInterface $output, + private ?CliDumper $dumper = null, + private ?ClonerInterface $cloner = null, + ) { if (class_exists(CliDumper::class)) { $this->handler = function ($var): string { $dumper = $this->dumper ??= new CliDumper(null, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR); diff --git a/Helper/ProgressIndicator.php b/Helper/ProgressIndicator.php index 8865ecc34..8ebf99145 100644 --- a/Helper/ProgressIndicator.php +++ b/Helper/ProgressIndicator.php @@ -31,13 +31,11 @@ class ProgressIndicator 'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)', ]; - private OutputInterface $output; private int $startTime; private ?string $format = null; private ?string $message = null; private array $indicatorValues; private int $indicatorCurrent; - private int $indicatorChangeInterval; private float $indicatorUpdateTime; private bool $started = false; @@ -50,9 +48,12 @@ class ProgressIndicator * @param int $indicatorChangeInterval Change interval in milliseconds * @param array|null $indicatorValues Animated indicator characters */ - public function __construct(OutputInterface $output, string $format = null, int $indicatorChangeInterval = 100, array $indicatorValues = null) - { - $this->output = $output; + public function __construct( + private OutputInterface $output, + string $format = null, + private int $indicatorChangeInterval = 100, + array $indicatorValues = null, + ) { $format ??= $this->determineBestFormat(); $indicatorValues ??= ['-', '\\', '|', '/']; @@ -63,7 +64,6 @@ public function __construct(OutputInterface $output, string $format = null, int } $this->format = self::getFormatDefinition($format); - $this->indicatorChangeInterval = $indicatorChangeInterval; $this->indicatorValues = $indicatorValues; $this->startTime = time(); } diff --git a/Helper/Table.php b/Helper/Table.php index fd2e94d5d..87cea7f77 100644 --- a/Helper/Table.php +++ b/Helper/Table.php @@ -45,7 +45,6 @@ class Table private array $rows = []; private array $effectiveColumnWidths = []; private int $numberOfColumns; - private OutputInterface $output; private TableStyle $style; private array $columnStyles = []; private array $columnWidths = []; @@ -55,10 +54,9 @@ class Table private static array $styles; - public function __construct(OutputInterface $output) - { - $this->output = $output; - + public function __construct( + private OutputInterface $output, + ) { self::$styles ??= self::initStyles(); $this->setStyle('default'); diff --git a/Helper/TableCell.php b/Helper/TableCell.php index 394b2bc95..1c4eeea20 100644 --- a/Helper/TableCell.php +++ b/Helper/TableCell.php @@ -18,17 +18,16 @@ */ class TableCell { - private string $value; private array $options = [ 'rowspan' => 1, 'colspan' => 1, 'style' => null, ]; - public function __construct(string $value = '', array $options = []) - { - $this->value = $value; - + public function __construct( + private string $value = '', + array $options = [], + ) { // check option names if ($diff = array_diff(array_keys($options), array_keys($this->options))) { throw new InvalidArgumentException(sprintf('The TableCell does not support the following options: \'%s\'.', implode('\', \'', $diff))); diff --git a/Helper/TableRows.php b/Helper/TableRows.php index 97d07726e..fb2dc2789 100644 --- a/Helper/TableRows.php +++ b/Helper/TableRows.php @@ -16,11 +16,9 @@ */ class TableRows implements \IteratorAggregate { - private \Closure $generator; - - public function __construct(\Closure $generator) - { - $this->generator = $generator; + public function __construct( + private \Closure $generator, + ) { } public function getIterator(): \Traversable diff --git a/Input/ArrayInput.php b/Input/ArrayInput.php index 03b200b13..2697e3721 100644 --- a/Input/ArrayInput.php +++ b/Input/ArrayInput.php @@ -25,12 +25,10 @@ */ class ArrayInput extends Input { - private array $parameters; - - public function __construct(array $parameters, InputDefinition $definition = null) - { - $this->parameters = $parameters; - + public function __construct( + private array $parameters, + InputDefinition $definition = null, + ) { parent::__construct($definition); } diff --git a/Input/InputArgument.php b/Input/InputArgument.php index 642ae6600..9d6c17fa3 100644 --- a/Input/InputArgument.php +++ b/Input/InputArgument.php @@ -29,11 +29,8 @@ class InputArgument public const OPTIONAL = 2; public const IS_ARRAY = 4; - private string $name; private int $mode; private string|int|bool|array|null|float $default; - private array|\Closure $suggestedValues; - private string $description; /** * @param string $name The argument name @@ -44,18 +41,20 @@ class InputArgument * * @throws InvalidArgumentException When argument mode is not valid */ - public function __construct(string $name, int $mode = null, string $description = '', string|bool|int|float|array $default = null, \Closure|array $suggestedValues = []) - { + public function __construct( + private string $name, + int $mode = null, + private string $description = '', + string|bool|int|float|array $default = null, + private \Closure|array $suggestedValues = [], + ) { if (null === $mode) { $mode = self::OPTIONAL; } elseif ($mode > 7 || $mode < 1) { throw new InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode)); } - $this->name = $name; $this->mode = $mode; - $this->description = $description; - $this->suggestedValues = $suggestedValues; $this->setDefault($default); } diff --git a/Input/InputOption.php b/Input/InputOption.php index f8e9b0dd6..5d305518a 100644 --- a/Input/InputOption.php +++ b/Input/InputOption.php @@ -54,8 +54,6 @@ class InputOption private string|array|null $shortcut; private int $mode; private string|int|bool|array|null|float $default; - private array|\Closure $suggestedValues; - private string $description; /** * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts @@ -65,8 +63,14 @@ class InputOption * * @throws InvalidArgumentException If option mode is invalid or incompatible */ - public function __construct(string $name, string|array $shortcut = null, int $mode = null, string $description = '', string|bool|int|float|array $default = null, array|\Closure $suggestedValues = []) - { + public function __construct( + string $name, + string|array $shortcut = null, + int $mode = null, + private string $description = '', + string|bool|int|float|array $default = null, + private array|\Closure $suggestedValues = [], + ) { if (str_starts_with($name, '--')) { $name = substr($name, 2); } @@ -101,8 +105,6 @@ public function __construct(string $name, string|array $shortcut = null, int $mo $this->name = $name; $this->shortcut = $shortcut; $this->mode = $mode; - $this->description = $description; - $this->suggestedValues = $suggestedValues; if ($suggestedValues && !$this->acceptValue()) { throw new LogicException('Cannot set suggested values if the option does not accept a value.'); diff --git a/Logger/ConsoleLogger.php b/Logger/ConsoleLogger.php index fddef50cd..ad6e49ce1 100644 --- a/Logger/ConsoleLogger.php +++ b/Logger/ConsoleLogger.php @@ -29,7 +29,6 @@ class ConsoleLogger extends AbstractLogger public const INFO = 'info'; public const ERROR = 'error'; - private OutputInterface $output; private array $verbosityLevelMap = [ LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL, LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL, @@ -52,9 +51,11 @@ class ConsoleLogger extends AbstractLogger ]; private bool $errored = false; - public function __construct(OutputInterface $output, array $verbosityLevelMap = [], array $formatLevelMap = []) - { - $this->output = $output; + public function __construct( + private OutputInterface $output, + array $verbosityLevelMap = [], + array $formatLevelMap = [], + ) { $this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap; $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap; } diff --git a/Messenger/RunCommandMessageHandler.php b/Messenger/RunCommandMessageHandler.php index 14f9c1764..1bc499494 100644 --- a/Messenger/RunCommandMessageHandler.php +++ b/Messenger/RunCommandMessageHandler.php @@ -22,8 +22,9 @@ */ final class RunCommandMessageHandler { - public function __construct(private readonly Application $application) - { + public function __construct( + private readonly Application $application, + ) { } public function __invoke(RunCommandMessage $message): RunCommandContext diff --git a/Question/ChoiceQuestion.php b/Question/ChoiceQuestion.php index e449ff683..0ccad051c 100644 --- a/Question/ChoiceQuestion.php +++ b/Question/ChoiceQuestion.php @@ -20,7 +20,6 @@ */ class ChoiceQuestion extends Question { - private array $choices; private bool $multiselect = false; private string $prompt = ' > '; private string $errorMessage = 'Value "%s" is invalid'; @@ -30,15 +29,17 @@ class ChoiceQuestion extends Question * @param array $choices The list of available choices * @param mixed $default The default answer to return */ - public function __construct(string $question, array $choices, mixed $default = null) - { + public function __construct( + string $question, + private array $choices, + mixed $default = null, + ) { if (!$choices) { throw new \LogicException('Choice question must have at least 1 choice available.'); } parent::__construct($question, $default); - $this->choices = $choices; $this->setValidator($this->getDefaultValidator()); $this->setAutocompleterValues($choices); } diff --git a/Question/ConfirmationQuestion.php b/Question/ConfirmationQuestion.php index 40eab2429..951d68140 100644 --- a/Question/ConfirmationQuestion.php +++ b/Question/ConfirmationQuestion.php @@ -18,18 +18,18 @@ */ class ConfirmationQuestion extends Question { - private string $trueAnswerRegex; - /** * @param string $question The question to ask to the user * @param bool $default The default answer to return, true or false * @param string $trueAnswerRegex A regex to match the "yes" answer */ - public function __construct(string $question, bool $default = true, string $trueAnswerRegex = '/^y/i') - { + public function __construct( + string $question, + bool $default = true, + private string $trueAnswerRegex = '/^y/i', + ) { parent::__construct($question, $default); - $this->trueAnswerRegex = $trueAnswerRegex; $this->setNormalizer($this->getDefaultNormalizer()); } diff --git a/Question/Question.php b/Question/Question.php index c79683cd5..a82125c91 100644 --- a/Question/Question.php +++ b/Question/Question.php @@ -21,13 +21,11 @@ */ class Question { - private string $question; private ?int $attempts = null; private bool $hidden = false; private bool $hiddenFallback = true; private ?\Closure $autocompleterCallback = null; private ?\Closure $validator = null; - private string|int|bool|null|float $default; private ?\Closure $normalizer = null; private bool $trimmable = true; private bool $multiline = false; @@ -36,10 +34,10 @@ class Question * @param string $question The question to ask to the user * @param string|bool|int|float|null $default The default answer to return if the user enters nothing */ - public function __construct(string $question, string|bool|int|float $default = null) - { - $this->question = $question; - $this->default = $default; + public function __construct( + private string $question, + private null|string|bool|int|float $default = null, + ) { } /** diff --git a/Style/OutputStyle.php b/Style/OutputStyle.php index 05076c00f..9f62ea312 100644 --- a/Style/OutputStyle.php +++ b/Style/OutputStyle.php @@ -23,11 +23,9 @@ */ abstract class OutputStyle implements OutputInterface, StyleInterface { - private OutputInterface $output; - - public function __construct(OutputInterface $output) - { - $this->output = $output; + public function __construct( + private OutputInterface $output, + ) { } public function newLine(int $count = 1): void diff --git a/Style/SymfonyStyle.php b/Style/SymfonyStyle.php index 0da5d6981..3fd531d98 100644 --- a/Style/SymfonyStyle.php +++ b/Style/SymfonyStyle.php @@ -40,22 +40,21 @@ class SymfonyStyle extends OutputStyle { public const MAX_LINE_LENGTH = 120; - private InputInterface $input; - private OutputInterface $output; private SymfonyQuestionHelper $questionHelper; private ProgressBar $progressBar; private int $lineLength; private TrimmedBufferOutput $bufferedOutput; - public function __construct(InputInterface $input, OutputInterface $output) - { - $this->input = $input; + public function __construct( + private InputInterface $input, + private OutputInterface $output, + ) { $this->bufferedOutput = new TrimmedBufferOutput(\DIRECTORY_SEPARATOR === '\\' ? 4 : 2, $output->getVerbosity(), false, clone $output->getFormatter()); // Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not. $width = (new Terminal())->getWidth() ?: self::MAX_LINE_LENGTH; $this->lineLength = min($width - (int) (\DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH); - parent::__construct($this->output = $output); + parent::__construct($output); } /** diff --git a/Tester/ApplicationTester.php b/Tester/ApplicationTester.php index 58aee54d6..cebb6f8eb 100644 --- a/Tester/ApplicationTester.php +++ b/Tester/ApplicationTester.php @@ -28,11 +28,9 @@ class ApplicationTester { use TesterTrait; - private Application $application; - - public function __construct(Application $application) - { - $this->application = $application; + public function __construct( + private Application $application, + ) { } /** diff --git a/Tester/CommandCompletionTester.php b/Tester/CommandCompletionTester.php index a90fe52ef..76cbaf14f 100644 --- a/Tester/CommandCompletionTester.php +++ b/Tester/CommandCompletionTester.php @@ -22,11 +22,9 @@ */ class CommandCompletionTester { - private Command $command; - - public function __construct(Command $command) - { - $this->command = $command; + public function __construct( + private Command $command, + ) { } /** diff --git a/Tester/CommandTester.php b/Tester/CommandTester.php index 2ff813b7d..d39cde7f6 100644 --- a/Tester/CommandTester.php +++ b/Tester/CommandTester.php @@ -24,11 +24,9 @@ class CommandTester { use TesterTrait; - private Command $command; - - public function __construct(Command $command) - { - $this->command = $command; + public function __construct( + private Command $command, + ) { } /** From e930278484a07b8c4779d9c021b7280bfa07bfee Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski Date: Mon, 15 Jan 2024 20:49:54 +0100 Subject: [PATCH 08/19] CS: enable ordered_types.null_adjustment=always_last --- Input/InputArgument.php | 2 +- Input/InputOption.php | 2 +- Question/Question.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Input/InputArgument.php b/Input/InputArgument.php index 9d6c17fa3..521048a06 100644 --- a/Input/InputArgument.php +++ b/Input/InputArgument.php @@ -30,7 +30,7 @@ class InputArgument public const IS_ARRAY = 4; private int $mode; - private string|int|bool|array|null|float $default; + private string|int|bool|array|float|null $default; /** * @param string $name The argument name diff --git a/Input/InputOption.php b/Input/InputOption.php index 5d305518a..870f18f73 100644 --- a/Input/InputOption.php +++ b/Input/InputOption.php @@ -53,7 +53,7 @@ class InputOption private string $name; private string|array|null $shortcut; private int $mode; - private string|int|bool|array|null|float $default; + private string|int|bool|array|float|null $default; /** * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts diff --git a/Question/Question.php b/Question/Question.php index a82125c91..46a60c798 100644 --- a/Question/Question.php +++ b/Question/Question.php @@ -36,7 +36,7 @@ class Question */ public function __construct( private string $question, - private null|string|bool|int|float $default = null, + private string|bool|int|float|null $default = null, ) { } From 7cc16b1ffc962425545f6497c469de518bf86c49 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 29 Jan 2024 09:28:05 +0100 Subject: [PATCH 09/19] [Console] Remove needless state from QuestionHelper --- Helper/QuestionHelper.php | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/Helper/QuestionHelper.php b/Helper/QuestionHelper.php index cb75ac914..0967156ca 100644 --- a/Helper/QuestionHelper.php +++ b/Helper/QuestionHelper.php @@ -34,11 +34,6 @@ */ class QuestionHelper extends Helper { - /** - * @var resource|null - */ - private $inputStream; - private static bool $stty = true; private static bool $stdinIsInteractive; @@ -59,16 +54,15 @@ public function ask(InputInterface $input, OutputInterface $output, Question $qu return $this->getDefaultAnswer($question); } - if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) { - $this->inputStream = $stream; - } + $inputStream = $input instanceof StreamableInputInterface ? $input->getStream() : null; + $inputStream ??= STDIN; try { if (!$question->getValidator()) { - return $this->doAsk($output, $question); + return $this->doAsk($inputStream, $output, $question); } - $interviewer = fn () => $this->doAsk($output, $question); + $interviewer = fn () => $this->doAsk($inputStream, $output, $question); return $this->validateAttempts($interviewer, $output, $question); } catch (MissingInputException $exception) { @@ -98,13 +92,14 @@ public static function disableStty(): void /** * Asks the question to the user. * + * @param resource $inputStream + * * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden */ - private function doAsk(OutputInterface $output, Question $question): mixed + private function doAsk($inputStream, OutputInterface $output, Question $question): mixed { $this->writePrompt($output, $question); - $inputStream = $this->inputStream ?: \STDIN; $autocomplete = $question->getAutocompleterCallback(); if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) { From f96f8d16b0a98719f3337e7f3e734d7971f7cb17 Mon Sep 17 00:00:00 2001 From: Jesper Noordsij Date: Fri, 13 Oct 2023 21:34:54 +0200 Subject: [PATCH 10/19] [Console] `InputArgument` and `InputOption` code cleanup --- Command/Command.php | 12 ++++++------ Input/InputArgument.php | 23 +++++++++++++++++------ Input/InputOption.php | 19 +++++++++++++++---- 3 files changed, 38 insertions(+), 16 deletions(-) diff --git a/Command/Command.php b/Command/Command.php index a9e52db94..03da6db43 100644 --- a/Command/Command.php +++ b/Command/Command.php @@ -283,7 +283,7 @@ public function run(InputInterface $input, OutputInterface $output): int } /** - * Adds suggestions to $suggestions for the current completion input (e.g. option or argument). + * Supplies suggestions when resolving possible completion options for input (e.g. option or argument). */ public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { @@ -401,8 +401,8 @@ public function getNativeDefinition(): InputDefinition /** * Adds an argument. * - * @param $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL - * @param $default The default value (for InputArgument::OPTIONAL mode only) + * @param $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL + * @param $default The default value (for InputArgument::OPTIONAL mode only) * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion * * @return $this @@ -420,9 +420,9 @@ public function addArgument(string $name, ?int $mode = null, string $description /** * Adds an option. * - * @param $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts - * @param $mode The option mode: One of the InputOption::VALUE_* constants - * @param $default The default value (must be null for InputOption::VALUE_NONE) + * @param $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts + * @param $mode The option mode: One of the InputOption::VALUE_* constants + * @param $default The default value (must be null for InputOption::VALUE_NONE) * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion * * @return $this diff --git a/Input/InputArgument.php b/Input/InputArgument.php index c33c6a057..a049bebe5 100644 --- a/Input/InputArgument.php +++ b/Input/InputArgument.php @@ -25,16 +25,26 @@ */ class InputArgument { + /** + * Providing an argument is required (e.g. just 'app:foo' is not allowed). + */ public const REQUIRED = 1; + + /** + * Providing an argument is optional (e.g. 'app:foo' and 'app:foo bar' are both allowed). This is the default behavior of arguments. + */ public const OPTIONAL = 2; + + /** + * The argument accepts multiple values and turn them into an array (e.g. 'app:foo bar baz' will result in value ['bar', 'baz']). + */ public const IS_ARRAY = 4; private int $mode; - private string|int|bool|array|float|null $default; /** * @param string $name The argument name - * @param int|null $mode The argument mode: a bit mask of self::REQUIRED, self::OPTIONAL and self::IS_ARRAY + * @param int-mask-of|null $mode The argument mode: a bit mask of self::REQUIRED, self::OPTIONAL and self::IS_ARRAY * @param string $description A description text * @param string|bool|int|float|array|null $default The default value (for self::OPTIONAL mode only) * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion @@ -50,7 +60,7 @@ public function __construct( ) { if (null === $mode) { $mode = self::OPTIONAL; - } elseif ($mode > 7 || $mode < 1) { + } elseif ($mode >= (self::IS_ARRAY << 1) || $mode < 1) { throw new InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode)); } @@ -89,8 +99,6 @@ public function isArray(): bool /** * Sets the default value. - * - * @throws LogicException When incorrect default value is given */ public function setDefault(string|bool|int|float|array|null $default): void { @@ -117,13 +125,16 @@ public function getDefault(): string|bool|int|float|array|null return $this->default; } + /** + * Returns true if the argument has values for input completion. + */ public function hasCompletion(): bool { return [] !== $this->suggestedValues; } /** - * Adds suggestions to $suggestions for the current completion input. + * Supplies suggestions when command resolves possible completion options for input. * * @see Command::complete() */ diff --git a/Input/InputOption.php b/Input/InputOption.php index d2ebb7827..50851c44c 100644 --- a/Input/InputOption.php +++ b/Input/InputOption.php @@ -46,18 +46,18 @@ class InputOption public const VALUE_IS_ARRAY = 8; /** - * The option may have either positive or negative value (e.g. --ansi or --no-ansi). + * The option allows passing a negated variant (e.g. --ansi or --no-ansi). */ public const VALUE_NEGATABLE = 16; private string $name; - private string|array|null $shortcut; + private ?string $shortcut; private int $mode; private string|int|bool|array|float|null $default; /** * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts - * @param int|null $mode The option mode: One of the VALUE_* constants + * @param int-mask-of|null $mode The option mode: One of the VALUE_* constants * @param string|bool|int|float|array|null $default The default value (must be null for self::VALUE_NONE) * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion * @@ -175,11 +175,19 @@ public function isArray(): bool return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); } + /** + * Returns true if the option allows passing a negated variant. + * + * @return bool true if mode is self::VALUE_NEGATABLE, false otherwise + */ public function isNegatable(): bool { return self::VALUE_NEGATABLE === (self::VALUE_NEGATABLE & $this->mode); } + /** + * Sets the default value. + */ public function setDefault(string|bool|int|float|array|null $default): void { if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) { @@ -213,13 +221,16 @@ public function getDescription(): string return $this->description; } + /** + * Returns true if the option has values for input completion. + */ public function hasCompletion(): bool { return [] !== $this->suggestedValues; } /** - * Adds suggestions to $suggestions for the current completion input. + * Supplies suggestions when command resolves possible completion options for input. * * @see Command::complete() */ From 82efc31d66f28eebfc21d23736fdadf4cb258f74 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sat, 3 Feb 2024 17:11:47 +0100 Subject: [PATCH 11/19] re-add accidentally removed property --- Input/InputArgument.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Input/InputArgument.php b/Input/InputArgument.php index a049bebe5..a5d949277 100644 --- a/Input/InputArgument.php +++ b/Input/InputArgument.php @@ -41,6 +41,7 @@ class InputArgument public const IS_ARRAY = 4; private int $mode; + private string|int|bool|array|float|null $default; /** * @param string $name The argument name From 63dbbae04e9d89a47183421422380330aebfd7d5 Mon Sep 17 00:00:00 2001 From: Adriaan Zonnenberg Date: Sat, 17 Feb 2024 14:49:48 +0100 Subject: [PATCH 12/19] [Console] Add descriptions to Fish completion output --- Completion/Output/FishCompletionOutput.php | 9 ++++++--- Resources/completion.fish | 6 +----- Tests/Completion/Output/FishCompletionOutputTest.php | 4 ++-- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Completion/Output/FishCompletionOutput.php b/Completion/Output/FishCompletionOutput.php index d2c414e48..356a974ea 100644 --- a/Completion/Output/FishCompletionOutput.php +++ b/Completion/Output/FishCompletionOutput.php @@ -21,11 +21,14 @@ class FishCompletionOutput implements CompletionOutputInterface { public function write(CompletionSuggestions $suggestions, OutputInterface $output): void { - $values = $suggestions->getValueSuggestions(); + $values = []; + foreach ($suggestions->getValueSuggestions() as $value) { + $values[] = $value->getValue().($value->getDescription() ? "\t".$value->getDescription() : ''); + } foreach ($suggestions->getOptionSuggestions() as $option) { - $values[] = '--'.$option->getName(); + $values[] = '--'.$option->getName().($option->getDescription() ? "\t".$option->getDescription() : ''); if ($option->isNegatable()) { - $values[] = '--no-'.$option->getName(); + $values[] = '--no-'.$option->getName().($option->getDescription() ? "\t".$option->getDescription() : ''); } } $output->write(implode("\n", $values)); diff --git a/Resources/completion.fish b/Resources/completion.fish index 1c34292ae..1853dd80f 100644 --- a/Resources/completion.fish +++ b/Resources/completion.fish @@ -19,11 +19,7 @@ function _sf_{{ COMMAND_NAME }} set completecmd $completecmd "-c$c" - set sfcomplete ($completecmd) - - for i in $sfcomplete - echo $i - end + $completecmd end complete -c '{{ COMMAND_NAME }}' -a '(_sf_{{ COMMAND_NAME }})' -f diff --git a/Tests/Completion/Output/FishCompletionOutputTest.php b/Tests/Completion/Output/FishCompletionOutputTest.php index 2e615d040..93456e138 100644 --- a/Tests/Completion/Output/FishCompletionOutputTest.php +++ b/Tests/Completion/Output/FishCompletionOutputTest.php @@ -23,11 +23,11 @@ public function getCompletionOutput(): CompletionOutputInterface public function getExpectedOptionsOutput(): string { - return "--option1\n--negatable\n--no-negatable"; + return "--option1\tFirst Option\n--negatable\tCan be negative\n--no-negatable\tCan be negative"; } public function getExpectedValuesOutput(): string { - return "Green\nRed\nYellow"; + return "Green\tBeans are green\nRed\tRose are red\nYellow\tCanaries are yellow"; } } From 31c74a59d8bffdcec73adb43b398f6942f7dc26b Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Mon, 4 Mar 2024 13:46:40 +0100 Subject: [PATCH 13/19] [Console] Document argv arrays for static analysis --- Input/ArgvInput.php | 3 +++ Input/StringInput.php | 2 ++ 2 files changed, 5 insertions(+) diff --git a/Input/ArgvInput.php b/Input/ArgvInput.php index 8621d62cf..6c95abc65 100644 --- a/Input/ArgvInput.php +++ b/Input/ArgvInput.php @@ -40,9 +40,11 @@ */ class ArgvInput extends Input { + /** @var list */ private array $tokens; private array $parsed; + /** @param list|null $argv */ public function __construct(?array $argv = null, ?InputDefinition $definition = null) { $argv ??= $_SERVER['argv'] ?? []; @@ -55,6 +57,7 @@ public function __construct(?array $argv = null, ?InputDefinition $definition = parent::__construct($definition); } + /** @param list $tokens */ protected function setTokens(array $tokens): void { $this->tokens = $tokens; diff --git a/Input/StringInput.php b/Input/StringInput.php index 33f0f4b39..835700143 100644 --- a/Input/StringInput.php +++ b/Input/StringInput.php @@ -40,6 +40,8 @@ public function __construct(string $input) /** * Tokenizes a string. * + * @return list + * * @throws InvalidArgumentException When unable to parse input (should never happen) */ private function tokenize(string $input): array From b66e3b8ac723816be3f9b9ba1e00cd8e19de417f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Mon, 11 Mar 2024 15:21:04 +0100 Subject: [PATCH 14/19] [Console] Add `ArgvInput::getRawTokens()` --- CHANGELOG.md | 5 +++++ Input/ArgvInput.php | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ff71d2fa..25d7f7179 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.1 +--- + + * Add `ArgvInput::getRawTokens()` + 7.0 --- diff --git a/Input/ArgvInput.php b/Input/ArgvInput.php index 6c95abc65..001bef093 100644 --- a/Input/ArgvInput.php +++ b/Input/ArgvInput.php @@ -345,6 +345,16 @@ public function getParameterOption(string|array $values, string|bool|int|float|a return $default; } + /** + * Returns un-parsed and not validated tokens. + * + * @return list + */ + public function getRawTokens(): array + { + return $this->tokens; + } + /** * Returns a stringified representation of the args passed to the command. */ From 117ef1f1c1898fec311571b4be90cb4046e344f1 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 2 Mar 2024 11:42:07 +0100 Subject: [PATCH 15/19] [Console] Add a way to use custom lock factory in lockableTrait --- Command/LockableTrait.php | 16 ++++++++----- Tests/Command/LockableTraitTest.php | 16 +++++++++++++ Tests/Fixtures/FooLock3Command.php | 35 +++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 Tests/Fixtures/FooLock3Command.php diff --git a/Command/LockableTrait.php b/Command/LockableTrait.php index cd7548f02..f0001cc52 100644 --- a/Command/LockableTrait.php +++ b/Command/LockableTrait.php @@ -26,6 +26,8 @@ trait LockableTrait { private ?LockInterface $lock = null; + private ?LockFactory $lockFactory = null; + /** * Locks a command. */ @@ -39,13 +41,17 @@ private function lock(?string $name = null, bool $blocking = false): bool throw new LogicException('A lock is already in place.'); } - if (SemaphoreStore::isSupported()) { - $store = new SemaphoreStore(); - } else { - $store = new FlockStore(); + if (null === $this->lockFactory) { + if (SemaphoreStore::isSupported()) { + $store = new SemaphoreStore(); + } else { + $store = new FlockStore(); + } + + $this->lockFactory = (new LockFactory($store)); } - $this->lock = (new LockFactory($store))->createLock($name ?: $this->getName()); + $this->lock = $this->lockFactory->createLock($name ?: $this->getName()); if (!$this->lock->acquire($blocking)) { $this->lock = null; diff --git a/Tests/Command/LockableTraitTest.php b/Tests/Command/LockableTraitTest.php index 77b54f9ee..0268d9681 100644 --- a/Tests/Command/LockableTraitTest.php +++ b/Tests/Command/LockableTraitTest.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Console\Tester\CommandTester; use Symfony\Component\Lock\LockFactory; +use Symfony\Component\Lock\SharedLockInterface; use Symfony\Component\Lock\Store\FlockStore; use Symfony\Component\Lock\Store\SemaphoreStore; @@ -26,6 +27,7 @@ public static function setUpBeforeClass(): void self::$fixturesPath = __DIR__.'/../Fixtures/'; require_once self::$fixturesPath.'/FooLockCommand.php'; require_once self::$fixturesPath.'/FooLock2Command.php'; + require_once self::$fixturesPath.'/FooLock3Command.php'; } public function testLockIsReleased() @@ -64,4 +66,18 @@ public function testMultipleLockCallsThrowLogicException() $tester = new CommandTester($command); $this->assertSame(1, $tester->execute([])); } + + public function testCustomLockFactoryIsUsed() + { + $lockFactory = $this->createMock(LockFactory::class); + $command = new \FooLock3Command($lockFactory); + + $tester = new CommandTester($command); + + $lock = $this->createMock(SharedLockInterface::class); + $lock->method('acquire')->willReturn(false); + + $lockFactory->expects(static::once())->method('createLock')->willReturn($lock); + $this->assertSame(1, $tester->execute([])); + } } diff --git a/Tests/Fixtures/FooLock3Command.php b/Tests/Fixtures/FooLock3Command.php new file mode 100644 index 000000000..78492de69 --- /dev/null +++ b/Tests/Fixtures/FooLock3Command.php @@ -0,0 +1,35 @@ +lockFactory = $lockFactory; + } + + protected function configure(): void + { + $this->setName('foo:lock3'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + if (!$this->lock()) { + return 1; + } + + $this->release(); + + return 2; + } +} From 07520ebce9e125cb845805626bced597d585faa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Wed, 20 Mar 2024 11:51:16 +0100 Subject: [PATCH 16/19] [Console] Allow to returns all tokens after the command name --- Input/ArgvInput.php | 23 +++++++++++++++++++++-- Tests/Input/ArgvInputTest.php | 27 +++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/Input/ArgvInput.php b/Input/ArgvInput.php index 001bef093..95703ba5f 100644 --- a/Input/ArgvInput.php +++ b/Input/ArgvInput.php @@ -348,11 +348,30 @@ public function getParameterOption(string|array $values, string|bool|int|float|a /** * Returns un-parsed and not validated tokens. * + * @param bool $strip Whether to return the raw parameters (false) or the values after the command name (true) + * * @return list */ - public function getRawTokens(): array + public function getRawTokens(bool $strip = false): array { - return $this->tokens; + if (!$strip) { + return $this->tokens; + } + + $parameters = []; + $keep = false; + foreach ($this->tokens as $value) { + if (!$keep && $value === $this->getFirstArgument()) { + $keep = true; + + continue; + } + if ($keep) { + $parameters[] = $value; + } + } + + return $parameters; } /** diff --git a/Tests/Input/ArgvInputTest.php b/Tests/Input/ArgvInputTest.php index a47d557b7..80ea06d67 100644 --- a/Tests/Input/ArgvInputTest.php +++ b/Tests/Input/ArgvInputTest.php @@ -562,4 +562,31 @@ public function testParseOptionWithValueOptionalGivenEmptyAndOptionalArgument() $this->assertEquals(['foo' => '0'], $input->getOptions(), '->parse() parses optional options with empty value as null'); $this->assertEquals(['name' => 'bar'], $input->getArguments(), '->parse() parses optional arguments'); } + + public function testGetRawTokensFalse() + { + $input = new ArgvInput(['cli.php', '--foo', 'bar']); + $this->assertSame(['--foo', 'bar'], $input->getRawTokens()); + } + + /** + * @dataProvider provideGetRawTokensTrueTests + */ + public function testGetRawTokensTrue(array $argv, array $expected) + { + $input = new ArgvInput($argv); + $this->assertSame($expected, $input->getRawTokens(true)); + } + + public static function provideGetRawTokensTrueTests(): iterable + { + yield [['app/console', 'foo:bar'], []]; + yield [['app/console', 'foo:bar', '--env=prod'], ['--env=prod']]; + yield [['app/console', 'foo:bar', '--env', 'prod'], ['--env', 'prod']]; + yield [['app/console', '--no-ansi', 'foo:bar', '--env', 'prod'], ['--env', 'prod']]; + yield [['app/console', '--no-ansi', 'foo:bar', '--env', 'prod'], ['--env', 'prod']]; + yield [['app/console', '--no-ansi', 'foo:bar', 'argument'], ['argument']]; + yield [['app/console', '--no-ansi', 'foo:bar', 'foo:bar'], ['foo:bar']]; + yield [['app/console', '--no-ansi', 'foo:bar', '--', 'argument'], ['--', 'argument']]; + } } From e5b4db4db27704fc0e153eacbe001feddeab0480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Ostroluck=C3=BD?= Date: Sun, 31 Mar 2024 15:15:18 +0200 Subject: [PATCH 17/19] Remove unnecessary empty usages --- Application.php | 6 +++--- Descriptor/XmlDescriptor.php | 2 +- Helper/Table.php | 2 +- Input/InputOption.php | 2 +- Output/ConsoleSectionOutput.php | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Application.php b/Application.php index 9c301471c..3f7ef0419 100644 --- a/Application.php +++ b/Application.php @@ -620,7 +620,7 @@ public function findNamespace(string $namespace): string $expr = implode('[^:]*:', array_map('preg_quote', explode(':', $namespace))).'[^:]*'; $namespaces = preg_grep('{^'.$expr.'}', $allNamespaces); - if (empty($namespaces)) { + if (!$namespaces) { $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace); if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) { @@ -674,12 +674,12 @@ public function find(string $name): Command $expr = implode('[^:]*:', array_map('preg_quote', explode(':', $name))).'[^:]*'; $commands = preg_grep('{^'.$expr.'}', $allCommands); - if (empty($commands)) { + if (!$commands) { $commands = preg_grep('{^'.$expr.'}i', $allCommands); } // if no commands matched or we just matched namespaces - if (empty($commands) || \count(preg_grep('{^'.$expr.'$}i', $commands)) < 1) { + if (!$commands || \count(preg_grep('{^'.$expr.'$}i', $commands)) < 1) { if (false !== $pos = strrpos($name, ':')) { // check if a namespace exists and contains commands $this->findNamespace(substr($name, 0, $pos)); diff --git a/Descriptor/XmlDescriptor.php b/Descriptor/XmlDescriptor.php index 866c71856..8e44c88c4 100644 --- a/Descriptor/XmlDescriptor.php +++ b/Descriptor/XmlDescriptor.php @@ -208,7 +208,7 @@ private function getInputOptionDocument(InputOption $option): \DOMDocument $defaults = \is_array($option->getDefault()) ? $option->getDefault() : (\is_bool($option->getDefault()) ? [var_export($option->getDefault(), true)] : ($option->getDefault() ? [$option->getDefault()] : [])); $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); - if (!empty($defaults)) { + if ($defaults) { foreach ($defaults as $default) { $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); $defaultXML->appendChild($dom->createTextNode($default)); diff --git a/Helper/Table.php b/Helper/Table.php index c1b7dba59..afdb67c49 100644 --- a/Helper/Table.php +++ b/Helper/Table.php @@ -726,7 +726,7 @@ private function fillNextRows(array $rows, int $line): array } else { $row = $this->copyRow($rows, $unmergedRowKey - 1); foreach ($unmergedRow as $column => $cell) { - if (!empty($cell)) { + if ($cell) { $row[$column] = $cell; } } diff --git a/Input/InputOption.php b/Input/InputOption.php index 50851c44c..617c348d5 100644 --- a/Input/InputOption.php +++ b/Input/InputOption.php @@ -75,7 +75,7 @@ public function __construct( $name = substr($name, 2); } - if (empty($name)) { + if (!$name) { throw new InvalidArgumentException('An option name cannot be empty.'); } diff --git a/Output/ConsoleSectionOutput.php b/Output/ConsoleSectionOutput.php index ded97c70e..09aa7fe99 100644 --- a/Output/ConsoleSectionOutput.php +++ b/Output/ConsoleSectionOutput.php @@ -63,7 +63,7 @@ public function setMaxHeight(int $maxHeight): void */ public function clear(?int $lines = null): void { - if (empty($this->content) || !$this->isDecorated()) { + if (!$this->content || !$this->isDecorated()) { return; } From 1b84910b638ce3265e132d7b42405b2db64f6f69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Ostroluck=C3=BD?= Date: Sat, 6 Apr 2024 19:21:51 +0200 Subject: [PATCH 18/19] [Console] Handle SIGQUIT signal As both PHP-FPM and NGINX use SIGQUIT for graceful shutdown, I believe it's necessary PHP applications should also support it. It's especially important in cases where you run PHP application in container running php/nginx image, since they override shutdown signal that docker uses to SIGQUIT, see https://github.com/nginxinc/docker-nginx/commit/3fb70ddd7094c1fdd50cc83d432643dc10ab6243 --- Application.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Application.php b/Application.php index 3f7ef0419..87eb7a6c2 100644 --- a/Application.php +++ b/Application.php @@ -97,7 +97,7 @@ public function __construct( $this->defaultCommand = 'list'; if (\defined('SIGINT') && SignalRegistry::isSupported()) { $this->signalRegistry = new SignalRegistry(); - $this->signalsToDispatchEvent = [\SIGINT, \SIGTERM, \SIGUSR1, \SIGUSR2]; + $this->signalsToDispatchEvent = [\SIGINT, \SIGQUIT, \SIGTERM, \SIGUSR1, \SIGUSR2]; } } @@ -984,7 +984,7 @@ protected function doRunCommand(Command $command, InputInterface $input, OutputI if (Terminal::hasSttyAvailable()) { $sttyMode = shell_exec('stty -g'); - foreach ([\SIGINT, \SIGTERM] as $signal) { + foreach ([\SIGINT, \SIGQUIT, \SIGTERM] as $signal) { $this->signalRegistry->register($signal, static fn () => shell_exec('stty '.$sttyMode)); } } From 965f054b640b28a4cfaa77cc69b53506393e3a4a Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 2 May 2024 13:32:55 +0200 Subject: [PATCH 19/19] Cleanup past sponsors --- README.md | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index ded2bc116..92f70e714 100644 --- a/README.md +++ b/README.md @@ -7,14 +7,7 @@ interfaces. Sponsor ------- -The Console component for Symfony 7.0 is [backed][1] by [Les-Tilleuls.coop][2]. - -Les-Tilleuls.coop is a team of 70+ Symfony experts who can help you design, develop and -fix your projects. They provide a wide range of professional services including development, -consulting, coaching, training and audits. They also are highly skilled in JS, Go and DevOps. -They are a worker cooperative! - -Help Symfony by [sponsoring][3] its development! +Help Symfony by [sponsoring][1] its development! Resources --------- @@ -31,6 +24,4 @@ Credits `Resources/bin/hiddeninput.exe` is a third party binary provided within this component. Find sources and license at https://github.com/Seldaek/hidden-input. -[1]: https://symfony.com/backers -[2]: https://les-tilleuls.coop -[3]: https://symfony.com/sponsor +[1]: https://symfony.com/sponsor