package and depencies

This commit is contained in:
RafficMohammed
2023-01-08 02:57:24 +05:30
parent d5332eb421
commit 1d54b8bc7f
4309 changed files with 193331 additions and 172289 deletions

View File

@@ -21,6 +21,7 @@ use Symfony\Component\Console\Command\SignalableCommandInterface;
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Completion\Suggestion;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleSignalEvent;
@@ -32,6 +33,7 @@ use Symfony\Component\Console\Exception\NamespaceNotFoundException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\DebugFormatterHelper;
use Symfony\Component\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Helper\HelperSet;
@@ -70,23 +72,23 @@ use Symfony\Contracts\Service\ResetInterface;
*/
class Application implements ResetInterface
{
private $commands = [];
private $wantHelps = false;
private $runningCommand;
private $name;
private $version;
private $commandLoader;
private $catchExceptions = true;
private $autoExit = true;
private $definition;
private $helperSet;
private $dispatcher;
private $terminal;
private $defaultCommand;
private $singleCommand = false;
private $initialized;
private $signalRegistry;
private $signalsToDispatchEvent = [];
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 $autoExit = true;
private InputDefinition $definition;
private HelperSet $helperSet;
private ?EventDispatcherInterface $dispatcher = null;
private Terminal $terminal;
private string $defaultCommand;
private bool $singleCommand = false;
private bool $initialized = false;
private SignalRegistry $signalRegistry;
private array $signalsToDispatchEvent = [];
public function __construct(string $name = 'UNKNOWN', string $version = 'UNKNOWN')
{
@@ -134,20 +136,15 @@ class Application implements ResetInterface
*
* @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}.
*/
public function run(InputInterface $input = null, OutputInterface $output = null)
public function run(InputInterface $input = null, OutputInterface $output = null): int
{
if (\function_exists('putenv')) {
@putenv('LINES='.$this->terminal->getHeight());
@putenv('COLUMNS='.$this->terminal->getWidth());
}
if (null === $input) {
$input = new ArgvInput();
}
if (null === $output) {
$output = new ConsoleOutput();
}
$input ??= new ArgvInput();
$output ??= new ConsoleOutput();
$renderException = function (\Throwable $e) use ($output) {
if ($output instanceof ConsoleOutputInterface) {
@@ -228,7 +225,7 @@ class Application implements ResetInterface
try {
// Makes ArgvInput::getFirstArgument() able to distinguish an option from an argument.
$input->bind($this->getDefinition());
} catch (ExceptionInterface $e) {
} catch (ExceptionInterface) {
// Errors must be ignored, full binding/validation happens later when the command is known.
}
@@ -258,7 +255,26 @@ class Application implements ResetInterface
// the command name MUST be the first element of the input
$command = $this->find($name);
} catch (\Throwable $e) {
if (!($e instanceof CommandNotFoundException && !$e instanceof NamespaceNotFoundException) || 1 !== \count($alternatives = $e->getAlternatives()) || !$input->isInteractive()) {
if (($e instanceof CommandNotFoundException && !$e instanceof NamespaceNotFoundException) && 1 === \count($alternatives = $e->getAlternatives()) && $input->isInteractive()) {
$alternative = $alternatives[0];
$style = new SymfonyStyle($input, $output);
$output->writeln('');
$formattedBlock = (new FormatterHelper())->formatBlock(sprintf('Command "%s" is not defined.', $name), 'error', true);
$output->writeln($formattedBlock);
if (!$style->confirm(sprintf('Do you want to run "%s" instead? ', $alternative), false)) {
if (null !== $this->dispatcher) {
$event = new ConsoleErrorEvent($input, $output, $e);
$this->dispatcher->dispatch($event, ConsoleEvents::ERROR);
return $event->getExitCode();
}
return 1;
}
$command = $this->find($alternative);
} else {
if (null !== $this->dispatcher) {
$event = new ConsoleErrorEvent($input, $output, $e);
$this->dispatcher->dispatch($event, ConsoleEvents::ERROR);
@@ -270,27 +286,24 @@ class Application implements ResetInterface
$e = $event->getError();
}
throw $e;
}
try {
if ($e instanceof CommandNotFoundException && $namespace = $this->findNamespace($name)) {
$helper = new DescriptorHelper();
$helper->describe($output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output, $this, [
'format' => 'txt',
'raw_text' => false,
'namespace' => $namespace,
'short' => false,
]);
$alternative = $alternatives[0];
return isset($event) ? $event->getExitCode() : 1;
}
$style = new SymfonyStyle($input, $output);
$output->writeln('');
$formattedBlock = (new FormatterHelper())->formatBlock(sprintf('Command "%s" is not defined.', $name), 'error', true);
$output->writeln($formattedBlock);
if (!$style->confirm(sprintf('Do you want to run "%s" instead? ', $alternative), false)) {
if (null !== $this->dispatcher) {
$event = new ConsoleErrorEvent($input, $output, $e);
$this->dispatcher->dispatch($event, ConsoleEvents::ERROR);
return $event->getExitCode();
throw $e;
} catch (NamespaceNotFoundException) {
throw $e;
}
return 1;
}
$command = $this->find($alternative);
}
if ($command instanceof LazyCommand) {
@@ -304,9 +317,6 @@ class Application implements ResetInterface
return $exitCode;
}
/**
* {@inheritdoc}
*/
public function reset()
{
}
@@ -318,16 +328,10 @@ class Application implements ResetInterface
/**
* Get the helper set associated with the command.
*
* @return HelperSet
*/
public function getHelperSet()
public function getHelperSet(): HelperSet
{
if (!$this->helperSet) {
$this->helperSet = $this->getDefaultHelperSet();
}
return $this->helperSet;
return $this->helperSet ??= $this->getDefaultHelperSet();
}
public function setDefinition(InputDefinition $definition)
@@ -337,14 +341,10 @@ class Application implements ResetInterface
/**
* Gets the InputDefinition related to this Application.
*
* @return InputDefinition
*/
public function getDefinition()
public function getDefinition(): InputDefinition
{
if (!$this->definition) {
$this->definition = $this->getDefaultInputDefinition();
}
$this->definition ??= $this->getDefaultInputDefinition();
if ($this->singleCommand) {
$inputDefinition = $this->definition;
@@ -365,18 +365,16 @@ class Application implements ResetInterface
CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType()
&& 'command' === $input->getCompletionName()
) {
$commandNames = [];
foreach ($this->all() as $name => $command) {
// skip hidden commands and aliased commands as they already get added below
if ($command->isHidden() || $command->getName() !== $name) {
continue;
}
$commandNames[] = $command->getName();
$suggestions->suggestValue(new Suggestion($command->getName(), $command->getDescription()));
foreach ($command->getAliases() as $name) {
$commandNames[] = $name;
$suggestions->suggestValue(new Suggestion($name, $command->getDescription()));
}
}
$suggestions->suggestValues(array_filter($commandNames));
return;
}
@@ -390,20 +388,16 @@ class Application implements ResetInterface
/**
* Gets the help message.
*
* @return string
*/
public function getHelp()
public function getHelp(): string
{
return $this->getLongVersion();
}
/**
* Gets whether to catch exceptions or not during commands execution.
*
* @return bool
*/
public function areExceptionsCaught()
public function areExceptionsCaught(): bool
{
return $this->catchExceptions;
}
@@ -418,10 +412,8 @@ class Application implements ResetInterface
/**
* Gets whether to automatically exit after a command execution or not.
*
* @return bool
*/
public function isAutoExitEnabled()
public function isAutoExitEnabled(): bool
{
return $this->autoExit;
}
@@ -436,10 +428,8 @@ class Application implements ResetInterface
/**
* Gets the name of the application.
*
* @return string
*/
public function getName()
public function getName(): string
{
return $this->name;
}
@@ -454,10 +444,8 @@ class Application implements ResetInterface
/**
* Gets the application version.
*
* @return string
*/
public function getVersion()
public function getVersion(): string
{
return $this->version;
}
@@ -490,10 +478,8 @@ class Application implements ResetInterface
/**
* Registers a new command.
*
* @return Command
*/
public function register(string $name)
public function register(string $name): Command
{
return $this->add(new Command($name));
}
@@ -586,14 +572,12 @@ class Application implements ResetInterface
/**
* Returns true if the command exists, false otherwise.
*
* @return bool
*/
public function has(string $name)
public function has(string $name): bool
{
$this->init();
return isset($this->commands[$name]) || ($this->commandLoader && $this->commandLoader->has($name) && $this->add($this->commandLoader->get($name)));
return isset($this->commands[$name]) || ($this->commandLoader?->has($name) && $this->add($this->commandLoader->get($name)));
}
/**
@@ -603,7 +587,7 @@ class Application implements ResetInterface
*
* @return string[]
*/
public function getNamespaces()
public function getNamespaces(): array
{
$namespaces = [];
foreach ($this->all() as $command) {
@@ -624,11 +608,9 @@ class Application implements ResetInterface
/**
* Finds a registered namespace by a name or an abbreviation.
*
* @return string
*
* @throws NamespaceNotFoundException When namespace is incorrect or ambiguous
*/
public function findNamespace(string $namespace)
public function findNamespace(string $namespace): string
{
$allNamespaces = $this->getNamespaces();
$expr = implode('[^:]*:', array_map('preg_quote', explode(':', $namespace))).'[^:]*';
@@ -820,7 +802,7 @@ class Application implements ResetInterface
*
* @return string[][]
*/
public static function getAbbreviations(array $names)
public static function getAbbreviations(array $names): array
{
$abbrevs = [];
foreach ($names as $name) {
@@ -1044,7 +1026,7 @@ class Application implements ResetInterface
try {
$command->mergeApplicationDefinition();
$input->bind($command->getDefinition());
} catch (ExceptionInterface $e) {
} catch (ExceptionInterface) {
// ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition
}
@@ -1081,20 +1063,16 @@ class Application implements ResetInterface
/**
* Gets the name of the command based on input.
*
* @return string|null
*/
protected function getCommandName(InputInterface $input)
protected function getCommandName(InputInterface $input): ?string
{
return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument();
}
/**
* Gets the default input definition.
*
* @return InputDefinition
*/
protected function getDefaultInputDefinition()
protected function getDefaultInputDefinition(): InputDefinition
{
return new InputDefinition([
new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
@@ -1112,17 +1090,15 @@ class Application implements ResetInterface
*
* @return Command[]
*/
protected function getDefaultCommands()
protected function getDefaultCommands(): array
{
return [new HelpCommand(), new ListCommand(), new CompleteCommand(), new DumpCompletionCommand()];
}
/**
* Gets the default helper set with the helpers that should always be available.
*
* @return HelperSet
*/
protected function getDefaultHelperSet()
protected function getDefaultHelperSet(): HelperSet
{
return new HelperSet([
new FormatterHelper(),
@@ -1144,10 +1120,8 @@ class Application implements ResetInterface
* Returns the namespace part of the command name.
*
* This method is not part of public API and should not be used directly.
*
* @return string
*/
public function extractNamespace(string $name, int $limit = null)
public function extractNamespace(string $name, int $limit = null): string
{
$parts = explode(':', $name, -1);
@@ -1207,7 +1181,7 @@ class Application implements ResetInterface
*
* @return $this
*/
public function setDefaultCommand(string $commandName, bool $isSingleCommand = false)
public function setDefaultCommand(string $commandName, bool $isSingleCommand = false): static
{
$this->defaultCommand = explode('|', ltrim($commandName, '|'))[0];

View File

@@ -1,6 +1,34 @@
CHANGELOG
=========
6.2
---
* Improve truecolor terminal detection in some cases
* Add support for 256 color terminals (conversion from Ansi24 to Ansi8 if terminal is capable of it)
* Deprecate calling `*Command::setApplication()`, `*FormatterStyle::setForeground/setBackground()`, `Helper::setHelpSet()`, `Input*::setDefault()`, `Question::setAutocompleterCallback/setValidator()`without any arguments
* Change the signature of `OutputFormatterStyleInterface::setForeground/setBackground()` to `setForeground/setBackground(?string)`
* Change the signature of `HelperInterface::setHelperSet()` to `setHelperSet(?HelperSet)`
6.1
---
* Add support to display table vertically when calling setVertical()
* Add method `__toString()` to `InputInterface`
* Added `OutputWrapper` to prevent truncated URL in `SymfonyStyle::createBlock`.
* Deprecate `Command::$defaultName` and `Command::$defaultDescription`, use the `AsCommand` attribute instead
* Add suggested values for arguments and options in input definition, for input completion
* Add `$resumeAt` parameter to `ProgressBar#start()`, so that one can easily 'resume' progress on longer tasks, and still get accurate `getEstimate()` and `getRemaining()` results.
6.0
---
* `Command::setHidden()` has a default value (`true`) for `$hidden` parameter and is final
* Remove `Helper::strlen()`, use `Helper::width()` instead
* Remove `Helper::strlenWithoutDecoration()`, use `Helper::removeDecoration()` instead
* `AddConsoleCommandPass` can not be configured anymore
* Remove `HelperSet::setCommand()` and `getCommand()` without replacement
5.4
---

View File

@@ -20,7 +20,7 @@ use Symfony\Component\Console\Output\OutputInterface;
*/
class GithubActionReporter
{
private $output;
private OutputInterface $output;
/**
* @see https://github.com/actions/toolkit/blob/5e5e1b7aacba68a53836a34db4a288c3c1c1585b/packages/core/src/command.ts#L80-L85

View File

@@ -49,9 +49,9 @@ final class Color
'conceal' => ['set' => 8, 'unset' => 28],
];
private $foreground;
private $background;
private $options = [];
private string $foreground;
private string $background;
private array $options = [];
public function __construct(string $foreground = '', string $background = '', array $options = [])
{
@@ -117,17 +117,7 @@ final class Color
}
if ('#' === $color[0]) {
$color = substr($color, 1);
if (3 === \strlen($color)) {
$color = $color[0].$color[0].$color[1].$color[1].$color[2].$color[2];
}
if (6 !== \strlen($color)) {
throw new InvalidArgumentException(sprintf('Invalid "%s" color.', $color));
}
return ($background ? '4' : '3').$this->convertHexColorToAnsi(hexdec($color));
return ($background ? '4' : '3').Terminal::getColorMode()->convertFromHexToAnsiColorCode($color);
}
if (isset(self::COLORS[$color])) {
@@ -140,41 +130,4 @@ final class Color
throw new InvalidArgumentException(sprintf('Invalid "%s" color; expected one of (%s).', $color, implode(', ', array_merge(array_keys(self::COLORS), array_keys(self::BRIGHT_COLORS)))));
}
private function convertHexColorToAnsi(int $color): string
{
$r = ($color >> 16) & 255;
$g = ($color >> 8) & 255;
$b = $color & 255;
// see https://github.com/termstandard/colors/ for more information about true color support
if ('truecolor' !== getenv('COLORTERM')) {
return (string) $this->degradeHexColorToAnsi($r, $g, $b);
}
return sprintf('8;2;%d;%d;%d', $r, $g, $b);
}
private function degradeHexColorToAnsi(int $r, int $g, int $b): int
{
if (0 === round($this->getSaturation($r, $g, $b) / 50)) {
return 0;
}
return (round($b / 255) << 2) | (round($g / 255) << 1) | round($r / 255);
}
private function getSaturation(int $r, int $g, int $b): int
{
$r = $r / 255;
$g = $g / 255;
$b = $b / 255;
$v = max($r, $g, $b);
if (0 === $diff = $v - min($r, $g, $b)) {
return 0;
}
return (int) $diff * 100 / $v;
}
}

View File

@@ -15,9 +15,11 @@ use Symfony\Component\Console\Application;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Completion\Suggestion;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Helper\HelperInterface;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
@@ -39,56 +41,69 @@ class Command
/**
* @var string|null The default command name
*
* @deprecated since Symfony 6.1, use the AsCommand attribute instead
*/
protected static $defaultName;
/**
* @var string|null The default command description
*
* @deprecated since Symfony 6.1, use the AsCommand attribute instead
*/
protected static $defaultDescription;
private $application;
private $name;
private $processTitle;
private $aliases = [];
private $definition;
private $hidden = false;
private $help = '';
private $description = '';
private $fullDefinition;
private $ignoreValidationErrors = false;
private $code;
private $synopsis = [];
private $usages = [];
private $helperSet;
private ?Application $application = null;
private ?string $name = null;
private ?string $processTitle = null;
private array $aliases = [];
private InputDefinition $definition;
private bool $hidden = false;
private string $help = '';
private string $description = '';
private ?InputDefinition $fullDefinition = null;
private bool $ignoreValidationErrors = false;
private ?\Closure $code = null;
private array $synopsis = [];
private array $usages = [];
private ?HelperSet $helperSet = null;
/**
* @return string|null
*/
public static function getDefaultName()
public static function getDefaultName(): ?string
{
$class = static::class;
if (\PHP_VERSION_ID >= 80000 && $attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class)) {
if ($attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class)) {
return $attribute[0]->newInstance()->name;
}
$r = new \ReflectionProperty($class, 'defaultName');
return $class === $r->class ? static::$defaultName : null;
if ($class !== $r->class || null === static::$defaultName) {
return null;
}
trigger_deprecation('symfony/console', '6.1', 'Relying on the static property "$defaultName" for setting a command name is deprecated. Add the "%s" attribute to the "%s" class instead.', AsCommand::class, static::class);
return static::$defaultName;
}
public static function getDefaultDescription(): ?string
{
$class = static::class;
if (\PHP_VERSION_ID >= 80000 && $attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class)) {
if ($attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class)) {
return $attribute[0]->newInstance()->description;
}
$r = new \ReflectionProperty($class, 'defaultDescription');
return $class === $r->class ? static::$defaultDescription : null;
if ($class !== $r->class || null === static::$defaultDescription) {
return null;
}
trigger_deprecation('symfony/console', '6.1', 'Relying on the static property "$defaultDescription" for setting a command description is deprecated. Add the "%s" attribute to the "%s" class instead.', AsCommand::class, static::class);
return static::$defaultDescription;
}
/**
@@ -134,6 +149,9 @@ class Command
public function setApplication(Application $application = null)
{
if (1 > \func_num_args()) {
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
}
$this->application = $application;
if ($application) {
$this->setHelperSet($application->getHelperSet());
@@ -151,20 +169,16 @@ class Command
/**
* Gets the helper set.
*
* @return HelperSet|null
*/
public function getHelperSet()
public function getHelperSet(): ?HelperSet
{
return $this->helperSet;
}
/**
* Gets the application instance for this command.
*
* @return Application|null
*/
public function getApplication()
public function getApplication(): ?Application
{
return $this->application;
}
@@ -247,7 +261,7 @@ class Command
* @see setCode()
* @see execute()
*/
public function run(InputInterface $input, OutputInterface $output)
public function run(InputInterface $input, OutputInterface $output): int
{
// add the application arguments and options
$this->mergeApplicationDefinition();
@@ -310,6 +324,12 @@ class Command
*/
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
$definition = $this->getDefinition();
if (CompletionInput::TYPE_OPTION_VALUE === $input->getCompletionType() && $definition->hasOption($input->getCompletionName())) {
$definition->getOption($input->getCompletionName())->complete($input, $suggestions);
} elseif (CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType() && $definition->hasArgument($input->getCompletionName())) {
$definition->getArgument($input->getCompletionName())->complete($input, $suggestions);
}
}
/**
@@ -326,7 +346,7 @@ class Command
*
* @see execute()
*/
public function setCode(callable $code)
public function setCode(callable $code): static
{
if ($code instanceof \Closure) {
$r = new \ReflectionFunction($code);
@@ -340,6 +360,8 @@ class Command
restore_error_handler();
}
}
} else {
$code = $code(...);
}
$this->code = $code;
@@ -377,11 +399,9 @@ class Command
/**
* Sets an array of argument and option instances.
*
* @param array|InputDefinition $definition An array of argument and option instances or a definition instance
*
* @return $this
*/
public function setDefinition($definition)
public function setDefinition(array|InputDefinition $definition): static
{
if ($definition instanceof InputDefinition) {
$this->definition = $definition;
@@ -396,10 +416,8 @@ class Command
/**
* Gets the InputDefinition attached to this Command.
*
* @return InputDefinition
*/
public function getDefinition()
public function getDefinition(): InputDefinition
{
return $this->fullDefinition ?? $this->getNativeDefinition();
}
@@ -411,34 +429,31 @@ class Command
* be changed by merging with the application InputDefinition.
*
* This method is not part of public API and should not be used directly.
*
* @return InputDefinition
*/
public function getNativeDefinition()
public function getNativeDefinition(): InputDefinition
{
if (null === $this->definition) {
throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', static::class));
}
return $this->definition;
return $this->definition ?? throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', static::class));
}
/**
* Adds an argument.
*
* @param int|null $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL
* @param mixed $default The default value (for InputArgument::OPTIONAL mode only)
*
* @throws InvalidArgumentException When argument mode is not valid
* @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<string|Suggestion> $suggestedValues The values used for input completion
*
* @return $this
*
* @throws InvalidArgumentException When argument mode is not valid
*/
public function addArgument(string $name, int $mode = null, string $description = '', $default = null)
public function addArgument(string $name, int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = null */): static
{
$this->definition->addArgument(new InputArgument($name, $mode, $description, $default));
if (null !== $this->fullDefinition) {
$this->fullDefinition->addArgument(new InputArgument($name, $mode, $description, $default));
$suggestedValues = 5 <= \func_num_args() ? func_get_arg(4) : [];
if (!\is_array($suggestedValues) && !$suggestedValues instanceof \Closure) {
throw new \TypeError(sprintf('Argument 5 passed to "%s()" must be array or \Closure, "%s" given.', __METHOD__, get_debug_type($suggestedValues)));
}
$this->definition->addArgument(new InputArgument($name, $mode, $description, $default, $suggestedValues));
$this->fullDefinition?->addArgument(new InputArgument($name, $mode, $description, $default, $suggestedValues));
return $this;
}
@@ -446,20 +461,23 @@ class Command
/**
* Adds an option.
*
* @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 InputOption::VALUE_* constants
* @param mixed $default The default value (must be null for InputOption::VALUE_NONE)
*
* @throws InvalidArgumentException If option mode is invalid or incompatible
* @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<string|Suggestion> $suggestedValues The values used for input completion
*
* @return $this
*
* @throws InvalidArgumentException If option mode is invalid or incompatible
*/
public function addOption(string $name, $shortcut = null, int $mode = null, string $description = '', $default = null)
public function addOption(string $name, string|array $shortcut = null, int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = [] */): static
{
$this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default));
if (null !== $this->fullDefinition) {
$this->fullDefinition->addOption(new InputOption($name, $shortcut, $mode, $description, $default));
$suggestedValues = 6 <= \func_num_args() ? func_get_arg(5) : [];
if (!\is_array($suggestedValues) && !$suggestedValues instanceof \Closure) {
throw new \TypeError(sprintf('Argument 5 passed to "%s()" must be array or \Closure, "%s" given.', __METHOD__, get_debug_type($suggestedValues)));
}
$this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default, $suggestedValues));
$this->fullDefinition?->addOption(new InputOption($name, $shortcut, $mode, $description, $default, $suggestedValues));
return $this;
}
@@ -476,7 +494,7 @@ class Command
*
* @throws InvalidArgumentException When the name is invalid
*/
public function setName(string $name)
public function setName(string $name): static
{
$this->validateName($name);
@@ -493,7 +511,7 @@ class Command
*
* @return $this
*/
public function setProcessTitle(string $title)
public function setProcessTitle(string $title): static
{
$this->processTitle = $title;
@@ -502,23 +520,18 @@ class Command
/**
* Returns the command name.
*
* @return string|null
*/
public function getName()
public function getName(): ?string
{
return $this->name;
}
/**
* @param bool $hidden Whether or not the command should be hidden from the list of commands
* The default value will be true in Symfony 6.0
*
* @return $this
*
* @final since Symfony 5.1
*/
public function setHidden(bool $hidden /* = true */)
public function setHidden(bool $hidden = true): static
{
$this->hidden = $hidden;
@@ -528,7 +541,7 @@ class Command
/**
* @return bool whether the command should be publicly shown or not
*/
public function isHidden()
public function isHidden(): bool
{
return $this->hidden;
}
@@ -538,7 +551,7 @@ class Command
*
* @return $this
*/
public function setDescription(string $description)
public function setDescription(string $description): static
{
$this->description = $description;
@@ -547,10 +560,8 @@ class Command
/**
* Returns the description for the command.
*
* @return string
*/
public function getDescription()
public function getDescription(): string
{
return $this->description;
}
@@ -560,7 +571,7 @@ class Command
*
* @return $this
*/
public function setHelp(string $help)
public function setHelp(string $help): static
{
$this->help = $help;
@@ -569,10 +580,8 @@ class Command
/**
* Returns the help for the command.
*
* @return string
*/
public function getHelp()
public function getHelp(): string
{
return $this->help;
}
@@ -580,13 +589,11 @@ class Command
/**
* Returns the processed help for the command replacing the %command.name% and
* %command.full_name% patterns with the real values dynamically.
*
* @return string
*/
public function getProcessedHelp()
public function getProcessedHelp(): string
{
$name = $this->name;
$isSingleCommand = $this->application && $this->application->isSingleCommand();
$isSingleCommand = $this->application?->isSingleCommand();
$placeholders = [
'%command.name%',
@@ -609,7 +616,7 @@ class Command
*
* @throws InvalidArgumentException When an alias is invalid
*/
public function setAliases(iterable $aliases)
public function setAliases(iterable $aliases): static
{
$list = [];
@@ -625,10 +632,8 @@ class Command
/**
* Returns the aliases for the command.
*
* @return array
*/
public function getAliases()
public function getAliases(): array
{
return $this->aliases;
}
@@ -637,10 +642,8 @@ class Command
* Returns the synopsis for the command.
*
* @param bool $short Whether to show the short version of the synopsis (with options folded) or not
*
* @return string
*/
public function getSynopsis(bool $short = false)
public function getSynopsis(bool $short = false): string
{
$key = $short ? 'short' : 'long';
@@ -656,7 +659,7 @@ class Command
*
* @return $this
*/
public function addUsage(string $usage)
public function addUsage(string $usage): static
{
if (!str_starts_with($usage, $this->name)) {
$usage = sprintf('%s %s', $this->name, $usage);
@@ -669,10 +672,8 @@ class Command
/**
* Returns alternative usages of the command.
*
* @return array
*/
public function getUsages()
public function getUsages(): array
{
return $this->usages;
}
@@ -680,12 +681,12 @@ class Command
/**
* Gets a helper instance by name.
*
* @return mixed
* @return HelperInterface
*
* @throws LogicException if no HelperSet is defined
* @throws InvalidArgumentException if the helper is not defined
*/
public function getHelper(string $name)
public function getHelper(string $name): mixed
{
if (null === $this->helperSet) {
throw new LogicException(sprintf('Cannot retrieve helper "%s" because there is no HelperSet defined. Did you forget to add your command to the application or to set the application on the command using the setApplication() method? You can also set the HelperSet directly using the setHelperSet() method.', $name));

View File

@@ -11,10 +11,13 @@
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Completion\Output\BashCompletionOutput;
use Symfony\Component\Console\Completion\Output\CompletionOutputInterface;
use Symfony\Component\Console\Completion\Output\FishCompletionOutput;
use Symfony\Component\Console\Completion\Output\ZshCompletionOutput;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Input\InputInterface;
@@ -26,9 +29,19 @@ use Symfony\Component\Console\Output\OutputInterface;
*
* @author Wouter de Jong <wouter@wouterj.nl>
*/
#[AsCommand(name: '|_complete', description: 'Internal command to provide shell completion suggestions')]
final class CompleteCommand extends Command
{
public const COMPLETION_API_VERSION = '1';
/**
* @deprecated since Symfony 6.1
*/
protected static $defaultName = '|_complete';
/**
* @deprecated since Symfony 6.1
*/
protected static $defaultDescription = 'Internal command to provide shell completion suggestions';
private $completionOutputs;
@@ -41,7 +54,11 @@ final class CompleteCommand extends Command
public function __construct(array $completionOutputs = [])
{
// must be set before the parent constructor, as the property value is used in configure()
$this->completionOutputs = $completionOutputs + ['bash' => BashCompletionOutput::class];
$this->completionOutputs = $completionOutputs + [
'bash' => BashCompletionOutput::class,
'fish' => FishCompletionOutput::class,
'zsh' => ZshCompletionOutput::class,
];
parent::__construct();
}
@@ -52,28 +69,29 @@ final class CompleteCommand extends Command
->addOption('shell', 's', InputOption::VALUE_REQUIRED, 'The shell type ("'.implode('", "', array_keys($this->completionOutputs)).'")')
->addOption('input', 'i', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'An array of input tokens (e.g. COMP_WORDS or argv)')
->addOption('current', 'c', InputOption::VALUE_REQUIRED, 'The index of the "input" array that the cursor is in (e.g. COMP_CWORD)')
->addOption('symfony', 'S', InputOption::VALUE_REQUIRED, 'The version of the completion script')
->addOption('api-version', 'a', InputOption::VALUE_REQUIRED, 'The API version of the completion script')
->addOption('symfony', 'S', InputOption::VALUE_REQUIRED, 'deprecated')
;
}
protected function initialize(InputInterface $input, OutputInterface $output)
{
$this->isDebug = filter_var(getenv('SYMFONY_COMPLETION_DEBUG'), \FILTER_VALIDATE_BOOLEAN);
$this->isDebug = filter_var(getenv('SYMFONY_COMPLETION_DEBUG'), \FILTER_VALIDATE_BOOL);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
try {
// uncomment when a bugfix or BC break has been introduced in the shell completion scripts
// $version = $input->getOption('symfony');
// if ($version && version_compare($version, 'x.y', '>=')) {
// $message = sprintf('Completion script version is not supported ("%s" given, ">=x.y" required).', $version);
// $this->log($message);
// "symfony" must be kept for compat with the shell scripts generated by Symfony Console 5.4 - 6.1
$version = $input->getOption('symfony') ? '1' : $input->getOption('api-version');
if ($version && version_compare($version, self::COMPLETION_API_VERSION, '<')) {
$message = sprintf('Completion script version is not supported ("%s" given, ">=%s" required).', $version, self::COMPLETION_API_VERSION);
$this->log($message);
// $output->writeln($message.' Install the Symfony completion script again by using the "completion" command.');
$output->writeln($message.' Install the Symfony completion script again by using the "completion" command.');
// return 126;
// }
return 126;
}
$shell = $input->getOption('shell');
if (!$shell) {
@@ -172,7 +190,7 @@ final class CompleteCommand extends Command
try {
$completionInput->bind($this->getApplication()->getDefinition());
} catch (ExceptionInterface $e) {
} catch (ExceptionInterface) {
}
return $completionInput;
@@ -187,7 +205,7 @@ final class CompleteCommand extends Command
}
return $this->getApplication()->find($inputName);
} catch (CommandNotFoundException $e) {
} catch (CommandNotFoundException) {
}
return null;

View File

@@ -11,8 +11,7 @@
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@@ -25,17 +24,20 @@ use Symfony\Component\Process\Process;
*
* @author Wouter de Jong <wouter@wouterj.nl>
*/
#[AsCommand(name: 'completion', description: 'Dump the shell completion script')]
final class DumpCompletionCommand extends Command
{
/**
* @deprecated since Symfony 6.1
*/
protected static $defaultName = 'completion';
/**
* @deprecated since Symfony 6.1
*/
protected static $defaultDescription = 'Dump the shell completion script';
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestArgumentValuesFor('shell')) {
$suggestions->suggestValues($this->getSupportedShells());
}
}
private array $supportedShells;
protected function configure()
{
@@ -43,37 +45,44 @@ final class DumpCompletionCommand extends Command
$commandName = basename($fullCommand);
$fullCommand = @realpath($fullCommand) ?: $fullCommand;
$shell = $this->guessShell();
[$rcFile, $completionFile] = match ($shell) {
'fish' => ['~/.config/fish/config.fish', "/etc/fish/completions/$commandName.fish"],
'zsh' => ['~/.zshrc', '$fpath[1]/'.$commandName],
default => ['~/.bashrc', "/etc/bash_completion.d/$commandName"],
};
$this
->setHelp(<<<EOH
The <info>%command.name%</> command dumps the shell completion script required
to use shell autocompletion (currently only bash completion is supported).
to use shell autocompletion (currently, bash and fish completion is supported).
<comment>Static installation
-------------------</>
Dump the script to a global completion file and restart your shell:
<info>%command.full_name% bash | sudo tee /etc/bash_completion.d/{$commandName}</>
<info>%command.full_name% {$shell} | sudo tee {$completionFile}</>
Or dump the script to a local file and source it:
<info>%command.full_name% bash > completion.sh</>
<info>%command.full_name% {$shell} > completion.sh</>
<comment># source the file whenever you use the project</>
<info>source completion.sh</>
<comment># or add this line at the end of your "~/.bashrc" file:</>
<comment># or add this line at the end of your "{$rcFile}" file:</>
<info>source /path/to/completion.sh</>
<comment>Dynamic installation
--------------------</>
Add this to the end of your shell configuration file (e.g. <info>"~/.bashrc"</>):
Add this to the end of your shell configuration file (e.g. <info>"{$rcFile}"</>):
<info>eval "$({$fullCommand} completion bash)"</>
<info>eval "$({$fullCommand} completion {$shell})"</>
EOH
)
->addArgument('shell', InputArgument::OPTIONAL, 'The shell type (e.g. "bash"), the value of the "$SHELL" env var will be used if this is not given')
->addArgument('shell', InputArgument::OPTIONAL, 'The shell type (e.g. "bash"), the value of the "$SHELL" env var will be used if this is not given', null, $this->getSupportedShells(...))
->addOption('debug', null, InputOption::VALUE_NONE, 'Tail the completion debug log')
;
}
@@ -105,7 +114,7 @@ EOH
return self::INVALID;
}
$output->write(str_replace(['{{ COMMAND_NAME }}', '{{ VERSION }}'], [$commandName, $this->getApplication()->getVersion()], file_get_contents($completionFile)));
$output->write(str_replace(['{{ COMMAND_NAME }}', '{{ VERSION }}'], [$commandName, CompleteCommand::COMPLETION_API_VERSION], file_get_contents($completionFile)));
return self::SUCCESS;
}
@@ -132,7 +141,7 @@ EOH
*/
private function getSupportedShells(): array
{
return array_map(function ($f) {
return $this->supportedShells ??= array_map(function ($f) {
return pathinfo($f, \PATHINFO_EXTENSION);
}, glob(__DIR__.'/../Resources/completion.*'));
}

View File

@@ -11,8 +11,6 @@
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Descriptor\ApplicationDescription;
use Symfony\Component\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Input\InputArgument;
@@ -27,11 +25,8 @@ use Symfony\Component\Console\Output\OutputInterface;
*/
class HelpCommand extends Command
{
private $command;
private Command $command;
/**
* {@inheritdoc}
*/
protected function configure()
{
$this->ignoreValidationErrors();
@@ -39,8 +34,12 @@ class HelpCommand extends Command
$this
->setName('help')
->setDefinition([
new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help', function () {
return array_keys((new ApplicationDescription($this->getApplication()))->getCommands());
}),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', function () {
return (new DescriptorHelper())->getFormats();
}),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'),
])
->setDescription('Display help for a command')
@@ -64,14 +63,9 @@ EOF
$this->command = $command;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
if (null === $this->command) {
$this->command = $this->getApplication()->find($input->getArgument('command_name'));
}
$this->command ??= $this->getApplication()->find($input->getArgument('command_name'));
$helper = new DescriptorHelper();
$helper->describe($output, $this->command, [
@@ -79,23 +73,8 @@ EOF
'raw_text' => $input->getOption('raw'),
]);
$this->command = null;
unset($this->command);
return 0;
}
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestArgumentValuesFor('command_name')) {
$descriptor = new ApplicationDescription($this->getApplication());
$suggestions->suggestValues(array_keys($descriptor->getCommands()));
return;
}
if ($input->mustSuggestOptionValuesFor('format')) {
$helper = new DescriptorHelper();
$suggestions->suggestValues($helper->getFormats());
}
}
}

View File

@@ -14,6 +14,8 @@ namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Completion\Suggestion;
use Symfony\Component\Console\Helper\HelperInterface;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
@@ -24,8 +26,8 @@ use Symfony\Component\Console\Output\OutputInterface;
*/
final class LazyCommand extends Command
{
private $command;
private $isEnabled;
private \Closure|Command $command;
private ?bool $isEnabled;
public function __construct(string $name, array $aliases, string $description, bool $isHidden, \Closure $commandFactory, ?bool $isEnabled = true)
{
@@ -45,6 +47,9 @@ final class LazyCommand extends Command
public function setApplication(Application $application = null): void
{
if (1 > \func_num_args()) {
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
}
if ($this->command instanceof parent) {
$this->command->setApplication($application);
}
@@ -76,10 +81,7 @@ final class LazyCommand extends Command
$this->getCommand()->complete($input, $suggestions);
}
/**
* @return $this
*/
public function setCode(callable $code): self
public function setCode(callable $code): static
{
$this->getCommand()->setCode($code);
@@ -94,10 +96,7 @@ final class LazyCommand extends Command
$this->getCommand()->mergeApplicationDefinition($mergeArgs);
}
/**
* @return $this
*/
public function setDefinition($definition): self
public function setDefinition(array|InputDefinition $definition): static
{
$this->getCommand()->setDefinition($definition);
@@ -115,39 +114,35 @@ final class LazyCommand extends Command
}
/**
* @return $this
* @param array|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion> $suggestedValues The values used for input completion
*/
public function addArgument(string $name, int $mode = null, string $description = '', $default = null): self
public function addArgument(string $name, int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = [] */): static
{
$this->getCommand()->addArgument($name, $mode, $description, $default);
$suggestedValues = 5 <= \func_num_args() ? func_get_arg(4) : [];
$this->getCommand()->addArgument($name, $mode, $description, $default, $suggestedValues);
return $this;
}
/**
* @return $this
* @param array|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion> $suggestedValues The values used for input completion
*/
public function addOption(string $name, $shortcut = null, int $mode = null, string $description = '', $default = null): self
public function addOption(string $name, string|array $shortcut = null, int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = [] */): static
{
$this->getCommand()->addOption($name, $shortcut, $mode, $description, $default);
$suggestedValues = 6 <= \func_num_args() ? func_get_arg(5) : [];
$this->getCommand()->addOption($name, $shortcut, $mode, $description, $default, $suggestedValues);
return $this;
}
/**
* @return $this
*/
public function setProcessTitle(string $title): self
public function setProcessTitle(string $title): static
{
$this->getCommand()->setProcessTitle($title);
return $this;
}
/**
* @return $this
*/
public function setHelp(string $help): self
public function setHelp(string $help): static
{
$this->getCommand()->setHelp($help);
@@ -169,10 +164,7 @@ final class LazyCommand extends Command
return $this->getCommand()->getSynopsis($short);
}
/**
* @return $this
*/
public function addUsage(string $usage): self
public function addUsage(string $usage): static
{
$this->getCommand()->addUsage($usage);
@@ -184,10 +176,7 @@ final class LazyCommand extends Command
return $this->getCommand()->getUsages();
}
/**
* @return mixed
*/
public function getHelper(string $name)
public function getHelper(string $name): HelperInterface
{
return $this->getCommand()->getHelper($name);
}

View File

@@ -11,8 +11,6 @@
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Descriptor\ApplicationDescription;
use Symfony\Component\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Input\InputArgument;
@@ -27,17 +25,18 @@ use Symfony\Component\Console\Output\OutputInterface;
*/
class ListCommand extends Command
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('list')
->setDefinition([
new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'),
new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name', null, function () {
return array_keys((new ApplicationDescription($this->getApplication()))->getNamespaces());
}),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', function () {
return (new DescriptorHelper())->getFormats();
}),
new InputOption('short', null, InputOption::VALUE_NONE, 'To skip describing commands\' arguments'),
])
->setDescription('List commands')
@@ -62,10 +61,7 @@ EOF
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$helper = new DescriptorHelper();
$helper->describe($output, $this->getApplication(), [
@@ -77,19 +73,4 @@ EOF
return 0;
}
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestArgumentValuesFor('namespace')) {
$descriptor = new ApplicationDescription($this->getApplication());
$suggestions->suggestValues(array_keys($descriptor->getNamespaces()));
return;
}
if ($input->mustSuggestOptionValuesFor('format')) {
$helper = new DescriptorHelper();
$suggestions->suggestValues($helper->getFormats());
}
}
}

View File

@@ -24,8 +24,7 @@ use Symfony\Component\Lock\Store\SemaphoreStore;
*/
trait LockableTrait
{
/** @var LockInterface|null */
private $lock;
private ?LockInterface $lock = null;
/**
* Locks a command.

View File

@@ -22,21 +22,17 @@ interface CommandLoaderInterface
/**
* Loads a command.
*
* @return Command
*
* @throws CommandNotFoundException
*/
public function get(string $name);
public function get(string $name): Command;
/**
* Checks if a command exists.
*
* @return bool
*/
public function has(string $name);
public function has(string $name): bool;
/**
* @return string[]
*/
public function getNames();
public function getNames(): array;
}

View File

@@ -12,6 +12,7 @@
namespace Symfony\Component\Console\CommandLoader;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\CommandNotFoundException;
/**
@@ -21,8 +22,8 @@ use Symfony\Component\Console\Exception\CommandNotFoundException;
*/
class ContainerCommandLoader implements CommandLoaderInterface
{
private $container;
private $commandMap;
private ContainerInterface $container;
private array $commandMap;
/**
* @param array $commandMap An array with command names as keys and service ids as values
@@ -33,10 +34,7 @@ class ContainerCommandLoader implements CommandLoaderInterface
$this->commandMap = $commandMap;
}
/**
* {@inheritdoc}
*/
public function get(string $name)
public function get(string $name): Command
{
if (!$this->has($name)) {
throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
@@ -45,18 +43,12 @@ class ContainerCommandLoader implements CommandLoaderInterface
return $this->container->get($this->commandMap[$name]);
}
/**
* {@inheritdoc}
*/
public function has(string $name)
public function has(string $name): bool
{
return isset($this->commandMap[$name]) && $this->container->has($this->commandMap[$name]);
}
/**
* {@inheritdoc}
*/
public function getNames()
public function getNames(): array
{
return array_keys($this->commandMap);
}

View File

@@ -11,6 +11,7 @@
namespace Symfony\Component\Console\CommandLoader;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\CommandNotFoundException;
/**
@@ -20,7 +21,7 @@ use Symfony\Component\Console\Exception\CommandNotFoundException;
*/
class FactoryCommandLoader implements CommandLoaderInterface
{
private $factories;
private array $factories;
/**
* @param callable[] $factories Indexed by command names
@@ -30,18 +31,12 @@ class FactoryCommandLoader implements CommandLoaderInterface
$this->factories = $factories;
}
/**
* {@inheritdoc}
*/
public function has(string $name)
public function has(string $name): bool
{
return isset($this->factories[$name]);
}
/**
* {@inheritdoc}
*/
public function get(string $name)
public function get(string $name): Command
{
if (!isset($this->factories[$name])) {
throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
@@ -52,10 +47,7 @@ class FactoryCommandLoader implements CommandLoaderInterface
return $factory();
}
/**
* {@inheritdoc}
*/
public function getNames()
public function getNames(): array
{
return array_keys($this->factories);
}

View File

@@ -64,9 +64,6 @@ final class CompletionInput extends ArgvInput
return $input;
}
/**
* {@inheritdoc}
*/
public function bind(InputDefinition $definition): void
{
parent::bind($definition);
@@ -84,7 +81,7 @@ final class CompletionInput extends ArgvInput
return;
}
if (null !== $option && $option->acceptValue()) {
if ($option?->acceptValue()) {
$this->completionType = self::TYPE_OPTION_VALUE;
$this->completionName = $option->getName();
$this->completionValue = $optionValue ?: (!str_starts_with($optionToken, '--') ? substr($optionToken, 2) : '');
@@ -97,7 +94,7 @@ final class CompletionInput extends ArgvInput
if ('-' === $previousToken[0] && '' !== trim($previousToken, '-')) {
// check if previous option accepted a value
$previousOption = $this->getOptionFromToken($previousToken);
if (null !== $previousOption && $previousOption->acceptValue()) {
if ($previousOption?->acceptValue()) {
$this->completionType = self::TYPE_OPTION_VALUE;
$this->completionName = $previousOption->getName();
$this->completionValue = $relevantToken;
@@ -183,7 +180,7 @@ final class CompletionInput extends ArgvInput
{
try {
return parent::parseToken($token, $parseOptions);
} catch (RuntimeException $e) {
} catch (RuntimeException) {
// suppress errors, completed input is almost never valid
}

View File

@@ -26,11 +26,9 @@ final class CompletionSuggestions
/**
* Add a suggested value for an input option or argument.
*
* @param string|Suggestion $value
*
* @return $this
*/
public function suggestValue($value): self
public function suggestValue(string|Suggestion $value): static
{
$this->valueSuggestions[] = !$value instanceof Suggestion ? new Suggestion($value) : $value;
@@ -44,7 +42,7 @@ final class CompletionSuggestions
*
* @return $this
*/
public function suggestValues(array $values): self
public function suggestValues(array $values): static
{
foreach ($values as $value) {
$this->suggestValue($value);
@@ -58,7 +56,7 @@ final class CompletionSuggestions
*
* @return $this
*/
public function suggestOption(InputOption $option): self
public function suggestOption(InputOption $option): static
{
$this->optionSuggestions[] = $option;
@@ -72,7 +70,7 @@ final class CompletionSuggestions
*
* @return $this
*/
public function suggestOptions(array $options): self
public function suggestOptions(array $options): static
{
foreach ($options as $option) {
$this->suggestOption($option);

View File

@@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Completion\Output;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @author Guillaume Aveline <guillaume.aveline@pm.me>
*/
class FishCompletionOutput implements CompletionOutputInterface
{
public function write(CompletionSuggestions $suggestions, OutputInterface $output): void
{
$values = $suggestions->getValueSuggestions();
foreach ($suggestions->getOptionSuggestions() as $option) {
$values[] = '--'.$option->getName();
if ($option->isNegatable()) {
$values[] = '--no-'.$option->getName();
}
}
$output->write(implode("\n", $values));
}
}

View File

@@ -0,0 +1,36 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Completion\Output;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @author Jitendra A <adhocore@gmail.com>
*/
class ZshCompletionOutput implements CompletionOutputInterface
{
public function write(CompletionSuggestions $suggestions, OutputInterface $output): void
{
$values = [];
foreach ($suggestions->getValueSuggestions() as $value) {
$values[] = $value->getValue().($value->getDescription() ? "\t".$value->getDescription() : '');
}
foreach ($suggestions->getOptionSuggestions() as $option) {
$values[] = '--'.$option->getName().($option->getDescription() ? "\t".$option->getDescription() : '');
if ($option->isNegatable()) {
$values[] = '--no-'.$option->getName().($option->getDescription() ? "\t".$option->getDescription() : '');
}
}
$output->write(implode("\n", $values)."\n");
}
}

View File

@@ -16,13 +16,12 @@ namespace Symfony\Component\Console\Completion;
*
* @author Wouter de Jong <wouter@wouterj.nl>
*/
class Suggestion
class Suggestion implements \Stringable
{
private $value;
public function __construct(string $value)
{
$this->value = $value;
public function __construct(
private readonly string $value,
private readonly string $description = ''
) {
}
public function getValue(): string
@@ -30,6 +29,11 @@ class Suggestion
return $this->value;
}
public function getDescription(): string
{
return $this->description;
}
public function __toString(): string
{
return $this->getValue();

View File

@@ -18,7 +18,7 @@ use Symfony\Component\Console\Output\OutputInterface;
*/
final class Cursor
{
private $output;
private OutputInterface $output;
private $input;
/**
@@ -33,7 +33,7 @@ final class Cursor
/**
* @return $this
*/
public function moveUp(int $lines = 1): self
public function moveUp(int $lines = 1): static
{
$this->output->write(sprintf("\x1b[%dA", $lines));
@@ -43,7 +43,7 @@ final class Cursor
/**
* @return $this
*/
public function moveDown(int $lines = 1): self
public function moveDown(int $lines = 1): static
{
$this->output->write(sprintf("\x1b[%dB", $lines));
@@ -53,7 +53,7 @@ final class Cursor
/**
* @return $this
*/
public function moveRight(int $columns = 1): self
public function moveRight(int $columns = 1): static
{
$this->output->write(sprintf("\x1b[%dC", $columns));
@@ -63,7 +63,7 @@ final class Cursor
/**
* @return $this
*/
public function moveLeft(int $columns = 1): self
public function moveLeft(int $columns = 1): static
{
$this->output->write(sprintf("\x1b[%dD", $columns));
@@ -73,7 +73,7 @@ final class Cursor
/**
* @return $this
*/
public function moveToColumn(int $column): self
public function moveToColumn(int $column): static
{
$this->output->write(sprintf("\x1b[%dG", $column));
@@ -83,7 +83,7 @@ final class Cursor
/**
* @return $this
*/
public function moveToPosition(int $column, int $row): self
public function moveToPosition(int $column, int $row): static
{
$this->output->write(sprintf("\x1b[%d;%dH", $row + 1, $column));
@@ -93,7 +93,7 @@ final class Cursor
/**
* @return $this
*/
public function savePosition(): self
public function savePosition(): static
{
$this->output->write("\x1b7");
@@ -103,7 +103,7 @@ final class Cursor
/**
* @return $this
*/
public function restorePosition(): self
public function restorePosition(): static
{
$this->output->write("\x1b8");
@@ -113,7 +113,7 @@ final class Cursor
/**
* @return $this
*/
public function hide(): self
public function hide(): static
{
$this->output->write("\x1b[?25l");
@@ -123,7 +123,7 @@ final class Cursor
/**
* @return $this
*/
public function show(): self
public function show(): static
{
$this->output->write("\x1b[?25h\x1b[?0c");
@@ -135,7 +135,7 @@ final class Cursor
*
* @return $this
*/
public function clearLine(): self
public function clearLine(): static
{
$this->output->write("\x1b[2K");
@@ -157,7 +157,7 @@ final class Cursor
*
* @return $this
*/
public function clearOutput(): self
public function clearOutput(): static
{
$this->output->write("\x1b[0J");
@@ -169,7 +169,7 @@ final class Cursor
*
* @return $this
*/
public function clearScreen(): self
public function clearScreen(): static
{
$this->output->write("\x1b[2J");
@@ -183,11 +183,7 @@ final class Cursor
{
static $isTtySupported;
if (null === $isTtySupported && \function_exists('proc_open')) {
$isTtySupported = (bool) @proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes);
}
if (!$isTtySupported) {
if (!$isTtySupported ??= '/' === \DIRECTORY_SEPARATOR && stream_isatty(\STDOUT)) {
return [1, 1];
}

View File

@@ -29,33 +29,16 @@ use Symfony\Component\DependencyInjection\TypedReference;
*/
class AddConsoleCommandPass implements CompilerPassInterface
{
private $commandLoaderServiceId;
private $commandTag;
private $noPreloadTag;
private $privateTagName;
public function __construct(string $commandLoaderServiceId = 'console.command_loader', string $commandTag = 'console.command', string $noPreloadTag = 'container.no_preload', string $privateTagName = 'container.private')
{
if (0 < \func_num_args()) {
trigger_deprecation('symfony/console', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
}
$this->commandLoaderServiceId = $commandLoaderServiceId;
$this->commandTag = $commandTag;
$this->noPreloadTag = $noPreloadTag;
$this->privateTagName = $privateTagName;
}
public function process(ContainerBuilder $container)
{
$commandServices = $container->findTaggedServiceIds($this->commandTag, true);
$commandServices = $container->findTaggedServiceIds('console.command', true);
$lazyCommandMap = [];
$lazyCommandRefs = [];
$serviceIds = [];
foreach ($commandServices as $id => $tags) {
$definition = $container->getDefinition($id);
$definition->addTag($this->noPreloadTag);
$definition->addTag('container.no_preload');
$class = $container->getParameterBag()->resolveValue($definition->getClass());
if (isset($tags[0]['command'])) {
@@ -65,7 +48,7 @@ class AddConsoleCommandPass implements CompilerPassInterface
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
}
if (!$r->isSubclassOf(Command::class)) {
throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class));
throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, 'console.command', Command::class));
}
$aliases = str_replace('%', '%%', $class::getDefaultName() ?? '');
}
@@ -78,7 +61,7 @@ class AddConsoleCommandPass implements CompilerPassInterface
}
if (null === $commandName) {
if (!$definition->isPublic() || $definition->isPrivate() || $definition->hasTag($this->privateTagName)) {
if (!$definition->isPublic() || $definition->isPrivate() || $definition->hasTag('container.private')) {
$commandId = 'console.command.public_alias.'.$id;
$container->setAlias($commandId, $id)->setPublic(true);
$id = $commandId;
@@ -104,7 +87,7 @@ class AddConsoleCommandPass implements CompilerPassInterface
$lazyCommandMap[$tag['command']] = $id;
}
$description = $description ?? $tag['description'] ?? null;
$description ??= $tag['description'] ?? null;
}
$definition->addMethodCall('setName', [$commandName]);
@@ -122,7 +105,7 @@ class AddConsoleCommandPass implements CompilerPassInterface
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
}
if (!$r->isSubclassOf(Command::class)) {
throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class));
throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, 'console.command', Command::class));
}
$description = str_replace('%', '%%', $class::getDefaultDescription() ?? '');
}
@@ -138,9 +121,9 @@ class AddConsoleCommandPass implements CompilerPassInterface
}
$container
->register($this->commandLoaderServiceId, ContainerCommandLoader::class)
->register('console.command_loader', ContainerCommandLoader::class)
->setPublic(true)
->addTag($this->noPreloadTag)
->addTag('container.no_preload')
->setArguments([ServiceLocatorTagPass::register($container, $lazyCommandRefs), $lazyCommandMap]);
$container->setParameter('console.command.ids', $serviceIds);

View File

@@ -24,24 +24,20 @@ class ApplicationDescription
{
public const GLOBAL_NAMESPACE = '_global';
private $application;
private $namespace;
private $showHidden;
/**
* @var array
*/
private $namespaces;
private Application $application;
private ?string $namespace;
private bool $showHidden;
private array $namespaces;
/**
* @var array<string, Command>
*/
private $commands;
private array $commands;
/**
* @var array<string, Command>
*/
private $aliases;
private array $aliases = [];
public function __construct(Application $application, string $namespace = null, bool $showHidden = false)
{
@@ -52,7 +48,7 @@ class ApplicationDescription
public function getNamespaces(): array
{
if (null === $this->namespaces) {
if (!isset($this->namespaces)) {
$this->inspectApplication();
}
@@ -64,7 +60,7 @@ class ApplicationDescription
*/
public function getCommands(): array
{
if (null === $this->commands) {
if (!isset($this->commands)) {
$this->inspectApplication();
}

View File

@@ -31,32 +31,18 @@ abstract class Descriptor implements DescriptorInterface
*/
protected $output;
/**
* {@inheritdoc}
*/
public function describe(OutputInterface $output, object $object, array $options = [])
{
$this->output = $output;
switch (true) {
case $object instanceof InputArgument:
$this->describeInputArgument($object, $options);
break;
case $object instanceof InputOption:
$this->describeInputOption($object, $options);
break;
case $object instanceof InputDefinition:
$this->describeInputDefinition($object, $options);
break;
case $object instanceof Command:
$this->describeCommand($object, $options);
break;
case $object instanceof Application:
$this->describeApplication($object, $options);
break;
default:
throw new InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_debug_type($object)));
}
match (true) {
$object instanceof InputArgument => $this->describeInputArgument($object, $options),
$object instanceof InputOption => $this->describeInputOption($object, $options),
$object instanceof InputDefinition => $this->describeInputDefinition($object, $options),
$object instanceof Command => $this->describeCommand($object, $options),
$object instanceof Application => $this->describeApplication($object, $options),
default => throw new InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_debug_type($object))),
};
}
/**

View File

@@ -26,17 +26,11 @@ use Symfony\Component\Console\Input\InputOption;
*/
class JsonDescriptor extends Descriptor
{
/**
* {@inheritdoc}
*/
protected function describeInputArgument(InputArgument $argument, array $options = [])
{
$this->writeData($this->getInputArgumentData($argument), $options);
}
/**
* {@inheritdoc}
*/
protected function describeInputOption(InputOption $option, array $options = [])
{
$this->writeData($this->getInputOptionData($option), $options);
@@ -45,25 +39,16 @@ class JsonDescriptor extends Descriptor
}
}
/**
* {@inheritdoc}
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = [])
{
$this->writeData($this->getInputDefinitionData($definition), $options);
}
/**
* {@inheritdoc}
*/
protected function describeCommand(Command $command, array $options = [])
{
$this->writeData($this->getCommandData($command, $options['short'] ?? false), $options);
}
/**
* {@inheritdoc}
*/
protected function describeApplication(Application $application, array $options = [])
{
$describedNamespace = $options['namespace'] ?? null;

View File

@@ -28,9 +28,6 @@ use Symfony\Component\Console\Output\OutputInterface;
*/
class MarkdownDescriptor extends Descriptor
{
/**
* {@inheritdoc}
*/
public function describe(OutputInterface $output, object $object, array $options = [])
{
$decorated = $output->isDecorated();
@@ -41,17 +38,11 @@ class MarkdownDescriptor extends Descriptor
$output->setDecorated($decorated);
}
/**
* {@inheritdoc}
*/
protected function write(string $content, bool $decorated = true)
{
parent::write($content, $decorated);
}
/**
* {@inheritdoc}
*/
protected function describeInputArgument(InputArgument $argument, array $options = [])
{
$this->write(
@@ -63,9 +54,6 @@ class MarkdownDescriptor extends Descriptor
);
}
/**
* {@inheritdoc}
*/
protected function describeInputOption(InputOption $option, array $options = [])
{
$name = '--'.$option->getName();
@@ -87,9 +75,6 @@ class MarkdownDescriptor extends Descriptor
);
}
/**
* {@inheritdoc}
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = [])
{
if ($showArguments = \count($definition->getArguments()) > 0) {
@@ -117,9 +102,6 @@ class MarkdownDescriptor extends Descriptor
}
}
/**
* {@inheritdoc}
*/
protected function describeCommand(Command $command, array $options = [])
{
if ($options['short'] ?? false) {
@@ -160,9 +142,6 @@ class MarkdownDescriptor extends Descriptor
}
}
/**
* {@inheritdoc}
*/
protected function describeApplication(Application $application, array $options = [])
{
$describedNamespace = $options['namespace'] ?? null;

View File

@@ -28,9 +28,6 @@ use Symfony\Component\Console\Input\InputOption;
*/
class TextDescriptor extends Descriptor
{
/**
* {@inheritdoc}
*/
protected function describeInputArgument(InputArgument $argument, array $options = [])
{
if (null !== $argument->getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) {
@@ -51,9 +48,6 @@ class TextDescriptor extends Descriptor
), $options);
}
/**
* {@inheritdoc}
*/
protected function describeInputOption(InputOption $option, array $options = [])
{
if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) {
@@ -89,9 +83,6 @@ class TextDescriptor extends Descriptor
), $options);
}
/**
* {@inheritdoc}
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = [])
{
$totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions());
@@ -131,9 +122,6 @@ class TextDescriptor extends Descriptor
}
}
/**
* {@inheritdoc}
*/
protected function describeCommand(Command $command, array $options = [])
{
$command->mergeApplicationDefinition(false);
@@ -169,9 +157,6 @@ class TextDescriptor extends Descriptor
}
}
/**
* {@inheritdoc}
*/
protected function describeApplication(Application $application, array $options = [])
{
$describedNamespace = $options['namespace'] ?? null;
@@ -245,9 +230,6 @@ class TextDescriptor extends Descriptor
}
}
/**
* {@inheritdoc}
*/
private function writeText(string $content, array $options = [])
{
$this->write(
@@ -273,10 +255,8 @@ class TextDescriptor extends Descriptor
/**
* Formats input option/argument default value.
*
* @param mixed $default
*/
private function formatDefaultValue($default): string
private function formatDefaultValue(mixed $default): string
{
if (\INF === $default) {
return 'INF';

View File

@@ -120,41 +120,26 @@ class XmlDescriptor extends Descriptor
return $dom;
}
/**
* {@inheritdoc}
*/
protected function describeInputArgument(InputArgument $argument, array $options = [])
{
$this->writeDocument($this->getInputArgumentDocument($argument));
}
/**
* {@inheritdoc}
*/
protected function describeInputOption(InputOption $option, array $options = [])
{
$this->writeDocument($this->getInputOptionDocument($option));
}
/**
* {@inheritdoc}
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = [])
{
$this->writeDocument($this->getInputDefinitionDocument($definition));
}
/**
* {@inheritdoc}
*/
protected function describeCommand(Command $command, array $options = [])
{
$this->writeDocument($this->getCommandDocument($command, $options['short'] ?? false));
}
/**
* {@inheritdoc}
*/
protected function describeApplication(Application $application, array $options = [])
{
$this->writeDocument($this->getApplicationDocument($application, $options['namespace'] ?? null, $options['short'] ?? false));

View File

@@ -26,7 +26,7 @@ final class ConsoleCommandEvent extends ConsoleEvent
/**
* Indicates if the command should be run or skipped.
*/
private $commandShouldRun = true;
private bool $commandShouldRun = true;
/**
* Disables the command, so it won't be run.

View File

@@ -22,8 +22,8 @@ use Symfony\Component\Console\Output\OutputInterface;
*/
final class ConsoleErrorEvent extends ConsoleEvent
{
private $error;
private $exitCode;
private \Throwable $error;
private int $exitCode;
public function __construct(InputInterface $input, OutputInterface $output, \Throwable $error, Command $command = null)
{
@@ -47,7 +47,6 @@ final class ConsoleErrorEvent extends ConsoleEvent
$this->exitCode = $exitCode;
$r = new \ReflectionProperty($this->error, 'code');
$r->setAccessible(true);
$r->setValue($this->error, $this->exitCode);
}

View File

@@ -25,8 +25,8 @@ class ConsoleEvent extends Event
{
protected $command;
private $input;
private $output;
private InputInterface $input;
private OutputInterface $output;
public function __construct(?Command $command, InputInterface $input, OutputInterface $output)
{
@@ -37,30 +37,24 @@ class ConsoleEvent extends Event
/**
* Gets the command that is executed.
*
* @return Command|null
*/
public function getCommand()
public function getCommand(): ?Command
{
return $this->command;
}
/**
* Gets the input instance.
*
* @return InputInterface
*/
public function getInput()
public function getInput(): InputInterface
{
return $this->input;
}
/**
* Gets the output instance.
*
* @return OutputInterface
*/
public function getOutput()
public function getOutput(): OutputInterface
{
return $this->output;
}

View File

@@ -20,7 +20,7 @@ use Symfony\Component\Console\Output\OutputInterface;
*/
final class ConsoleSignalEvent extends ConsoleEvent
{
private $handlingSignal;
private int $handlingSignal;
public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $handlingSignal)
{

View File

@@ -22,7 +22,7 @@ use Symfony\Component\Console\Output\OutputInterface;
*/
final class ConsoleTerminateEvent extends ConsoleEvent
{
private $exitCode;
private int $exitCode;
public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $exitCode)
{

View File

@@ -24,7 +24,7 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
*/
class ErrorListener implements EventSubscriberInterface
{
private $logger;
private ?LoggerInterface $logger;
public function __construct(LoggerInterface $logger = null)
{
@@ -69,7 +69,7 @@ class ErrorListener implements EventSubscriberInterface
$this->logger->debug('Command "{command}" exited with code "{code}"', ['command' => $inputString, 'code' => $exitCode]);
}
public static function getSubscribedEvents()
public static function getSubscribedEvents(): array
{
return [
ConsoleEvents::ERROR => ['onConsoleError', -128],
@@ -79,10 +79,10 @@ class ErrorListener implements EventSubscriberInterface
private static function getInputString(ConsoleEvent $event): ?string
{
$commandName = $event->getCommand() ? $event->getCommand()->getName() : null;
$commandName = $event->getCommand()?->getName();
$input = $event->getInput();
if (method_exists($input, '__toString')) {
if ($input instanceof \Stringable) {
if ($commandName) {
return str_replace(["'$commandName'", "\"$commandName\""], $commandName, (string) $input);
}

View File

@@ -18,7 +18,7 @@ namespace Symfony\Component\Console\Exception;
*/
class CommandNotFoundException extends \InvalidArgumentException implements ExceptionInterface
{
private $alternatives;
private array $alternatives;
/**
* @param string $message Exception message to throw
@@ -36,7 +36,7 @@ class CommandNotFoundException extends \InvalidArgumentException implements Exce
/**
* @return string[]
*/
public function getAlternatives()
public function getAlternatives(): array
{
return $this->alternatives;
}

View File

@@ -16,52 +16,34 @@ namespace Symfony\Component\Console\Formatter;
*/
final class NullOutputFormatter implements OutputFormatterInterface
{
private $style;
private NullOutputFormatterStyle $style;
/**
* {@inheritdoc}
*/
public function format(?string $message): ?string
{
return null;
}
/**
* {@inheritdoc}
*/
public function getStyle(string $name): OutputFormatterStyleInterface
{
// to comply with the interface we must return a OutputFormatterStyleInterface
return $this->style ?? $this->style = new NullOutputFormatterStyle();
return $this->style ??= new NullOutputFormatterStyle();
}
/**
* {@inheritdoc}
*/
public function hasStyle(string $name): bool
{
return false;
}
/**
* {@inheritdoc}
*/
public function isDecorated(): bool
{
return false;
}
/**
* {@inheritdoc}
*/
public function setDecorated(bool $decorated): void
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function setStyle(string $name, OutputFormatterStyleInterface $style): void
{
// do nothing

View File

@@ -16,49 +16,37 @@ namespace Symfony\Component\Console\Formatter;
*/
final class NullOutputFormatterStyle implements OutputFormatterStyleInterface
{
/**
* {@inheritdoc}
*/
public function apply(string $text): string
{
return $text;
}
/**
* {@inheritdoc}
*/
public function setBackground(string $color = null): void
{
if (1 > \func_num_args()) {
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
}
// do nothing
}
/**
* {@inheritdoc}
*/
public function setForeground(string $color = null): void
{
if (1 > \func_num_args()) {
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
}
// do nothing
}
/**
* {@inheritdoc}
*/
public function setOption(string $option): void
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function setOptions(array $options): void
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function unsetOption(string $option): void
{
// do nothing

View File

@@ -21,9 +21,9 @@ use Symfony\Component\Console\Exception\InvalidArgumentException;
*/
class OutputFormatter implements WrappableOutputFormatterInterface
{
private $decorated;
private $styles = [];
private $styleStack;
private bool $decorated;
private array $styles = [];
private OutputFormatterStyleStack $styleStack;
public function __clone()
{
@@ -35,10 +35,8 @@ class OutputFormatter implements WrappableOutputFormatterInterface
/**
* Escapes "<" and ">" special chars in given text.
*
* @return string
*/
public static function escape(string $text)
public static function escape(string $text): string
{
$text = preg_replace('/([^\\\\]|^)([<>])/', '$1\\\\$2', $text);
@@ -83,42 +81,27 @@ class OutputFormatter implements WrappableOutputFormatterInterface
$this->styleStack = new OutputFormatterStyleStack();
}
/**
* {@inheritdoc}
*/
public function setDecorated(bool $decorated)
{
$this->decorated = $decorated;
}
/**
* {@inheritdoc}
*/
public function isDecorated()
public function isDecorated(): bool
{
return $this->decorated;
}
/**
* {@inheritdoc}
*/
public function setStyle(string $name, OutputFormatterStyleInterface $style)
{
$this->styles[strtolower($name)] = $style;
}
/**
* {@inheritdoc}
*/
public function hasStyle(string $name)
public function hasStyle(string $name): bool
{
return isset($this->styles[strtolower($name)]);
}
/**
* {@inheritdoc}
*/
public function getStyle(string $name)
public function getStyle(string $name): OutputFormatterStyleInterface
{
if (!$this->hasStyle($name)) {
throw new InvalidArgumentException(sprintf('Undefined style: "%s".', $name));
@@ -127,17 +110,11 @@ class OutputFormatter implements WrappableOutputFormatterInterface
return $this->styles[strtolower($name)];
}
/**
* {@inheritdoc}
*/
public function format(?string $message)
public function format(?string $message): ?string
{
return $this->formatAndWrap($message, 0);
}
/**
* {@inheritdoc}
*/
public function formatAndWrap(?string $message, int $width)
{
if (null === $message) {
@@ -163,7 +140,7 @@ class OutputFormatter implements WrappableOutputFormatterInterface
$offset = $pos + \strlen($text);
// opening tag?
if ($open = '/' != $text[1]) {
if ($open = '/' !== $text[1]) {
$tag = $matches[1][$i][0];
} else {
$tag = $matches[3][$i][0] ?? '';
@@ -186,10 +163,7 @@ class OutputFormatter implements WrappableOutputFormatterInterface
return strtr($output, ["\0" => '\\', '\\<' => '<', '\\>' => '>']);
}
/**
* @return OutputFormatterStyleStack
*/
public function getStyleStack()
public function getStyleStack(): OutputFormatterStyleStack
{
return $this->styleStack;
}
@@ -261,7 +235,7 @@ class OutputFormatter implements WrappableOutputFormatterInterface
$text = $prefix.preg_replace('~([^\\n]{'.$width.'})\\ *~', "\$1\n", $text);
$text = rtrim($text, "\n").($matches[1] ?? '');
if (!$currentLineLength && '' !== $current && "\n" !== substr($current, -1)) {
if (!$currentLineLength && '' !== $current && !str_ends_with($current, "\n")) {
$text = "\n".$text;
}

View File

@@ -25,10 +25,8 @@ interface OutputFormatterInterface
/**
* Whether the output will decorate messages.
*
* @return bool
*/
public function isDecorated();
public function isDecorated(): bool;
/**
* Sets a new style.
@@ -37,24 +35,18 @@ interface OutputFormatterInterface
/**
* Checks if output formatter has style with specified name.
*
* @return bool
*/
public function hasStyle(string $name);
public function hasStyle(string $name): bool;
/**
* Gets style options from style with specified name.
*
* @return OutputFormatterStyleInterface
*
* @throws \InvalidArgumentException When style isn't defined
*/
public function getStyle(string $name);
public function getStyle(string $name): OutputFormatterStyleInterface;
/**
* Formats a message according to the given styles.
*
* @return string|null
*/
public function format(?string $message);
public function format(?string $message): ?string;
}

View File

@@ -20,12 +20,12 @@ use Symfony\Component\Console\Color;
*/
class OutputFormatterStyle implements OutputFormatterStyleInterface
{
private $color;
private $foreground;
private $background;
private $options;
private $href;
private $handlesHrefGracefully;
private Color $color;
private string $foreground;
private string $background;
private array $options;
private ?string $href = null;
private bool $handlesHrefGracefully;
/**
* Initializes output formatter style.
@@ -38,19 +38,19 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface
$this->color = new Color($this->foreground = $foreground ?: '', $this->background = $background ?: '', $this->options = $options);
}
/**
* {@inheritdoc}
*/
public function setForeground(string $color = null)
{
if (1 > \func_num_args()) {
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
}
$this->color = new Color($this->foreground = $color ?: '', $this->background, $this->options);
}
/**
* {@inheritdoc}
*/
public function setBackground(string $color = null)
{
if (1 > \func_num_args()) {
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
}
$this->color = new Color($this->foreground, $this->background = $color ?: '', $this->options);
}
@@ -59,18 +59,12 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface
$this->href = $url;
}
/**
* {@inheritdoc}
*/
public function setOption(string $option)
{
$this->options[] = $option;
$this->color = new Color($this->foreground, $this->background, $this->options);
}
/**
* {@inheritdoc}
*/
public function unsetOption(string $option)
{
$pos = array_search($option, $this->options);
@@ -81,23 +75,15 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface
$this->color = new Color($this->foreground, $this->background, $this->options);
}
/**
* {@inheritdoc}
*/
public function setOptions(array $options)
{
$this->color = new Color($this->foreground, $this->background, $this->options = $options);
}
/**
* {@inheritdoc}
*/
public function apply(string $text)
public function apply(string $text): string
{
if (null === $this->handlesHrefGracefully) {
$this->handlesHrefGracefully = 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR')
&& (!getenv('KONSOLE_VERSION') || (int) getenv('KONSOLE_VERSION') > 201100);
}
$this->handlesHrefGracefully ??= 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR')
&& (!getenv('KONSOLE_VERSION') || (int) getenv('KONSOLE_VERSION') > 201100);
if (null !== $this->href && $this->handlesHrefGracefully) {
$text = "\033]8;;$this->href\033\\$text\033]8;;\033\\";

View File

@@ -21,12 +21,12 @@ interface OutputFormatterStyleInterface
/**
* Sets style foreground color.
*/
public function setForeground(string $color = null);
public function setForeground(?string $color);
/**
* Sets style background color.
*/
public function setBackground(string $color = null);
public function setBackground(?string $color);
/**
* Sets some specific style option.
@@ -45,8 +45,6 @@ interface OutputFormatterStyleInterface
/**
* Applies the style to a given text.
*
* @return string
*/
public function apply(string $text);
public function apply(string $text): string;
}

View File

@@ -22,9 +22,9 @@ class OutputFormatterStyleStack implements ResetInterface
/**
* @var OutputFormatterStyleInterface[]
*/
private $styles;
private array $styles = [];
private $emptyStyle;
private OutputFormatterStyleInterface $emptyStyle;
public function __construct(OutputFormatterStyleInterface $emptyStyle = null)
{
@@ -51,13 +51,11 @@ class OutputFormatterStyleStack implements ResetInterface
/**
* Pops a style from the stack.
*
* @return OutputFormatterStyleInterface
*
* @throws InvalidArgumentException When style tags incorrectly nested
*/
public function pop(OutputFormatterStyleInterface $style = null)
public function pop(OutputFormatterStyleInterface $style = null): OutputFormatterStyleInterface
{
if (empty($this->styles)) {
if (!$this->styles) {
return $this->emptyStyle;
}
@@ -78,12 +76,10 @@ class OutputFormatterStyleStack implements ResetInterface
/**
* Computes current style with stacks top codes.
*
* @return OutputFormatterStyle
*/
public function getCurrent()
public function getCurrent(): OutputFormatterStyleInterface
{
if (empty($this->styles)) {
if (!$this->styles) {
return $this->emptyStyle;
}
@@ -93,17 +89,14 @@ class OutputFormatterStyleStack implements ResetInterface
/**
* @return $this
*/
public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle)
public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle): static
{
$this->emptyStyle = $emptyStyle;
return $this;
}
/**
* @return OutputFormatterStyleInterface
*/
public function getEmptyStyle()
public function getEmptyStyle(): OutputFormatterStyleInterface
{
return $this->emptyStyle;
}

View File

@@ -20,6 +20,8 @@ interface WrappableOutputFormatterInterface extends OutputFormatterInterface
{
/**
* Formats a message according to the given styles, wrapping at `$width` (0 means no wrapping).
*
* @return string
*/
public function formatAndWrap(?string $message, int $width);
}

View File

@@ -21,15 +21,13 @@ namespace Symfony\Component\Console\Helper;
class DebugFormatterHelper extends Helper
{
private const COLORS = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default'];
private $started = [];
private $count = -1;
private array $started = [];
private int $count = -1;
/**
* Starts a debug formatting session.
*
* @return string
*/
public function start(string $id, string $message, string $prefix = 'RUN')
public function start(string $id, string $message, string $prefix = 'RUN'): string
{
$this->started[$id] = ['border' => ++$this->count % \count(self::COLORS)];
@@ -38,10 +36,8 @@ class DebugFormatterHelper extends Helper
/**
* Adds progress to a formatting session.
*
* @return string
*/
public function progress(string $id, string $buffer, bool $error = false, string $prefix = 'OUT', string $errorPrefix = 'ERR')
public function progress(string $id, string $buffer, bool $error = false, string $prefix = 'OUT', string $errorPrefix = 'ERR'): string
{
$message = '';
@@ -74,10 +70,8 @@ class DebugFormatterHelper extends Helper
/**
* Stops a formatting session.
*
* @return string
*/
public function stop(string $id, string $message, bool $successful, string $prefix = 'RES')
public function stop(string $id, string $message, bool $successful, string $prefix = 'RES'): string
{
$trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : '';
@@ -97,10 +91,7 @@ class DebugFormatterHelper extends Helper
return sprintf('<bg=%s> </>', self::COLORS[$this->started[$id]['border']]);
}
/**
* {@inheritdoc}
*/
public function getName()
public function getName(): string
{
return 'debug_formatter';
}

View File

@@ -29,7 +29,7 @@ class DescriptorHelper extends Helper
/**
* @var DescriptorInterface[]
*/
private $descriptors = [];
private array $descriptors = [];
public function __construct()
{
@@ -70,17 +70,14 @@ class DescriptorHelper extends Helper
*
* @return $this
*/
public function register(string $format, DescriptorInterface $descriptor)
public function register(string $format, DescriptorInterface $descriptor): static
{
$this->descriptors[$format] = $descriptor;
return $this;
}
/**
* {@inheritdoc}
*/
public function getName()
public function getName(): string
{
return 'descriptor';
}

View File

@@ -21,10 +21,10 @@ use Symfony\Component\VarDumper\Dumper\CliDumper;
*/
final class Dumper
{
private $output;
private $dumper;
private $cloner;
private $handler;
private OutputInterface $output;
private ?CliDumper $dumper;
private ?ClonerInterface $cloner;
private \Closure $handler;
public function __construct(OutputInterface $output, CliDumper $dumper = null, ClonerInterface $cloner = null)
{
@@ -34,30 +34,25 @@ final class Dumper
if (class_exists(CliDumper::class)) {
$this->handler = function ($var): string {
$dumper = $this->dumper ?? $this->dumper = new CliDumper(null, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR);
$dumper = $this->dumper ??= new CliDumper(null, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR);
$dumper->setColors($this->output->isDecorated());
return rtrim($dumper->dump(($this->cloner ?? $this->cloner = new VarCloner())->cloneVar($var)->withRefHandles(false), true));
return rtrim($dumper->dump(($this->cloner ??= new VarCloner())->cloneVar($var)->withRefHandles(false), true));
};
} else {
$this->handler = function ($var): string {
switch (true) {
case null === $var:
return 'null';
case true === $var:
return 'true';
case false === $var:
return 'false';
case \is_string($var):
return '"'.$var.'"';
default:
return rtrim(print_r($var, true));
}
return match (true) {
null === $var => 'null',
true === $var => 'true',
false === $var => 'false',
\is_string($var) => '"'.$var.'"',
default => rtrim(print_r($var, true)),
};
};
}
}
public function __invoke($var): string
public function __invoke(mixed $var): string
{
return ($this->handler)($var);
}

View File

@@ -22,22 +22,16 @@ class FormatterHelper extends Helper
{
/**
* Formats a message within a section.
*
* @return string
*/
public function formatSection(string $section, string $message, string $style = 'info')
public function formatSection(string $section, string $message, string $style = 'info'): string
{
return sprintf('<%s>[%s]</%s> %s', $style, $section, $style, $message);
}
/**
* Formats a message as a block of text.
*
* @param string|array $messages The message to write in the block
*
* @return string
*/
public function formatBlock($messages, string $style, bool $large = false)
public function formatBlock(string|array $messages, string $style, bool $large = false): string
{
if (!\is_array($messages)) {
$messages = [$messages];
@@ -68,10 +62,8 @@ class FormatterHelper extends Helper
/**
* Truncates a message to the given length.
*
* @return string
*/
public function truncate(string $message, int $length, string $suffix = '...')
public function truncate(string $message, int $length, string $suffix = '...'): string
{
$computedLength = $length - self::width($suffix);
@@ -82,10 +74,7 @@ class FormatterHelper extends Helper
return self::substr($message, 0, $length).$suffix;
}
/**
* {@inheritdoc}
*/
public function getName()
public function getName(): string
{
return 'formatter';
}

View File

@@ -23,43 +23,26 @@ abstract class Helper implements HelperInterface
{
protected $helperSet = null;
/**
* {@inheritdoc}
*/
public function setHelperSet(HelperSet $helperSet = null)
{
if (1 > \func_num_args()) {
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
}
$this->helperSet = $helperSet;
}
/**
* {@inheritdoc}
*/
public function getHelperSet()
public function getHelperSet(): ?HelperSet
{
return $this->helperSet;
}
/**
* Returns the length of a string, using mb_strwidth if it is available.
*
* @deprecated since Symfony 5.3
*
* @return int
*/
public static function strlen(?string $string)
{
trigger_deprecation('symfony/console', '5.3', 'Method "%s()" is deprecated and will be removed in Symfony 6.0. Use Helper::width() or Helper::length() instead.', __METHOD__);
return self::width($string);
}
/**
* Returns the width of a string, using mb_strwidth if it is available.
* The width is how many characters positions the string will use.
*/
public static function width(?string $string): int
{
$string ?? $string = '';
$string ??= '';
if (preg_match('//u', $string)) {
return (new UnicodeString($string))->width(false);
@@ -78,7 +61,7 @@ abstract class Helper implements HelperInterface
*/
public static function length(?string $string): int
{
$string ?? $string = '';
$string ??= '';
if (preg_match('//u', $string)) {
return (new UnicodeString($string))->length();
@@ -93,12 +76,10 @@ abstract class Helper implements HelperInterface
/**
* Returns the subset of a string, using mb_substr if it is available.
*
* @return string
*/
public static function substr(?string $string, int $from, int $length = null)
public static function substr(?string $string, int $from, int $length = null): string
{
$string ?? $string = '';
$string ??= '';
if (false === $encoding = mb_detect_encoding($string, null, true)) {
return substr($string, $from, $length);
@@ -107,7 +88,7 @@ abstract class Helper implements HelperInterface
return mb_substr($string, $from, $length, $encoding);
}
public static function formatTime($secs)
public static function formatTime(int|float $secs)
{
static $timeFormats = [
[0, '< 1 sec'],
@@ -153,16 +134,6 @@ abstract class Helper implements HelperInterface
return sprintf('%d B', $memory);
}
/**
* @deprecated since Symfony 5.3
*/
public static function strlenWithoutDecoration(OutputFormatterInterface $formatter, ?string $string)
{
trigger_deprecation('symfony/console', '5.3', 'Method "%s()" is deprecated and will be removed in Symfony 6.0. Use Helper::removeDecoration() instead.', __METHOD__);
return self::width(self::removeDecoration($formatter, $string));
}
public static function removeDecoration(OutputFormatterInterface $formatter, ?string $string)
{
$isDecorated = $formatter->isDecorated();

View File

@@ -21,14 +21,12 @@ interface HelperInterface
/**
* Sets the helper set associated with this helper.
*/
public function setHelperSet(HelperSet $helperSet = null);
public function setHelperSet(?HelperSet $helperSet);
/**
* Gets the helper set associated with this helper.
*
* @return HelperSet|null
*/
public function getHelperSet();
public function getHelperSet(): ?HelperSet;
/**
* Returns the canonical name of this helper.

View File

@@ -11,7 +11,6 @@
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
@@ -19,16 +18,15 @@ use Symfony\Component\Console\Exception\InvalidArgumentException;
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @implements \IteratorAggregate<string, Helper>
* @implements \IteratorAggregate<string, HelperInterface>
*/
class HelperSet implements \IteratorAggregate
{
/** @var array<string, Helper> */
private $helpers = [];
private $command;
/** @var array<string, HelperInterface> */
private array $helpers = [];
/**
* @param Helper[] $helpers An array of helper
* @param HelperInterface[] $helpers
*/
public function __construct(array $helpers = [])
{
@@ -49,10 +47,8 @@ class HelperSet implements \IteratorAggregate
/**
* Returns true if the helper if defined.
*
* @return bool
*/
public function has(string $name)
public function has(string $name): bool
{
return isset($this->helpers[$name]);
}
@@ -60,11 +56,9 @@ class HelperSet implements \IteratorAggregate
/**
* Gets a helper value.
*
* @return HelperInterface
*
* @throws InvalidArgumentException if the helper is not defined
*/
public function get(string $name)
public function get(string $name): HelperInterface
{
if (!$this->has($name)) {
throw new InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name));
@@ -73,35 +67,7 @@ class HelperSet implements \IteratorAggregate
return $this->helpers[$name];
}
/**
* @deprecated since Symfony 5.4
*/
public function setCommand(Command $command = null)
{
trigger_deprecation('symfony/console', '5.4', 'Method "%s()" is deprecated.', __METHOD__);
$this->command = $command;
}
/**
* Gets the command associated with this helper set.
*
* @return Command
*
* @deprecated since Symfony 5.4
*/
public function getCommand()
{
trigger_deprecation('symfony/console', '5.4', 'Method "%s()" is deprecated.', __METHOD__);
return $this->command;
}
/**
* @return \Traversable<string, Helper>
*/
#[\ReturnTypeWillChange]
public function getIterator()
public function getIterator(): \Traversable
{
return new \ArrayIterator($this->helpers);
}

View File

@@ -23,9 +23,6 @@ abstract class InputAwareHelper extends Helper implements InputAwareInterface
{
protected $input;
/**
* {@inheritdoc}
*/
public function setInput(InputInterface $input)
{
$this->input = $input;

View File

@@ -0,0 +1,76 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
/**
* Simple output wrapper for "tagged outputs" instead of wordwrap(). This solution is based on a StackOverflow
* answer: https://stackoverflow.com/a/20434776/1476819 from user557597 (alias SLN).
*
* (?:
* # -- Words/Characters
* ( # (1 start)
* (?> # Atomic Group - Match words with valid breaks
* .{1,16} # 1-N characters
* # Followed by one of 4 prioritized, non-linebreak whitespace
* (?: # break types:
* (?<= [^\S\r\n] ) # 1. - Behind a non-linebreak whitespace
* [^\S\r\n]? # ( optionally accept an extra non-linebreak whitespace )
* | (?= \r? \n ) # 2. - Ahead a linebreak
* | $ # 3. - EOS
* | [^\S\r\n] # 4. - Accept an extra non-linebreak whitespace
* )
* ) # End atomic group
* |
* .{1,16} # No valid word breaks, just break on the N'th character
* ) # (1 end)
* (?: \r? \n )? # Optional linebreak after Words/Characters
* |
* # -- Or, Linebreak
* (?: \r? \n | $ ) # Stand alone linebreak or at EOS
* )
*
* @author Krisztián Ferenczi <ferenczi.krisztian@gmail.com>
*
* @see https://stackoverflow.com/a/20434776/1476819
*/
final class OutputWrapper
{
private const TAG_OPEN_REGEX_SEGMENT = '[a-z](?:[^\\\\<>]*+ | \\\\.)*';
private const TAG_CLOSE_REGEX_SEGMENT = '[a-z][^<>]*+';
private const URL_PATTERN = 'https?://\S+';
public function __construct(
private bool $allowCutUrls = false
) {
}
public function wrap(string $text, int $width, string $break = "\n"): string
{
if (!$width) {
return $text;
}
$tagPattern = sprintf('<(?:(?:%s)|/(?:%s)?)>', self::TAG_OPEN_REGEX_SEGMENT, self::TAG_CLOSE_REGEX_SEGMENT);
$limitPattern = "{1,$width}";
$patternBlocks = [$tagPattern];
if (!$this->allowCutUrls) {
$patternBlocks[] = self::URL_PATTERN;
}
$patternBlocks[] = '.';
$blocks = implode('|', $patternBlocks);
$rowPattern = "(?:$blocks)$limitPattern";
$pattern = sprintf('#(?:((?>(%1$s)((?<=[^\S\r\n])[^\S\r\n]?|(?=\r?\n)|$|[^\S\r\n]))|(%1$s))(?:\r?\n)?|(?:\r?\n|$))#imux', $rowPattern);
$output = rtrim(preg_replace($pattern, '\\1'.$break, $text), $break);
return str_replace(' '.$break, $break, $output);
}
}

View File

@@ -32,7 +32,7 @@ class ProcessHelper extends Helper
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
*/
public function run(OutputInterface $output, $cmd, string $error = null, callable $callback = null, int $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE): Process
public function run(OutputInterface $output, array|Process $cmd, string $error = null, callable $callback = null, int $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE): Process
{
if (!class_exists(Process::class)) {
throw new \LogicException('The ProcessHelper cannot be run as the Process component is not installed. Try running "compose require symfony/process".');
@@ -48,10 +48,6 @@ class ProcessHelper extends Helper
$cmd = [$cmd];
}
if (!\is_array($cmd)) {
throw new \TypeError(sprintf('The "command" argument of "%s()" must be an array or a "%s" instance, "%s" given.', __METHOD__, Process::class, get_debug_type($cmd)));
}
if (\is_string($cmd[0] ?? null)) {
$process = new Process($cmd);
$cmd = [];
@@ -98,7 +94,7 @@ class ProcessHelper extends Helper
*
* @see run()
*/
public function mustRun(OutputInterface $output, $cmd, string $error = null, callable $callback = null): Process
public function mustRun(OutputInterface $output, array|Process $cmd, string $error = null, callable $callback = null): Process
{
$process = $this->run($output, $cmd, $error, $callback);
@@ -134,9 +130,6 @@ class ProcessHelper extends Helper
return str_replace('<', '\\<', $str);
}
/**
* {@inheritdoc}
*/
public function getName(): string
{
return 'process';

View File

@@ -36,31 +36,32 @@ final class ProgressBar
private const FORMAT_DEBUG_NOMAX = 'debug_nomax';
private const FORMAT_NORMAL_NOMAX = 'normal_nomax';
private $barWidth = 28;
private $barChar;
private $emptyBarChar = '-';
private $progressChar = '>';
private $format;
private $internalFormat;
private $redrawFreq = 1;
private $writeCount;
private $lastWriteTime;
private $minSecondsBetweenRedraws = 0;
private $maxSecondsBetweenRedraws = 1;
private $output;
private $step = 0;
private $max;
private $startTime;
private $stepWidth;
private $percent = 0.0;
private $messages = [];
private $overwrite = true;
private $terminal;
private $previousMessage;
private $cursor;
private int $barWidth = 28;
private string $barChar;
private string $emptyBarChar = '-';
private string $progressChar = '>';
private ?string $format = null;
private ?string $internalFormat = null;
private ?int $redrawFreq = 1;
private int $writeCount = 0;
private float $lastWriteTime = 0;
private float $minSecondsBetweenRedraws = 0;
private float $maxSecondsBetweenRedraws = 1;
private OutputInterface $output;
private int $step = 0;
private int $startingStep = 0;
private ?int $max = null;
private int $startTime;
private int $stepWidth;
private float $percent = 0.0;
private array $messages = [];
private bool $overwrite = true;
private Terminal $terminal;
private ?string $previousMessage = null;
private Cursor $cursor;
private static $formatters;
private static $formats;
private static array $formatters;
private static array $formats;
/**
* @param int $max Maximum steps (0 if unknown)
@@ -102,9 +103,7 @@ final class ProgressBar
*/
public static function setPlaceholderFormatterDefinition(string $name, callable $callable): void
{
if (!self::$formatters) {
self::$formatters = self::initPlaceholderFormatters();
}
self::$formatters ??= self::initPlaceholderFormatters();
self::$formatters[$name] = $callable;
}
@@ -116,9 +115,7 @@ final class ProgressBar
*/
public static function getPlaceholderFormatterDefinition(string $name): ?callable
{
if (!self::$formatters) {
self::$formatters = self::initPlaceholderFormatters();
}
self::$formatters ??= self::initPlaceholderFormatters();
return self::$formatters[$name] ?? null;
}
@@ -133,9 +130,7 @@ final class ProgressBar
*/
public static function setFormatDefinition(string $name, string $format): void
{
if (!self::$formats) {
self::$formats = self::initFormats();
}
self::$formats ??= self::initFormats();
self::$formats[$name] = $format;
}
@@ -147,9 +142,7 @@ final class ProgressBar
*/
public static function getFormatDefinition(string $name): ?string
{
if (!self::$formats) {
self::$formats = self::initFormats();
}
self::$formats ??= self::initFormats();
return self::$formats[$name] ?? null;
}
@@ -206,11 +199,11 @@ final class ProgressBar
public function getEstimated(): float
{
if (!$this->step) {
if (0 === $this->step || $this->step === $this->startingStep) {
return 0;
}
return round((time() - $this->startTime) / $this->step * $this->max);
return round((time() - $this->startTime) / ($this->step - $this->startingStep) * $this->max);
}
public function getRemaining(): float
@@ -219,7 +212,7 @@ final class ProgressBar
return 0;
}
return round((time() - $this->startTime) / $this->step * ($this->max - $this->step));
return round((time() - $this->startTime) / ($this->step - $this->startingStep) * ($this->max - $this->step));
}
public function setBarWidth(int $size)
@@ -309,13 +302,16 @@ final class ProgressBar
/**
* Starts the progress output.
*
* @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged
* @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged
* @param int $startAt The starting point of the bar (useful e.g. when resuming a previously started bar)
*/
public function start(int $max = null)
public function start(int $max = null, int $startAt = 0): void
{
$this->startTime = time();
$this->step = 0;
$this->percent = 0.0;
$this->step = $startAt;
$this->startingStep = $startAt;
$startAt > 0 ? $this->setProgress($startAt) : $this->percent = 0.0;
if (null !== $max) {
$this->setMaxSteps($max);
@@ -495,17 +491,13 @@ final class ProgressBar
private function determineBestFormat(): string
{
switch ($this->output->getVerbosity()) {
return match ($this->output->getVerbosity()) {
// OutputInterface::VERBOSITY_QUIET: display is disabled anyway
case OutputInterface::VERBOSITY_VERBOSE:
return $this->max ? self::FORMAT_VERBOSE : self::FORMAT_VERBOSE_NOMAX;
case OutputInterface::VERBOSITY_VERY_VERBOSE:
return $this->max ? self::FORMAT_VERY_VERBOSE : self::FORMAT_VERY_VERBOSE_NOMAX;
case OutputInterface::VERBOSITY_DEBUG:
return $this->max ? self::FORMAT_DEBUG : self::FORMAT_DEBUG_NOMAX;
default:
return $this->max ? self::FORMAT_NORMAL : self::FORMAT_NORMAL_NOMAX;
}
OutputInterface::VERBOSITY_VERBOSE => $this->max ? self::FORMAT_VERBOSE : self::FORMAT_VERBOSE_NOMAX,
OutputInterface::VERBOSITY_VERY_VERBOSE => $this->max ? self::FORMAT_VERY_VERBOSE : self::FORMAT_VERY_VERBOSE_NOMAX,
OutputInterface::VERBOSITY_DEBUG => $this->max ? self::FORMAT_DEBUG : self::FORMAT_DEBUG_NOMAX,
default => $this->max ? self::FORMAT_NORMAL : self::FORMAT_NORMAL_NOMAX,
};
}
private static function initPlaceholderFormatters(): array
@@ -572,6 +564,8 @@ final class ProgressBar
private function buildLine(): string
{
\assert(null !== $this->format);
$regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i";
$callback = function ($matches) {
if ($formatter = $this::getPlaceholderFormatterDefinition($matches[1])) {

View File

@@ -31,20 +31,20 @@ class ProgressIndicator
'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)',
];
private $output;
private $startTime;
private $format;
private $message;
private $indicatorValues;
private $indicatorCurrent;
private $indicatorChangeInterval;
private $indicatorUpdateTime;
private $started = false;
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;
/**
* @var array<string, callable>
*/
private static $formatters;
private static array $formatters;
/**
* @param int $indicatorChangeInterval Change interval in milliseconds
@@ -54,14 +54,8 @@ class ProgressIndicator
{
$this->output = $output;
if (null === $format) {
$format = $this->determineBestFormat();
}
if (null === $indicatorValues) {
$indicatorValues = ['-', '\\', '|', '/'];
}
$format ??= $this->determineBestFormat();
$indicatorValues ??= ['-', '\\', '|', '/'];
$indicatorValues = array_values($indicatorValues);
if (2 > \count($indicatorValues)) {
@@ -146,10 +140,8 @@ class ProgressIndicator
/**
* Gets the format for a given name.
*
* @return string|null
*/
public static function getFormatDefinition(string $name)
public static function getFormatDefinition(string $name): ?string
{
return self::FORMATS[$name] ?? null;
}
@@ -161,23 +153,17 @@ class ProgressIndicator
*/
public static function setPlaceholderFormatterDefinition(string $name, callable $callable)
{
if (!self::$formatters) {
self::$formatters = self::initPlaceholderFormatters();
}
self::$formatters ??= self::initPlaceholderFormatters();
self::$formatters[$name] = $callable;
}
/**
* Gets the placeholder formatter for a given name (including the delimiter char like %).
*
* @return callable|null
*/
public static function getPlaceholderFormatterDefinition(string $name)
public static function getPlaceholderFormatterDefinition(string $name): ?callable
{
if (!self::$formatters) {
self::$formatters = self::initPlaceholderFormatters();
}
self::$formatters ??= self::initPlaceholderFormatters();
return self::$formatters[$name] ?? null;
}
@@ -199,16 +185,13 @@ class ProgressIndicator
private function determineBestFormat(): string
{
switch ($this->output->getVerbosity()) {
return match ($this->output->getVerbosity()) {
// OutputInterface::VERBOSITY_QUIET: display is disabled anyway
case OutputInterface::VERBOSITY_VERBOSE:
return $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi';
case OutputInterface::VERBOSITY_VERY_VERBOSE:
case OutputInterface::VERBOSITY_DEBUG:
return $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi';
default:
return $this->output->isDecorated() ? 'normal' : 'normal_no_ansi';
}
OutputInterface::VERBOSITY_VERBOSE => $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi',
OutputInterface::VERBOSITY_VERY_VERBOSE,
OutputInterface::VERBOSITY_DEBUG => $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi',
default => $this->output->isDecorated() ? 'normal' : 'normal_no_ansi',
};
}
/**
@@ -229,6 +212,9 @@ class ProgressIndicator
return round(microtime(true) * 1000);
}
/**
* @return array<string, \Closure>
*/
private static function initPlaceholderFormatters(): array
{
return [

View File

@@ -39,8 +39,8 @@ class QuestionHelper extends Helper
*/
private $inputStream;
private static $stty = true;
private static $stdinIsInteractive;
private static bool $stty = true;
private static bool $stdinIsInteractive;
/**
* Asks a question to the user.
@@ -49,7 +49,7 @@ class QuestionHelper extends Helper
*
* @throws RuntimeException If there is no data to read in the input stream
*/
public function ask(InputInterface $input, OutputInterface $output, Question $question)
public function ask(InputInterface $input, OutputInterface $output, Question $question): mixed
{
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
@@ -84,10 +84,7 @@ class QuestionHelper extends Helper
}
}
/**
* {@inheritdoc}
*/
public function getName()
public function getName(): string
{
return 'question';
}
@@ -103,11 +100,9 @@ class QuestionHelper extends Helper
/**
* Asks the question to the user.
*
* @return mixed
*
* @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
*/
private function doAsk(OutputInterface $output, Question $question)
private function doAsk(OutputInterface $output, Question $question): mixed
{
$this->writePrompt($output, $question);
@@ -142,6 +137,7 @@ class QuestionHelper extends Helper
}
if ($output instanceof ConsoleSectionOutput) {
$output->addContent(''); // add EOL to the question
$output->addContent($ret);
}
@@ -154,10 +150,7 @@ class QuestionHelper extends Helper
return $ret;
}
/**
* @return mixed
*/
private function getDefaultAnswer(Question $question)
private function getDefaultAnswer(Question $question): mixed
{
$default = $question->getDefault();
@@ -205,7 +198,7 @@ class QuestionHelper extends Helper
/**
* @return string[]
*/
protected function formatChoiceQuestionChoices(ChoiceQuestion $question, string $tag)
protected function formatChoiceQuestionChoices(ChoiceQuestion $question, string $tag): array
{
$messages = [];
@@ -411,7 +404,7 @@ class QuestionHelper extends Helper
$exe = __DIR__.'/../Resources/bin/hiddeninput.exe';
// handle code running from a phar
if ('phar:' === substr(__FILE__, 0, 5)) {
if (str_starts_with(__FILE__, 'phar:')) {
$tmpExe = sys_get_temp_dir().'/hiddeninput.exe';
copy($exe, $tmpExe);
$exe = $tmpExe;
@@ -437,6 +430,11 @@ class QuestionHelper extends Helper
$value = fgets($inputStream, 4096);
if (4095 === \strlen($value)) {
$errOutput = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output;
$errOutput->warning('The value was possibly truncated by your shell or terminal emulator');
}
if (self::$stty && Terminal::hasSttyAvailable()) {
shell_exec('stty '.$sttyMode);
}
@@ -457,11 +455,9 @@ class QuestionHelper extends Helper
*
* @param callable $interviewer A callable that will ask for a question and return the result
*
* @return mixed The validated response
*
* @throws \Exception In case the max number of attempts has been reached and no valid response has been given
*/
private function validateAttempts(callable $interviewer, OutputInterface $output, Question $question)
private function validateAttempts(callable $interviewer, OutputInterface $output, Question $question): mixed
{
$error = null;
$attempts = $question->getMaxAttempts();
@@ -488,7 +484,7 @@ class QuestionHelper extends Helper
return false;
}
if (null !== self::$stdinIsInteractive) {
if (isset(self::$stdinIsInteractive)) {
return self::$stdinIsInteractive;
}
@@ -514,10 +510,8 @@ class QuestionHelper extends Helper
*
* @param resource $inputStream The handler resource
* @param Question $question The question being asked
*
* @return string|false The input received, false in case input could not be read
*/
private function readInput($inputStream, Question $question)
private function readInput($inputStream, Question $question): string|false
{
if (!$question->isMultiline()) {
$cp = $this->setIOCodepage();
@@ -543,11 +537,6 @@ class QuestionHelper extends Helper
return $this->resetIOCodepage($cp, $ret);
}
/**
* Sets console I/O to the host code page.
*
* @return int Previous code page in IBM/EBCDIC format
*/
private function setIOCodepage(): int
{
if (\function_exists('sapi_windows_cp_set')) {
@@ -562,12 +551,8 @@ class QuestionHelper extends Helper
/**
* Sets console I/O to the specified code page and converts the user input.
*
* @param string|false $input
*
* @return string|false
*/
private function resetIOCodepage(int $cp, $input)
private function resetIOCodepage(int $cp, string|false $input): string|false
{
if (0 !== $cp) {
sapi_windows_cp_set($cp);

View File

@@ -25,9 +25,6 @@ use Symfony\Component\Console\Style\SymfonyStyle;
*/
class SymfonyQuestionHelper extends QuestionHelper
{
/**
* {@inheritdoc}
*/
protected function writePrompt(OutputInterface $output, Question $question)
{
$text = OutputFormatter::escapeTrailingBackslash($question->getQuestion());
@@ -83,9 +80,6 @@ class SymfonyQuestionHelper extends QuestionHelper
$output->write($prompt);
}
/**
* {@inheritdoc}
*/
protected function writeError(OutputInterface $output, \Exception $error)
{
if ($output instanceof SymfonyStyle) {

View File

@@ -35,70 +35,31 @@ class Table
private const SEPARATOR_BOTTOM = 3;
private const BORDER_OUTSIDE = 0;
private const BORDER_INSIDE = 1;
private const DISPLAY_ORIENTATION_DEFAULT = 'default';
private const DISPLAY_ORIENTATION_HORIZONTAL = 'horizontal';
private const DISPLAY_ORIENTATION_VERTICAL = 'vertical';
private $headerTitle;
private $footerTitle;
private ?string $headerTitle = null;
private ?string $footerTitle = null;
private array $headers = [];
private array $rows = [];
private array $effectiveColumnWidths = [];
private int $numberOfColumns;
private OutputInterface $output;
private TableStyle $style;
private array $columnStyles = [];
private array $columnWidths = [];
private array $columnMaxWidths = [];
private bool $rendered = false;
private string $displayOrientation = self::DISPLAY_ORIENTATION_DEFAULT;
/**
* Table headers.
*/
private $headers = [];
/**
* Table rows.
*/
private $rows = [];
private $horizontal = false;
/**
* Column widths cache.
*/
private $effectiveColumnWidths = [];
/**
* Number of columns cache.
*
* @var int
*/
private $numberOfColumns;
/**
* @var OutputInterface
*/
private $output;
/**
* @var TableStyle
*/
private $style;
/**
* @var array
*/
private $columnStyles = [];
/**
* User set column widths.
*
* @var array
*/
private $columnWidths = [];
private $columnMaxWidths = [];
/**
* @var array<string, TableStyle>|null
*/
private static $styles;
private $rendered = false;
private static array $styles;
public function __construct(OutputInterface $output)
{
$this->output = $output;
if (!self::$styles) {
self::$styles = self::initStyles();
}
self::$styles ??= self::initStyles();
$this->setStyle('default');
}
@@ -108,39 +69,27 @@ class Table
*/
public static function setStyleDefinition(string $name, TableStyle $style)
{
if (!self::$styles) {
self::$styles = self::initStyles();
}
self::$styles ??= self::initStyles();
self::$styles[$name] = $style;
}
/**
* Gets a style definition by name.
*
* @return TableStyle
*/
public static function getStyleDefinition(string $name)
public static function getStyleDefinition(string $name): TableStyle
{
if (!self::$styles) {
self::$styles = self::initStyles();
}
self::$styles ??= self::initStyles();
if (isset(self::$styles[$name])) {
return self::$styles[$name];
}
throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
return self::$styles[$name] ?? throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
}
/**
* Sets table style.
*
* @param TableStyle|string $name The style name or a TableStyle instance
*
* @return $this
*/
public function setStyle($name)
public function setStyle(TableStyle|string $name): static
{
$this->style = $this->resolveStyle($name);
@@ -149,10 +98,8 @@ class Table
/**
* Gets the current table style.
*
* @return TableStyle
*/
public function getStyle()
public function getStyle(): TableStyle
{
return $this->style;
}
@@ -164,7 +111,7 @@ class Table
*
* @return $this
*/
public function setColumnStyle(int $columnIndex, $name)
public function setColumnStyle(int $columnIndex, TableStyle|string $name): static
{
$this->columnStyles[$columnIndex] = $this->resolveStyle($name);
@@ -175,10 +122,8 @@ class Table
* Gets the current style for a column.
*
* If style was not set, it returns the global table style.
*
* @return TableStyle
*/
public function getColumnStyle(int $columnIndex)
public function getColumnStyle(int $columnIndex): TableStyle
{
return $this->columnStyles[$columnIndex] ?? $this->getStyle();
}
@@ -188,7 +133,7 @@ class Table
*
* @return $this
*/
public function setColumnWidth(int $columnIndex, int $width)
public function setColumnWidth(int $columnIndex, int $width): static
{
$this->columnWidths[$columnIndex] = $width;
@@ -200,7 +145,7 @@ class Table
*
* @return $this
*/
public function setColumnWidths(array $widths)
public function setColumnWidths(array $widths): static
{
$this->columnWidths = [];
foreach ($widths as $index => $width) {
@@ -218,7 +163,7 @@ class Table
*
* @return $this
*/
public function setColumnMaxWidth(int $columnIndex, int $width): self
public function setColumnMaxWidth(int $columnIndex, int $width): static
{
if (!$this->output->getFormatter() instanceof WrappableOutputFormatterInterface) {
throw new \LogicException(sprintf('Setting a maximum column width is only supported when using a "%s" formatter, got "%s".', WrappableOutputFormatterInterface::class, get_debug_type($this->output->getFormatter())));
@@ -232,10 +177,10 @@ class Table
/**
* @return $this
*/
public function setHeaders(array $headers)
public function setHeaders(array $headers): static
{
$headers = array_values($headers);
if (!empty($headers) && !\is_array($headers[0])) {
if ($headers && !\is_array($headers[0])) {
$headers = [$headers];
}
@@ -244,6 +189,9 @@ class Table
return $this;
}
/**
* @return $this
*/
public function setRows(array $rows)
{
$this->rows = [];
@@ -254,7 +202,7 @@ class Table
/**
* @return $this
*/
public function addRows(array $rows)
public function addRows(array $rows): static
{
foreach ($rows as $row) {
$this->addRow($row);
@@ -266,7 +214,7 @@ class Table
/**
* @return $this
*/
public function addRow($row)
public function addRow(TableSeparator|array $row): static
{
if ($row instanceof TableSeparator) {
$this->rows[] = $row;
@@ -274,10 +222,6 @@ class Table
return $this;
}
if (!\is_array($row)) {
throw new InvalidArgumentException('A row must be an array or a TableSeparator instance.');
}
$this->rows[] = array_values($row);
return $this;
@@ -288,7 +232,7 @@ class Table
*
* @return $this
*/
public function appendRow($row): self
public function appendRow(TableSeparator|array $row): static
{
if (!$this->output instanceof ConsoleSectionOutput) {
throw new RuntimeException(sprintf('Output should be an instance of "%s" when calling "%s".', ConsoleSectionOutput::class, __METHOD__));
@@ -307,7 +251,7 @@ class Table
/**
* @return $this
*/
public function setRow($column, array $row)
public function setRow(int|string $column, array $row): static
{
$this->rows[$column] = $row;
@@ -317,7 +261,7 @@ class Table
/**
* @return $this
*/
public function setHeaderTitle(?string $title): self
public function setHeaderTitle(?string $title): static
{
$this->headerTitle = $title;
@@ -327,7 +271,7 @@ class Table
/**
* @return $this
*/
public function setFooterTitle(?string $title): self
public function setFooterTitle(?string $title): static
{
$this->footerTitle = $title;
@@ -337,9 +281,19 @@ class Table
/**
* @return $this
*/
public function setHorizontal(bool $horizontal = true): self
public function setHorizontal(bool $horizontal = true): static
{
$this->horizontal = $horizontal;
$this->displayOrientation = $horizontal ? self::DISPLAY_ORIENTATION_HORIZONTAL : self::DISPLAY_ORIENTATION_DEFAULT;
return $this;
}
/**
* @return $this
*/
public function setVertical(bool $vertical = true): static
{
$this->displayOrientation = $vertical ? self::DISPLAY_ORIENTATION_VERTICAL : self::DISPLAY_ORIENTATION_DEFAULT;
return $this;
}
@@ -360,8 +314,13 @@ class Table
public function render()
{
$divider = new TableSeparator();
if ($this->horizontal) {
$rows = [];
$isCellWithColspan = static fn ($cell) => $cell instanceof TableCell && $cell->getColspan() >= 2;
$horizontal = self::DISPLAY_ORIENTATION_HORIZONTAL === $this->displayOrientation;
$vertical = self::DISPLAY_ORIENTATION_VERTICAL === $this->displayOrientation;
$rows = [];
if ($horizontal) {
foreach ($this->headers[0] ?? [] as $i => $header) {
$rows[$i] = [$header];
foreach ($this->rows as $row) {
@@ -370,13 +329,48 @@ class Table
}
if (isset($row[$i])) {
$rows[$i][] = $row[$i];
} elseif ($rows[$i][0] instanceof TableCell && $rows[$i][0]->getColspan() >= 2) {
} elseif ($isCellWithColspan($rows[$i][0])) {
// Noop, there is a "title"
} else {
$rows[$i][] = null;
}
}
}
} elseif ($vertical) {
$formatter = $this->output->getFormatter();
$maxHeaderLength = array_reduce($this->headers[0] ?? [], static fn ($max, $header) => max($max, Helper::width(Helper::removeDecoration($formatter, $header))), 0);
foreach ($this->rows as $row) {
if ($row instanceof TableSeparator) {
continue;
}
if ($rows) {
$rows[] = [$divider];
}
$containsColspan = false;
foreach ($row as $cell) {
if ($containsColspan = $isCellWithColspan($cell)) {
break;
}
}
$headers = $this->headers[0] ?? [];
$maxRows = max(\count($headers), \count($row));
for ($i = 0; $i < $maxRows; ++$i) {
$cell = (string) ($row[$i] ?? '');
if ($headers && !$containsColspan) {
$rows[] = [sprintf(
'<comment>%s</>: %s',
str_pad($headers[$i] ?? '', $maxHeaderLength, ' ', \STR_PAD_LEFT),
$cell
)];
} elseif ('' !== $cell) {
$rows[] = [$cell];
}
}
}
} else {
$rows = array_merge($this->headers, [$divider], $this->rows);
}
@@ -386,8 +380,8 @@ class Table
$rowGroups = $this->buildTableRows($rows);
$this->calculateColumnsWidth($rowGroups);
$isHeader = !$this->horizontal;
$isFirstRow = $this->horizontal;
$isHeader = !$horizontal;
$isFirstRow = $horizontal;
$hasTitle = (bool) $this->headerTitle;
foreach ($rowGroups as $rowGroup) {
@@ -431,7 +425,12 @@ class Table
$hasTitle = false;
}
if ($this->horizontal) {
if ($vertical) {
$isHeader = false;
$isFirstRow = false;
}
if ($horizontal) {
$this->renderRow($row, $this->style->getCellRowFormat(), $this->style->getCellHeaderFormat());
} else {
$this->renderRow($row, $isHeader ? $this->style->getCellHeaderFormat() : $this->style->getCellRowFormat());
@@ -453,7 +452,7 @@ class Table
*/
private function renderRowSeparator(int $type = self::SEPARATOR_MID, string $title = null, string $titleFormat = null)
{
if (0 === $count = $this->numberOfColumns) {
if (!$count = $this->numberOfColumns) {
return;
}
@@ -570,11 +569,11 @@ class Table
$cellFormat = '<'.$tag.'>%s</>';
}
if (strstr($content, '</>')) {
if (str_contains($content, '</>')) {
$content = str_replace('</>', '', $content);
$width -= 3;
}
if (strstr($content, '<fg=default;bg=default>')) {
if (str_contains($content, '<fg=default;bg=default>')) {
$content = str_replace('<fg=default;bg=default>', '', $content);
$width -= \strlen('<fg=default;bg=default>');
}
@@ -618,10 +617,10 @@ class Table
if (isset($this->columnMaxWidths[$column]) && Helper::width(Helper::removeDecoration($formatter, $cell)) > $this->columnMaxWidths[$column]) {
$cell = $formatter->formatAndWrap($cell, $this->columnMaxWidths[$column] * $colspan);
}
if (!strstr($cell ?? '', "\n")) {
if (!str_contains($cell ?? '', "\n")) {
continue;
}
$escaped = implode("\n", array_map([OutputFormatter::class, 'escapeTrailingBackslash'], explode("\n", $cell)));
$escaped = implode("\n", array_map(OutputFormatter::escapeTrailingBackslash(...), explode("\n", $cell)));
$cell = $cell instanceof TableCell ? new TableCell($escaped, ['colspan' => $cell->getColspan()]) : $escaped;
$lines = explode("\n", str_replace("\n", "<fg=default;bg=default></>\n", $cell));
foreach ($lines as $lineKey => $line) {
@@ -662,7 +661,7 @@ class Table
++$numberOfRows; // Add row for header separator
}
if (\count($this->rows) > 0) {
if ($this->rows) {
++$numberOfRows; // Add row for footer separator
}
@@ -678,13 +677,13 @@ class Table
{
$unmergedRows = [];
foreach ($rows[$line] as $column => $cell) {
if (null !== $cell && !$cell instanceof TableCell && !\is_scalar($cell) && !(\is_object($cell) && method_exists($cell, '__toString'))) {
if (null !== $cell && !$cell instanceof TableCell && !\is_scalar($cell) && !$cell instanceof \Stringable) {
throw new InvalidArgumentException(sprintf('A cell must be a TableCell, a scalar or an object implementing "__toString()", "%s" given.', get_debug_type($cell)));
}
if ($cell instanceof TableCell && $cell->getRowspan() > 1) {
$nbLines = $cell->getRowspan() - 1;
$lines = [$cell];
if (strstr($cell, "\n")) {
if (str_contains($cell, "\n")) {
$lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell));
$nbLines = \count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines;
@@ -847,7 +846,7 @@ class Table
private function cleanup()
{
$this->effectiveColumnWidths = [];
$this->numberOfColumns = null;
unset($this->numberOfColumns);
}
/**
@@ -900,16 +899,12 @@ class Table
];
}
private function resolveStyle($name): TableStyle
private function resolveStyle(TableStyle|string $name): TableStyle
{
if ($name instanceof TableStyle) {
return $name;
}
if (isset(self::$styles[$name])) {
return self::$styles[$name];
}
throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
return self::$styles[$name] ?? throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
}
}

View File

@@ -18,8 +18,8 @@ use Symfony\Component\Console\Exception\InvalidArgumentException;
*/
class TableCell
{
private $value;
private $options = [
private string $value;
private array $options = [
'rowspan' => 1,
'colspan' => 1,
'style' => null,
@@ -43,30 +43,24 @@ class TableCell
/**
* Returns the cell value.
*
* @return string
*/
public function __toString()
public function __toString(): string
{
return $this->value;
}
/**
* Gets number of colspan.
*
* @return int
*/
public function getColspan()
public function getColspan(): int
{
return (int) $this->options['colspan'];
}
/**
* Gets number of rowspan.
*
* @return int
*/
public function getRowspan()
public function getRowspan(): int
{
return (int) $this->options['rowspan'];
}

View File

@@ -32,7 +32,7 @@ class TableCellStyle
'right' => \STR_PAD_LEFT,
];
private $options = [
private array $options = [
'fg' => 'default',
'bg' => 'default',
'options' => null,
@@ -63,7 +63,7 @@ class TableCellStyle
*
* @return string[]
*/
public function getTagOptions()
public function getTagOptions(): array
{
return array_filter(
$this->getOptions(),
@@ -74,10 +74,7 @@ class TableCellStyle
);
}
/**
* @return int
*/
public function getPadByAlign()
public function getPadByAlign(): int
{
return self::ALIGN_MAP[$this->getOptions()['align']];
}

View File

@@ -16,7 +16,7 @@ namespace Symfony\Component\Console\Helper;
*/
class TableRows implements \IteratorAggregate
{
private $generator;
private \Closure $generator;
public function __construct(\Closure $generator)
{

View File

@@ -23,37 +23,37 @@ use Symfony\Component\Console\Exception\LogicException;
*/
class TableStyle
{
private $paddingChar = ' ';
private $horizontalOutsideBorderChar = '-';
private $horizontalInsideBorderChar = '-';
private $verticalOutsideBorderChar = '|';
private $verticalInsideBorderChar = '|';
private $crossingChar = '+';
private $crossingTopRightChar = '+';
private $crossingTopMidChar = '+';
private $crossingTopLeftChar = '+';
private $crossingMidRightChar = '+';
private $crossingBottomRightChar = '+';
private $crossingBottomMidChar = '+';
private $crossingBottomLeftChar = '+';
private $crossingMidLeftChar = '+';
private $crossingTopLeftBottomChar = '+';
private $crossingTopMidBottomChar = '+';
private $crossingTopRightBottomChar = '+';
private $headerTitleFormat = '<fg=black;bg=white;options=bold> %s </>';
private $footerTitleFormat = '<fg=black;bg=white;options=bold> %s </>';
private $cellHeaderFormat = '<info>%s</info>';
private $cellRowFormat = '%s';
private $cellRowContentFormat = ' %s ';
private $borderFormat = '%s';
private $padType = \STR_PAD_RIGHT;
private string $paddingChar = ' ';
private string $horizontalOutsideBorderChar = '-';
private string $horizontalInsideBorderChar = '-';
private string $verticalOutsideBorderChar = '|';
private string $verticalInsideBorderChar = '|';
private string $crossingChar = '+';
private string $crossingTopRightChar = '+';
private string $crossingTopMidChar = '+';
private string $crossingTopLeftChar = '+';
private string $crossingMidRightChar = '+';
private string $crossingBottomRightChar = '+';
private string $crossingBottomMidChar = '+';
private string $crossingBottomLeftChar = '+';
private string $crossingMidLeftChar = '+';
private string $crossingTopLeftBottomChar = '+';
private string $crossingTopMidBottomChar = '+';
private string $crossingTopRightBottomChar = '+';
private string $headerTitleFormat = '<fg=black;bg=white;options=bold> %s </>';
private string $footerTitleFormat = '<fg=black;bg=white;options=bold> %s </>';
private string $cellHeaderFormat = '<info>%s</info>';
private string $cellRowFormat = '%s';
private string $cellRowContentFormat = ' %s ';
private string $borderFormat = '%s';
private int $padType = \STR_PAD_RIGHT;
/**
* Sets padding character, used for cell padding.
*
* @return $this
*/
public function setPaddingChar(string $paddingChar)
public function setPaddingChar(string $paddingChar): static
{
if (!$paddingChar) {
throw new LogicException('The padding char must not be empty.');
@@ -66,10 +66,8 @@ class TableStyle
/**
* Gets padding character, used for cell padding.
*
* @return string
*/
public function getPaddingChar()
public function getPaddingChar(): string
{
return $this->paddingChar;
}
@@ -90,7 +88,7 @@ class TableStyle
*
* @return $this
*/
public function setHorizontalBorderChars(string $outside, string $inside = null): self
public function setHorizontalBorderChars(string $outside, string $inside = null): static
{
$this->horizontalOutsideBorderChar = $outside;
$this->horizontalInsideBorderChar = $inside ?? $outside;
@@ -115,7 +113,7 @@ class TableStyle
*
* @return $this
*/
public function setVerticalBorderChars(string $outside, string $inside = null): self
public function setVerticalBorderChars(string $outside, string $inside = null): static
{
$this->verticalOutsideBorderChar = $outside;
$this->verticalInsideBorderChar = $inside ?? $outside;
@@ -169,7 +167,7 @@ class TableStyle
*
* @return $this
*/
public function setCrossingChars(string $cross, string $topLeft, string $topMid, string $topRight, string $midRight, string $bottomRight, string $bottomMid, string $bottomLeft, string $midLeft, string $topLeftBottom = null, string $topMidBottom = null, string $topRightBottom = null): self
public function setCrossingChars(string $cross, string $topLeft, string $topMid, string $topRight, string $midRight, string $bottomRight, string $bottomMid, string $bottomLeft, string $midLeft, string $topLeftBottom = null, string $topMidBottom = null, string $topRightBottom = null): static
{
$this->crossingChar = $cross;
$this->crossingTopLeftChar = $topLeft;
@@ -199,10 +197,8 @@ class TableStyle
/**
* Gets crossing character.
*
* @return string
*/
public function getCrossingChar()
public function getCrossingChar(): string
{
return $this->crossingChar;
}
@@ -235,7 +231,7 @@ class TableStyle
*
* @return $this
*/
public function setCellHeaderFormat(string $cellHeaderFormat)
public function setCellHeaderFormat(string $cellHeaderFormat): static
{
$this->cellHeaderFormat = $cellHeaderFormat;
@@ -244,10 +240,8 @@ class TableStyle
/**
* Gets header cell format.
*
* @return string
*/
public function getCellHeaderFormat()
public function getCellHeaderFormat(): string
{
return $this->cellHeaderFormat;
}
@@ -257,7 +251,7 @@ class TableStyle
*
* @return $this
*/
public function setCellRowFormat(string $cellRowFormat)
public function setCellRowFormat(string $cellRowFormat): static
{
$this->cellRowFormat = $cellRowFormat;
@@ -266,10 +260,8 @@ class TableStyle
/**
* Gets row cell format.
*
* @return string
*/
public function getCellRowFormat()
public function getCellRowFormat(): string
{
return $this->cellRowFormat;
}
@@ -279,7 +271,7 @@ class TableStyle
*
* @return $this
*/
public function setCellRowContentFormat(string $cellRowContentFormat)
public function setCellRowContentFormat(string $cellRowContentFormat): static
{
$this->cellRowContentFormat = $cellRowContentFormat;
@@ -288,10 +280,8 @@ class TableStyle
/**
* Gets row cell content format.
*
* @return string
*/
public function getCellRowContentFormat()
public function getCellRowContentFormat(): string
{
return $this->cellRowContentFormat;
}
@@ -301,7 +291,7 @@ class TableStyle
*
* @return $this
*/
public function setBorderFormat(string $borderFormat)
public function setBorderFormat(string $borderFormat): static
{
$this->borderFormat = $borderFormat;
@@ -310,10 +300,8 @@ class TableStyle
/**
* Gets table border format.
*
* @return string
*/
public function getBorderFormat()
public function getBorderFormat(): string
{
return $this->borderFormat;
}
@@ -323,7 +311,7 @@ class TableStyle
*
* @return $this
*/
public function setPadType(int $padType)
public function setPadType(int $padType): static
{
if (!\in_array($padType, [\STR_PAD_LEFT, \STR_PAD_RIGHT, \STR_PAD_BOTH], true)) {
throw new InvalidArgumentException('Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH).');
@@ -336,10 +324,8 @@ class TableStyle
/**
* Gets cell padding type.
*
* @return int
*/
public function getPadType()
public function getPadType(): int
{
return $this->padType;
}
@@ -352,7 +338,7 @@ class TableStyle
/**
* @return $this
*/
public function setHeaderTitleFormat(string $format): self
public function setHeaderTitleFormat(string $format): static
{
$this->headerTitleFormat = $format;
@@ -367,7 +353,7 @@ class TableStyle
/**
* @return $this
*/
public function setFooterTitleFormat(string $format): self
public function setFooterTitleFormat(string $format): static
{
$this->footerTitleFormat = $format;

View File

@@ -40,12 +40,12 @@ use Symfony\Component\Console\Exception\RuntimeException;
*/
class ArgvInput extends Input
{
private $tokens;
private $parsed;
private array $tokens;
private array $parsed;
public function __construct(array $argv = null, InputDefinition $definition = null)
{
$argv = $argv ?? $_SERVER['argv'] ?? [];
$argv ??= $_SERVER['argv'] ?? [];
// strip the application name
array_shift($argv);
@@ -60,9 +60,6 @@ class ArgvInput extends Input
$this->tokens = $tokens;
}
/**
* {@inheritdoc}
*/
protected function parse()
{
$parseOptions = true;
@@ -199,7 +196,7 @@ class ArgvInput extends Input
*
* @throws RuntimeException When option given doesn't exist
*/
private function addShortOption(string $shortcut, $value)
private function addShortOption(string $shortcut, mixed $value)
{
if (!$this->definition->hasShortcut($shortcut)) {
throw new RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut));
@@ -213,7 +210,7 @@ class ArgvInput extends Input
*
* @throws RuntimeException When option given doesn't exist
*/
private function addLongOption(string $name, $value)
private function addLongOption(string $name, mixed $value)
{
if (!$this->definition->hasOption($name)) {
if (!$this->definition->hasNegation($name)) {
@@ -263,10 +260,7 @@ class ArgvInput extends Input
}
}
/**
* {@inheritdoc}
*/
public function getFirstArgument()
public function getFirstArgument(): ?string
{
$isOption = false;
foreach ($this->tokens as $i => $token) {
@@ -298,10 +292,7 @@ class ArgvInput extends Input
return null;
}
/**
* {@inheritdoc}
*/
public function hasParameterOption($values, bool $onlyParams = false)
public function hasParameterOption(string|array $values, bool $onlyParams = false): bool
{
$values = (array) $values;
@@ -323,10 +314,7 @@ class ArgvInput extends Input
return false;
}
/**
* {@inheritdoc}
*/
public function getParameterOption($values, $default = false, bool $onlyParams = false)
public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false): mixed
{
$values = (array) $values;
$tokens = $this->tokens;
@@ -356,10 +344,8 @@ class ArgvInput extends Input
/**
* Returns a stringified representation of the args passed to the command.
*
* @return string
*/
public function __toString()
public function __toString(): string
{
$tokens = array_map(function ($token) {
if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) {

View File

@@ -25,7 +25,7 @@ use Symfony\Component\Console\Exception\InvalidOptionException;
*/
class ArrayInput extends Input
{
private $parameters;
private array $parameters;
public function __construct(array $parameters, InputDefinition $definition = null)
{
@@ -34,10 +34,7 @@ class ArrayInput extends Input
parent::__construct($definition);
}
/**
* {@inheritdoc}
*/
public function getFirstArgument()
public function getFirstArgument(): ?string
{
foreach ($this->parameters as $param => $value) {
if ($param && \is_string($param) && '-' === $param[0]) {
@@ -50,10 +47,7 @@ class ArrayInput extends Input
return null;
}
/**
* {@inheritdoc}
*/
public function hasParameterOption($values, bool $onlyParams = false)
public function hasParameterOption(string|array $values, bool $onlyParams = false): bool
{
$values = (array) $values;
@@ -74,10 +68,7 @@ class ArrayInput extends Input
return false;
}
/**
* {@inheritdoc}
*/
public function getParameterOption($values, $default = false, bool $onlyParams = false)
public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false): mixed
{
$values = (array) $values;
@@ -100,10 +91,8 @@ class ArrayInput extends Input
/**
* Returns a stringified representation of the args passed to the command.
*
* @return string
*/
public function __toString()
public function __toString(): string
{
$params = [];
foreach ($this->parameters as $param => $val) {
@@ -117,16 +106,13 @@ class ArrayInput extends Input
$params[] = $param.('' != $val ? $glue.$this->escapeToken($val) : '');
}
} else {
$params[] = \is_array($val) ? implode(' ', array_map([$this, 'escapeToken'], $val)) : $this->escapeToken($val);
$params[] = \is_array($val) ? implode(' ', array_map($this->escapeToken(...), $val)) : $this->escapeToken($val);
}
}
return implode(' ', $params);
}
/**
* {@inheritdoc}
*/
protected function parse()
{
foreach ($this->parameters as $key => $value) {
@@ -148,7 +134,7 @@ class ArrayInput extends Input
*
* @throws InvalidOptionException When option given doesn't exist
*/
private function addShortOption(string $shortcut, $value)
private function addShortOption(string $shortcut, mixed $value)
{
if (!$this->definition->hasShortcut($shortcut)) {
throw new InvalidOptionException(sprintf('The "-%s" option does not exist.', $shortcut));
@@ -163,7 +149,7 @@ class ArrayInput extends Input
* @throws InvalidOptionException When option given doesn't exist
* @throws InvalidOptionException When a required value is missing
*/
private function addLongOption(string $name, $value)
private function addLongOption(string $name, mixed $value)
{
if (!$this->definition->hasOption($name)) {
if (!$this->definition->hasNegation($name)) {
@@ -194,12 +180,9 @@ class ArrayInput extends Input
/**
* Adds an argument value.
*
* @param string|int $name The argument name
* @param mixed $value The value for the argument
*
* @throws InvalidArgumentException When argument given doesn't exist
*/
private function addArgument($name, $value)
private function addArgument(string|int $name, mixed $value)
{
if (!$this->definition->hasArgument($name)) {
throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));

View File

@@ -43,9 +43,6 @@ abstract class Input implements InputInterface, StreamableInputInterface
}
}
/**
* {@inheritdoc}
*/
public function bind(InputDefinition $definition)
{
$this->arguments = [];
@@ -60,9 +57,6 @@ abstract class Input implements InputInterface, StreamableInputInterface
*/
abstract protected function parse();
/**
* {@inheritdoc}
*/
public function validate()
{
$definition = $this->definition;
@@ -77,34 +71,22 @@ abstract class Input implements InputInterface, StreamableInputInterface
}
}
/**
* {@inheritdoc}
*/
public function isInteractive()
public function isInteractive(): bool
{
return $this->interactive;
}
/**
* {@inheritdoc}
*/
public function setInteractive(bool $interactive)
{
$this->interactive = $interactive;
}
/**
* {@inheritdoc}
*/
public function getArguments()
public function getArguments(): array
{
return array_merge($this->definition->getArgumentDefaults(), $this->arguments);
}
/**
* {@inheritdoc}
*/
public function getArgument(string $name)
public function getArgument(string $name): mixed
{
if (!$this->definition->hasArgument($name)) {
throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
@@ -113,10 +95,7 @@ abstract class Input implements InputInterface, StreamableInputInterface
return $this->arguments[$name] ?? $this->definition->getArgument($name)->getDefault();
}
/**
* {@inheritdoc}
*/
public function setArgument(string $name, $value)
public function setArgument(string $name, mixed $value)
{
if (!$this->definition->hasArgument($name)) {
throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
@@ -125,26 +104,17 @@ abstract class Input implements InputInterface, StreamableInputInterface
$this->arguments[$name] = $value;
}
/**
* {@inheritdoc}
*/
public function hasArgument(string $name)
public function hasArgument(string $name): bool
{
return $this->definition->hasArgument($name);
}
/**
* {@inheritdoc}
*/
public function getOptions()
public function getOptions(): array
{
return array_merge($this->definition->getOptionDefaults(), $this->options);
}
/**
* {@inheritdoc}
*/
public function getOption(string $name)
public function getOption(string $name): mixed
{
if ($this->definition->hasNegation($name)) {
if (null === $value = $this->getOption($this->definition->negationToName($name))) {
@@ -161,10 +131,7 @@ abstract class Input implements InputInterface, StreamableInputInterface
return \array_key_exists($name, $this->options) ? $this->options[$name] : $this->definition->getOption($name)->getDefault();
}
/**
* {@inheritdoc}
*/
public function setOption(string $name, $value)
public function setOption(string $name, mixed $value)
{
if ($this->definition->hasNegation($name)) {
$this->options[$this->definition->negationToName($name)] = !$value;
@@ -177,35 +144,24 @@ abstract class Input implements InputInterface, StreamableInputInterface
$this->options[$name] = $value;
}
/**
* {@inheritdoc}
*/
public function hasOption(string $name)
public function hasOption(string $name): bool
{
return $this->definition->hasOption($name) || $this->definition->hasNegation($name);
}
/**
* Escapes a token through escapeshellarg if it contains unsafe chars.
*
* @return string
*/
public function escapeToken(string $token)
public function escapeToken(string $token): string
{
return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token);
}
/**
* {@inheritdoc}
*/
public function setStream($stream)
{
$this->stream = $stream;
}
/**
* {@inheritdoc}
*/
public function getStream()
{
return $this->stream;

View File

@@ -11,6 +11,10 @@
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Completion\Suggestion;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
@@ -25,20 +29,22 @@ class InputArgument
public const OPTIONAL = 2;
public const IS_ARRAY = 4;
private $name;
private $mode;
private $default;
private $description;
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
* @param int|null $mode The argument mode: self::REQUIRED or self::OPTIONAL
* @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<string|Suggestion> $suggestedValues The values used for input completion
*
* @throws InvalidArgumentException When argument mode is not valid
*/
public function __construct(string $name, int $mode = null, string $description = '', $default = null)
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;
@@ -49,16 +55,15 @@ class InputArgument
$this->name = $name;
$this->mode = $mode;
$this->description = $description;
$this->suggestedValues = $suggestedValues;
$this->setDefault($default);
}
/**
* Returns the argument name.
*
* @return string
*/
public function getName()
public function getName(): string
{
return $this->name;
}
@@ -68,7 +73,7 @@ class InputArgument
*
* @return bool true if parameter mode is self::REQUIRED, false otherwise
*/
public function isRequired()
public function isRequired(): bool
{
return self::REQUIRED === (self::REQUIRED & $this->mode);
}
@@ -78,7 +83,7 @@ class InputArgument
*
* @return bool true if mode is self::IS_ARRAY, false otherwise
*/
public function isArray()
public function isArray(): bool
{
return self::IS_ARRAY === (self::IS_ARRAY & $this->mode);
}
@@ -86,12 +91,13 @@ class InputArgument
/**
* Sets the default value.
*
* @param string|bool|int|float|array|null $default
*
* @throws LogicException When incorrect default value is given
*/
public function setDefault($default = null)
public function setDefault(string|bool|int|float|array $default = null)
{
if (1 > \func_num_args()) {
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
}
if ($this->isRequired() && null !== $default) {
throw new LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.');
}
@@ -109,20 +115,37 @@ class InputArgument
/**
* Returns the default value.
*
* @return string|bool|int|float|array|null
*/
public function getDefault()
public function getDefault(): string|bool|int|float|array|null
{
return $this->default;
}
public function hasCompletion(): bool
{
return [] !== $this->suggestedValues;
}
/**
* Adds suggestions to $suggestions for the current completion input.
*
* @see Command::complete()
*/
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
$values = $this->suggestedValues;
if ($values instanceof \Closure && !\is_array($values = $values($input))) {
throw new LogicException(sprintf('Closure for argument "%s" must return an array. Got "%s".', $this->name, get_debug_type($values)));
}
if ($values) {
$suggestions->suggestValues($values);
}
}
/**
* Returns the description text.
*
* @return string
*/
public function getDescription()
public function getDescription(): string
{
return $this->description;
}

View File

@@ -28,13 +28,13 @@ use Symfony\Component\Console\Exception\LogicException;
*/
class InputDefinition
{
private $arguments;
private $requiredCount;
private $lastArrayArgument;
private $lastOptionalArgument;
private $options;
private $negations;
private $shortcuts;
private array $arguments = [];
private int $requiredCount = 0;
private ?InputArgument $lastArrayArgument = null;
private ?InputArgument $lastOptionalArgument = null;
private array $options = [];
private array $negations = [];
private array $shortcuts = [];
/**
* @param array $definition An array of InputArgument and InputOption instance
@@ -124,13 +124,9 @@ class InputDefinition
/**
* Returns an InputArgument by name or by position.
*
* @param string|int $name The InputArgument name or position
*
* @return InputArgument
*
* @throws InvalidArgumentException When argument given doesn't exist
*/
public function getArgument($name)
public function getArgument(string|int $name): InputArgument
{
if (!$this->hasArgument($name)) {
throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
@@ -143,12 +139,8 @@ class InputDefinition
/**
* Returns true if an InputArgument object exists by name or position.
*
* @param string|int $name The InputArgument name or position
*
* @return bool
*/
public function hasArgument($name)
public function hasArgument(string|int $name): bool
{
$arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments;
@@ -160,27 +152,23 @@ class InputDefinition
*
* @return InputArgument[]
*/
public function getArguments()
public function getArguments(): array
{
return $this->arguments;
}
/**
* Returns the number of InputArguments.
*
* @return int
*/
public function getArgumentCount()
public function getArgumentCount(): int
{
return null !== $this->lastArrayArgument ? \PHP_INT_MAX : \count($this->arguments);
}
/**
* Returns the number of required InputArguments.
*
* @return int
*/
public function getArgumentRequiredCount()
public function getArgumentRequiredCount(): int
{
return $this->requiredCount;
}
@@ -188,7 +176,7 @@ class InputDefinition
/**
* @return array<string|bool|int|float|array|null>
*/
public function getArgumentDefaults()
public function getArgumentDefaults(): array
{
$values = [];
foreach ($this->arguments as $argument) {
@@ -262,11 +250,9 @@ class InputDefinition
/**
* Returns an InputOption by name.
*
* @return InputOption
*
* @throws InvalidArgumentException When option given doesn't exist
*/
public function getOption(string $name)
public function getOption(string $name): InputOption
{
if (!$this->hasOption($name)) {
throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name));
@@ -280,10 +266,8 @@ class InputDefinition
*
* This method can't be used to check if the user included the option when
* executing the command (use getOption() instead).
*
* @return bool
*/
public function hasOption(string $name)
public function hasOption(string $name): bool
{
return isset($this->options[$name]);
}
@@ -293,17 +277,15 @@ class InputDefinition
*
* @return InputOption[]
*/
public function getOptions()
public function getOptions(): array
{
return $this->options;
}
/**
* Returns true if an InputOption object exists by shortcut.
*
* @return bool
*/
public function hasShortcut(string $name)
public function hasShortcut(string $name): bool
{
return isset($this->shortcuts[$name]);
}
@@ -318,10 +300,8 @@ class InputDefinition
/**
* Gets an InputOption by shortcut.
*
* @return InputOption
*/
public function getOptionForShortcut(string $shortcut)
public function getOptionForShortcut(string $shortcut): InputOption
{
return $this->getOption($this->shortcutToName($shortcut));
}
@@ -329,7 +309,7 @@ class InputDefinition
/**
* @return array<string|bool|int|float|array|null>
*/
public function getOptionDefaults()
public function getOptionDefaults(): array
{
$values = [];
foreach ($this->options as $option) {
@@ -373,10 +353,8 @@ class InputDefinition
/**
* Gets the synopsis.
*
* @return string
*/
public function getSynopsis(bool $short = false)
public function getSynopsis(bool $short = false): string
{
$elements = [];

View File

@@ -18,15 +18,16 @@ use Symfony\Component\Console\Exception\RuntimeException;
* InputInterface is the interface implemented by all input classes.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @method string __toString() Returns a stringified representation of the args passed to the command.
* InputArguments MUST be escaped as well as the InputOption values passed to the command.
*/
interface InputInterface
{
/**
* Returns the first argument from the raw parameters (not parsed).
*
* @return string|null
*/
public function getFirstArgument();
public function getFirstArgument(): ?string;
/**
* Returns true if the raw parameters (not parsed) contain a value.
@@ -38,10 +39,8 @@ interface InputInterface
*
* @param string|array $values The values to look for in the raw parameters (can be an array)
* @param bool $onlyParams Only check real parameters, skip those following an end of options (--) signal
*
* @return bool
*/
public function hasParameterOption($values, bool $onlyParams = false);
public function hasParameterOption(string|array $values, bool $onlyParams = false): bool;
/**
* Returns the value of a raw option (not parsed).
@@ -57,7 +56,7 @@ interface InputInterface
*
* @return mixed
*/
public function getParameterOption($values, $default = false, bool $onlyParams = false);
public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false);
/**
* Binds the current Input instance with the given arguments and options.
@@ -78,7 +77,7 @@ interface InputInterface
*
* @return array<string|bool|int|float|array|null>
*/
public function getArguments();
public function getArguments(): array;
/**
* Returns the argument value for a given argument name.
@@ -92,25 +91,21 @@ interface InputInterface
/**
* Sets an argument value by name.
*
* @param mixed $value The argument value
*
* @throws InvalidArgumentException When argument given doesn't exist
*/
public function setArgument(string $name, $value);
public function setArgument(string $name, mixed $value);
/**
* Returns true if an InputArgument object exists by name or position.
*
* @return bool
*/
public function hasArgument(string $name);
public function hasArgument(string $name): bool;
/**
* Returns all the given options merged with the default values.
*
* @return array<string|bool|int|float|array|null>
*/
public function getOptions();
public function getOptions(): array;
/**
* Returns the option value for a given option name.
@@ -124,25 +119,19 @@ interface InputInterface
/**
* Sets an option value by name.
*
* @param mixed $value The option value
*
* @throws InvalidArgumentException When option given doesn't exist
*/
public function setOption(string $name, $value);
public function setOption(string $name, mixed $value);
/**
* Returns true if an InputOption object exists by name.
*
* @return bool
*/
public function hasOption(string $name);
public function hasOption(string $name): bool;
/**
* Is this input means interactive?
*
* @return bool
*/
public function isInteractive();
public function isInteractive(): bool;
/**
* Sets the input interactivity.

View File

@@ -11,6 +11,10 @@
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Completion\Suggestion;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
@@ -46,20 +50,22 @@ class InputOption
*/
public const VALUE_NEGATABLE = 16;
private $name;
private $shortcut;
private $mode;
private $default;
private $description;
private string $name;
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
* @param int|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<string|Suggestion> $suggestedValues The values used for input completion
*
* @throws InvalidArgumentException If option mode is invalid or incompatible
*/
public function __construct(string $name, $shortcut = null, int $mode = null, string $description = '', $default = null)
public function __construct(string $name, string|array $shortcut = null, int $mode = null, string $description = '', string|bool|int|float|array $default = null, array|\Closure $suggestedValues = [])
{
if (str_starts_with($name, '--')) {
$name = substr($name, 2);
@@ -96,7 +102,11 @@ class InputOption
$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.');
}
if ($this->isArray() && !$this->acceptValue()) {
throw new InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.');
}
@@ -109,20 +119,16 @@ class InputOption
/**
* Returns the option shortcut.
*
* @return string|null
*/
public function getShortcut()
public function getShortcut(): ?string
{
return $this->shortcut;
}
/**
* Returns the option name.
*
* @return string
*/
public function getName()
public function getName(): string
{
return $this->name;
}
@@ -132,7 +138,7 @@ class InputOption
*
* @return bool true if value mode is not self::VALUE_NONE, false otherwise
*/
public function acceptValue()
public function acceptValue(): bool
{
return $this->isValueRequired() || $this->isValueOptional();
}
@@ -142,7 +148,7 @@ class InputOption
*
* @return bool true if value mode is self::VALUE_REQUIRED, false otherwise
*/
public function isValueRequired()
public function isValueRequired(): bool
{
return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode);
}
@@ -152,7 +158,7 @@ class InputOption
*
* @return bool true if value mode is self::VALUE_OPTIONAL, false otherwise
*/
public function isValueOptional()
public function isValueOptional(): bool
{
return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode);
}
@@ -162,7 +168,7 @@ class InputOption
*
* @return bool true if mode is self::VALUE_IS_ARRAY, false otherwise
*/
public function isArray()
public function isArray(): bool
{
return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode);
}
@@ -172,11 +178,11 @@ class InputOption
return self::VALUE_NEGATABLE === (self::VALUE_NEGATABLE & $this->mode);
}
/**
* @param string|bool|int|float|array|null $default
*/
public function setDefault($default = null)
public function setDefault(string|bool|int|float|array $default = null)
{
if (1 > \func_num_args()) {
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
}
if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) {
throw new LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.');
}
@@ -194,30 +200,45 @@ class InputOption
/**
* Returns the default value.
*
* @return string|bool|int|float|array|null
*/
public function getDefault()
public function getDefault(): string|bool|int|float|array|null
{
return $this->default;
}
/**
* Returns the description text.
*
* @return string
*/
public function getDescription()
public function getDescription(): string
{
return $this->description;
}
public function hasCompletion(): bool
{
return [] !== $this->suggestedValues;
}
/**
* Adds suggestions to $suggestions for the current completion input.
*
* @see Command::complete()
*/
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
$values = $this->suggestedValues;
if ($values instanceof \Closure && !\is_array($values = $values($input))) {
throw new LogicException(sprintf('Closure for option "%s" must return an array. Got "%s".', $this->name, get_debug_type($values)));
}
if ($values) {
$suggestions->suggestValues($values);
}
}
/**
* Checks whether the given option equals this one.
*
* @return bool
*/
public function equals(self $option)
public function equals(self $option): bool
{
return $option->getName() === $this->getName()
&& $option->getShortcut() === $this->getShortcut()

View File

@@ -24,6 +24,9 @@ use Symfony\Component\Console\Exception\InvalidArgumentException;
*/
class StringInput extends ArgvInput
{
/**
* @deprecated since Symfony 6.1
*/
public const REGEX_STRING = '([^\s]+?)(?:\s|(?<!\\\\)"|(?<!\\\\)\'|$)';
public const REGEX_UNQUOTED_STRING = '([^\s\\\\]+?)';
public const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')';

View File

@@ -29,8 +29,8 @@ class ConsoleLogger extends AbstractLogger
public const INFO = 'info';
public const ERROR = 'error';
private $output;
private $verbosityLevelMap = [
private OutputInterface $output;
private array $verbosityLevelMap = [
LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL,
LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL,
LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL,
@@ -40,7 +40,7 @@ class ConsoleLogger extends AbstractLogger
LogLevel::INFO => OutputInterface::VERBOSITY_VERY_VERBOSE,
LogLevel::DEBUG => OutputInterface::VERBOSITY_DEBUG,
];
private $formatLevelMap = [
private array $formatLevelMap = [
LogLevel::EMERGENCY => self::ERROR,
LogLevel::ALERT => self::ERROR,
LogLevel::CRITICAL => self::ERROR,
@@ -50,7 +50,7 @@ class ConsoleLogger extends AbstractLogger
LogLevel::INFO => self::INFO,
LogLevel::DEBUG => self::INFO,
];
private $errored = false;
private bool $errored = false;
public function __construct(OutputInterface $output, array $verbosityLevelMap = [], array $formatLevelMap = [])
{
@@ -59,12 +59,7 @@ class ConsoleLogger extends AbstractLogger
$this->formatLevelMap = $formatLevelMap + $this->formatLevelMap;
}
/**
* {@inheritdoc}
*
* @return void
*/
public function log($level, $message, array $context = [])
public function log($level, $message, array $context = []): void
{
if (!isset($this->verbosityLevelMap[$level])) {
throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level));
@@ -89,10 +84,8 @@ class ConsoleLogger extends AbstractLogger
/**
* Returns true when any messages have been logged at error levels.
*
* @return bool
*/
public function hasErrored()
public function hasErrored(): bool
{
return $this->errored;
}
@@ -110,12 +103,12 @@ class ConsoleLogger extends AbstractLogger
$replacements = [];
foreach ($context as $key => $val) {
if (null === $val || \is_scalar($val) || (\is_object($val) && method_exists($val, '__toString'))) {
if (null === $val || \is_scalar($val) || $val instanceof \Stringable) {
$replacements["{{$key}}"] = $val;
} elseif ($val instanceof \DateTimeInterface) {
$replacements["{{$key}}"] = $val->format(\DateTime::RFC3339);
$replacements["{{$key}}"] = $val->format(\DateTimeInterface::RFC3339);
} elseif (\is_object($val)) {
$replacements["{{$key}}"] = '[object '.\get_class($val).']';
$replacements["{{$key}}"] = '[object '.$val::class.']';
} else {
$replacements["{{$key}}"] = '['.\gettype($val).']';
}

View File

@@ -0,0 +1,124 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* @author Fabien Potencier <fabien@symfony.com>
* @author Julien Boudry <julien@condorcet.vote>
*/
enum AnsiColorMode
{
/*
* Classical 4-bit Ansi colors, including 8 classical colors and 8 bright color. Output syntax is "ESC[${foreGroundColorcode};${backGroundColorcode}m"
* Must be compatible with all terminals and it's the minimal version supported.
*/
case Ansi4;
/*
* 8-bit Ansi colors (240 differents colors + 16 duplicate color codes, ensuring backward compatibility).
* Output syntax is: "ESC[38;5;${foreGroundColorcode};48;5;${backGroundColorcode}m"
* Should be compatible with most terminals.
*/
case Ansi8;
/*
* 24-bit Ansi colors (RGB).
* Output syntax is: "ESC[38;2;${foreGroundColorcodeRed};${foreGroundColorcodeGreen};${foreGroundColorcodeBlue};48;2;${backGroundColorcodeRed};${backGroundColorcodeGreen};${backGroundColorcodeBlue}m"
* May be compatible with many modern terminals.
*/
case Ansi24;
/**
* Converts an RGB hexadecimal color to the corresponding Ansi code.
*/
public function convertFromHexToAnsiColorCode(string $hexColor): string
{
$hexColor = str_replace('#', '', $hexColor);
if (3 === \strlen($hexColor)) {
$hexColor = $hexColor[0].$hexColor[0].$hexColor[1].$hexColor[1].$hexColor[2].$hexColor[2];
}
if (6 !== \strlen($hexColor)) {
throw new InvalidArgumentException(sprintf('Invalid "#%s" color.', $hexColor));
}
$color = hexdec($hexColor);
$r = ($color >> 16) & 255;
$g = ($color >> 8) & 255;
$b = $color & 255;
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)
};
}
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}.")
};
}
private function degradeHexColorToAnsi4(int $r, int $g, int $b): int
{
if (0 === round($this->getSaturation($r, $g, $b) / 50)) {
return 0;
}
return (int) ((round($b / 255) << 2) | (round($g / 255) << 1) | round($r / 255));
}
private function getSaturation(int $r, int $g, int $b): int
{
$r = $r / 255;
$g = $g / 255;
$b = $b / 255;
$v = max($r, $g, $b);
if (0 === $diff = $v - min($r, $g, $b)) {
return 0;
}
return (int) ((int) $diff * 100 / $v);
}
/**
* Inspired from https://github.com/ajalt/colormath/blob/e464e0da1b014976736cf97250063248fc77b8e7/colormath/src/commonMain/kotlin/com/github/ajalt/colormath/model/Ansi256.kt code (MIT license).
*/
private function degradeHexColorToAnsi8(int $r, int $g, int $b): int
{
if ($r === $g && $g === $b) {
if ($r < 8) {
return 16;
}
if ($r > 248) {
return 231;
}
return (int) round(($r - 8) / 247 * 24) + 232;
} else {
return 16 +
(36 * (int) round($r / 255 * 5)) +
(6 * (int) round($g / 255 * 5)) +
(int) round($b / 255 * 5);
}
}
}

View File

@@ -16,14 +16,12 @@ namespace Symfony\Component\Console\Output;
*/
class BufferedOutput extends Output
{
private $buffer = '';
private string $buffer = '';
/**
* Empties buffer and returns its content.
*
* @return string
*/
public function fetch()
public function fetch(): string
{
$content = $this->buffer;
$this->buffer = '';
@@ -31,9 +29,6 @@ class BufferedOutput extends Output
return $content;
}
/**
* {@inheritdoc}
*/
protected function doWrite(string $message, bool $newline)
{
$this->buffer .= $message;

View File

@@ -29,8 +29,8 @@ use Symfony\Component\Console\Formatter\OutputFormatterInterface;
*/
class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
{
private $stderr;
private $consoleSectionOutputs = [];
private OutputInterface $stderr;
private array $consoleSectionOutputs = [];
/**
* @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
@@ -64,44 +64,29 @@ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
return new ConsoleSectionOutput($this->getStream(), $this->consoleSectionOutputs, $this->getVerbosity(), $this->isDecorated(), $this->getFormatter());
}
/**
* {@inheritdoc}
*/
public function setDecorated(bool $decorated)
{
parent::setDecorated($decorated);
$this->stderr->setDecorated($decorated);
}
/**
* {@inheritdoc}
*/
public function setFormatter(OutputFormatterInterface $formatter)
{
parent::setFormatter($formatter);
$this->stderr->setFormatter($formatter);
}
/**
* {@inheritdoc}
*/
public function setVerbosity(int $level)
{
parent::setVerbosity($level);
$this->stderr->setVerbosity($level);
}
/**
* {@inheritdoc}
*/
public function getErrorOutput()
public function getErrorOutput(): OutputInterface
{
return $this->stderr;
}
/**
* {@inheritdoc}
*/
public function setErrorOutput(OutputInterface $error)
{
$this->stderr = $error;
@@ -110,10 +95,8 @@ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
/**
* Returns true if current environment supports writing console output to
* STDOUT.
*
* @return bool
*/
protected function hasStdoutSupport()
protected function hasStdoutSupport(): bool
{
return false === $this->isRunningOS400();
}
@@ -121,10 +104,8 @@ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
/**
* Returns true if current environment supports writing console output to
* STDERR.
*
* @return bool
*/
protected function hasStderrSupport()
protected function hasStderrSupport(): bool
{
return false === $this->isRunningOS400();
}

View File

@@ -21,10 +21,8 @@ interface ConsoleOutputInterface extends OutputInterface
{
/**
* Gets the OutputInterface for errors.
*
* @return OutputInterface
*/
public function getErrorOutput();
public function getErrorOutput(): OutputInterface;
public function setErrorOutput(OutputInterface $error);

View File

@@ -21,10 +21,11 @@ use Symfony\Component\Console\Terminal;
*/
class ConsoleSectionOutput extends StreamOutput
{
private $content = [];
private $lines = 0;
private $sections;
private $terminal;
private array $content = [];
private int $lines = 0;
private array $sections;
private Terminal $terminal;
private int $maxHeight = 0;
/**
* @param resource $stream
@@ -38,6 +39,23 @@ class ConsoleSectionOutput extends StreamOutput
$this->terminal = new Terminal();
}
/**
* Defines a maximum number of lines for this section.
*
* When more lines are added, the section will automatically scroll to the
* end (i.e. remove the first lines to comply with the max height).
*/
public function setMaxHeight(int $maxHeight): void
{
// when changing max height, clear output of current section and redraw again with the new height
$existingContent = $this->popStreamContentUntilCurrentSection($this->maxHeight ? min($this->maxHeight, $this->lines) : $this->lines);
$this->maxHeight = $maxHeight;
parent::doWrite($this->getVisibleContent(), false);
parent::doWrite($existingContent, false);
}
/**
* Clears previous output for this section.
*
@@ -50,7 +68,7 @@ class ConsoleSectionOutput extends StreamOutput
}
if ($lines) {
array_splice($this->content, -($lines * 2)); // Multiply lines by 2 to cater for each new line added between content
array_splice($this->content, -$lines);
} else {
$lines = $this->lines;
$this->content = [];
@@ -58,15 +76,13 @@ class ConsoleSectionOutput extends StreamOutput
$this->lines -= $lines;
parent::doWrite($this->popStreamContentUntilCurrentSection($lines), false);
parent::doWrite($this->popStreamContentUntilCurrentSection($this->maxHeight ? min($this->maxHeight, $lines) : $lines), false);
}
/**
* Overwrites the previous output with a new message.
*
* @param array|string $message
*/
public function overwrite($message)
public function overwrite(string|iterable $message)
{
$this->clear();
$this->writeln($message);
@@ -77,21 +93,62 @@ class ConsoleSectionOutput extends StreamOutput
return implode('', $this->content);
}
/**
* @internal
*/
public function addContent(string $input)
public function getVisibleContent(): string
{
foreach (explode(\PHP_EOL, $input) as $lineContent) {
$this->lines += ceil($this->getDisplayLength($lineContent) / $this->terminal->getWidth()) ?: 1;
$this->content[] = $lineContent;
$this->content[] = \PHP_EOL;
if (0 === $this->maxHeight) {
return $this->getContent();
}
return implode('', \array_slice($this->content, -$this->maxHeight));
}
/**
* {@inheritdoc}
* @internal
*/
public function addContent(string $input, bool $newline = true): int
{
$width = $this->terminal->getWidth();
$lines = explode(\PHP_EOL, $input);
$linesAdded = 0;
$count = \count($lines) - 1;
foreach ($lines as $i => $lineContent) {
// re-add the line break (that has been removed in the above `explode()` for
// - every line that is not the last line
// - if $newline is required, also add it to the last line
if ($i < $count || $newline) {
$lineContent .= \PHP_EOL;
}
// skip line if there is no text (or newline for that matter)
if ('' === $lineContent) {
continue;
}
// For the first line, check if the previous line (last entry of `$this->content`)
// needs to be continued (i.e. does not end with a line break).
if (0 === $i
&& (false !== $lastLine = end($this->content))
&& !str_ends_with($lastLine, \PHP_EOL)
) {
// deduct the line count of the previous line
$this->lines -= (int) ceil($this->getDisplayLength($lastLine) / $width) ?: 1;
// concatenate previous and new line
$lineContent = $lastLine.$lineContent;
// replace last entry of `$this->content` with the new expanded line
array_splice($this->content, -1, 1, $lineContent);
} else {
// otherwise just add the new content
$this->content[] = $lineContent;
}
$linesAdded += (int) ceil($this->getDisplayLength($lineContent) / $width) ?: 1;
}
$this->lines += $linesAdded;
return $linesAdded;
}
protected function doWrite(string $message, bool $newline)
{
if (!$this->isDecorated()) {
@@ -100,11 +157,28 @@ class ConsoleSectionOutput extends StreamOutput
return;
}
$erasedContent = $this->popStreamContentUntilCurrentSection();
// Check if the previous line (last entry of `$this->content`) needs to be continued
// (i.e. does not end with a line break). In which case, it needs to be erased first.
$linesToClear = $deleteLastLine = ($lastLine = end($this->content) ?: '') && !str_ends_with($lastLine, \PHP_EOL) ? 1 : 0;
$this->addContent($message);
$linesAdded = $this->addContent($message, $newline);
parent::doWrite($message, true);
if ($lineOverflow = $this->maxHeight > 0 && $this->lines > $this->maxHeight) {
// on overflow, clear the whole section and redraw again (to remove the first lines)
$linesToClear = $this->maxHeight;
}
$erasedContent = $this->popStreamContentUntilCurrentSection($linesToClear);
if ($lineOverflow) {
// redraw existing lines of the section
$previousLinesOfSection = \array_slice($this->content, $this->lines - $this->maxHeight, $this->maxHeight - $linesAdded);
parent::doWrite(implode('', $previousLinesOfSection), false);
}
// if the last line was removed, re-print its content together with the new content.
// otherwise, just print the new content.
parent::doWrite($deleteLastLine ? $lastLine.$message : $message, true);
parent::doWrite($erasedContent, false);
}
@@ -123,7 +197,12 @@ class ConsoleSectionOutput extends StreamOutput
}
$numberOfLinesToClear += $section->lines;
$erasedContent[] = $section->getContent();
if ('' !== $sectionContent = $section->getVisibleContent()) {
if (!str_ends_with($sectionContent, \PHP_EOL)) {
$sectionContent .= \PHP_EOL;
}
$erasedContent[] = $sectionContent;
}
}
if ($numberOfLinesToClear > 0) {

View File

@@ -24,104 +24,65 @@ use Symfony\Component\Console\Formatter\OutputFormatterInterface;
*/
class NullOutput implements OutputInterface
{
private $formatter;
private NullOutputFormatter $formatter;
/**
* {@inheritdoc}
*/
public function setFormatter(OutputFormatterInterface $formatter)
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function getFormatter()
public function getFormatter(): OutputFormatterInterface
{
if ($this->formatter) {
return $this->formatter;
}
// to comply with the interface we must return a OutputFormatterInterface
return $this->formatter = new NullOutputFormatter();
return $this->formatter ??= new NullOutputFormatter();
}
/**
* {@inheritdoc}
*/
public function setDecorated(bool $decorated)
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function isDecorated()
public function isDecorated(): bool
{
return false;
}
/**
* {@inheritdoc}
*/
public function setVerbosity(int $level)
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function getVerbosity()
public function getVerbosity(): int
{
return self::VERBOSITY_QUIET;
}
/**
* {@inheritdoc}
*/
public function isQuiet()
public function isQuiet(): bool
{
return true;
}
/**
* {@inheritdoc}
*/
public function isVerbose()
public function isVerbose(): bool
{
return false;
}
/**
* {@inheritdoc}
*/
public function isVeryVerbose()
public function isVeryVerbose(): bool
{
return false;
}
/**
* {@inheritdoc}
*/
public function isDebug()
public function isDebug(): bool
{
return false;
}
/**
* {@inheritdoc}
*/
public function writeln($messages, int $options = self::OUTPUT_NORMAL)
public function writeln(string|iterable $messages, int $options = self::OUTPUT_NORMAL)
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function write($messages, bool $newline = false, int $options = self::OUTPUT_NORMAL)
public function write(string|iterable $messages, bool $newline = false, int $options = self::OUTPUT_NORMAL)
{
// do nothing
}

View File

@@ -29,8 +29,8 @@ use Symfony\Component\Console\Formatter\OutputFormatterInterface;
*/
abstract class Output implements OutputInterface
{
private $verbosity;
private $formatter;
private int $verbosity;
private OutputFormatterInterface $formatter;
/**
* @param int|null $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
@@ -44,98 +44,62 @@ abstract class Output implements OutputInterface
$this->formatter->setDecorated($decorated);
}
/**
* {@inheritdoc}
*/
public function setFormatter(OutputFormatterInterface $formatter)
{
$this->formatter = $formatter;
}
/**
* {@inheritdoc}
*/
public function getFormatter()
public function getFormatter(): OutputFormatterInterface
{
return $this->formatter;
}
/**
* {@inheritdoc}
*/
public function setDecorated(bool $decorated)
{
$this->formatter->setDecorated($decorated);
}
/**
* {@inheritdoc}
*/
public function isDecorated()
public function isDecorated(): bool
{
return $this->formatter->isDecorated();
}
/**
* {@inheritdoc}
*/
public function setVerbosity(int $level)
{
$this->verbosity = $level;
}
/**
* {@inheritdoc}
*/
public function getVerbosity()
public function getVerbosity(): int
{
return $this->verbosity;
}
/**
* {@inheritdoc}
*/
public function isQuiet()
public function isQuiet(): bool
{
return self::VERBOSITY_QUIET === $this->verbosity;
}
/**
* {@inheritdoc}
*/
public function isVerbose()
public function isVerbose(): bool
{
return self::VERBOSITY_VERBOSE <= $this->verbosity;
}
/**
* {@inheritdoc}
*/
public function isVeryVerbose()
public function isVeryVerbose(): bool
{
return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity;
}
/**
* {@inheritdoc}
*/
public function isDebug()
public function isDebug(): bool
{
return self::VERBOSITY_DEBUG <= $this->verbosity;
}
/**
* {@inheritdoc}
*/
public function writeln($messages, int $options = self::OUTPUT_NORMAL)
public function writeln(string|iterable $messages, int $options = self::OUTPUT_NORMAL)
{
$this->write($messages, true, $options);
}
/**
* {@inheritdoc}
*/
public function write($messages, bool $newline = false, int $options = self::OUTPUT_NORMAL)
public function write(string|iterable $messages, bool $newline = false, int $options = self::OUTPUT_NORMAL)
{
if (!is_iterable($messages)) {
$messages = [$messages];

View File

@@ -33,19 +33,19 @@ interface OutputInterface
/**
* Writes a message to the output.
*
* @param string|iterable $messages The message as an iterable of strings or a single string
* @param bool $newline Whether to add a newline
* @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
* @param bool $newline Whether to add a newline
* @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants),
* 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
*/
public function write($messages, bool $newline = false, int $options = 0);
public function write(string|iterable $messages, bool $newline = false, int $options = 0);
/**
* Writes a message to the output and adds a newline at the end.
*
* @param string|iterable $messages The message as an iterable of strings or a single string
* @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
* @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants),
* 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
*/
public function writeln($messages, int $options = 0);
public function writeln(string|iterable $messages, int $options = 0);
/**
* Sets the verbosity of the output.
@@ -54,38 +54,28 @@ interface OutputInterface
/**
* Gets the current verbosity of the output.
*
* @return int
*/
public function getVerbosity();
public function getVerbosity(): int;
/**
* Returns whether verbosity is quiet (-q).
*
* @return bool
*/
public function isQuiet();
public function isQuiet(): bool;
/**
* Returns whether verbosity is verbose (-v).
*
* @return bool
*/
public function isVerbose();
public function isVerbose(): bool;
/**
* Returns whether verbosity is very verbose (-vv).
*
* @return bool
*/
public function isVeryVerbose();
public function isVeryVerbose(): bool;
/**
* Returns whether verbosity is debug (-vvv).
*
* @return bool
*/
public function isDebug();
public function isDebug(): bool;
/**
* Sets the decorated flag.
@@ -94,17 +84,13 @@ interface OutputInterface
/**
* Gets the decorated flag.
*
* @return bool
*/
public function isDecorated();
public function isDecorated(): bool;
public function setFormatter(OutputFormatterInterface $formatter);
/**
* Returns current output formatter instance.
*
* @return OutputFormatterInterface
*/
public function getFormatter();
public function getFormatter(): OutputFormatterInterface;
}

View File

@@ -47,9 +47,7 @@ class StreamOutput extends Output
$this->stream = $stream;
if (null === $decorated) {
$decorated = $this->hasColorSupport();
}
$decorated ??= $this->hasColorSupport();
parent::__construct($verbosity, $decorated, $formatter);
}
@@ -64,9 +62,6 @@ class StreamOutput extends Output
return $this->stream;
}
/**
* {@inheritdoc}
*/
protected function doWrite(string $message, bool $newline)
{
if ($newline) {
@@ -91,7 +86,7 @@ class StreamOutput extends Output
*
* @return bool true if the stream supports colorization, false otherwise
*/
protected function hasColorSupport()
protected function hasColorSupport(): bool
{
// Follow https://no-color.org/
if (isset($_SERVER['NO_COLOR']) || false !== getenv('NO_COLOR')) {

View File

@@ -21,8 +21,8 @@ use Symfony\Component\Console\Formatter\OutputFormatterInterface;
*/
class TrimmedBufferOutput extends Output
{
private $maxLength;
private $buffer = '';
private int $maxLength;
private string $buffer = '';
public function __construct(int $maxLength, ?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, OutputFormatterInterface $formatter = null)
{
@@ -36,10 +36,8 @@ class TrimmedBufferOutput extends Output
/**
* Empties buffer and returns its content.
*
* @return string
*/
public function fetch()
public function fetch(): string
{
$content = $this->buffer;
$this->buffer = '';
@@ -47,9 +45,6 @@ class TrimmedBufferOutput extends Output
return $content;
}
/**
* {@inheritdoc}
*/
protected function doWrite(string $message, bool $newline)
{
$this->buffer .= $message;

View File

@@ -20,17 +20,17 @@ use Symfony\Component\Console\Exception\InvalidArgumentException;
*/
class ChoiceQuestion extends Question
{
private $choices;
private $multiselect = false;
private $prompt = ' > ';
private $errorMessage = 'Value "%s" is invalid';
private array $choices;
private bool $multiselect = false;
private string $prompt = ' > ';
private string $errorMessage = 'Value "%s" is invalid';
/**
* @param string $question The question to ask to the user
* @param array $choices The list of available choices
* @param mixed $default The default answer to return
*/
public function __construct(string $question, array $choices, $default = null)
public function __construct(string $question, array $choices, mixed $default = null)
{
if (!$choices) {
throw new \LogicException('Choice question must have at least 1 choice available.');
@@ -45,10 +45,8 @@ class ChoiceQuestion extends Question
/**
* Returns available choices.
*
* @return array
*/
public function getChoices()
public function getChoices(): array
{
return $this->choices;
}
@@ -60,7 +58,7 @@ class ChoiceQuestion extends Question
*
* @return $this
*/
public function setMultiselect(bool $multiselect)
public function setMultiselect(bool $multiselect): static
{
$this->multiselect = $multiselect;
$this->setValidator($this->getDefaultValidator());
@@ -70,20 +68,16 @@ class ChoiceQuestion extends Question
/**
* Returns whether the choices are multiselect.
*
* @return bool
*/
public function isMultiselect()
public function isMultiselect(): bool
{
return $this->multiselect;
}
/**
* Gets the prompt for choices.
*
* @return string
*/
public function getPrompt()
public function getPrompt(): string
{
return $this->prompt;
}
@@ -93,7 +87,7 @@ class ChoiceQuestion extends Question
*
* @return $this
*/
public function setPrompt(string $prompt)
public function setPrompt(string $prompt): static
{
$this->prompt = $prompt;
@@ -107,7 +101,7 @@ class ChoiceQuestion extends Question
*
* @return $this
*/
public function setErrorMessage(string $errorMessage)
public function setErrorMessage(string $errorMessage): static
{
$this->errorMessage = $errorMessage;
$this->setValidator($this->getDefaultValidator());

View File

@@ -18,7 +18,7 @@ namespace Symfony\Component\Console\Question;
*/
class ConfirmationQuestion extends Question
{
private $trueAnswerRegex;
private string $trueAnswerRegex;
/**
* @param string $question The question to ask to the user

View File

@@ -21,22 +21,22 @@ use Symfony\Component\Console\Exception\LogicException;
*/
class Question
{
private $question;
private $attempts;
private $hidden = false;
private $hiddenFallback = true;
private $autocompleterCallback;
private $validator;
private $default;
private $normalizer;
private $trimmable = true;
private $multiline = false;
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;
/**
* @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, $default = null)
public function __construct(string $question, string|bool|int|float $default = null)
{
$this->question = $question;
$this->default = $default;
@@ -44,20 +44,16 @@ class Question
/**
* Returns the question.
*
* @return string
*/
public function getQuestion()
public function getQuestion(): string
{
return $this->question;
}
/**
* Returns the default answer.
*
* @return string|bool|int|float|null
*/
public function getDefault()
public function getDefault(): string|bool|int|float|null
{
return $this->default;
}
@@ -75,7 +71,7 @@ class Question
*
* @return $this
*/
public function setMultiline(bool $multiline): self
public function setMultiline(bool $multiline): static
{
$this->multiline = $multiline;
@@ -84,10 +80,8 @@ class Question
/**
* Returns whether the user response must be hidden.
*
* @return bool
*/
public function isHidden()
public function isHidden(): bool
{
return $this->hidden;
}
@@ -99,7 +93,7 @@ class Question
*
* @throws LogicException In case the autocompleter is also used
*/
public function setHidden(bool $hidden)
public function setHidden(bool $hidden): static
{
if ($this->autocompleterCallback) {
throw new LogicException('A hidden question cannot use the autocompleter.');
@@ -112,10 +106,8 @@ class Question
/**
* In case the response cannot be hidden, whether to fallback on non-hidden question or not.
*
* @return bool
*/
public function isHiddenFallback()
public function isHiddenFallback(): bool
{
return $this->hiddenFallback;
}
@@ -125,7 +117,7 @@ class Question
*
* @return $this
*/
public function setHiddenFallback(bool $fallback)
public function setHiddenFallback(bool $fallback): static
{
$this->hiddenFallback = $fallback;
@@ -134,10 +126,8 @@ class Question
/**
* Gets values for the autocompleter.
*
* @return iterable|null
*/
public function getAutocompleterValues()
public function getAutocompleterValues(): ?iterable
{
$callback = $this->getAutocompleterCallback();
@@ -151,7 +141,7 @@ class Question
*
* @throws LogicException
*/
public function setAutocompleterValues(?iterable $values)
public function setAutocompleterValues(?iterable $values): static
{
if (\is_array($values)) {
$values = $this->isAssoc($values) ? array_merge(array_keys($values), array_values($values)) : array_values($values);
@@ -162,7 +152,7 @@ class Question
} elseif ($values instanceof \Traversable) {
$valueCache = null;
$callback = static function () use ($values, &$valueCache) {
return $valueCache ?? $valueCache = iterator_to_array($values, false);
return $valueCache ??= iterator_to_array($values, false);
};
} else {
$callback = null;
@@ -186,13 +176,16 @@ class Question
*
* @return $this
*/
public function setAutocompleterCallback(callable $callback = null): self
public function setAutocompleterCallback(callable $callback = null): static
{
if (1 > \func_num_args()) {
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
}
if ($this->hidden && null !== $callback) {
throw new LogicException('A hidden question cannot use the autocompleter.');
}
$this->autocompleterCallback = $callback;
$this->autocompleterCallback = null === $callback ? null : $callback(...);
return $this;
}
@@ -202,19 +195,20 @@ class Question
*
* @return $this
*/
public function setValidator(callable $validator = null)
public function setValidator(callable $validator = null): static
{
$this->validator = $validator;
if (1 > \func_num_args()) {
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
}
$this->validator = null === $validator ? null : $validator(...);
return $this;
}
/**
* Gets the validator for the question.
*
* @return callable|null
*/
public function getValidator()
public function getValidator(): ?callable
{
return $this->validator;
}
@@ -228,7 +222,7 @@ class Question
*
* @throws InvalidArgumentException in case the number of attempts is invalid
*/
public function setMaxAttempts(?int $attempts)
public function setMaxAttempts(?int $attempts): static
{
if (null !== $attempts && $attempts < 1) {
throw new InvalidArgumentException('Maximum number of attempts must be a positive value.');
@@ -243,10 +237,8 @@ class Question
* Gets the maximum number of attempts.
*
* Null means an unlimited number of attempts.
*
* @return int|null
*/
public function getMaxAttempts()
public function getMaxAttempts(): ?int
{
return $this->attempts;
}
@@ -258,9 +250,9 @@ class Question
*
* @return $this
*/
public function setNormalizer(callable $normalizer)
public function setNormalizer(callable $normalizer): static
{
$this->normalizer = $normalizer;
$this->normalizer = $normalizer(...);
return $this;
}
@@ -269,10 +261,8 @@ class Question
* Gets the normalizer for the response.
*
* The normalizer can ba a callable (a string), a closure or a class implementing __invoke.
*
* @return callable|null
*/
public function getNormalizer()
public function getNormalizer(): ?callable
{
return $this->normalizer;
}
@@ -290,7 +280,7 @@ class Question
/**
* @return $this
*/
public function setTrimmable(bool $trimmable): self
public function setTrimmable(bool $trimmable): static
{
$this->trimmable = $trimmable;

View File

@@ -4,18 +4,6 @@ Console Component
The Console component eases the creation of beautiful and testable command line
interfaces.
Sponsor
-------
The Console component for Symfony 5.4/6.0 is [backed][1] by [Les-Tilleuls.coop][2].
Les-Tilleuls.coop is a team of 50+ Symfony experts who can help you design, develop and
fix your projects. We provide a wide range of professional services including development,
consulting, coaching, training and audits. We also are highly skilled in JS, Go and DevOps.
We are a worker cooperative!
Help Symfony by [sponsoring][3] its development!
Resources
---------
@@ -30,7 +18,3 @@ 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

View File

@@ -25,7 +25,7 @@ _sf_{{ COMMAND_NAME }}() {
local cur prev words cword
_get_comp_words_by_ref -n := cur prev words cword
local completecmd=("$sf_cmd" "_complete" "--no-interaction" "-sbash" "-c$cword" "-S{{ VERSION }}")
local completecmd=("$sf_cmd" "_complete" "--no-interaction" "-sbash" "-c$cword" "-a{{ VERSION }}")
for w in ${words[@]}; do
w=$(printf -- '%b' "$w")
# remove quotes from typed values

View File

@@ -0,0 +1,29 @@
# This file is part of the Symfony package.
#
# (c) Fabien Potencier <fabien@symfony.com>
#
# For the full copyright and license information, please view
# https://symfony.com/doc/current/contributing/code/license.html
function _sf_{{ COMMAND_NAME }}
set sf_cmd (commandline -o)
set c (count (commandline -oc))
set completecmd "$sf_cmd[1]" "_complete" "--no-interaction" "-sfish" "-a{{ VERSION }}"
for i in $sf_cmd
if [ $i != "" ]
set completecmd $completecmd "-i$i"
end
end
set completecmd $completecmd "-c$c"
set sfcomplete ($completecmd)
for i in $sfcomplete
echo $i
end
end
complete -c '{{ COMMAND_NAME }}' -a '(_sf_{{ COMMAND_NAME }})' -f

View File

@@ -0,0 +1,80 @@
# This file is part of the Symfony package.
#
# (c) Fabien Potencier <fabien@symfony.com>
#
# For the full copyright and license information, please view
# https://symfony.com/doc/current/contributing/code/license.html
#
# zsh completions for {{ COMMAND_NAME }}
#
# References:
# - https://github.com/spf13/cobra/blob/master/zsh_completions.go
# - https://github.com/symfony/symfony/blob/5.4/src/Symfony/Component/Console/Resources/completion.bash
#
_sf_{{ COMMAND_NAME }}() {
local lastParam flagPrefix requestComp out comp
local -a completions
# The user could have moved the cursor backwards on the command-line.
# We need to trigger completion from the $CURRENT location, so we need
# to truncate the command-line ($words) up to the $CURRENT location.
# (We cannot use $CURSOR as its value does not work when a command is an alias.)
words=("${=words[1,CURRENT]}") lastParam=${words[-1]}
# For zsh, when completing a flag with an = (e.g., {{ COMMAND_NAME }} -n=<TAB>)
# completions must be prefixed with the flag
setopt local_options BASH_REMATCH
if [[ "${lastParam}" =~ '-.*=' ]]; then
# We are dealing with a flag with an =
flagPrefix="-P ${BASH_REMATCH}"
fi
# Prepare the command to obtain completions
requestComp="${words[0]} ${words[1]} _complete --no-interaction -szsh -a{{ VERSION }} -c$((CURRENT-1))" i=""
for w in ${words[@]}; do
w=$(printf -- '%b' "$w")
# remove quotes from typed values
quote="${w:0:1}"
if [ "$quote" = \' ]; then
w="${w%\'}"
w="${w#\'}"
elif [ "$quote" = \" ]; then
w="${w%\"}"
w="${w#\"}"
fi
# empty values are ignored
if [ ! -z "$w" ]; then
i="${i}-i${w} "
fi
done
# Ensure at least 1 input
if [ "${i}" = "" ]; then
requestComp="${requestComp} -i\" \""
else
requestComp="${requestComp} ${i}"
fi
# Use eval to handle any environment variables and such
out=$(eval ${requestComp} 2>/dev/null)
while IFS='\n' read -r comp; do
if [ -n "$comp" ]; then
# If requested, completions are returned with a description.
# The description is preceded by a TAB character.
# For zsh's _describe, we need to use a : instead of a TAB.
# We first need to escape any : as part of the completion itself.
comp=${comp//:/\\:}
local tab=$(printf '\t')
comp=${comp//$tab/:}
completions+=${comp}
fi
done < <(printf "%s\n" "${out[@]}")
# Let inbuilt _describe handle completions
eval _describe "completions" completions $flagPrefix
return $?
}
compdef _sf_{{ COMMAND_NAME }} {{ COMMAND_NAME }}

View File

@@ -13,7 +13,7 @@ namespace Symfony\Component\Console\SignalRegistry;
final class SignalRegistry
{
private $signalHandlers = [];
private array $signalHandlers = [];
public function __construct()
{
@@ -34,20 +34,12 @@ final class SignalRegistry
$this->signalHandlers[$signal][] = $signalHandler;
pcntl_signal($signal, [$this, 'handle']);
pcntl_signal($signal, $this->handle(...));
}
public static function isSupported(): bool
{
if (!\function_exists('pcntl_signal')) {
return false;
}
if (\in_array('pcntl_signal', explode(',', \ini_get('disable_functions')))) {
return false;
}
return true;
return \function_exists('pcntl_signal');
}
/**

View File

@@ -20,14 +20,14 @@ use Symfony\Component\Console\Output\OutputInterface;
*/
class SingleCommandApplication extends Command
{
private $version = 'UNKNOWN';
private $autoExit = true;
private $running = false;
private string $version = 'UNKNOWN';
private bool $autoExit = true;
private bool $running = false;
/**
* @return $this
*/
public function setVersion(string $version): self
public function setVersion(string $version): static
{
$this->version = $version;
@@ -39,7 +39,7 @@ class SingleCommandApplication extends Command
*
* @return $this
*/
public function setAutoExit(bool $autoExit): self
public function setAutoExit(bool $autoExit): static
{
$this->autoExit = $autoExit;

View File

@@ -23,121 +23,79 @@ use Symfony\Component\Console\Output\OutputInterface;
*/
abstract class OutputStyle implements OutputInterface, StyleInterface
{
private $output;
private OutputInterface $output;
public function __construct(OutputInterface $output)
{
$this->output = $output;
}
/**
* {@inheritdoc}
*/
public function newLine(int $count = 1)
{
$this->output->write(str_repeat(\PHP_EOL, $count));
}
/**
* @return ProgressBar
*/
public function createProgressBar(int $max = 0)
public function createProgressBar(int $max = 0): ProgressBar
{
return new ProgressBar($this->output, $max);
}
/**
* {@inheritdoc}
*/
public function write($messages, bool $newline = false, int $type = self::OUTPUT_NORMAL)
public function write(string|iterable $messages, bool $newline = false, int $type = self::OUTPUT_NORMAL)
{
$this->output->write($messages, $newline, $type);
}
/**
* {@inheritdoc}
*/
public function writeln($messages, int $type = self::OUTPUT_NORMAL)
public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORMAL)
{
$this->output->writeln($messages, $type);
}
/**
* {@inheritdoc}
*/
public function setVerbosity(int $level)
{
$this->output->setVerbosity($level);
}
/**
* {@inheritdoc}
*/
public function getVerbosity()
public function getVerbosity(): int
{
return $this->output->getVerbosity();
}
/**
* {@inheritdoc}
*/
public function setDecorated(bool $decorated)
{
$this->output->setDecorated($decorated);
}
/**
* {@inheritdoc}
*/
public function isDecorated()
public function isDecorated(): bool
{
return $this->output->isDecorated();
}
/**
* {@inheritdoc}
*/
public function setFormatter(OutputFormatterInterface $formatter)
{
$this->output->setFormatter($formatter);
}
/**
* {@inheritdoc}
*/
public function getFormatter()
public function getFormatter(): OutputFormatterInterface
{
return $this->output->getFormatter();
}
/**
* {@inheritdoc}
*/
public function isQuiet()
public function isQuiet(): bool
{
return $this->output->isQuiet();
}
/**
* {@inheritdoc}
*/
public function isVerbose()
public function isVerbose(): bool
{
return $this->output->isVerbose();
}
/**
* {@inheritdoc}
*/
public function isVeryVerbose()
public function isVeryVerbose(): bool
{
return $this->output->isVeryVerbose();
}
/**
* {@inheritdoc}
*/
public function isDebug()
public function isDebug(): bool
{
return $this->output->isDebug();
}

View File

@@ -35,45 +35,33 @@ interface StyleInterface
/**
* Formats informational text.
*
* @param string|array $message
*/
public function text($message);
public function text(string|array $message);
/**
* Formats a success result bar.
*
* @param string|array $message
*/
public function success($message);
public function success(string|array $message);
/**
* Formats an error result bar.
*
* @param string|array $message
*/
public function error($message);
public function error(string|array $message);
/**
* Formats an warning result bar.
*
* @param string|array $message
*/
public function warning($message);
public function warning(string|array $message);
/**
* Formats a note admonition.
*
* @param string|array $message
*/
public function note($message);
public function note(string|array $message);
/**
* Formats a caution admonition.
*
* @param string|array $message
*/
public function caution($message);
public function caution(string|array $message);
/**
* Formats a table.
@@ -82,33 +70,23 @@ interface StyleInterface
/**
* Asks a question.
*
* @return mixed
*/
public function ask(string $question, string $default = null, callable $validator = null);
public function ask(string $question, string $default = null, callable $validator = null): mixed;
/**
* Asks a question with the user input hidden.
*
* @return mixed
*/
public function askHidden(string $question, callable $validator = null);
public function askHidden(string $question, callable $validator = null): mixed;
/**
* Asks for confirmation.
*
* @return bool
*/
public function confirm(string $question, bool $default = true);
public function confirm(string $question, bool $default = true): bool;
/**
* Asks a choice question.
*
* @param string|int|null $default
*
* @return mixed
*/
public function choice(string $question, array $choices, $default = null);
public function choice(string $question, array $choices, mixed $default = null): mixed;
/**
* Add newline(s).

View File

@@ -15,6 +15,7 @@ use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Helper\OutputWrapper;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Helper\SymfonyQuestionHelper;
use Symfony\Component\Console\Helper\Table;
@@ -38,12 +39,12 @@ class SymfonyStyle extends OutputStyle
{
public const MAX_LINE_LENGTH = 120;
private $input;
private $output;
private $questionHelper;
private $progressBar;
private $lineLength;
private $bufferedOutput;
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)
{
@@ -58,10 +59,8 @@ class SymfonyStyle extends OutputStyle
/**
* Formats a message as a block of text.
*
* @param string|array $messages The message to write in the block
*/
public function block($messages, string $type = null, string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = true)
public function block(string|array $messages, string $type = null, string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = true)
{
$messages = \is_array($messages) ? array_values($messages) : [$messages];
@@ -70,9 +69,6 @@ class SymfonyStyle extends OutputStyle
$this->newLine();
}
/**
* {@inheritdoc}
*/
public function title(string $message)
{
$this->autoPrependBlock();
@@ -83,9 +79,6 @@ class SymfonyStyle extends OutputStyle
$this->newLine();
}
/**
* {@inheritdoc}
*/
public function section(string $message)
{
$this->autoPrependBlock();
@@ -96,9 +89,6 @@ class SymfonyStyle extends OutputStyle
$this->newLine();
}
/**
* {@inheritdoc}
*/
public function listing(array $elements)
{
$this->autoPrependText();
@@ -110,10 +100,7 @@ class SymfonyStyle extends OutputStyle
$this->newLine();
}
/**
* {@inheritdoc}
*/
public function text($message)
public function text(string|array $message)
{
$this->autoPrependText();
@@ -125,67 +112,45 @@ class SymfonyStyle extends OutputStyle
/**
* Formats a command comment.
*
* @param string|array $message
*/
public function comment($message)
public function comment(string|array $message)
{
$this->block($message, null, null, '<fg=default;bg=default> // </>', false, false);
}
/**
* {@inheritdoc}
*/
public function success($message)
public function success(string|array $message)
{
$this->block($message, 'OK', 'fg=black;bg=green', ' ', true);
}
/**
* {@inheritdoc}
*/
public function error($message)
public function error(string|array $message)
{
$this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true);
}
/**
* {@inheritdoc}
*/
public function warning($message)
public function warning(string|array $message)
{
$this->block($message, 'WARNING', 'fg=black;bg=yellow', ' ', true);
}
/**
* {@inheritdoc}
*/
public function note($message)
public function note(string|array $message)
{
$this->block($message, 'NOTE', 'fg=yellow', ' ! ');
}
/**
* Formats an info message.
*
* @param string|array $message
*/
public function info($message)
public function info(string|array $message)
{
$this->block($message, 'INFO', 'fg=green', ' ', true);
}
/**
* {@inheritdoc}
*/
public function caution($message)
public function caution(string|array $message)
{
$this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true);
}
/**
* {@inheritdoc}
*/
public function table(array $headers, array $rows)
{
$this->createTable()
@@ -219,10 +184,8 @@ class SymfonyStyle extends OutputStyle
* * 'A title'
* * ['key' => 'value']
* * new TableSeparator()
*
* @param string|array|TableSeparator ...$list
*/
public function definitionList(...$list)
public function definitionList(string|array|TableSeparator ...$list)
{
$headers = [];
$row = [];
@@ -247,10 +210,7 @@ class SymfonyStyle extends OutputStyle
$this->horizontalTable($headers, [$row]);
}
/**
* {@inheritdoc}
*/
public function ask(string $question, string $default = null, callable $validator = null)
public function ask(string $question, string $default = null, callable $validator = null): mixed
{
$question = new Question($question, $default);
$question->setValidator($validator);
@@ -258,10 +218,7 @@ class SymfonyStyle extends OutputStyle
return $this->askQuestion($question);
}
/**
* {@inheritdoc}
*/
public function askHidden(string $question, callable $validator = null)
public function askHidden(string $question, callable $validator = null): mixed
{
$question = new Question($question);
@@ -271,58 +228,43 @@ class SymfonyStyle extends OutputStyle
return $this->askQuestion($question);
}
/**
* {@inheritdoc}
*/
public function confirm(string $question, bool $default = true)
public function confirm(string $question, bool $default = true): bool
{
return $this->askQuestion(new ConfirmationQuestion($question, $default));
}
/**
* {@inheritdoc}
*/
public function choice(string $question, array $choices, $default = null)
public function choice(string $question, array $choices, mixed $default = null, bool $multiSelect = false): mixed
{
if (null !== $default) {
$values = array_flip($choices);
$default = $values[$default] ?? $default;
}
return $this->askQuestion(new ChoiceQuestion($question, $choices, $default));
$questionChoice = new ChoiceQuestion($question, $choices, $default);
$questionChoice->setMultiselect($multiSelect);
return $this->askQuestion($questionChoice);
}
/**
* {@inheritdoc}
*/
public function progressStart(int $max = 0)
{
$this->progressBar = $this->createProgressBar($max);
$this->progressBar->start();
}
/**
* {@inheritdoc}
*/
public function progressAdvance(int $step = 1)
{
$this->getProgressBar()->advance($step);
}
/**
* {@inheritdoc}
*/
public function progressFinish()
{
$this->getProgressBar()->finish();
$this->newLine(2);
$this->progressBar = null;
unset($this->progressBar);
}
/**
* {@inheritdoc}
*/
public function createProgressBar(int $max = 0)
public function createProgressBar(int $max = 0): ProgressBar
{
$progressBar = parent::createProgressBar($max);
@@ -345,18 +287,13 @@ class SymfonyStyle extends OutputStyle
$this->newLine(2);
}
/**
* @return mixed
*/
public function askQuestion(Question $question)
public function askQuestion(Question $question): mixed
{
if ($this->input->isInteractive()) {
$this->autoPrependBlock();
}
if (!$this->questionHelper) {
$this->questionHelper = new SymfonyQuestionHelper();
}
$this->questionHelper ??= new SymfonyQuestionHelper();
$answer = $this->questionHelper->ask($this->input, $this, $question);
@@ -368,10 +305,7 @@ class SymfonyStyle extends OutputStyle
return $answer;
}
/**
* {@inheritdoc}
*/
public function writeln($messages, int $type = self::OUTPUT_NORMAL)
public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORMAL)
{
if (!is_iterable($messages)) {
$messages = [$messages];
@@ -383,10 +317,7 @@ class SymfonyStyle extends OutputStyle
}
}
/**
* {@inheritdoc}
*/
public function write($messages, bool $newline = false, int $type = self::OUTPUT_NORMAL)
public function write(string|iterable $messages, bool $newline = false, int $type = self::OUTPUT_NORMAL)
{
if (!is_iterable($messages)) {
$messages = [$messages];
@@ -398,9 +329,6 @@ class SymfonyStyle extends OutputStyle
}
}
/**
* {@inheritdoc}
*/
public function newLine(int $count = 1)
{
parent::newLine($count);
@@ -409,10 +337,8 @@ class SymfonyStyle extends OutputStyle
/**
* Returns a new instance which makes use of stderr if available.
*
* @return self
*/
public function getErrorStyle()
public function getErrorStyle(): self
{
return new self($this->input, $this->getErrorOutput());
}
@@ -428,11 +354,8 @@ class SymfonyStyle extends OutputStyle
private function getProgressBar(): ProgressBar
{
if (!$this->progressBar) {
throw new RuntimeException('The ProgressBar is not started.');
}
return $this->progressBar;
return $this->progressBar
?? throw new RuntimeException('The ProgressBar is not started.');
}
private function autoPrependBlock(): void
@@ -471,22 +394,25 @@ class SymfonyStyle extends OutputStyle
if (null !== $type) {
$type = sprintf('[%s] ', $type);
$indentLength = \strlen($type);
$indentLength = Helper::width($type);
$lineIndentation = str_repeat(' ', $indentLength);
}
// wrap and add newlines for each element
$outputWrapper = new OutputWrapper();
foreach ($messages as $key => $message) {
if ($escape) {
$message = OutputFormatter::escape($message);
}
$decorationLength = Helper::width($message) - Helper::width(Helper::removeDecoration($this->getFormatter(), $message));
$messageLineLength = min($this->lineLength - $prefixLength - $indentLength + $decorationLength, $this->lineLength);
$messageLines = explode(\PHP_EOL, wordwrap($message, $messageLineLength, \PHP_EOL, true));
foreach ($messageLines as $messageLine) {
$lines[] = $messageLine;
}
$lines = array_merge(
$lines,
explode(\PHP_EOL, $outputWrapper->wrap(
$message,
$this->lineLength - $prefixLength - $indentLength,
\PHP_EOL
))
);
if (\count($messages) > 1 && $key < \count($messages) - 1) {
$lines[] = '';

View File

@@ -11,18 +11,79 @@
namespace Symfony\Component\Console;
use Symfony\Component\Console\Output\AnsiColorMode;
class Terminal
{
private static $width;
private static $height;
private static $stty;
public const DEFAULT_COLOR_MODE = AnsiColorMode::Ansi4;
private static ?AnsiColorMode $colorMode = null;
private static ?int $width = null;
private static ?int $height = null;
private static ?bool $stty = null;
/**
* About Ansi color types: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
* For more information about true color support with terminals https://github.com/termstandard/colors/.
*/
public static function getColorMode(): AnsiColorMode
{
// Use Cache from previous run (or user forced mode)
if (null !== self::$colorMode) {
return self::$colorMode;
}
// Try with $COLORTERM first
if (\is_string($colorterm = getenv('COLORTERM'))) {
$colorterm = strtolower($colorterm);
if (str_contains($colorterm, 'truecolor')) {
self::setColorMode(AnsiColorMode::Ansi24);
return self::$colorMode;
}
if (str_contains($colorterm, '256color')) {
self::setColorMode(AnsiColorMode::Ansi8);
return self::$colorMode;
}
}
// Try with $TERM
if (\is_string($term = getenv('TERM'))) {
$term = strtolower($term);
if (str_contains($term, 'truecolor')) {
self::setColorMode(AnsiColorMode::Ansi24);
return self::$colorMode;
}
if (str_contains($term, '256color')) {
self::setColorMode(AnsiColorMode::Ansi8);
return self::$colorMode;
}
}
self::setColorMode(self::DEFAULT_COLOR_MODE);
return self::$colorMode;
}
/**
* Force a terminal color mode rendering.
*/
public static function setColorMode(?AnsiColorMode $colorMode): void
{
self::$colorMode = $colorMode;
}
/**
* Gets the terminal width.
*
* @return int
*/
public function getWidth()
public function getWidth(): int
{
$width = getenv('COLUMNS');
if (false !== $width) {
@@ -38,10 +99,8 @@ class Terminal
/**
* Gets the terminal height.
*
* @return int
*/
public function getHeight()
public function getHeight(): int
{
$height = getenv('LINES');
if (false !== $height) {
@@ -110,11 +169,11 @@ class Terminal
private static function initDimensionsUsingStty()
{
if ($sttyString = self::getSttyColumns()) {
if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) {
if (preg_match('/rows.(\d+);.columns.(\d+);/is', $sttyString, $matches)) {
// extract [w, h] from "rows h; columns w;"
self::$width = (int) $matches[2];
self::$height = (int) $matches[1];
} elseif (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) {
} elseif (preg_match('/;.(\d+).rows;.(\d+).columns/is', $sttyString, $matches)) {
// extract [w, h] from "; h rows; w columns"
self::$width = (int) $matches[2];
self::$height = (int) $matches[1];
@@ -143,10 +202,10 @@ class Terminal
*/
private static function getSttyColumns(): ?string
{
return self::readFromProcess('stty -a | grep columns');
return self::readFromProcess(['stty', '-a']);
}
private static function readFromProcess(string $command): ?string
private static function readFromProcess(string|array $command): ?string
{
if (!\function_exists('proc_open')) {
return null;

View File

@@ -28,7 +28,7 @@ class ApplicationTester
{
use TesterTrait;
private $application;
private Application $application;
public function __construct(Application $application)
{
@@ -47,7 +47,7 @@ class ApplicationTester
*
* @return int The command exit code
*/
public function run(array $input, array $options = [])
public function run(array $input, array $options = []): int
{
$prevShellVerbosity = getenv('SHELL_VERBOSITY');

View File

@@ -24,7 +24,7 @@ class CommandTester
{
use TesterTrait;
private $command;
private Command $command;
public function __construct(Command $command)
{
@@ -46,7 +46,7 @@ class CommandTester
*
* @return int The command exit code
*/
public function execute(array $input, array $options = [])
public function execute(array $input, array $options = []): int
{
// set the command name automatically if the application requires
// this argument and no command name was passed

View File

@@ -16,33 +16,21 @@ use Symfony\Component\Console\Command\Command;
final class CommandIsSuccessful extends Constraint
{
/**
* {@inheritdoc}
*/
public function toString(): string
{
return 'is successful';
}
/**
* {@inheritdoc}
*/
protected function matches($other): bool
{
return Command::SUCCESS === $other;
}
/**
* {@inheritdoc}
*/
protected function failureDescription($other): string
{
return 'the command '.$this->toString();
}
/**
* {@inheritdoc}
*/
protected function additionalFailureDescription($other): string
{
$mapping = [

View File

@@ -23,25 +23,20 @@ use Symfony\Component\Console\Tester\Constraint\CommandIsSuccessful;
*/
trait TesterTrait
{
/** @var StreamOutput */
private $output;
private $inputs = [];
private $captureStreamsIndependently = false;
/** @var InputInterface */
private $input;
/** @var int */
private $statusCode;
private StreamOutput $output;
private array $inputs = [];
private bool $captureStreamsIndependently = false;
private InputInterface $input;
private int $statusCode;
/**
* Gets the display returned by the last execution of the command or application.
*
* @throws \RuntimeException If it's called before the execute method
*
* @return string
*/
public function getDisplay(bool $normalize = false)
public function getDisplay(bool $normalize = false): string
{
if (null === $this->output) {
if (!isset($this->output)) {
throw new \RuntimeException('Output not initialized, did you execute the command before requesting the display?');
}
@@ -60,10 +55,8 @@ trait TesterTrait
* Gets the output written to STDERR by the application.
*
* @param bool $normalize Whether to normalize end of lines to \n or not
*
* @return string
*/
public function getErrorOutput(bool $normalize = false)
public function getErrorOutput(bool $normalize = false): string
{
if (!$this->captureStreamsIndependently) {
throw new \LogicException('The error output is not available when the tester is run without "capture_stderr_separately" option set.');
@@ -82,20 +75,16 @@ trait TesterTrait
/**
* Gets the input instance used by the last execution of the command or application.
*
* @return InputInterface
*/
public function getInput()
public function getInput(): InputInterface
{
return $this->input;
}
/**
* Gets the output instance used by the last execution of the command or application.
*
* @return OutputInterface
*/
public function getOutput()
public function getOutput(): OutputInterface
{
return $this->output;
}
@@ -104,16 +93,10 @@ trait TesterTrait
* Gets the status code returned by the last execution of the command or application.
*
* @throws \RuntimeException If it's called before the execute method
*
* @return int
*/
public function getStatusCode()
public function getStatusCode(): int
{
if (null === $this->statusCode) {
throw new \RuntimeException('Status code not initialized, did you execute the command before requesting the status code?');
}
return $this->statusCode;
return $this->statusCode ?? throw new \RuntimeException('Status code not initialized, did you execute the command before requesting the status code?');
}
public function assertCommandIsSuccessful(string $message = ''): void
@@ -129,7 +112,7 @@ trait TesterTrait
*
* @return $this
*/
public function setInputs(array $inputs)
public function setInputs(array $inputs): static
{
$this->inputs = $inputs;
@@ -169,12 +152,10 @@ trait TesterTrait
$reflectedOutput = new \ReflectionObject($this->output);
$strErrProperty = $reflectedOutput->getProperty('stderr');
$strErrProperty->setAccessible(true);
$strErrProperty->setValue($this->output, $errorOutput);
$reflectedParent = $reflectedOutput->getParentClass();
$streamProperty = $reflectedParent->getProperty('stream');
$streamProperty->setAccessible(true);
$streamProperty->setValue($this->output, fopen('php://memory', 'w', false));
}
}

View File

@@ -16,25 +16,23 @@
}
],
"require": {
"php": ">=7.2.5",
"php": ">=8.1",
"symfony/deprecation-contracts": "^2.1|^3",
"symfony/polyfill-mbstring": "~1.0",
"symfony/polyfill-php73": "^1.9",
"symfony/polyfill-php80": "^1.16",
"symfony/service-contracts": "^1.1|^2|^3",
"symfony/string": "^5.1|^6.0"
"symfony/string": "^5.4|^6.0"
},
"require-dev": {
"symfony/config": "^4.4|^5.0|^6.0",
"symfony/event-dispatcher": "^4.4|^5.0|^6.0",
"symfony/dependency-injection": "^4.4|^5.0|^6.0",
"symfony/lock": "^4.4|^5.0|^6.0",
"symfony/process": "^4.4|^5.0|^6.0",
"symfony/var-dumper": "^4.4|^5.0|^6.0",
"psr/log": "^1|^2"
"symfony/config": "^5.4|^6.0",
"symfony/event-dispatcher": "^5.4|^6.0",
"symfony/dependency-injection": "^5.4|^6.0",
"symfony/lock": "^5.4|^6.0",
"symfony/process": "^5.4|^6.0",
"symfony/var-dumper": "^5.4|^6.0",
"psr/log": "^1|^2|^3"
},
"provide": {
"psr/log-implementation": "1.0|2.0"
"psr/log-implementation": "1.0|2.0|3.0"
},
"suggest": {
"symfony/event-dispatcher": "",
@@ -43,12 +41,11 @@
"psr/log": "For using the console logger"
},
"conflict": {
"psr/log": ">=3",
"symfony/dependency-injection": "<4.4",
"symfony/dotenv": "<5.1",
"symfony/event-dispatcher": "<4.4",
"symfony/lock": "<4.4",
"symfony/process": "<4.4"
"symfony/dependency-injection": "<5.4",
"symfony/dotenv": "<5.4",
"symfony/event-dispatcher": "<5.4",
"symfony/lock": "<5.4",
"symfony/process": "<5.4"
},
"autoload": {
"psr-4": { "Symfony\\Component\\Console\\": "" },

View File

@@ -26,11 +26,11 @@ use Symfony\Component\CssSelector\XPath\Translator;
*/
class CssSelectorConverter
{
private $translator;
private $cache;
private Translator $translator;
private array $cache;
private static $xmlCache = [];
private static $htmlCache = [];
private static array $xmlCache = [];
private static array $htmlCache = [];
/**
* @param bool $html Whether HTML support should be enabled. Disable it for XML documents
@@ -59,11 +59,9 @@ class CssSelectorConverter
*
* Optionally, a prefix can be added to the resulting XPath
* expression with the $prefix parameter.
*
* @return string
*/
public function toXPath(string $cssExpr, string $prefix = 'descendant-or-self::')
public function toXPath(string $cssExpr, string $prefix = 'descendant-or-self::'): string
{
return $this->cache[$prefix][$cssExpr] ?? $this->cache[$prefix][$cssExpr] = $this->translator->cssToXPath($cssExpr, $prefix);
return $this->cache[$prefix][$cssExpr] ??= $this->translator->cssToXPath($cssExpr, $prefix);
}
}

View File

@@ -23,42 +23,27 @@ use Symfony\Component\CssSelector\Parser\Token;
*/
class SyntaxErrorException extends ParseException
{
/**
* @return self
*/
public static function unexpectedToken(string $expectedValue, Token $foundToken)
public static function unexpectedToken(string $expectedValue, Token $foundToken): self
{
return new self(sprintf('Expected %s, but %s found.', $expectedValue, $foundToken));
}
/**
* @return self
*/
public static function pseudoElementFound(string $pseudoElement, string $unexpectedLocation)
public static function pseudoElementFound(string $pseudoElement, string $unexpectedLocation): self
{
return new self(sprintf('Unexpected pseudo-element "::%s" found %s.', $pseudoElement, $unexpectedLocation));
}
/**
* @return self
*/
public static function unclosedString(int $position)
public static function unclosedString(int $position): self
{
return new self(sprintf('Unclosed/invalid string at %s.', $position));
}
/**
* @return self
*/
public static function nestedNot()
public static function nestedNot(): self
{
return new self('Got nested ::not().');
}
/**
* @return self
*/
public static function stringAsFunctionArgument()
public static function stringAsFunctionArgument(): self
{
return new self('String not allowed as function argument.');
}

Some files were not shown because too many files have changed in this diff Show More