updated-packages

This commit is contained in:
RafficMohammed
2023-01-08 00:13:22 +05:30
parent 3ff7df7487
commit da241bacb6
12659 changed files with 563377 additions and 510538 deletions

View File

@@ -36,14 +36,16 @@ use Symfony\Component\Console\Input\InputAwareInterface;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\StreamableInputInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\Debug\ErrorHandler as LegacyErrorHandler;
use Symfony\Component\Debug\Exception\FatalThrowableError;
use Symfony\Component\ErrorHandler\ErrorHandler;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy;
use Symfony\Contracts\Service\ResetInterface;
/**
* An Application is the container for a collection of commands.
@@ -60,9 +62,9 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Application
class Application implements ResetInterface
{
private $commands = array();
private $commands = [];
private $wantHelps = false;
private $runningCommand;
private $name;
@@ -75,7 +77,7 @@ class Application
private $dispatcher;
private $terminal;
private $defaultCommand;
private $singleCommand;
private $singleCommand = false;
private $initialized;
/**
@@ -90,9 +92,12 @@ class Application
$this->defaultCommand = 'list';
}
/**
* @final since Symfony 4.3, the type-hint will be updated to the interface from symfony/contracts in 5.0
*/
public function setDispatcher(EventDispatcherInterface $dispatcher)
{
$this->dispatcher = $dispatcher;
$this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher);
}
public function setCommandLoader(CommandLoaderInterface $commandLoader)
@@ -109,8 +114,10 @@ class Application
*/
public function run(InputInterface $input = null, OutputInterface $output = null)
{
putenv('LINES='.$this->terminal->getHeight());
putenv('COLUMNS='.$this->terminal->getWidth());
if (\function_exists('putenv')) {
@putenv('LINES='.$this->terminal->getHeight());
@putenv('COLUMNS='.$this->terminal->getWidth());
}
if (null === $input) {
$input = new ArgvInput();
@@ -120,22 +127,19 @@ class Application
$output = new ConsoleOutput();
}
$renderException = function ($e) use ($output) {
if (!$e instanceof \Exception) {
$e = class_exists(FatalThrowableError::class) ? new FatalThrowableError($e) : new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
}
$renderException = function (\Throwable $e) use ($output) {
if ($output instanceof ConsoleOutputInterface) {
$this->renderException($e, $output->getErrorOutput());
$this->renderThrowable($e, $output->getErrorOutput());
} else {
$this->renderException($e, $output);
$this->renderThrowable($e, $output);
}
};
if ($phpHandler = set_exception_handler($renderException)) {
restore_exception_handler();
if (!\is_array($phpHandler) || !$phpHandler[0] instanceof ErrorHandler) {
$debugHandler = true;
} elseif ($debugHandler = $phpHandler[0]->setExceptionHandler($renderException)) {
$phpHandler[0]->setExceptionHandler($debugHandler);
if (!\is_array($phpHandler) || (!$phpHandler[0] instanceof ErrorHandler && !$phpHandler[0] instanceof LegacyErrorHandler)) {
$errorHandler = true;
} elseif ($errorHandler = $phpHandler[0]->setExceptionHandler($renderException)) {
$phpHandler[0]->setExceptionHandler($errorHandler);
}
}
@@ -153,7 +157,7 @@ class Application
$exitCode = $e->getCode();
if (is_numeric($exitCode)) {
$exitCode = (int) $exitCode;
if (0 === $exitCode) {
if ($exitCode <= 0) {
$exitCode = 1;
}
} else {
@@ -167,7 +171,7 @@ class Application
restore_exception_handler();
}
restore_exception_handler();
} elseif (!$debugHandler) {
} elseif (!$errorHandler) {
$finalHandler = $phpHandler[0]->setExceptionHandler(null);
if ($finalHandler !== $renderException) {
$phpHandler[0]->setExceptionHandler($finalHandler);
@@ -193,17 +197,24 @@ class Application
*/
public function doRun(InputInterface $input, OutputInterface $output)
{
if (true === $input->hasParameterOption(array('--version', '-V'), true)) {
if (true === $input->hasParameterOption(['--version', '-V'], true)) {
$output->writeln($this->getLongVersion());
return 0;
}
try {
// Makes ArgvInput::getFirstArgument() able to distinguish an option from an argument.
$input->bind($this->getDefinition());
} catch (ExceptionInterface $e) {
// Errors must be ignored, full binding/validation happens later when the command is known.
}
$name = $this->getCommandName($input);
if (true === $input->hasParameterOption(array('--help', '-h'), true)) {
if (true === $input->hasParameterOption(['--help', '-h'], true)) {
if (!$name) {
$name = 'help';
$input = new ArrayInput(array('command_name' => $this->defaultCommand));
$input = new ArrayInput(['command_name' => $this->defaultCommand]);
} else {
$this->wantHelps = true;
}
@@ -214,9 +225,9 @@ class Application
$definition = $this->getDefinition();
$definition->setArguments(array_merge(
$definition->getArguments(),
array(
[
'command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name),
)
]
));
}
@@ -228,7 +239,7 @@ class Application
if (!($e instanceof CommandNotFoundException && !$e instanceof NamespaceNotFoundException) || 1 !== \count($alternatives = $e->getAlternatives()) || !$input->isInteractive()) {
if (null !== $this->dispatcher) {
$event = new ConsoleErrorEvent($input, $output, $e);
$this->dispatcher->dispatch(ConsoleEvents::ERROR, $event);
$this->dispatcher->dispatch($event, ConsoleEvents::ERROR);
if (0 === $event->getExitCode()) {
return 0;
@@ -243,11 +254,13 @@ class Application
$alternative = $alternatives[0];
$style = new SymfonyStyle($input, $output);
$style->block(sprintf("\nCommand \"%s\" is not defined.\n", $name), null, 'error');
$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(ConsoleEvents::ERROR, $event);
$this->dispatcher->dispatch($event, ConsoleEvents::ERROR);
return $event->getExitCode();
}
@@ -265,6 +278,13 @@ class Application
return $exitCode;
}
/**
* {@inheritdoc}
*/
public function reset()
{
}
public function setHelperSet(HelperSet $helperSet)
{
$this->helperSet = $helperSet;
@@ -461,12 +481,11 @@ class Application
if (!$command->isEnabled()) {
$command->setApplication(null);
return;
return null;
}
if (null === $command->getDefinition()) {
throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', \get_class($command)));
}
// Will throw if the command is not correctly initialized.
$command->getDefinition();
if (!$command->getName()) {
throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', \get_class($command)));
@@ -498,6 +517,11 @@ class Application
throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name));
}
// When the command has a different name than the one used at the command loader level
if (!isset($this->commands[$name])) {
throw new CommandNotFoundException(sprintf('The "%s" command cannot be found because it is registered under multiple names. Make sure you don\'t set a different name via constructor or "setName()".', $name));
}
$command = $this->commands[$name];
if ($this->wantHelps) {
@@ -535,8 +559,12 @@ class Application
*/
public function getNamespaces()
{
$namespaces = array();
$namespaces = [];
foreach ($this->all() as $command) {
if ($command->isHidden()) {
continue;
}
$namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName()));
foreach ($command->getAliases() as $alias) {
@@ -580,7 +608,7 @@ class Application
$exact = \in_array($namespace, $namespaces, true);
if (\count($namespaces) > 1 && !$exact) {
throw new NamespaceNotFoundException(sprintf("The namespace \"%s\" is ambiguous.\nDid you mean one of these?\n%s", $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))), array_values($namespaces));
throw new NamespaceNotFoundException(sprintf("The namespace \"%s\" is ambiguous.\nDid you mean one of these?\n%s.", $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))), array_values($namespaces));
}
return $exact ? $namespace : reset($namespaces);
@@ -602,7 +630,20 @@ class Application
{
$this->init();
$aliases = array();
$aliases = [];
foreach ($this->commands as $command) {
foreach ($command->getAliases() as $alias) {
if (!$this->has($alias)) {
$this->commands[$alias] = $command;
}
}
}
if ($this->has($name)) {
return $this->get($name);
}
$allCommands = $this->commandLoader ? array_merge($this->commandLoader->getNames(), array_keys($this->commands)) : array_keys($this->commands);
$expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name);
$commands = preg_grep('{^'.$expr.'}', $allCommands);
@@ -621,6 +662,11 @@ class Application
$message = sprintf('Command "%s" is not defined.', $name);
if ($alternatives = $this->findAlternatives($name, $allCommands)) {
// remove hidden commands
$alternatives = array_filter($alternatives, function ($name) {
return !$this->get($name)->isHidden();
});
if (1 == \count($alternatives)) {
$message .= "\n\nDid you mean this?\n ";
} else {
@@ -629,42 +675,58 @@ class Application
$message .= implode("\n ", $alternatives);
}
throw new CommandNotFoundException($message, $alternatives);
throw new CommandNotFoundException($message, array_values($alternatives));
}
// filter out aliases for commands which are already on the list
if (\count($commands) > 1) {
$commandList = $this->commandLoader ? array_merge(array_flip($this->commandLoader->getNames()), $this->commands) : $this->commands;
$commands = array_unique(array_filter($commands, function ($nameOrAlias) use ($commandList, $commands, &$aliases) {
$commandName = $commandList[$nameOrAlias] instanceof Command ? $commandList[$nameOrAlias]->getName() : $nameOrAlias;
$commands = array_unique(array_filter($commands, function ($nameOrAlias) use (&$commandList, $commands, &$aliases) {
if (!$commandList[$nameOrAlias] instanceof Command) {
$commandList[$nameOrAlias] = $this->commandLoader->get($nameOrAlias);
}
$commandName = $commandList[$nameOrAlias]->getName();
$aliases[$nameOrAlias] = $commandName;
return $commandName === $nameOrAlias || !\in_array($commandName, $commands);
}));
}
$exact = \in_array($name, $commands, true) || isset($aliases[$name]);
if (\count($commands) > 1 && !$exact) {
if (\count($commands) > 1) {
$usableWidth = $this->terminal->getWidth() - 10;
$abbrevs = array_values($commands);
$maxLen = 0;
foreach ($abbrevs as $abbrev) {
$maxLen = max(Helper::strlen($abbrev), $maxLen);
}
$abbrevs = array_map(function ($cmd) use ($commandList, $usableWidth, $maxLen) {
if (!$commandList[$cmd] instanceof Command) {
return $cmd;
$abbrevs = array_map(function ($cmd) use ($commandList, $usableWidth, $maxLen, &$commands) {
if ($commandList[$cmd]->isHidden()) {
unset($commands[array_search($cmd, $commands)]);
return false;
}
$abbrev = str_pad($cmd, $maxLen, ' ').' '.$commandList[$cmd]->getDescription();
return Helper::strlen($abbrev) > $usableWidth ? Helper::substr($abbrev, 0, $usableWidth - 3).'...' : $abbrev;
}, array_values($commands));
$suggestions = $this->getAbbreviationSuggestions($abbrevs);
throw new CommandNotFoundException(sprintf("Command \"%s\" is ambiguous.\nDid you mean one of these?\n%s", $name, $suggestions), array_values($commands));
if (\count($commands) > 1) {
$suggestions = $this->getAbbreviationSuggestions(array_filter($abbrevs));
throw new CommandNotFoundException(sprintf("Command \"%s\" is ambiguous.\nDid you mean one of these?\n%s.", $name, $suggestions), array_values($commands));
}
}
return $this->get($exact ? $name : reset($commands));
$command = $this->get(reset($commands));
if ($command->isHidden()) {
@trigger_error(sprintf('Command "%s" is hidden, finding it using an abbreviation is deprecated since Symfony 4.4, use its full name instead.', $command->getName()), \E_USER_DEPRECATED);
}
return $command;
}
/**
@@ -695,7 +757,7 @@ class Application
return $commands;
}
$commands = array();
$commands = [];
foreach ($this->commands as $name => $command) {
if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) {
$commands[$name] = $command;
@@ -722,7 +784,7 @@ class Application
*/
public static function getAbbreviations($names)
{
$abbrevs = array();
$abbrevs = [];
foreach ($names as $name) {
for ($len = \strlen($name); $len > 0; --$len) {
$abbrev = substr($name, 0, $len);
@@ -735,51 +797,107 @@ class Application
/**
* Renders a caught exception.
*
* @deprecated since Symfony 4.4, use "renderThrowable()" instead
*/
public function renderException(\Exception $e, OutputInterface $output)
{
@trigger_error(sprintf('The "%s::renderException()" method is deprecated since Symfony 4.4, use "renderThrowable()" instead.', __CLASS__), \E_USER_DEPRECATED);
$output->writeln('', OutputInterface::VERBOSITY_QUIET);
$this->doRenderException($e, $output);
$this->finishRenderThrowableOrException($output);
}
public function renderThrowable(\Throwable $e, OutputInterface $output): void
{
if (__CLASS__ !== static::class && __CLASS__ === (new \ReflectionMethod($this, 'renderThrowable'))->getDeclaringClass()->getName() && __CLASS__ !== (new \ReflectionMethod($this, 'renderException'))->getDeclaringClass()->getName()) {
@trigger_error(sprintf('The "%s::renderException()" method is deprecated since Symfony 4.4, use "renderThrowable()" instead.', __CLASS__), \E_USER_DEPRECATED);
if (!$e instanceof \Exception) {
$e = class_exists(FatalThrowableError::class) ? new FatalThrowableError($e) : new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine());
}
$this->renderException($e, $output);
return;
}
$output->writeln('', OutputInterface::VERBOSITY_QUIET);
$this->doRenderThrowable($e, $output);
$this->finishRenderThrowableOrException($output);
}
private function finishRenderThrowableOrException(OutputInterface $output): void
{
if (null !== $this->runningCommand) {
$output->writeln(sprintf('<info>%s</info>', sprintf($this->runningCommand->getSynopsis(), $this->getName())), OutputInterface::VERBOSITY_QUIET);
$output->writeln(sprintf('<info>%s</info>', OutputFormatter::escape(sprintf($this->runningCommand->getSynopsis(), $this->getName()))), OutputInterface::VERBOSITY_QUIET);
$output->writeln('', OutputInterface::VERBOSITY_QUIET);
}
}
/**
* @deprecated since Symfony 4.4, use "doRenderThrowable()" instead
*/
protected function doRenderException(\Exception $e, OutputInterface $output)
{
@trigger_error(sprintf('The "%s::doRenderException()" method is deprecated since Symfony 4.4, use "doRenderThrowable()" instead.', __CLASS__), \E_USER_DEPRECATED);
$this->doActuallyRenderThrowable($e, $output);
}
protected function doRenderThrowable(\Throwable $e, OutputInterface $output): void
{
if (__CLASS__ !== static::class && __CLASS__ === (new \ReflectionMethod($this, 'doRenderThrowable'))->getDeclaringClass()->getName() && __CLASS__ !== (new \ReflectionMethod($this, 'doRenderException'))->getDeclaringClass()->getName()) {
@trigger_error(sprintf('The "%s::doRenderException()" method is deprecated since Symfony 4.4, use "doRenderThrowable()" instead.', __CLASS__), \E_USER_DEPRECATED);
if (!$e instanceof \Exception) {
$e = class_exists(FatalThrowableError::class) ? new FatalThrowableError($e) : new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine());
}
$this->doRenderException($e, $output);
return;
}
$this->doActuallyRenderThrowable($e, $output);
}
private function doActuallyRenderThrowable(\Throwable $e, OutputInterface $output): void
{
do {
$message = trim($e->getMessage());
if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
$class = \get_class($e);
$class = 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class;
$class = get_debug_type($e);
$title = sprintf(' [%s%s] ', $class, 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : '');
$len = Helper::strlen($title);
} else {
$len = 0;
}
if (false !== strpos($message, "class@anonymous\0")) {
$message = preg_replace_callback('/class@anonymous\x00.*?\.php0x?[0-9a-fA-F]++/', function ($m) {
return \class_exists($m[0], false) ? get_parent_class($m[0]).'@anonymous' : $m[0];
if (str_contains($message, "@anonymous\0")) {
$message = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) {
return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0];
}, $message);
}
$width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : PHP_INT_MAX;
$lines = array();
foreach ('' !== $message ? preg_split('/\r?\n/', $message) : array() as $line) {
$width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : \PHP_INT_MAX;
$lines = [];
foreach ('' !== $message ? preg_split('/\r?\n/', $message) : [] as $line) {
foreach ($this->splitStringByWidth($line, $width - 4) as $line) {
// pre-format lines to get the right string length
$lineLength = Helper::strlen($line) + 4;
$lines[] = array($line, $lineLength);
$lines[] = [$line, $lineLength];
$len = max($lineLength, $len);
}
}
$messages = array();
$messages = [];
if (!$e instanceof ExceptionInterface || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
$messages[] = sprintf('<comment>%s</comment>', OutputFormatter::escape(sprintf('In %s line %s:', basename($e->getFile()) ?: 'n/a', $e->getLine() ?: 'n/a')));
}
@@ -801,21 +919,21 @@ class Application
// exception related properties
$trace = $e->getTrace();
array_unshift($trace, array(
array_unshift($trace, [
'function' => '',
'file' => $e->getFile() ?: 'n/a',
'line' => $e->getLine() ?: 'n/a',
'args' => array(),
));
'args' => [],
]);
for ($i = 0, $count = \count($trace); $i < $count; ++$i) {
$class = isset($trace[$i]['class']) ? $trace[$i]['class'] : '';
$type = isset($trace[$i]['type']) ? $trace[$i]['type'] : '';
$function = $trace[$i]['function'];
$file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a';
$line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a';
$class = $trace[$i]['class'] ?? '';
$type = $trace[$i]['type'] ?? '';
$function = $trace[$i]['function'] ?? '';
$file = $trace[$i]['file'] ?? 'n/a';
$line = $trace[$i]['line'] ?? 'n/a';
$output->writeln(sprintf(' %s%s%s() at <info>%s:%s</info>', $class, $type, $function, $file, $line), OutputInterface::VERBOSITY_QUIET);
$output->writeln(sprintf(' %s%s at <info>%s:%s</info>', $class, $function ? $type.$function.'()' : '', $file, $line), OutputInterface::VERBOSITY_QUIET);
}
$output->writeln('', OutputInterface::VERBOSITY_QUIET);
@@ -828,35 +946,35 @@ class Application
*/
protected function configureIO(InputInterface $input, OutputInterface $output)
{
if (true === $input->hasParameterOption(array('--ansi'), true)) {
if (true === $input->hasParameterOption(['--ansi'], true)) {
$output->setDecorated(true);
} elseif (true === $input->hasParameterOption(array('--no-ansi'), true)) {
} elseif (true === $input->hasParameterOption(['--no-ansi'], true)) {
$output->setDecorated(false);
}
if (true === $input->hasParameterOption(array('--no-interaction', '-n'), true)) {
if (true === $input->hasParameterOption(['--no-interaction', '-n'], true)) {
$input->setInteractive(false);
} elseif (\function_exists('posix_isatty')) {
$inputStream = null;
if ($input instanceof StreamableInputInterface) {
$inputStream = $input->getStream();
}
if (!@posix_isatty($inputStream) && false === getenv('SHELL_INTERACTIVE')) {
$input->setInteractive(false);
}
}
switch ($shellVerbosity = (int) getenv('SHELL_VERBOSITY')) {
case -1: $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); break;
case 1: $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); break;
case 2: $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); break;
case 3: $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); break;
default: $shellVerbosity = 0; break;
case -1:
$output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
break;
case 1:
$output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
break;
case 2:
$output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
break;
case 3:
$output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
break;
default:
$shellVerbosity = 0;
break;
}
if (true === $input->hasParameterOption(array('--quiet', '-q'), true)) {
if (true === $input->hasParameterOption(['--quiet', '-q'], true)) {
$output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
$shellVerbosity = -1;
} else {
@@ -876,7 +994,9 @@ class Application
$input->setInteractive(false);
}
putenv('SHELL_VERBOSITY='.$shellVerbosity);
if (\function_exists('putenv')) {
@putenv('SHELL_VERBOSITY='.$shellVerbosity);
}
$_ENV['SHELL_VERBOSITY'] = $shellVerbosity;
$_SERVER['SHELL_VERBOSITY'] = $shellVerbosity;
}
@@ -913,7 +1033,7 @@ class Application
$e = null;
try {
$this->dispatcher->dispatch(ConsoleEvents::COMMAND, $event);
$this->dispatcher->dispatch($event, ConsoleEvents::COMMAND);
if ($event->commandShouldRun()) {
$exitCode = $command->run($input, $output);
@@ -922,7 +1042,7 @@ class Application
}
} catch (\Throwable $e) {
$event = new ConsoleErrorEvent($input, $output, $e, $command);
$this->dispatcher->dispatch(ConsoleEvents::ERROR, $event);
$this->dispatcher->dispatch($event, ConsoleEvents::ERROR);
$e = $event->getError();
if (0 === $exitCode = $event->getExitCode()) {
@@ -931,7 +1051,7 @@ class Application
}
$event = new ConsoleTerminateEvent($command, $input, $output, $exitCode);
$this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event);
$this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE);
if (null !== $e) {
throw $e;
@@ -943,7 +1063,7 @@ class Application
/**
* Gets the name of the command based on input.
*
* @return string The command name
* @return string|null
*/
protected function getCommandName(InputInterface $input)
{
@@ -957,7 +1077,7 @@ class Application
*/
protected function getDefaultInputDefinition()
{
return new InputDefinition(array(
return new InputDefinition([
new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'),
@@ -967,7 +1087,7 @@ class Application
new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'),
new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'),
new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'),
));
]);
}
/**
@@ -977,7 +1097,7 @@ class Application
*/
protected function getDefaultCommands()
{
return array(new HelpCommand(), new ListCommand());
return [new HelpCommand(), new ListCommand()];
}
/**
@@ -987,22 +1107,18 @@ class Application
*/
protected function getDefaultHelperSet()
{
return new HelperSet(array(
return new HelperSet([
new FormatterHelper(),
new DebugFormatterHelper(),
new ProcessHelper(),
new QuestionHelper(),
));
]);
}
/**
* Returns abbreviated suggestions in string format.
*
* @param array $abbrevs Abbreviated suggestions to convert
*
* @return string A formatted string of abbreviated suggestions
*/
private function getAbbreviationSuggestions($abbrevs)
private function getAbbreviationSuggestions(array $abbrevs): string
{
return ' '.implode("\n ", $abbrevs);
}
@@ -1019,8 +1135,7 @@ class Application
*/
public function extractNamespace($name, $limit = null)
{
$parts = explode(':', $name);
array_pop($parts);
$parts = explode(':', $name, -1);
return implode(':', null === $limit ? $parts : \array_slice($parts, 0, $limit));
}
@@ -1029,17 +1144,14 @@ class Application
* Finds alternative of $name among $collection,
* if nothing is found in $collection, try in $abbrevs.
*
* @param string $name The string
* @param iterable $collection The collection
*
* @return string[] A sorted array of similar string
*/
private function findAlternatives($name, $collection)
private function findAlternatives(string $name, iterable $collection): array
{
$threshold = 1e3;
$alternatives = array();
$alternatives = [];
$collectionParts = array();
$collectionParts = [];
foreach ($collection as $item) {
$collectionParts[$item] = explode(':', $item);
}
@@ -1055,7 +1167,7 @@ class Application
}
$lev = levenshtein($subname, $parts[$i]);
if ($lev <= \strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) {
if ($lev <= \strlen($subname) / 3 || '' !== $subname && str_contains($parts[$i], $subname)) {
$alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev;
} elseif ($exists) {
$alternatives[$collectionName] += $threshold;
@@ -1065,13 +1177,13 @@ class Application
foreach ($collection as $item) {
$lev = levenshtein($name, $item);
if ($lev <= \strlen($name) / 3 || false !== strpos($item, $name)) {
if ($lev <= \strlen($name) / 3 || str_contains($item, $name)) {
$alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
}
}
$alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; });
ksort($alternatives, SORT_NATURAL | SORT_FLAG_CASE);
ksort($alternatives, \SORT_NATURAL | \SORT_FLAG_CASE);
return array_keys($alternatives);
}
@@ -1098,7 +1210,15 @@ class Application
return $this;
}
private function splitStringByWidth($string, $width)
/**
* @internal
*/
public function isSingleCommand(): bool
{
return $this->singleCommand;
}
private function splitStringByWidth(string $string, int $width): array
{
// str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly.
// additionally, array_slice() is not enough as some character has doubled width.
@@ -1108,17 +1228,23 @@ class Application
}
$utf8String = mb_convert_encoding($string, 'utf8', $encoding);
$lines = array();
$lines = [];
$line = '';
foreach (preg_split('//u', $utf8String) as $char) {
// test if $char could be appended to current line
if (mb_strwidth($line.$char, 'utf8') <= $width) {
$line .= $char;
continue;
$offset = 0;
while (preg_match('/.{1,10000}/u', $utf8String, $m, 0, $offset)) {
$offset += \strlen($m[0]);
foreach (preg_split('//u', $m[0]) as $char) {
// test if $char could be appended to current line
if (mb_strwidth($line.$char, 'utf8') <= $width) {
$line .= $char;
continue;
}
// if not, push current line to array and make new line
$lines[] = str_pad($line, $width);
$line = $char;
}
// if not, push current line to array and make new line
$lines[] = str_pad($line, $width);
$line = $char;
}
$lines[] = \count($lines) ? str_pad($line, $width) : $line;
@@ -1131,15 +1257,13 @@ class Application
/**
* Returns all namespaces of the command name.
*
* @param string $name The full name of the command
*
* @return string[] The namespaces of the command
*/
private function extractAllNamespaces($name)
private function extractAllNamespaces(string $name): array
{
// -1 as third argument is needed to skip the command short name when exploding
$parts = explode(':', $name, -1);
$namespaces = array();
$namespaces = [];
foreach ($parts as $part) {
if (\count($namespaces)) {

View File

@@ -1,10 +1,32 @@
CHANGELOG
=========
4.4.0
-----
* deprecated finding hidden commands using an abbreviation, use the full name instead
* added `Question::setTrimmable` default to true to allow the answer to be trimmed
* added method `minSecondsBetweenRedraws()` and `maxSecondsBetweenRedraws()` on `ProgressBar`
* `Application` implements `ResetInterface`
* marked all dispatched event classes as `@final`
* added support for displaying table horizontally
* deprecated returning `null` from `Command::execute()`, return `0` instead
* Deprecated the `Application::renderException()` and `Application::doRenderException()` methods,
use `renderThrowable()` and `doRenderThrowable()` instead.
* added support for the `NO_COLOR` env var (https://no-color.org/)
4.3.0
-----
* added support for hyperlinks
* added `ProgressBar::iterate()` method that simplify updating the progress bar when iterating
* added `Question::setAutocompleterCallback()` to provide a callback function
that dynamically generates suggestions as the user types
4.2.0
-----
* allowed passing commands as `array($process, 'ENV_VAR' => 'value')` to
* allowed passing commands as `[$process, 'ENV_VAR' => 'value']` to
`ProcessHelper::run()` to pass environment variables
* deprecated passing a command as a string to `ProcessHelper::run()`,
pass it the command as an array of its arguments instead
@@ -24,10 +46,10 @@ CHANGELOG
* `OutputFormatter` throws an exception when unknown options are used
* removed `QuestionHelper::setInputStream()/getInputStream()`
* removed `Application::getTerminalWidth()/getTerminalHeight()` and
`Application::setTerminalDimensions()/getTerminalDimensions()`
* removed `ConsoleExceptionEvent`
* removed `ConsoleEvents::EXCEPTION`
* removed `Application::getTerminalWidth()/getTerminalHeight()` and
`Application::setTerminalDimensions()/getTerminalDimensions()`
* removed `ConsoleExceptionEvent`
* removed `ConsoleEvents::EXCEPTION`
3.4.0
-----
@@ -44,29 +66,29 @@ CHANGELOG
3.3.0
-----
* added `ExceptionListener`
* added `AddConsoleCommandPass` (originally in FrameworkBundle)
* [BC BREAK] `Input::getOption()` no longer returns the default value for options
with value optional explicitly passed empty
* added console.error event to catch exceptions thrown by other listeners
* deprecated console.exception event in favor of console.error
* added ability to handle `CommandNotFoundException` through the
`console.error` event
* deprecated default validation in `SymfonyQuestionHelper::ask`
* added `ExceptionListener`
* added `AddConsoleCommandPass` (originally in FrameworkBundle)
* [BC BREAK] `Input::getOption()` no longer returns the default value for options
with value optional explicitly passed empty
* added console.error event to catch exceptions thrown by other listeners
* deprecated console.exception event in favor of console.error
* added ability to handle `CommandNotFoundException` through the
`console.error` event
* deprecated default validation in `SymfonyQuestionHelper::ask`
3.2.0
------
* added `setInputs()` method to CommandTester for ease testing of commands expecting inputs
* added `setStream()` and `getStream()` methods to Input (implement StreamableInputInterface)
* added StreamableInputInterface
* added LockableTrait
* added `setInputs()` method to CommandTester for ease testing of commands expecting inputs
* added `setStream()` and `getStream()` methods to Input (implement StreamableInputInterface)
* added StreamableInputInterface
* added LockableTrait
3.1.0
-----
* added truncate method to FormatterHelper
* added setColumnWidth(s) method to Table
* added setColumnWidth(s) method to Table
2.8.3
-----

View File

@@ -37,17 +37,17 @@ class Command
private $application;
private $name;
private $processTitle;
private $aliases = array();
private $aliases = [];
private $definition;
private $hidden = false;
private $help;
private $description;
private $help = '';
private $description = '';
private $ignoreValidationErrors = false;
private $applicationDefinitionMerged = false;
private $applicationDefinitionMergedWithArgs = false;
private $code;
private $synopsis = array();
private $usages = array();
private $synopsis = [];
private $usages = [];
private $helperSet;
/**
@@ -55,7 +55,7 @@ class Command
*/
public static function getDefaultName()
{
$class = \get_called_class();
$class = static::class;
$r = new \ReflectionProperty($class, 'defaultName');
return $class === $r->class ? static::$defaultName : null;
@@ -105,7 +105,7 @@ class Command
/**
* Gets the helper set.
*
* @return HelperSet A HelperSet instance
* @return HelperSet|null A HelperSet instance
*/
public function getHelperSet()
{
@@ -115,7 +115,7 @@ class Command
/**
* Gets the application instance for this command.
*
* @return Application An Application instance
* @return Application|null An Application instance
*/
public function getApplication()
{
@@ -150,7 +150,7 @@ class Command
* execute() method, you set the code to execute by passing
* a Closure to the setCode() method.
*
* @return int|null null or 0 if everything went fine, or an error code
* @return int 0 if everything went fine, or an exit code
*
* @throws LogicException When this abstract method is not implemented
*
@@ -195,7 +195,7 @@ class Command
*
* @return int The command exit code
*
* @throws \Exception When binding input fails. Bypass this by calling {@link ignoreValidationErrors()}.
* @throws ExceptionInterface When input binding fails. Bypass this by calling {@link ignoreValidationErrors()}.
*
* @see setCode()
* @see execute()
@@ -223,7 +223,7 @@ class Command
if (null !== $this->processTitle) {
if (\function_exists('cli_set_process_title')) {
if (!@cli_set_process_title($this->processTitle)) {
if ('Darwin' === PHP_OS) {
if ('Darwin' === \PHP_OS) {
$output->writeln('<comment>Running "cli_set_process_title" as an unprivileged user is not supported on MacOS.</comment>', OutputInterface::VERBOSITY_VERY_VERBOSE);
} else {
cli_set_process_title($this->processTitle);
@@ -250,9 +250,13 @@ class Command
$input->validate();
if ($this->code) {
$statusCode = \call_user_func($this->code, $input, $output);
$statusCode = ($this->code)($input, $output);
} else {
$statusCode = $this->execute($input, $output);
if (!\is_int($statusCode)) {
@trigger_error(sprintf('Return value of "%s::execute()" should always be of the type int since Symfony 4.4, %s returned.', static::class, \gettype($statusCode)), \E_USER_DEPRECATED);
}
}
return is_numeric($statusCode) ? (int) $statusCode : 0;
@@ -277,7 +281,14 @@ class Command
if ($code instanceof \Closure) {
$r = new \ReflectionFunction($code);
if (null === $r->getClosureThis()) {
$code = \Closure::bind($code, $this);
set_error_handler(static function () {});
try {
if ($c = \Closure::bind($code, $this)) {
$code = $c;
}
} finally {
restore_error_handler();
}
}
}
@@ -339,6 +350,10 @@ class Command
*/
public function getDefinition()
{
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;
}
@@ -360,10 +375,10 @@ class Command
/**
* Adds an argument.
*
* @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|string[]|null $default The default value (for self::OPTIONAL mode only)
* @param string $name The argument name
* @param int|null $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL
* @param string $description A description text
* @param mixed $default The default value (for InputArgument::OPTIONAL mode only)
*
* @throws InvalidArgumentException When argument mode is not valid
*
@@ -379,11 +394,11 @@ class Command
/**
* Adds an option.
*
* @param string $name The option name
* @param string|array $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 $description A description text
* @param string|string[]|int|bool|null $default The default value (must be null for self::VALUE_NONE)
* @param string $name The option name
* @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 string $description A description text
* @param mixed $default The default value (must be null for InputOption::VALUE_NONE)
*
* @throws InvalidArgumentException If option mode is invalid or incompatible
*
@@ -425,8 +440,6 @@ class Command
* This feature should be used only when creating a long process command,
* like a daemon.
*
* PHP 5.5+ or the proctitle PECL library is required
*
* @param string $title The process title
*
* @return $this
@@ -441,7 +454,7 @@ class Command
/**
* Returns the command name.
*
* @return string The command name
* @return string|null
*/
public function getName()
{
@@ -451,7 +464,7 @@ class Command
/**
* @param bool $hidden Whether or not the command should be hidden from the list of commands
*
* @return Command The current instance
* @return $this
*/
public function setHidden($hidden)
{
@@ -525,15 +538,16 @@ class Command
public function getProcessedHelp()
{
$name = $this->name;
$isSingleCommand = $this->application && $this->application->isSingleCommand();
$placeholders = array(
$placeholders = [
'%command.name%',
'%command.full_name%',
);
$replacements = array(
];
$replacements = [
$name,
$_SERVER['PHP_SELF'].' '.$name,
);
$isSingleCommand ? $_SERVER['PHP_SELF'] : $_SERVER['PHP_SELF'].' '.$name,
];
return str_replace($placeholders, $replacements, $this->getHelp() ?: $this->getDescription());
}
@@ -550,7 +564,7 @@ class Command
public function setAliases($aliases)
{
if (!\is_array($aliases) && !$aliases instanceof \Traversable) {
throw new InvalidArgumentException('$aliases must be an array or an instance of \Traversable');
throw new InvalidArgumentException('$aliases must be an array or an instance of \Traversable.');
}
foreach ($aliases as $alias) {
@@ -599,7 +613,7 @@ class Command
*/
public function addUsage($usage)
{
if (0 !== strpos($usage, $this->name)) {
if (!str_starts_with($usage, $this->name)) {
$usage = sprintf('%s %s', $this->name, $usage);
}

View File

@@ -35,12 +35,12 @@ class HelpCommand extends Command
$this
->setName('help')
->setDefinition(array(
->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 InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'),
))
->setDescription('Displays help for a command')
])
->setDescription('Display help for a command')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command displays help for a given command:
@@ -71,11 +71,13 @@ EOF
}
$helper = new DescriptorHelper();
$helper->describe($output, $this->command, array(
$helper->describe($output, $this->command, [
'format' => $input->getOption('format'),
'raw_text' => $input->getOption('raw'),
));
]);
$this->command = null;
return 0;
}
}

View File

@@ -33,7 +33,7 @@ class ListCommand extends Command
$this
->setName('list')
->setDefinition($this->createDefinition())
->setDescription('Lists commands')
->setDescription('List commands')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command lists all commands:
@@ -69,22 +69,21 @@ EOF
protected function execute(InputInterface $input, OutputInterface $output)
{
$helper = new DescriptorHelper();
$helper->describe($output, $this->getApplication(), array(
$helper->describe($output, $this->getApplication(), [
'format' => $input->getOption('format'),
'raw_text' => $input->getOption('raw'),
'namespace' => $input->getArgument('namespace'),
));
]);
return 0;
}
/**
* {@inheritdoc}
*/
private function createDefinition()
private function createDefinition(): InputDefinition
{
return new InputDefinition(array(
return new InputDefinition([
new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'),
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'),
));
]);
}
}

View File

@@ -12,8 +12,8 @@
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Lock\Factory;
use Symfony\Component\Lock\Lock;
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\Store\FlockStore;
use Symfony\Component\Lock\Store\SemaphoreStore;
@@ -29,10 +29,8 @@ trait LockableTrait
/**
* Locks a command.
*
* @return bool
*/
private function lock($name = null, $blocking = false)
private function lock(string $name = null, bool $blocking = false): bool
{
if (!class_exists(SemaphoreStore::class)) {
throw new LogicException('To enable the locking feature you must install the symfony/lock component.');
@@ -48,7 +46,7 @@ trait LockableTrait
$store = new FlockStore();
}
$this->lock = (new Factory($store))->createLock($name ?: $this->getName());
$this->lock = (new LockFactory($store))->createLock($name ?: $this->getName());
if (!$this->lock->acquire($blocking)) {
$this->lock = null;

View File

@@ -1,5 +1,14 @@
<?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\CommandLoader;
use Symfony\Component\Console\Command\Command;

View File

@@ -1,5 +1,14 @@
<?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\CommandLoader;
use Psr\Container\ContainerInterface;
@@ -16,8 +25,7 @@ class ContainerCommandLoader implements CommandLoaderInterface
private $commandMap;
/**
* @param ContainerInterface $container A container from which to load command services
* @param array $commandMap An array with command names as keys and service ids as values
* @param array $commandMap An array with command names as keys and service ids as values
*/
public function __construct(ContainerInterface $container, array $commandMap)
{

View File

@@ -21,11 +21,11 @@ final class ConsoleEvents
/**
* The COMMAND event allows you to attach listeners before any command is
* executed by the console. It also allows you to modify the command, input and output
* before they are handled to the command.
* before they are handed to the command.
*
* @Event("Symfony\Component\Console\Event\ConsoleCommandEvent")
*/
const COMMAND = 'console.command';
public const COMMAND = 'console.command';
/**
* The TERMINATE event allows you to attach listeners after a command is
@@ -33,7 +33,7 @@ final class ConsoleEvents
*
* @Event("Symfony\Component\Console\Event\ConsoleTerminateEvent")
*/
const TERMINATE = 'console.terminate';
public const TERMINATE = 'console.terminate';
/**
* The ERROR event occurs when an uncaught exception or error appears.
@@ -43,5 +43,5 @@ final class ConsoleEvents
*
* @Event("Symfony\Component\Console\Event\ConsoleErrorEvent")
*/
const ERROR = 'console.error';
public const ERROR = 'console.error';
}

View File

@@ -38,9 +38,9 @@ class AddConsoleCommandPass implements CompilerPassInterface
public function process(ContainerBuilder $container)
{
$commandServices = $container->findTaggedServiceIds($this->commandTag, true);
$lazyCommandMap = array();
$lazyCommandRefs = array();
$serviceIds = array();
$lazyCommandMap = [];
$lazyCommandRefs = [];
$serviceIds = [];
foreach ($commandServices as $id => $tags) {
$definition = $container->getDefinition($id);
@@ -55,7 +55,7 @@ class AddConsoleCommandPass implements CompilerPassInterface
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));
}
$commandName = $class::getDefaultName();
$commandName = null !== $class::getDefaultName() ? str_replace('%', '%%', $class::getDefaultName()) : null;
}
if (null === $commandName) {
@@ -72,7 +72,7 @@ class AddConsoleCommandPass implements CompilerPassInterface
unset($tags[0]);
$lazyCommandMap[$commandName] = $id;
$lazyCommandRefs[$id] = new TypedReference($id, $class);
$aliases = array();
$aliases = [];
foreach ($tags as $tag) {
if (isset($tag['command'])) {
@@ -81,17 +81,17 @@ class AddConsoleCommandPass implements CompilerPassInterface
}
}
$definition->addMethodCall('setName', array($commandName));
$definition->addMethodCall('setName', [$commandName]);
if ($aliases) {
$definition->addMethodCall('setAliases', array($aliases));
$definition->addMethodCall('setAliases', [$aliases]);
}
}
$container
->register($this->commandLoaderServiceId, ContainerCommandLoader::class)
->setPublic(true)
->setArguments(array(ServiceLocatorTagPass::register($container, $lazyCommandRefs), $lazyCommandMap));
->setArguments([ServiceLocatorTagPass::register($container, $lazyCommandRefs), $lazyCommandMap]);
$container->setParameter('console.command.ids', $serviceIds);
}

View File

@@ -22,7 +22,7 @@ use Symfony\Component\Console\Exception\CommandNotFoundException;
*/
class ApplicationDescription
{
const GLOBAL_NAMESPACE = '_global';
public const GLOBAL_NAMESPACE = '_global';
private $application;
private $namespace;
@@ -50,10 +50,7 @@ class ApplicationDescription
$this->showHidden = $showHidden;
}
/**
* @return array
*/
public function getNamespaces()
public function getNamespaces(): array
{
if (null === $this->namespaces) {
$this->inspectApplication();
@@ -65,7 +62,7 @@ class ApplicationDescription
/**
* @return Command[]
*/
public function getCommands()
public function getCommands(): array
{
if (null === $this->commands) {
$this->inspectApplication();
@@ -75,29 +72,25 @@ class ApplicationDescription
}
/**
* @param string $name
*
* @return Command
*
* @throws CommandNotFoundException
*/
public function getCommand($name)
public function getCommand(string $name): Command
{
if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) {
throw new CommandNotFoundException(sprintf('Command %s does not exist.', $name));
throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
}
return isset($this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name];
return $this->commands[$name] ?? $this->aliases[$name];
}
private function inspectApplication()
{
$this->commands = array();
$this->namespaces = array();
$this->commands = [];
$this->namespaces = [];
$all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null);
foreach ($this->sortCommands($all) as $namespace => $commands) {
$names = array();
$names = [];
/** @var Command $command */
foreach ($commands as $name => $command) {
@@ -114,31 +107,37 @@ class ApplicationDescription
$names[] = $name;
}
$this->namespaces[$namespace] = array('id' => $namespace, 'commands' => $names);
$this->namespaces[$namespace] = ['id' => $namespace, 'commands' => $names];
}
}
private function sortCommands(array $commands): array
{
$namespacedCommands = array();
$globalCommands = array();
$namespacedCommands = [];
$globalCommands = [];
$sortedCommands = [];
foreach ($commands as $name => $command) {
$key = $this->application->extractNamespace($name, 1);
if (!$key) {
$globalCommands['_global'][$name] = $command;
if (\in_array($key, ['', self::GLOBAL_NAMESPACE], true)) {
$globalCommands[$name] = $command;
} else {
$namespacedCommands[$key][$name] = $command;
}
}
ksort($namespacedCommands);
$namespacedCommands = array_merge($globalCommands, $namespacedCommands);
foreach ($namespacedCommands as &$commandsSet) {
ksort($commandsSet);
if ($globalCommands) {
ksort($globalCommands);
$sortedCommands[self::GLOBAL_NAMESPACE] = $globalCommands;
}
// unset reference to keep scope clear
unset($commandsSet);
return $namespacedCommands;
if ($namespacedCommands) {
ksort($namespacedCommands, \SORT_STRING);
foreach ($namespacedCommands as $key => $commandsSet) {
ksort($commandsSet);
$sortedCommands[$key] = $commandsSet;
}
}
return $sortedCommands;
}
}

View File

@@ -34,7 +34,7 @@ abstract class Descriptor implements DescriptorInterface
/**
* {@inheritdoc}
*/
public function describe(OutputInterface $output, $object, array $options = array())
public function describe(OutputInterface $output, $object, array $options = [])
{
$this->output = $output;
@@ -72,36 +72,26 @@ abstract class Descriptor implements DescriptorInterface
/**
* Describes an InputArgument instance.
*
* @return string|mixed
*/
abstract protected function describeInputArgument(InputArgument $argument, array $options = array());
abstract protected function describeInputArgument(InputArgument $argument, array $options = []);
/**
* Describes an InputOption instance.
*
* @return string|mixed
*/
abstract protected function describeInputOption(InputOption $option, array $options = array());
abstract protected function describeInputOption(InputOption $option, array $options = []);
/**
* Describes an InputDefinition instance.
*
* @return string|mixed
*/
abstract protected function describeInputDefinition(InputDefinition $definition, array $options = array());
abstract protected function describeInputDefinition(InputDefinition $definition, array $options = []);
/**
* Describes a Command instance.
*
* @return string|mixed
*/
abstract protected function describeCommand(Command $command, array $options = array());
abstract protected function describeCommand(Command $command, array $options = []);
/**
* Describes an Application instance.
*
* @return string|mixed
*/
abstract protected function describeApplication(Application $application, array $options = array());
abstract protected function describeApplication(Application $application, array $options = []);
}

View File

@@ -23,9 +23,7 @@ interface DescriptorInterface
/**
* Describes an object if supported.
*
* @param OutputInterface $output
* @param object $object
* @param array $options
* @param object $object
*/
public function describe(OutputInterface $output, $object, array $options = array());
public function describe(OutputInterface $output, $object, array $options = []);
}

View File

@@ -29,7 +29,7 @@ class JsonDescriptor extends Descriptor
/**
* {@inheritdoc}
*/
protected function describeInputArgument(InputArgument $argument, array $options = array())
protected function describeInputArgument(InputArgument $argument, array $options = [])
{
$this->writeData($this->getInputArgumentData($argument), $options);
}
@@ -37,7 +37,7 @@ class JsonDescriptor extends Descriptor
/**
* {@inheritdoc}
*/
protected function describeInputOption(InputOption $option, array $options = array())
protected function describeInputOption(InputOption $option, array $options = [])
{
$this->writeData($this->getInputOptionData($option), $options);
}
@@ -45,7 +45,7 @@ class JsonDescriptor extends Descriptor
/**
* {@inheritdoc}
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = array())
protected function describeInputDefinition(InputDefinition $definition, array $options = [])
{
$this->writeData($this->getInputDefinitionData($definition), $options);
}
@@ -53,7 +53,7 @@ class JsonDescriptor extends Descriptor
/**
* {@inheritdoc}
*/
protected function describeCommand(Command $command, array $options = array())
protected function describeCommand(Command $command, array $options = [])
{
$this->writeData($this->getCommandData($command), $options);
}
@@ -61,17 +61,17 @@ class JsonDescriptor extends Descriptor
/**
* {@inheritdoc}
*/
protected function describeApplication(Application $application, array $options = array())
protected function describeApplication(Application $application, array $options = [])
{
$describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
$describedNamespace = $options['namespace'] ?? null;
$description = new ApplicationDescription($application, $describedNamespace, true);
$commands = array();
$commands = [];
foreach ($description->getCommands() as $command) {
$commands[] = $this->getCommandData($command);
}
$data = array();
$data = [];
if ('UNKNOWN' !== $application->getName()) {
$data['application']['name'] = $application->getName();
if ('UNKNOWN' !== $application->getVersion()) {
@@ -92,77 +92,65 @@ class JsonDescriptor extends Descriptor
/**
* Writes data as json.
*
* @return array|string
*/
private function writeData(array $data, array $options)
{
$this->write(json_encode($data, isset($options['json_encoding']) ? $options['json_encoding'] : 0));
$flags = $options['json_encoding'] ?? 0;
$this->write(json_encode($data, $flags));
}
/**
* @return array
*/
private function getInputArgumentData(InputArgument $argument)
private function getInputArgumentData(InputArgument $argument): array
{
return array(
return [
'name' => $argument->getName(),
'is_required' => $argument->isRequired(),
'is_array' => $argument->isArray(),
'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $argument->getDescription()),
'default' => INF === $argument->getDefault() ? 'INF' : $argument->getDefault(),
);
'default' => \INF === $argument->getDefault() ? 'INF' : $argument->getDefault(),
];
}
/**
* @return array
*/
private function getInputOptionData(InputOption $option)
private function getInputOptionData(InputOption $option): array
{
return array(
return [
'name' => '--'.$option->getName(),
'shortcut' => $option->getShortcut() ? '-'.str_replace('|', '|-', $option->getShortcut()) : '',
'accept_value' => $option->acceptValue(),
'is_value_required' => $option->isValueRequired(),
'is_multiple' => $option->isArray(),
'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $option->getDescription()),
'default' => INF === $option->getDefault() ? 'INF' : $option->getDefault(),
);
'default' => \INF === $option->getDefault() ? 'INF' : $option->getDefault(),
];
}
/**
* @return array
*/
private function getInputDefinitionData(InputDefinition $definition)
private function getInputDefinitionData(InputDefinition $definition): array
{
$inputArguments = array();
$inputArguments = [];
foreach ($definition->getArguments() as $name => $argument) {
$inputArguments[$name] = $this->getInputArgumentData($argument);
}
$inputOptions = array();
$inputOptions = [];
foreach ($definition->getOptions() as $name => $option) {
$inputOptions[$name] = $this->getInputOptionData($option);
}
return array('arguments' => $inputArguments, 'options' => $inputOptions);
return ['arguments' => $inputArguments, 'options' => $inputOptions];
}
/**
* @return array
*/
private function getCommandData(Command $command)
private function getCommandData(Command $command): array
{
$command->getSynopsis();
$command->mergeApplicationDefinition(false);
return array(
return [
'name' => $command->getName(),
'usage' => array_merge(array($command->getSynopsis()), $command->getUsages(), $command->getAliases()),
'usage' => array_merge([$command->getSynopsis()], $command->getUsages(), $command->getAliases()),
'description' => $command->getDescription(),
'help' => $command->getProcessedHelp(),
'definition' => $this->getInputDefinitionData($command->getNativeDefinition()),
'hidden' => $command->isHidden(),
);
];
}
}

View File

@@ -31,7 +31,7 @@ class MarkdownDescriptor extends Descriptor
/**
* {@inheritdoc}
*/
public function describe(OutputInterface $output, $object, array $options = array())
public function describe(OutputInterface $output, $object, array $options = [])
{
$decorated = $output->isDecorated();
$output->setDecorated(false);
@@ -52,7 +52,7 @@ class MarkdownDescriptor extends Descriptor
/**
* {@inheritdoc}
*/
protected function describeInputArgument(InputArgument $argument, array $options = array())
protected function describeInputArgument(InputArgument $argument, array $options = [])
{
$this->write(
'#### `'.($argument->getName() ?: '<none>')."`\n\n"
@@ -66,7 +66,7 @@ class MarkdownDescriptor extends Descriptor
/**
* {@inheritdoc}
*/
protected function describeInputOption(InputOption $option, array $options = array())
protected function describeInputOption(InputOption $option, array $options = [])
{
$name = '--'.$option->getName();
if ($option->getShortcut()) {
@@ -86,7 +86,7 @@ class MarkdownDescriptor extends Descriptor
/**
* {@inheritdoc}
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = array())
protected function describeInputDefinition(InputDefinition $definition, array $options = [])
{
if ($showArguments = \count($definition->getArguments()) > 0) {
$this->write('### Arguments');
@@ -112,7 +112,7 @@ class MarkdownDescriptor extends Descriptor
/**
* {@inheritdoc}
*/
protected function describeCommand(Command $command, array $options = array())
protected function describeCommand(Command $command, array $options = [])
{
$command->getSynopsis();
$command->mergeApplicationDefinition(false);
@@ -122,7 +122,7 @@ class MarkdownDescriptor extends Descriptor
.str_repeat('-', Helper::strlen($command->getName()) + 2)."\n\n"
.($command->getDescription() ? $command->getDescription()."\n\n" : '')
.'### Usage'."\n\n"
.array_reduce(array_merge(array($command->getSynopsis()), $command->getAliases(), $command->getUsages()), function ($carry, $usage) {
.array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), function ($carry, $usage) {
return $carry.'* `'.$usage.'`'."\n";
})
);
@@ -141,9 +141,9 @@ class MarkdownDescriptor extends Descriptor
/**
* {@inheritdoc}
*/
protected function describeApplication(Application $application, array $options = array())
protected function describeApplication(Application $application, array $options = [])
{
$describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
$describedNamespace = $options['namespace'] ?? null;
$description = new ApplicationDescription($application, $describedNamespace);
$title = $this->getApplicationTitle($application);
@@ -167,7 +167,7 @@ class MarkdownDescriptor extends Descriptor
}
}
private function getApplicationTitle(Application $application)
private function getApplicationTitle(Application $application): string
{
if ('UNKNOWN' !== $application->getName()) {
if ('UNKNOWN' !== $application->getVersion()) {

View File

@@ -31,7 +31,7 @@ class TextDescriptor extends Descriptor
/**
* {@inheritdoc}
*/
protected function describeInputArgument(InputArgument $argument, array $options = array())
protected function describeInputArgument(InputArgument $argument, array $options = [])
{
if (null !== $argument->getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) {
$default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($argument->getDefault()));
@@ -39,7 +39,7 @@ class TextDescriptor extends Descriptor
$default = '';
}
$totalWidth = isset($options['total_width']) ? $options['total_width'] : Helper::strlen($argument->getName());
$totalWidth = $options['total_width'] ?? Helper::strlen($argument->getName());
$spacingWidth = $totalWidth - \strlen($argument->getName());
$this->writeText(sprintf(' <info>%s</info> %s%s%s',
@@ -54,7 +54,7 @@ class TextDescriptor extends Descriptor
/**
* {@inheritdoc}
*/
protected function describeInputOption(InputOption $option, array $options = array())
protected function describeInputOption(InputOption $option, array $options = [])
{
if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) {
$default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($option->getDefault()));
@@ -71,7 +71,7 @@ class TextDescriptor extends Descriptor
}
}
$totalWidth = isset($options['total_width']) ? $options['total_width'] : $this->calculateTotalWidthForOptions(array($option));
$totalWidth = $options['total_width'] ?? $this->calculateTotalWidthForOptions([$option]);
$synopsis = sprintf('%s%s',
$option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ',
sprintf('--%s%s', $option->getName(), $value)
@@ -92,7 +92,7 @@ class TextDescriptor extends Descriptor
/**
* {@inheritdoc}
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = array())
protected function describeInputDefinition(InputDefinition $definition, array $options = [])
{
$totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions());
foreach ($definition->getArguments() as $argument) {
@@ -103,7 +103,7 @@ class TextDescriptor extends Descriptor
$this->writeText('<comment>Arguments:</comment>', $options);
$this->writeText("\n");
foreach ($definition->getArguments() as $argument) {
$this->describeInputArgument($argument, array_merge($options, array('total_width' => $totalWidth)));
$this->describeInputArgument($argument, array_merge($options, ['total_width' => $totalWidth]));
$this->writeText("\n");
}
}
@@ -113,20 +113,20 @@ class TextDescriptor extends Descriptor
}
if ($definition->getOptions()) {
$laterOptions = array();
$laterOptions = [];
$this->writeText('<comment>Options:</comment>', $options);
foreach ($definition->getOptions() as $option) {
if (\strlen($option->getShortcut()) > 1) {
if (\strlen($option->getShortcut() ?? '') > 1) {
$laterOptions[] = $option;
continue;
}
$this->writeText("\n");
$this->describeInputOption($option, array_merge($options, array('total_width' => $totalWidth)));
$this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
}
foreach ($laterOptions as $option) {
$this->writeText("\n");
$this->describeInputOption($option, array_merge($options, array('total_width' => $totalWidth)));
$this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
}
}
}
@@ -134,7 +134,7 @@ class TextDescriptor extends Descriptor
/**
* {@inheritdoc}
*/
protected function describeCommand(Command $command, array $options = array())
protected function describeCommand(Command $command, array $options = [])
{
$command->getSynopsis(true);
$command->getSynopsis(false);
@@ -148,7 +148,7 @@ class TextDescriptor extends Descriptor
}
$this->writeText('<comment>Usage:</comment>', $options);
foreach (array_merge(array($command->getSynopsis(true)), $command->getAliases(), $command->getUsages()) as $usage) {
foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) {
$this->writeText("\n");
$this->writeText(' '.OutputFormatter::escape($usage), $options);
}
@@ -174,9 +174,9 @@ class TextDescriptor extends Descriptor
/**
* {@inheritdoc}
*/
protected function describeApplication(Application $application, array $options = array())
protected function describeApplication(Application $application, array $options = [])
{
$describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
$describedNamespace = $options['namespace'] ?? null;
$description = new ApplicationDescription($application, $describedNamespace);
if (isset($options['raw_text']) && $options['raw_text']) {
@@ -212,7 +212,7 @@ class TextDescriptor extends Descriptor
// calculate max. width based on available commands per namespace
$width = $this->getColumnWidth(array_merge(...array_values(array_map(function ($namespace) use ($commands) {
return array_intersect($namespace['commands'], array_keys($commands));
}, $namespaces))));
}, array_values($namespaces)))));
if ($describedNamespace) {
$this->writeText(sprintf('<comment>Available commands for the "%s" namespace:</comment>', $describedNamespace), $options);
@@ -250,7 +250,7 @@ class TextDescriptor extends Descriptor
/**
* {@inheritdoc}
*/
private function writeText($content, array $options = array())
private function writeText(string $content, array $options = [])
{
$this->write(
isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content,
@@ -280,7 +280,7 @@ class TextDescriptor extends Descriptor
*/
private function formatDefaultValue($default): string
{
if (INF === $default) {
if (\INF === $default) {
return 'INF';
}
@@ -294,15 +294,15 @@ class TextDescriptor extends Descriptor
}
}
return str_replace('\\\\', '\\', json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
return str_replace('\\\\', '\\', json_encode($default, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE));
}
/**
* @param (Command|string)[] $commands
* @param array<Command|string> $commands
*/
private function getColumnWidth(array $commands): int
{
$widths = array();
$widths = [];
foreach ($commands as $command) {
if ($command instanceof Command) {

View File

@@ -26,10 +26,7 @@ use Symfony\Component\Console\Input\InputOption;
*/
class XmlDescriptor extends Descriptor
{
/**
* @return \DOMDocument
*/
public function getInputDefinitionDocument(InputDefinition $definition)
public function getInputDefinitionDocument(InputDefinition $definition): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($definitionXML = $dom->createElement('definition'));
@@ -47,10 +44,7 @@ class XmlDescriptor extends Descriptor
return $dom;
}
/**
* @return \DOMDocument
*/
public function getCommandDocument(Command $command)
public function getCommandDocument(Command $command): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($commandXML = $dom->createElement('command'));
@@ -64,7 +58,7 @@ class XmlDescriptor extends Descriptor
$commandXML->appendChild($usagesXML = $dom->createElement('usages'));
foreach (array_merge(array($command->getSynopsis()), $command->getAliases(), $command->getUsages()) as $usage) {
foreach (array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()) as $usage) {
$usagesXML->appendChild($dom->createElement('usage', $usage));
}
@@ -80,13 +74,7 @@ class XmlDescriptor extends Descriptor
return $dom;
}
/**
* @param Application $application
* @param string|null $namespace
*
* @return \DOMDocument
*/
public function getApplicationDocument(Application $application, $namespace = null)
public function getApplicationDocument(Application $application, string $namespace = null): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($rootXml = $dom->createElement('symfony'));
@@ -130,7 +118,7 @@ class XmlDescriptor extends Descriptor
/**
* {@inheritdoc}
*/
protected function describeInputArgument(InputArgument $argument, array $options = array())
protected function describeInputArgument(InputArgument $argument, array $options = [])
{
$this->writeDocument($this->getInputArgumentDocument($argument));
}
@@ -138,7 +126,7 @@ class XmlDescriptor extends Descriptor
/**
* {@inheritdoc}
*/
protected function describeInputOption(InputOption $option, array $options = array())
protected function describeInputOption(InputOption $option, array $options = [])
{
$this->writeDocument($this->getInputOptionDocument($option));
}
@@ -146,7 +134,7 @@ class XmlDescriptor extends Descriptor
/**
* {@inheritdoc}
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = array())
protected function describeInputDefinition(InputDefinition $definition, array $options = [])
{
$this->writeDocument($this->getInputDefinitionDocument($definition));
}
@@ -154,7 +142,7 @@ class XmlDescriptor extends Descriptor
/**
* {@inheritdoc}
*/
protected function describeCommand(Command $command, array $options = array())
protected function describeCommand(Command $command, array $options = [])
{
$this->writeDocument($this->getCommandDocument($command));
}
@@ -162,9 +150,9 @@ class XmlDescriptor extends Descriptor
/**
* {@inheritdoc}
*/
protected function describeApplication(Application $application, array $options = array())
protected function describeApplication(Application $application, array $options = [])
{
$this->writeDocument($this->getApplicationDocument($application, isset($options['namespace']) ? $options['namespace'] : null));
$this->writeDocument($this->getApplicationDocument($application, $options['namespace'] ?? null));
}
/**
@@ -179,8 +167,6 @@ class XmlDescriptor extends Descriptor
/**
* Writes DOM document.
*
* @return \DOMDocument|string
*/
private function writeDocument(\DOMDocument $dom)
{
@@ -200,7 +186,7 @@ class XmlDescriptor extends Descriptor
$descriptionXML->appendChild($dom->createTextNode($argument->getDescription()));
$objectXML->appendChild($defaultsXML = $dom->createElement('defaults'));
$defaults = \is_array($argument->getDefault()) ? $argument->getDefault() : (\is_bool($argument->getDefault()) ? array(var_export($argument->getDefault(), true)) : ($argument->getDefault() ? array($argument->getDefault()) : array()));
$defaults = \is_array($argument->getDefault()) ? $argument->getDefault() : (\is_bool($argument->getDefault()) ? [var_export($argument->getDefault(), true)] : ($argument->getDefault() ? [$argument->getDefault()] : []));
foreach ($defaults as $default) {
$defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
$defaultXML->appendChild($dom->createTextNode($default));
@@ -215,7 +201,7 @@ class XmlDescriptor extends Descriptor
$dom->appendChild($objectXML = $dom->createElement('option'));
$objectXML->setAttribute('name', '--'.$option->getName());
$pos = strpos($option->getShortcut(), '|');
$pos = strpos($option->getShortcut() ?? '', '|');
if (false !== $pos) {
$objectXML->setAttribute('shortcut', '-'.substr($option->getShortcut(), 0, $pos));
$objectXML->setAttribute('shortcuts', '-'.str_replace('|', '|-', $option->getShortcut()));
@@ -229,7 +215,7 @@ class XmlDescriptor extends Descriptor
$descriptionXML->appendChild($dom->createTextNode($option->getDescription()));
if ($option->acceptValue()) {
$defaults = \is_array($option->getDefault()) ? $option->getDefault() : (\is_bool($option->getDefault()) ? array(var_export($option->getDefault(), true)) : ($option->getDefault() ? array($option->getDefault()) : array()));
$defaults = \is_array($option->getDefault()) ? $option->getDefault() : (\is_bool($option->getDefault()) ? [var_export($option->getDefault(), true)] : ($option->getDefault() ? [$option->getDefault()] : []));
$objectXML->appendChild($defaultsXML = $dom->createElement('defaults'));
if (!empty($defaults)) {

View File

@@ -15,13 +15,15 @@ namespace Symfony\Component\Console\Event;
* Allows to do things before the command is executed, like skipping the command or changing the input.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final since Symfony 4.4
*/
class ConsoleCommandEvent extends ConsoleEvent
{
/**
* The return code for skipped commands, this will also be passed into the terminate event.
*/
const RETURN_CODE_DISABLED = 113;
public const RETURN_CODE_DISABLED = 113;
/**
* Indicates if the command should be run or skipped.

View File

@@ -53,6 +53,6 @@ final class ConsoleErrorEvent extends ConsoleEvent
public function getExitCode(): int
{
return null !== $this->exitCode ? $this->exitCode : (\is_int($this->error->getCode()) && 0 !== $this->error->getCode() ? $this->error->getCode() : 1);
return $this->exitCode ?? (\is_int($this->error->getCode()) && 0 !== $this->error->getCode() ? $this->error->getCode() : 1);
}
}

View File

@@ -28,7 +28,7 @@ class ConsoleEvent extends Event
private $input;
private $output;
public function __construct(Command $command = null, InputInterface $input, OutputInterface $output)
public function __construct(?Command $command, InputInterface $input, OutputInterface $output)
{
$this->command = $command;
$this->input = $input;

View File

@@ -19,6 +19,8 @@ use Symfony\Component\Console\Output\OutputInterface;
* Allows to manipulate the exit code of a command after its execution.
*
* @author Francesco Levorato <git@flevour.net>
*
* @final since Symfony 4.4
*/
class ConsoleTerminateEvent extends ConsoleEvent
{

View File

@@ -40,10 +40,12 @@ class ErrorListener implements EventSubscriberInterface
$error = $event->getError();
if (!$inputString = $this->getInputString($event)) {
return $this->logger->error('An error occurred while using the console. Message: "{message}"', array('exception' => $error, 'message' => $error->getMessage()));
$this->logger->critical('An error occurred while using the console. Message: "{message}"', ['exception' => $error, 'message' => $error->getMessage()]);
return;
}
$this->logger->error('Error thrown while running command "{command}". Message: "{message}"', array('exception' => $error, 'command' => $inputString, 'message' => $error->getMessage()));
$this->logger->critical('Error thrown while running command "{command}". Message: "{message}"', ['exception' => $error, 'command' => $inputString, 'message' => $error->getMessage()]);
}
public function onConsoleTerminate(ConsoleTerminateEvent $event)
@@ -59,28 +61,30 @@ class ErrorListener implements EventSubscriberInterface
}
if (!$inputString = $this->getInputString($event)) {
return $this->logger->debug('The console exited with code "{code}"', array('code' => $exitCode));
$this->logger->debug('The console exited with code "{code}"', ['code' => $exitCode]);
return;
}
$this->logger->debug('Command "{command}" exited with code "{code}"', array('command' => $inputString, 'code' => $exitCode));
$this->logger->debug('Command "{command}" exited with code "{code}"', ['command' => $inputString, 'code' => $exitCode]);
}
public static function getSubscribedEvents()
{
return array(
ConsoleEvents::ERROR => array('onConsoleError', -128),
ConsoleEvents::TERMINATE => array('onConsoleTerminate', -128),
);
return [
ConsoleEvents::ERROR => ['onConsoleError', -128],
ConsoleEvents::TERMINATE => ['onConsoleTerminate', -128],
];
}
private static function getInputString(ConsoleEvent $event)
private static function getInputString(ConsoleEvent $event): ?string
{
$commandName = $event->getCommand() ? $event->getCommand()->getName() : null;
$input = $event->getInput();
if (method_exists($input, '__toString')) {
if ($commandName) {
return str_replace(array("'$commandName'", "\"$commandName\""), $commandName, (string) $input);
return str_replace(["'$commandName'", "\"$commandName\""], $commandName, (string) $input);
}
return (string) $input;

View File

@@ -21,12 +21,12 @@ class CommandNotFoundException extends \InvalidArgumentException implements Exce
private $alternatives;
/**
* @param string $message Exception message to throw
* @param array $alternatives List of similar defined names
* @param int $code Exception code
* @param \Exception $previous Previous exception used for the exception chaining
* @param string $message Exception message to throw
* @param string[] $alternatives List of similar defined names
* @param int $code Exception code
* @param \Throwable|null $previous Previous exception used for the exception chaining
*/
public function __construct(string $message, array $alternatives = array(), int $code = 0, \Exception $previous = null)
public function __construct(string $message, array $alternatives = [], int $code = 0, \Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
@@ -34,7 +34,7 @@ class CommandNotFoundException extends \InvalidArgumentException implements Exce
}
/**
* @return array A list of similar defined names
* @return string[] A list of similar defined names
*/
public function getAlternatives()
{

View File

@@ -12,7 +12,7 @@
namespace Symfony\Component\Console\Exception;
/**
* Represents an incorrect option name typed in the console.
* Represents an incorrect option name or value typed in the console.
*
* @author Jérôme Tamarelle <jerome@tamarelle.net>
*/

View File

@@ -0,0 +1,21 @@
<?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\Exception;
/**
* Represents failure to read input from stdin.
*
* @author Gabriel Ostrolucký <gabriel.ostrolucky@gmail.com>
*/
class MissingInputException extends RuntimeException implements ExceptionInterface
{
}

View File

@@ -22,11 +22,19 @@ use Symfony\Component\Console\Exception\InvalidArgumentException;
class OutputFormatter implements WrappableOutputFormatterInterface
{
private $decorated;
private $styles = array();
private $styles = [];
private $styleStack;
public function __clone()
{
$this->styleStack = clone $this->styleStack;
foreach ($this->styles as $key => $value) {
$this->styles[$key] = clone $value;
}
}
/**
* Escapes "<" special char in given text.
* Escapes "<" and ">" special chars in given text.
*
* @param string $text Text to escape
*
@@ -34,7 +42,7 @@ class OutputFormatter implements WrappableOutputFormatterInterface
*/
public static function escape($text)
{
$text = preg_replace('/([^\\\\]?)</', '$1\\<', $text);
$text = preg_replace('/([^\\\\]|^)([<>])/', '$1\\\\$2', $text);
return self::escapeTrailingBackslash($text);
}
@@ -42,15 +50,11 @@ class OutputFormatter implements WrappableOutputFormatterInterface
/**
* Escapes trailing "\" in given text.
*
* @param string $text Text to escape
*
* @return string Escaped text
*
* @internal
*/
public static function escapeTrailingBackslash($text)
public static function escapeTrailingBackslash(string $text): string
{
if ('\\' === substr($text, -1)) {
if (str_ends_with($text, '\\')) {
$len = \strlen($text);
$text = rtrim($text, '\\');
$text = str_replace("\0", '', $text);
@@ -63,10 +67,9 @@ class OutputFormatter implements WrappableOutputFormatterInterface
/**
* Initializes console output formatter.
*
* @param bool $decorated Whether this formatter should actually decorate strings
* @param OutputFormatterStyleInterface[] $styles Array of "name => FormatterStyle" instances
* @param OutputFormatterStyleInterface[] $styles Array of "name => FormatterStyle" instances
*/
public function __construct(bool $decorated = false, array $styles = array())
public function __construct(bool $decorated = false, array $styles = [])
{
$this->decorated = $decorated;
@@ -120,7 +123,7 @@ class OutputFormatter implements WrappableOutputFormatterInterface
public function getStyle($name)
{
if (!$this->hasStyle($name)) {
throw new InvalidArgumentException(sprintf('Undefined style: %s', $name));
throw new InvalidArgumentException(sprintf('Undefined style: "%s".', $name));
}
return $this->styles[strtolower($name)];
@@ -141,9 +144,10 @@ class OutputFormatter implements WrappableOutputFormatterInterface
{
$offset = 0;
$output = '';
$tagRegex = '[a-z][a-z0-9,_=;-]*+';
$openTagRegex = '[a-z](?:[^\\\\<>]*+ | \\\\.)*';
$closeTagRegex = '[a-z][^<>]*+';
$currentLineLength = 0;
preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#ix", $message, $matches, PREG_OFFSET_CAPTURE);
preg_match_all("#<(($openTagRegex) | /($closeTagRegex)?)>#ix", $message, $matches, \PREG_OFFSET_CAPTURE);
foreach ($matches[0] as $i => $match) {
$pos = $match[1];
$text = $match[0];
@@ -160,13 +164,13 @@ class OutputFormatter implements WrappableOutputFormatterInterface
if ($open = '/' != $text[1]) {
$tag = $matches[1][$i][0];
} else {
$tag = isset($matches[3][$i][0]) ? $matches[3][$i][0] : '';
$tag = $matches[3][$i][0] ?? '';
}
if (!$open && !$tag) {
// </>
$this->styleStack->pop();
} elseif (false === $style = $this->createStyleFromString(strtolower($tag))) {
} elseif (null === $style = $this->createStyleFromString($tag)) {
$output .= $this->applyCurrentStyle($text, $output, $width, $currentLineLength);
} elseif ($open) {
$this->styleStack->push($style);
@@ -177,11 +181,7 @@ class OutputFormatter implements WrappableOutputFormatterInterface
$output .= $this->applyCurrentStyle(substr($message, $offset), $output, $width, $currentLineLength);
if (false !== strpos($output, "\0")) {
return strtr($output, array("\0" => '\\', '\\<' => '<'));
}
return str_replace('\\<', '<', $output);
return strtr($output, ["\0" => '\\', '\\<' => '<', '\\>' => '>']);
}
/**
@@ -194,35 +194,37 @@ class OutputFormatter implements WrappableOutputFormatterInterface
/**
* Tries to create new style instance from string.
*
* @return OutputFormatterStyle|false False if string is not format string
*/
private function createStyleFromString(string $string)
private function createStyleFromString(string $string): ?OutputFormatterStyleInterface
{
if (isset($this->styles[$string])) {
return $this->styles[$string];
}
if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', $string, $matches, PREG_SET_ORDER)) {
return false;
if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', $string, $matches, \PREG_SET_ORDER)) {
return null;
}
$style = new OutputFormatterStyle();
foreach ($matches as $match) {
array_shift($match);
$match[0] = strtolower($match[0]);
if ('fg' == $match[0]) {
$style->setForeground($match[1]);
$style->setForeground(strtolower($match[1]));
} elseif ('bg' == $match[0]) {
$style->setBackground($match[1]);
$style->setBackground(strtolower($match[1]));
} elseif ('href' === $match[0]) {
$url = preg_replace('{\\\\([<>])}', '$1', $match[1]);
$style->setHref($url);
} elseif ('options' === $match[0]) {
preg_match_all('([^,;]+)', $match[1], $options);
preg_match_all('([^,;]+)', strtolower($match[1]), $options);
$options = array_shift($options);
foreach ($options as $option) {
$style->setOption($option);
}
} else {
return false;
return null;
}
}
@@ -262,8 +264,12 @@ class OutputFormatter implements WrappableOutputFormatterInterface
}
$lines = explode("\n", $text);
if ($width === $currentLineLength = \strlen(end($lines))) {
$currentLineLength = 0;
foreach ($lines as $line) {
$currentLineLength += \strlen($line);
if ($width <= $currentLineLength) {
$currentLineLength = 0;
}
}
if ($this->isDecorated()) {

View File

@@ -35,8 +35,7 @@ interface OutputFormatterInterface
/**
* Sets a new style.
*
* @param string $name The style name
* @param OutputFormatterStyleInterface $style The style instance
* @param string $name The style name
*/
public function setStyle($name, OutputFormatterStyleInterface $style);

View File

@@ -20,48 +20,49 @@ use Symfony\Component\Console\Exception\InvalidArgumentException;
*/
class OutputFormatterStyle implements OutputFormatterStyleInterface
{
private static $availableForegroundColors = array(
'black' => array('set' => 30, 'unset' => 39),
'red' => array('set' => 31, 'unset' => 39),
'green' => array('set' => 32, 'unset' => 39),
'yellow' => array('set' => 33, 'unset' => 39),
'blue' => array('set' => 34, 'unset' => 39),
'magenta' => array('set' => 35, 'unset' => 39),
'cyan' => array('set' => 36, 'unset' => 39),
'white' => array('set' => 37, 'unset' => 39),
'default' => array('set' => 39, 'unset' => 39),
);
private static $availableBackgroundColors = array(
'black' => array('set' => 40, 'unset' => 49),
'red' => array('set' => 41, 'unset' => 49),
'green' => array('set' => 42, 'unset' => 49),
'yellow' => array('set' => 43, 'unset' => 49),
'blue' => array('set' => 44, 'unset' => 49),
'magenta' => array('set' => 45, 'unset' => 49),
'cyan' => array('set' => 46, 'unset' => 49),
'white' => array('set' => 47, 'unset' => 49),
'default' => array('set' => 49, 'unset' => 49),
);
private static $availableOptions = array(
'bold' => array('set' => 1, 'unset' => 22),
'underscore' => array('set' => 4, 'unset' => 24),
'blink' => array('set' => 5, 'unset' => 25),
'reverse' => array('set' => 7, 'unset' => 27),
'conceal' => array('set' => 8, 'unset' => 28),
);
private static $availableForegroundColors = [
'black' => ['set' => 30, 'unset' => 39],
'red' => ['set' => 31, 'unset' => 39],
'green' => ['set' => 32, 'unset' => 39],
'yellow' => ['set' => 33, 'unset' => 39],
'blue' => ['set' => 34, 'unset' => 39],
'magenta' => ['set' => 35, 'unset' => 39],
'cyan' => ['set' => 36, 'unset' => 39],
'white' => ['set' => 37, 'unset' => 39],
'default' => ['set' => 39, 'unset' => 39],
];
private static $availableBackgroundColors = [
'black' => ['set' => 40, 'unset' => 49],
'red' => ['set' => 41, 'unset' => 49],
'green' => ['set' => 42, 'unset' => 49],
'yellow' => ['set' => 43, 'unset' => 49],
'blue' => ['set' => 44, 'unset' => 49],
'magenta' => ['set' => 45, 'unset' => 49],
'cyan' => ['set' => 46, 'unset' => 49],
'white' => ['set' => 47, 'unset' => 49],
'default' => ['set' => 49, 'unset' => 49],
];
private static $availableOptions = [
'bold' => ['set' => 1, 'unset' => 22],
'underscore' => ['set' => 4, 'unset' => 24],
'blink' => ['set' => 5, 'unset' => 25],
'reverse' => ['set' => 7, 'unset' => 27],
'conceal' => ['set' => 8, 'unset' => 28],
];
private $foreground;
private $background;
private $options = array();
private $href;
private $options = [];
private $handlesHrefGracefully;
/**
* Initializes output formatter style.
*
* @param string|null $foreground The style foreground color name
* @param string|null $background The style background color name
* @param array $options The style options
*/
public function __construct(string $foreground = null, string $background = null, array $options = array())
public function __construct(string $foreground = null, string $background = null, array $options = [])
{
if (null !== $foreground) {
$this->setForeground($foreground);
@@ -75,11 +76,7 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface
}
/**
* Sets style foreground color.
*
* @param string|null $color The color name
*
* @throws InvalidArgumentException When the color name isn't defined
* {@inheritdoc}
*/
public function setForeground($color = null)
{
@@ -90,18 +87,14 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface
}
if (!isset(static::$availableForegroundColors[$color])) {
throw new InvalidArgumentException(sprintf('Invalid foreground color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableForegroundColors))));
throw new InvalidArgumentException(sprintf('Invalid foreground color specified: "%s". Expected one of (%s).', $color, implode(', ', array_keys(static::$availableForegroundColors))));
}
$this->foreground = static::$availableForegroundColors[$color];
}
/**
* Sets style background color.
*
* @param string|null $color The color name
*
* @throws InvalidArgumentException When the color name isn't defined
* {@inheritdoc}
*/
public function setBackground($color = null)
{
@@ -112,23 +105,24 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface
}
if (!isset(static::$availableBackgroundColors[$color])) {
throw new InvalidArgumentException(sprintf('Invalid background color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableBackgroundColors))));
throw new InvalidArgumentException(sprintf('Invalid background color specified: "%s". Expected one of (%s).', $color, implode(', ', array_keys(static::$availableBackgroundColors))));
}
$this->background = static::$availableBackgroundColors[$color];
}
public function setHref(string $url): void
{
$this->href = $url;
}
/**
* Sets some specific style option.
*
* @param string $option The option name
*
* @throws InvalidArgumentException When the option name isn't defined
* {@inheritdoc}
*/
public function setOption($option)
{
if (!isset(static::$availableOptions[$option])) {
throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions))));
throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s).', $option, implode(', ', array_keys(static::$availableOptions))));
}
if (!\in_array(static::$availableOptions[$option], $this->options)) {
@@ -137,16 +131,12 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface
}
/**
* Unsets some specific style option.
*
* @param string $option The option name
*
* @throws InvalidArgumentException When the option name isn't defined
* {@inheritdoc}
*/
public function unsetOption($option)
{
if (!isset(static::$availableOptions[$option])) {
throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions))));
throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s).', $option, implode(', ', array_keys(static::$availableOptions))));
}
$pos = array_search(static::$availableOptions[$option], $this->options);
@@ -160,7 +150,7 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface
*/
public function setOptions(array $options)
{
$this->options = array();
$this->options = [];
foreach ($options as $option) {
$this->setOption($option);
@@ -168,16 +158,17 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface
}
/**
* Applies the style to a given text.
*
* @param string $text The text to style
*
* @return string
* {@inheritdoc}
*/
public function apply($text)
{
$setCodes = array();
$unsetCodes = array();
$setCodes = [];
$unsetCodes = [];
if (null === $this->handlesHrefGracefully) {
$this->handlesHrefGracefully = 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR')
&& (!getenv('KONSOLE_VERSION') || (int) getenv('KONSOLE_VERSION') > 201100);
}
if (null !== $this->foreground) {
$setCodes[] = $this->foreground['set'];
@@ -187,11 +178,14 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface
$setCodes[] = $this->background['set'];
$unsetCodes[] = $this->background['unset'];
}
if (\count($this->options)) {
foreach ($this->options as $option) {
$setCodes[] = $option['set'];
$unsetCodes[] = $option['unset'];
}
foreach ($this->options as $option) {
$setCodes[] = $option['set'];
$unsetCodes[] = $option['unset'];
}
if (null !== $this->href && $this->handlesHrefGracefully) {
$text = "\033]8;;$this->href\033\\$text\033]8;;\033\\";
}
if (0 === \count($setCodes)) {

View File

@@ -21,7 +21,7 @@ interface OutputFormatterStyleInterface
/**
* Sets style foreground color.
*
* @param string $color The color name
* @param string|null $color The color name
*/
public function setForeground($color = null);

View File

@@ -28,7 +28,7 @@ class OutputFormatterStyleStack implements ResetInterface
public function __construct(OutputFormatterStyleInterface $emptyStyle = null)
{
$this->emptyStyle = $emptyStyle ?: new OutputFormatterStyle();
$this->emptyStyle = $emptyStyle ?? new OutputFormatterStyle();
$this->reset();
}
@@ -37,7 +37,7 @@ class OutputFormatterStyleStack implements ResetInterface
*/
public function reset()
{
$this->styles = array();
$this->styles = [];
}
/**

View File

@@ -20,8 +20,8 @@ namespace Symfony\Component\Console\Helper;
*/
class DebugFormatterHelper extends Helper
{
private $colors = array('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default');
private $started = array();
private $colors = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default'];
private $started = [];
private $count = -1;
/**
@@ -35,7 +35,7 @@ class DebugFormatterHelper extends Helper
*/
public function start($id, $message, $prefix = 'RUN')
{
$this->started[$id] = array('border' => ++$this->count % \count($this->colors));
$this->started[$id] = ['border' => ++$this->count % \count($this->colors)];
return sprintf("%s<bg=blue;fg=white> %s </> <fg=blue>%s</>\n", $this->getBorder($id), $prefix, $message);
}
@@ -107,12 +107,7 @@ class DebugFormatterHelper extends Helper
return $message;
}
/**
* @param string $id The id of the formatting session
*
* @return string
*/
private function getBorder($id)
private function getBorder(string $id): string
{
return sprintf('<bg=%s> </>', $this->colors[$this->started[$id]['border']]);
}

View File

@@ -29,7 +29,7 @@ class DescriptorHelper extends Helper
/**
* @var DescriptorInterface[]
*/
private $descriptors = array();
private $descriptors = [];
public function __construct()
{
@@ -48,18 +48,16 @@ class DescriptorHelper extends Helper
* * format: string, the output format name
* * raw_text: boolean, sets output type as raw
*
* @param OutputInterface $output
* @param object $object
* @param array $options
* @param object $object
*
* @throws InvalidArgumentException when the given format is not supported
*/
public function describe(OutputInterface $output, $object, array $options = array())
public function describe(OutputInterface $output, $object, array $options = [])
{
$options = array_merge(array(
$options = array_merge([
'raw_text' => false,
'format' => 'txt',
), $options);
], $options);
if (!isset($this->descriptors[$options['format']])) {
throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $options['format']));
@@ -72,8 +70,7 @@ class DescriptorHelper extends Helper
/**
* Registers a descriptor.
*
* @param string $format
* @param DescriptorInterface $descriptor
* @param string $format
*
* @return $this
*/

View File

@@ -0,0 +1,64 @@
<?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;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\VarDumper\Cloner\ClonerInterface;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;
/**
* @author Roland Franssen <franssen.roland@gmail.com>
*/
final class Dumper
{
private $output;
private $dumper;
private $cloner;
private $handler;
public function __construct(OutputInterface $output, CliDumper $dumper = null, ClonerInterface $cloner = null)
{
$this->output = $output;
$this->dumper = $dumper;
$this->cloner = $cloner;
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->setColors($this->output->isDecorated());
return rtrim($dumper->dump(($this->cloner ?? $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));
}
};
}
}
public function __invoke($var): string
{
return ($this->handler)($var);
}
}

View File

@@ -46,20 +46,20 @@ class FormatterHelper extends Helper
public function formatBlock($messages, $style, $large = false)
{
if (!\is_array($messages)) {
$messages = array($messages);
$messages = [$messages];
}
$len = 0;
$lines = array();
$lines = [];
foreach ($messages as $message) {
$message = OutputFormatter::escape($message);
$lines[] = sprintf($large ? ' %s ' : ' %s ', $message);
$len = max($this->strlen($message) + ($large ? 4 : 2), $len);
$len = max(self::strlen($message) + ($large ? 4 : 2), $len);
}
$messages = $large ? array(str_repeat(' ', $len)) : array();
$messages = $large ? [str_repeat(' ', $len)] : [];
for ($i = 0; isset($lines[$i]); ++$i) {
$messages[] = $lines[$i].str_repeat(' ', $len - $this->strlen($lines[$i]));
$messages[] = $lines[$i].str_repeat(' ', $len - self::strlen($lines[$i]));
}
if ($large) {
$messages[] = str_repeat(' ', $len);
@@ -83,17 +83,13 @@ class FormatterHelper extends Helper
*/
public function truncate($message, $length, $suffix = '...')
{
$computedLength = $length - $this->strlen($suffix);
$computedLength = $length - self::strlen($suffix);
if ($computedLength > $this->strlen($message)) {
if ($computedLength > self::strlen($message)) {
return $message;
}
if (false === $encoding = mb_detect_encoding($message, null, true)) {
return substr($message, 0, $length).$suffix;
}
return mb_substr($message, 0, $length, $encoding).$suffix;
return self::substr($message, 0, $length).$suffix;
}
/**

View File

@@ -47,6 +47,8 @@ abstract class Helper implements HelperInterface
*/
public static function strlen($string)
{
$string = (string) $string;
if (false === $encoding = mb_detect_encoding($string, null, true)) {
return \strlen($string);
}
@@ -65,6 +67,8 @@ abstract class Helper implements HelperInterface
*/
public static function substr($string, $from, $length = null)
{
$string = (string) $string;
if (false === $encoding = mb_detect_encoding($string, null, true)) {
return substr($string, $from, $length);
}
@@ -74,17 +78,17 @@ abstract class Helper implements HelperInterface
public static function formatTime($secs)
{
static $timeFormats = array(
array(0, '< 1 sec'),
array(1, '1 sec'),
array(2, 'secs', 1),
array(60, '1 min'),
array(120, 'mins', 60),
array(3600, '1 hr'),
array(7200, 'hrs', 3600),
array(86400, '1 day'),
array(172800, 'days', 86400),
);
static $timeFormats = [
[0, '< 1 sec'],
[1, '1 sec'],
[2, 'secs', 1],
[60, '1 min'],
[120, 'mins', 60],
[3600, '1 hr'],
[7200, 'hrs', 3600],
[86400, '1 day'],
[172800, 'days', 86400],
];
foreach ($timeFormats as $index => $format) {
if ($secs >= $format[0]) {
@@ -131,6 +135,8 @@ abstract class Helper implements HelperInterface
$string = $formatter->format($string);
// remove already formatted characters
$string = preg_replace("/\033\[[^m]*m/", '', $string);
// remove terminal hyperlinks
$string = preg_replace('/\\033]8;[^;]*;[^\\033]*\\033\\\\/', '', $string);
$formatter->setDecorated($isDecorated);
return $string;

View File

@@ -24,13 +24,13 @@ class HelperSet implements \IteratorAggregate
/**
* @var Helper[]
*/
private $helpers = array();
private $helpers = [];
private $command;
/**
* @param Helper[] $helpers An array of helper
*/
public function __construct(array $helpers = array())
public function __construct(array $helpers = [])
{
foreach ($helpers as $alias => $helper) {
$this->set($helper, \is_int($alias) ? null : $alias);
@@ -40,8 +40,7 @@ class HelperSet implements \IteratorAggregate
/**
* Sets a helper.
*
* @param HelperInterface $helper The helper instance
* @param string $alias An alias
* @param string $alias An alias
*/
public function set(HelperInterface $helper, $alias = null)
{
@@ -99,8 +98,9 @@ class HelperSet implements \IteratorAggregate
}
/**
* @return Helper[]
* @return \Traversable<Helper>
*/
#[\ReturnTypeWillChange]
public function getIterator()
{
return new \ArrayIterator($this->helpers);

View File

@@ -28,17 +28,20 @@ class ProcessHelper extends Helper
/**
* Runs an external process.
*
* @param OutputInterface $output An OutputInterface instance
* @param array|Process $cmd An instance of Process or an array of the command and arguments
* @param string|null $error An error message that must be displayed if something went wrong
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
* @param int $verbosity The threshold for verbosity
* @param array|Process $cmd An instance of Process or an array of the command and arguments
* @param string|null $error An error message that must be displayed if something went wrong
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
* @param int $verbosity The threshold for verbosity
*
* @return Process The process that ran
*/
public function run(OutputInterface $output, $cmd, $error = null, callable $callback = null, $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE)
{
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".');
}
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
@@ -46,22 +49,22 @@ class ProcessHelper extends Helper
$formatter = $this->getHelperSet()->get('debug_formatter');
if ($cmd instanceof Process) {
$cmd = array($cmd);
$cmd = [$cmd];
}
if (!\is_array($cmd)) {
@trigger_error(sprintf('Passing a command as a string to "%s()" is deprecated since Symfony 4.2, pass it the command as an array of arguments instead.', __METHOD__), E_USER_DEPRECATED);
$cmd = array(\method_exists(Process::class, 'fromShellCommandline') ? Process::fromShellCommandline($cmd) : new Process($cmd));
@trigger_error(sprintf('Passing a command as a string to "%s()" is deprecated since Symfony 4.2, pass it the command as an array of arguments instead.', __METHOD__), \E_USER_DEPRECATED);
$cmd = [method_exists(Process::class, 'fromShellCommandline') ? Process::fromShellCommandline($cmd) : new Process($cmd)];
}
if (\is_string($cmd[0] ?? null)) {
$process = new Process($cmd);
$cmd = array();
$cmd = [];
} elseif (($cmd[0] ?? null) instanceof Process) {
$process = $cmd[0];
unset($cmd[0]);
} else {
throw new \InvalidArgumentException(sprintf('Invalid command provided to "%s()": the command should an array whose first is element is either the path to the binary to run of a "Process" object.', __METHOD__));
throw new \InvalidArgumentException(sprintf('Invalid command provided to "%s()": the command should be an array whose first element is either the path to the binary to run or a "Process" object.', __METHOD__));
}
if ($verbosity <= $output->getVerbosity()) {
@@ -92,11 +95,10 @@ class ProcessHelper extends Helper
* This is identical to run() except that an exception is thrown if the process
* exits with a non-zero exit code.
*
* @param OutputInterface $output An OutputInterface instance
* @param string|Process $cmd An instance of Process or a command to run
* @param string|null $error An error message that must be displayed if something went wrong
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
* @param array|Process $cmd An instance of Process or a command to run
* @param string|null $error An error message that must be displayed if something went wrong
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
*
* @return Process The process that ran
*
@@ -118,10 +120,6 @@ class ProcessHelper extends Helper
/**
* Wraps a Process callback to add debugging output.
*
* @param OutputInterface $output An OutputInterface interface
* @param Process $process The Process
* @param callable|null $callback A PHP callable
*
* @return callable
*/
public function wrapCallback(OutputInterface $output, Process $process, callable $callback = null)
@@ -136,12 +134,12 @@ class ProcessHelper extends Helper
$output->write($formatter->progress(spl_object_hash($process), $this->escapeString($buffer), Process::ERR === $type));
if (null !== $callback) {
\call_user_func($callback, $type, $buffer);
$callback($type, $buffer);
}
};
}
private function escapeString($str)
private function escapeString(string $str): string
{
return str_replace('<', '\\<', $str);
}

View File

@@ -32,6 +32,10 @@ final class ProgressBar
private $format;
private $internalFormat;
private $redrawFreq = 1;
private $writeCount;
private $lastWriteTime;
private $minSecondsBetweenRedraws = 0;
private $maxSecondsBetweenRedraws = 1;
private $output;
private $step = 0;
private $max;
@@ -39,19 +43,18 @@ final class ProgressBar
private $stepWidth;
private $percent = 0.0;
private $formatLineCount;
private $messages = array();
private $messages = [];
private $overwrite = true;
private $terminal;
private $firstRun = true;
private $previousMessage;
private static $formatters;
private static $formats;
/**
* @param OutputInterface $output An OutputInterface instance
* @param int $max Maximum steps (0 if unknown)
* @param int $max Maximum steps (0 if unknown)
*/
public function __construct(OutputInterface $output, int $max = 0)
public function __construct(OutputInterface $output, int $max = 0, float $minSecondsBetweenRedraws = 0.1)
{
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
@@ -61,12 +64,17 @@ final class ProgressBar
$this->setMaxSteps($max);
$this->terminal = new Terminal();
if (0 < $minSecondsBetweenRedraws) {
$this->redrawFreq = null;
$this->minSecondsBetweenRedraws = $minSecondsBetweenRedraws;
}
if (!$this->output->isDecorated()) {
// disable overwrite when output does not support ANSI codes.
$this->overwrite = false;
// set a reasonable redraw frequency so output isn't flooded
$this->setRedrawFrequency($max / 10);
$this->redrawFreq = null;
}
$this->startTime = time();
@@ -102,7 +110,7 @@ final class ProgressBar
self::$formatters = self::initPlaceholderFormatters();
}
return isset(self::$formatters[$name]) ? self::$formatters[$name] : null;
return self::$formatters[$name] ?? null;
}
/**
@@ -135,7 +143,7 @@ final class ProgressBar
self::$formats = self::initFormats();
}
return isset(self::$formats[$name]) ? self::$formats[$name] : null;
return self::$formats[$name] ?? null;
}
/**
@@ -183,6 +191,11 @@ final class ProgressBar
return $this->percent;
}
public function getBarOffset(): int
{
return floor($this->max ? $this->percent * $this->barWidth : (null === $this->redrawFreq ? (int) (min(5, $this->barWidth / 15) * $this->writeCount) : $this->step) % $this->barWidth);
}
public function setBarWidth(int $size)
{
$this->barWidth = max(1, $size);
@@ -236,11 +249,39 @@ final class ProgressBar
/**
* Sets the redraw frequency.
*
* @param int|float $freq The frequency in steps
* @param int|null $freq The frequency in steps
*/
public function setRedrawFrequency(int $freq)
public function setRedrawFrequency(?int $freq)
{
$this->redrawFreq = max($freq, 1);
$this->redrawFreq = null !== $freq ? max(1, $freq) : null;
}
public function minSecondsBetweenRedraws(float $seconds): void
{
$this->minSecondsBetweenRedraws = $seconds;
}
public function maxSecondsBetweenRedraws(float $seconds): void
{
$this->maxSecondsBetweenRedraws = $seconds;
}
/**
* Returns an iterator that will automatically update the progress bar when iterated.
*
* @param int|null $max Number of steps to complete the bar (0 if indeterminate), if null it will be inferred from $iterable
*/
public function iterate(iterable $iterable, int $max = null): iterable
{
$this->start($max ?? (is_countable($iterable) ? \count($iterable) : 0));
foreach ($iterable as $key => $value) {
yield $key => $value;
$this->advance();
}
$this->finish();
}
/**
@@ -287,11 +328,27 @@ final class ProgressBar
$step = 0;
}
$prevPeriod = (int) ($this->step / $this->redrawFreq);
$currPeriod = (int) ($step / $this->redrawFreq);
$redrawFreq = $this->redrawFreq ?? (($this->max ?: 10) / 10);
$prevPeriod = (int) ($this->step / $redrawFreq);
$currPeriod = (int) ($step / $redrawFreq);
$this->step = $step;
$this->percent = $this->max ? (float) $this->step / $this->max : 0;
if ($prevPeriod !== $currPeriod || $this->max === $step) {
$timeInterval = microtime(true) - $this->lastWriteTime;
// Draw regardless of other limits
if ($this->max === $step) {
$this->display();
return;
}
// Throttling
if ($timeInterval < $this->minSecondsBetweenRedraws) {
return;
}
// Draw each step period, but not too late
if ($prevPeriod !== $currPeriod || $timeInterval >= $this->maxSecondsBetweenRedraws) {
$this->display();
}
}
@@ -375,31 +432,43 @@ final class ProgressBar
*/
private function overwrite(string $message): void
{
if ($this->previousMessage === $message) {
return;
}
$originalMessage = $message;
if ($this->overwrite) {
if (!$this->firstRun) {
if (null !== $this->previousMessage) {
if ($this->output instanceof ConsoleSectionOutput) {
$lines = floor(Helper::strlen($message) / $this->terminal->getWidth()) + $this->formatLineCount + 1;
$this->output->clear($lines);
$messageLines = explode("\n", $message);
$lineCount = \count($messageLines);
foreach ($messageLines as $messageLine) {
$messageLineLength = Helper::strlenWithoutDecoration($this->output->getFormatter(), $messageLine);
if ($messageLineLength > $this->terminal->getWidth()) {
$lineCount += floor($messageLineLength / $this->terminal->getWidth());
}
}
$this->output->clear($lineCount);
} else {
// Move the cursor to the beginning of the line
$this->output->write("\x0D");
// Erase the line
$this->output->write("\x1B[2K");
// Erase previous lines
if ($this->formatLineCount > 0) {
$this->output->write(str_repeat("\x1B[1A\x1B[2K", $this->formatLineCount));
$message = str_repeat("\x1B[1A\x1B[2K", $this->formatLineCount).$message;
}
// Move the cursor to the beginning of the line and erase the line
$message = "\x0D\x1B[2K$message";
}
}
} elseif ($this->step > 0) {
$this->output->writeln('');
$message = \PHP_EOL.$message;
}
$this->firstRun = false;
$this->previousMessage = $originalMessage;
$this->lastWriteTime = microtime(true);
$this->output->write($message);
++$this->writeCount;
}
private function determineBestFormat(): string
@@ -419,9 +488,9 @@ final class ProgressBar
private static function initPlaceholderFormatters(): array
{
return array(
'bar' => function (ProgressBar $bar, OutputInterface $output) {
$completeBars = floor($bar->getMaxSteps() > 0 ? $bar->getProgressPercent() * $bar->getBarWidth() : $bar->getProgress() % $bar->getBarWidth());
return [
'bar' => function (self $bar, OutputInterface $output) {
$completeBars = $bar->getBarOffset();
$display = str_repeat($bar->getBarCharacter(), $completeBars);
if ($completeBars < $bar->getBarWidth()) {
$emptyBars = $bar->getBarWidth() - $completeBars - Helper::strlenWithoutDecoration($output->getFormatter(), $bar->getProgressCharacter());
@@ -430,10 +499,10 @@ final class ProgressBar
return $display;
},
'elapsed' => function (ProgressBar $bar) {
'elapsed' => function (self $bar) {
return Helper::formatTime(time() - $bar->getStartTime());
},
'remaining' => function (ProgressBar $bar) {
'remaining' => function (self $bar) {
if (!$bar->getMaxSteps()) {
throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.');
}
@@ -446,7 +515,7 @@ final class ProgressBar
return Helper::formatTime($remaining);
},
'estimated' => function (ProgressBar $bar) {
'estimated' => function (self $bar) {
if (!$bar->getMaxSteps()) {
throw new LogicException('Unable to display the estimated time if the maximum number of steps is not set.');
}
@@ -459,24 +528,24 @@ final class ProgressBar
return Helper::formatTime($estimated);
},
'memory' => function (ProgressBar $bar) {
'memory' => function (self $bar) {
return Helper::formatMemory(memory_get_usage(true));
},
'current' => function (ProgressBar $bar) {
return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', STR_PAD_LEFT);
'current' => function (self $bar) {
return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', \STR_PAD_LEFT);
},
'max' => function (ProgressBar $bar) {
'max' => function (self $bar) {
return $bar->getMaxSteps();
},
'percent' => function (ProgressBar $bar) {
'percent' => function (self $bar) {
return floor($bar->getProgressPercent() * 100);
},
);
];
}
private static function initFormats(): array
{
return array(
return [
'normal' => ' %current%/%max% [%bar%] %percent:3s%%',
'normal_nomax' => ' %current% [%bar%]',
@@ -488,7 +557,7 @@ final class ProgressBar
'debug' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%',
'debug_nomax' => ' %current% [%bar%] %elapsed:6s% %memory:6s%',
);
];
}
private function buildLine(): string
@@ -496,7 +565,7 @@ final class ProgressBar
$regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i";
$callback = function ($matches) {
if ($formatter = $this::getPlaceholderFormatterDefinition($matches[1])) {
$text = \call_user_func($formatter, $this, $this->output);
$text = $formatter($this, $this->output);
} elseif (isset($this->messages[$matches[1]])) {
$text = $this->messages[$matches[1]];
} else {

View File

@@ -34,10 +34,9 @@ class ProgressIndicator
private static $formats;
/**
* @param OutputInterface $output
* @param string|null $format Indicator format
* @param int $indicatorChangeInterval Change interval in milliseconds
* @param array|null $indicatorValues Animated indicator characters
* @param string|null $format Indicator format
* @param int $indicatorChangeInterval Change interval in milliseconds
* @param array|null $indicatorValues Animated indicator characters
*/
public function __construct(OutputInterface $output, string $format = null, int $indicatorChangeInterval = 100, array $indicatorValues = null)
{
@@ -48,7 +47,7 @@ class ProgressIndicator
}
if (null === $indicatorValues) {
$indicatorValues = array('-', '\\', '|', '/');
$indicatorValues = ['-', '\\', '|', '/'];
}
$indicatorValues = array_values($indicatorValues);
@@ -150,7 +149,7 @@ class ProgressIndicator
self::$formats = self::initFormats();
}
return isset(self::$formats[$name]) ? self::$formats[$name] : null;
return self::$formats[$name] ?? null;
}
/**
@@ -183,7 +182,7 @@ class ProgressIndicator
self::$formatters = self::initPlaceholderFormatters();
}
return isset(self::$formatters[$name]) ? self::$formatters[$name] : null;
return self::$formatters[$name] ?? null;
}
private function display()
@@ -192,18 +191,16 @@ class ProgressIndicator
return;
}
$self = $this;
$this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) use ($self) {
if ($formatter = $self::getPlaceholderFormatterDefinition($matches[1])) {
return \call_user_func($formatter, $self);
$this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) {
if ($formatter = self::getPlaceholderFormatterDefinition($matches[1])) {
return $formatter($this);
}
return $matches[0];
}, $this->format));
}, $this->format ?? ''));
}
private function determineBestFormat()
private function determineBestFormat(): string
{
switch ($this->output->getVerbosity()) {
// OutputInterface::VERBOSITY_QUIET: display is disabled anyway
@@ -230,32 +227,32 @@ class ProgressIndicator
}
}
private function getCurrentTimeInMilliseconds()
private function getCurrentTimeInMilliseconds(): float
{
return round(microtime(true) * 1000);
}
private static function initPlaceholderFormatters()
private static function initPlaceholderFormatters(): array
{
return array(
'indicator' => function (ProgressIndicator $indicator) {
return [
'indicator' => function (self $indicator) {
return $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)];
},
'message' => function (ProgressIndicator $indicator) {
'message' => function (self $indicator) {
return $indicator->message;
},
'elapsed' => function (ProgressIndicator $indicator) {
'elapsed' => function (self $indicator) {
return Helper::formatTime(time() - $indicator->startTime);
},
'memory' => function () {
return Helper::formatMemory(memory_get_usage(true));
},
);
];
}
private static function initFormats()
private static function initFormats(): array
{
return array(
return [
'normal' => ' %indicator% %message%',
'normal_no_ansi' => ' %message%',
@@ -264,6 +261,6 @@ class ProgressIndicator
'very_verbose' => ' %indicator% %message% (%elapsed:6s%, %memory:6s%)',
'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)',
);
];
}
}

View File

@@ -11,6 +11,7 @@
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Exception\MissingInputException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
@@ -21,6 +22,7 @@ use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Terminal;
/**
* The QuestionHelper class provides helpers to interact with the user.
@@ -31,7 +33,8 @@ class QuestionHelper extends Helper
{
private $inputStream;
private static $shell;
private static $stty;
private static $stty = true;
private static $stdinIsInteractive;
/**
* Asks a question to the user.
@@ -47,38 +50,32 @@ class QuestionHelper extends Helper
}
if (!$input->isInteractive()) {
$default = $question->getDefault();
if (null !== $default && $question instanceof ChoiceQuestion) {
$choices = $question->getChoices();
if (!$question->isMultiselect()) {
return isset($choices[$default]) ? $choices[$default] : $default;
}
$default = explode(',', $default);
foreach ($default as $k => $v) {
$v = trim($v);
$default[$k] = isset($choices[$v]) ? $choices[$v] : $v;
}
}
return $default;
return $this->getDefaultAnswer($question);
}
if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) {
$this->inputStream = $stream;
}
if (!$question->getValidator()) {
return $this->doAsk($output, $question);
try {
if (!$question->getValidator()) {
return $this->doAsk($output, $question);
}
$interviewer = function () use ($output, $question) {
return $this->doAsk($output, $question);
};
return $this->validateAttempts($interviewer, $output, $question);
} catch (MissingInputException $exception) {
$input->setInteractive(false);
if (null === $fallbackOutput = $this->getDefaultAnswer($question)) {
throw $exception;
}
return $fallbackOutput;
}
$interviewer = function () use ($output, $question) {
return $this->doAsk($output, $question);
};
return $this->validateAttempts($interviewer, $output, $question);
}
/**
@@ -100,7 +97,7 @@ class QuestionHelper extends Helper
/**
* Asks the question to the user.
*
* @return bool|mixed|string|null
* @return mixed
*
* @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
*/
@@ -108,14 +105,15 @@ class QuestionHelper extends Helper
{
$this->writePrompt($output, $question);
$inputStream = $this->inputStream ?: STDIN;
$autocomplete = $question->getAutocompleterValues();
$inputStream = $this->inputStream ?: \STDIN;
$autocomplete = $question->getAutocompleterCallback();
if (null === $autocomplete || !$this->hasSttyAvailable()) {
if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) {
$ret = false;
if ($question->isHidden()) {
try {
$ret = trim($this->getHiddenResponse($output, $inputStream));
$hiddenResponse = $this->getHiddenResponse($output, $inputStream, $question->isTrimmable());
$ret = $question->isTrimmable() ? trim($hiddenResponse) : $hiddenResponse;
} catch (RuntimeException $e) {
if (!$question->isHiddenFallback()) {
throw $e;
@@ -124,14 +122,19 @@ class QuestionHelper extends Helper
}
if (false === $ret) {
$cp = $this->setIOCodepage();
$ret = fgets($inputStream, 4096);
$ret = $this->resetIOCodepage($cp, $ret);
if (false === $ret) {
throw new RuntimeException('Aborted');
throw new MissingInputException('Aborted.');
}
if ($question->isTrimmable()) {
$ret = trim($ret);
}
$ret = trim($ret);
}
} else {
$ret = trim($this->autocomplete($output, $question, $inputStream, \is_array($autocomplete) ? $autocomplete : iterator_to_array($autocomplete, false)));
$autocomplete = $this->autocomplete($output, $question, $inputStream, $autocomplete);
$ret = $question->isTrimmable() ? trim($autocomplete) : $autocomplete;
}
if ($output instanceof ConsoleSectionOutput) {
@@ -147,6 +150,36 @@ class QuestionHelper extends Helper
return $ret;
}
/**
* @return mixed
*/
private function getDefaultAnswer(Question $question)
{
$default = $question->getDefault();
if (null === $default) {
return $default;
}
if ($validator = $question->getValidator()) {
return \call_user_func($question->getValidator(), $default);
} elseif ($question instanceof ChoiceQuestion) {
$choices = $question->getChoices();
if (!$question->isMultiselect()) {
return $choices[$default] ?? $default;
}
$default = explode(',', $default);
foreach ($default as $k => $v) {
$v = $question->isTrimmable() ? trim($v) : $v;
$default[$k] = $choices[$v] ?? $v;
}
}
return $default;
}
/**
* Outputs the question prompt.
*/
@@ -155,15 +188,9 @@ class QuestionHelper extends Helper
$message = $question->getQuestion();
if ($question instanceof ChoiceQuestion) {
$maxWidth = max(array_map(array($this, 'strlen'), array_keys($question->getChoices())));
$messages = (array) $question->getQuestion();
foreach ($question->getChoices() as $key => $value) {
$width = $maxWidth - $this->strlen($key);
$messages[] = ' [<info>'.$key.str_repeat(' ', $width).'</info>] '.$value;
}
$output->writeln($messages);
$output->writeln(array_merge([
$question->getQuestion(),
], $this->formatChoiceQuestionChoices($question, 'info')));
$message = $question->getPrompt();
}
@@ -171,6 +198,26 @@ class QuestionHelper extends Helper
$output->write($message);
}
/**
* @param string $tag
*
* @return string[]
*/
protected function formatChoiceQuestionChoices(ChoiceQuestion $question, $tag)
{
$messages = [];
$maxWidth = max(array_map([__CLASS__, 'strlen'], array_keys($choices = $question->getChoices())));
foreach ($choices as $key => $value) {
$padding = str_repeat(' ', $maxWidth - self::strlen($key));
$messages[] = sprintf(" [<$tag>%s$padding</$tag>] %s", $key, $value);
}
return $messages;
}
/**
* Outputs an error message.
*/
@@ -188,17 +235,16 @@ class QuestionHelper extends Helper
/**
* Autocompletes a question.
*
* @param OutputInterface $output
* @param Question $question
* @param resource $inputStream
* @param resource $inputStream
*/
private function autocomplete(OutputInterface $output, Question $question, $inputStream, array $autocomplete): string
private function autocomplete(OutputInterface $output, Question $question, $inputStream, callable $autocomplete): string
{
$fullChoice = '';
$ret = '';
$i = 0;
$ofs = -1;
$matches = $autocomplete;
$matches = $autocomplete($ret);
$numMatches = \count($matches);
$sttyMode = shell_exec('stty -g');
@@ -213,24 +259,28 @@ class QuestionHelper extends Helper
while (!feof($inputStream)) {
$c = fread($inputStream, 1);
// Backspace Character
if ("\177" === $c) {
// as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false.
if (false === $c || ('' === $ret && '' === $c && null === $question->getDefault())) {
shell_exec(sprintf('stty %s', $sttyMode));
throw new MissingInputException('Aborted.');
} elseif ("\177" === $c) { // Backspace Character
if (0 === $numMatches && 0 !== $i) {
--$i;
$fullChoice = self::substr($fullChoice, 0, $i);
// Move cursor backwards
$output->write("\033[1D");
}
if (0 === $i) {
$ofs = -1;
$matches = $autocomplete;
$matches = $autocomplete($ret);
$numMatches = \count($matches);
} else {
$numMatches = 0;
}
// Pop the last character off the end of our string
$ret = substr($ret, 0, $i);
$ret = self::substr($ret, 0, $i);
} elseif ("\033" === $c) {
// Did we read an escape sequence?
$c .= fread($inputStream, 2);
@@ -251,10 +301,21 @@ class QuestionHelper extends Helper
} elseif (\ord($c) < 32) {
if ("\t" === $c || "\n" === $c) {
if ($numMatches > 0 && -1 !== $ofs) {
$ret = $matches[$ofs];
$ret = (string) $matches[$ofs];
// Echo out remaining chars for current match
$output->write(substr($ret, $i));
$i = \strlen($ret);
$remainingCharacters = substr($ret, \strlen(trim($this->mostRecentlyEnteredValue($fullChoice))));
$output->write($remainingCharacters);
$fullChoice .= $remainingCharacters;
$i = (false === $encoding = mb_detect_encoding($fullChoice, null, true)) ? \strlen($fullChoice) : mb_strlen($fullChoice, $encoding);
$matches = array_filter(
$autocomplete($ret),
function ($match) use ($ret) {
return '' === $ret || str_starts_with($match, $ret);
}
);
$numMatches = \count($matches);
$ofs = -1;
}
if ("\n" === $c) {
@@ -267,16 +328,27 @@ class QuestionHelper extends Helper
continue;
} else {
if ("\x80" <= $c) {
$c .= fread($inputStream, ["\xC0" => 1, "\xD0" => 1, "\xE0" => 2, "\xF0" => 3][$c & "\xF0"]);
}
$output->write($c);
$ret .= $c;
$fullChoice .= $c;
++$i;
$tempRet = $ret;
if ($question instanceof ChoiceQuestion && $question->isMultiselect()) {
$tempRet = $this->mostRecentlyEnteredValue($fullChoice);
}
$numMatches = 0;
$ofs = 0;
foreach ($autocomplete as $value) {
foreach ($autocomplete($ret) as $value) {
// If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
if (0 === strpos($value, $ret)) {
if (str_starts_with($value, $tempRet)) {
$matches[$numMatches++] = $value;
}
}
@@ -288,8 +360,9 @@ class QuestionHelper extends Helper
if ($numMatches > 0 && -1 !== $ofs) {
// Save cursor position
$output->write("\0337");
// Write highlighted text
$output->write('<hl>'.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $i)).'</hl>');
// Write highlighted text, complete the partially entered response
$charactersEntered = \strlen(trim($this->mostRecentlyEnteredValue($fullChoice)));
$output->write('<hl>'.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $charactersEntered)).'</hl>');
// Restore cursor position
$output->write("\0338");
}
@@ -298,18 +371,33 @@ class QuestionHelper extends Helper
// Reset stty so it behaves normally again
shell_exec(sprintf('stty %s', $sttyMode));
return $ret;
return $fullChoice;
}
private function mostRecentlyEnteredValue(string $entered): string
{
// Determine the most recent value that the user entered
if (!str_contains($entered, ',')) {
return $entered;
}
$choices = explode(',', $entered);
if ('' !== $lastChoice = trim($choices[\count($choices) - 1])) {
return $lastChoice;
}
return $entered;
}
/**
* Gets a hidden response from user.
*
* @param OutputInterface $output An Output instance
* @param resource $inputStream The handler resource
* @param resource $inputStream The handler resource
* @param bool $trimmable Is the answer trimmable
*
* @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
*/
private function getHiddenResponse(OutputInterface $output, $inputStream): string
private function getHiddenResponse(OutputInterface $output, $inputStream, bool $trimmable = true): string
{
if ('\\' === \DIRECTORY_SEPARATOR) {
$exe = __DIR__.'/../Resources/bin/hiddeninput.exe';
@@ -321,7 +409,8 @@ class QuestionHelper extends Helper
$exe = $tmpExe;
}
$value = rtrim(shell_exec($exe));
$sExec = shell_exec('"'.$exe.'"');
$value = $trimmable ? rtrim($sExec) : $sExec;
$output->writeln('');
if (isset($tmpExe)) {
@@ -331,41 +420,34 @@ class QuestionHelper extends Helper
return $value;
}
if ($this->hasSttyAvailable()) {
if (self::$stty && Terminal::hasSttyAvailable()) {
$sttyMode = shell_exec('stty -g');
shell_exec('stty -echo');
$value = fgets($inputStream, 4096);
} elseif ($this->isInteractiveInput($inputStream)) {
throw new RuntimeException('Unable to hide the response.');
}
$value = fgets($inputStream, 4096);
if (self::$stty && Terminal::hasSttyAvailable()) {
shell_exec(sprintf('stty %s', $sttyMode));
}
if (false === $value) {
throw new RuntimeException('Aborted');
}
if (false === $value) {
throw new MissingInputException('Aborted.');
}
if ($trimmable) {
$value = trim($value);
$output->writeln('');
return $value;
}
$output->writeln('');
if (false !== $shell = $this->getShell()) {
$readCmd = 'csh' === $shell ? 'set mypassword = $<' : 'read -r mypassword';
$command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd);
$value = rtrim(shell_exec($command));
$output->writeln('');
return $value;
}
throw new RuntimeException('Unable to hide the response.');
return $value;
}
/**
* Validates an attempt.
*
* @param callable $interviewer A callable that will ask for a question and return the result
* @param OutputInterface $output An Output instance
* @param Question $question A Question instance
* @param callable $interviewer A callable that will ask for a question and return the result
*
* @return mixed The validated response
*
@@ -375,13 +457,14 @@ class QuestionHelper extends Helper
{
$error = null;
$attempts = $question->getMaxAttempts();
while (null === $attempts || $attempts--) {
if (null !== $error) {
$this->writeError($output, $error);
}
try {
return \call_user_func($question->getValidator(), $interviewer());
return $question->getValidator()($interviewer());
} catch (RuntimeException $e) {
throw $e;
} catch (\Exception $error) {
@@ -391,44 +474,67 @@ class QuestionHelper extends Helper
throw $error;
}
/**
* Returns a valid unix shell.
*
* @return string|bool The valid shell name, false in case no valid shell is found
*/
private function getShell()
private function isInteractiveInput($inputStream): bool
{
if (null !== self::$shell) {
return self::$shell;
if ('php://stdin' !== (stream_get_meta_data($inputStream)['uri'] ?? null)) {
return false;
}
self::$shell = false;
if (null !== self::$stdinIsInteractive) {
return self::$stdinIsInteractive;
}
if (file_exists('/usr/bin/env')) {
// handle other OSs with bash/zsh/ksh/csh if available to hide the answer
$test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null";
foreach (array('bash', 'zsh', 'ksh', 'csh') as $sh) {
if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) {
self::$shell = $sh;
break;
}
if (\function_exists('stream_isatty')) {
return self::$stdinIsInteractive = @stream_isatty(fopen('php://stdin', 'r'));
}
if (\function_exists('posix_isatty')) {
return self::$stdinIsInteractive = @posix_isatty(fopen('php://stdin', 'r'));
}
if (!\function_exists('exec')) {
return self::$stdinIsInteractive = true;
}
exec('stty 2> /dev/null', $output, $status);
return self::$stdinIsInteractive = 1 !== $status;
}
/**
* 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')) {
$cp = sapi_windows_cp_get();
sapi_windows_cp_set(sapi_windows_cp_get('oem'));
return $cp;
}
return 0;
}
/**
* 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)
{
if (0 !== $cp) {
sapi_windows_cp_set($cp);
if (false !== $input && '' !== $input) {
$input = sapi_windows_cp_conv(sapi_windows_cp_get('oem'), $cp, $input);
}
}
return self::$shell;
}
/**
* Returns whether Stty is available or not.
*/
private function hasSttyAvailable(): bool
{
if (null !== self::$stty) {
return self::$stty;
}
exec('stty 2>&1', $output, $exitcode);
return self::$stty = 0 === $exitcode;
return $input;
}
}

View File

@@ -58,7 +58,7 @@ class SymfonyQuestionHelper extends QuestionHelper
case $question instanceof ChoiceQuestion:
$choices = $question->getChoices();
$text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape(isset($choices[$default]) ? $choices[$default] : $default));
$text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape($choices[$default] ?? $default));
break;
@@ -68,15 +68,15 @@ class SymfonyQuestionHelper extends QuestionHelper
$output->writeln($text);
if ($question instanceof ChoiceQuestion) {
$width = max(array_map('strlen', array_keys($question->getChoices())));
$prompt = ' > ';
foreach ($question->getChoices() as $key => $value) {
$output->writeln(sprintf(" [<comment>%-${width}s</comment>] %s", $key, $value));
}
if ($question instanceof ChoiceQuestion) {
$output->writeln($this->formatChoiceQuestionChoices($question, 'comment'));
$prompt = $question->getPrompt();
}
$output->write(' > ');
$output->write($prompt);
}
/**

View File

@@ -13,6 +13,7 @@ namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\WrappableOutputFormatterInterface;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;
@@ -41,17 +42,18 @@ class Table
/**
* Table headers.
*/
private $headers = array();
private $headers = [];
/**
* Table rows.
*/
private $rows = array();
private $rows = [];
private $horizontal = false;
/**
* Column widths cache.
*/
private $effectiveColumnWidths = array();
private $effectiveColumnWidths = [];
/**
* Number of columns cache.
@@ -73,15 +75,15 @@ class Table
/**
* @var array
*/
private $columnStyles = array();
private $columnStyles = [];
/**
* User set column widths.
*
* @var array
*/
private $columnWidths = array();
private $columnMaxWidths = array();
private $columnWidths = [];
private $columnMaxWidths = [];
private static $styles;
@@ -101,8 +103,7 @@ class Table
/**
* Sets a style definition.
*
* @param string $name The style name
* @param TableStyle $style A TableStyle instance
* @param string $name The style name
*/
public static function setStyleDefinition($name, TableStyle $style)
{
@@ -206,13 +207,11 @@ class Table
/**
* Sets the minimum width of all columns.
*
* @param array $widths
*
* @return $this
*/
public function setColumnWidths(array $widths)
{
$this->columnWidths = array();
$this->columnWidths = [];
foreach ($widths as $index => $width) {
$this->setColumnWidth($index, $width);
}
@@ -243,7 +242,7 @@ class Table
{
$headers = array_values($headers);
if (!empty($headers) && !\is_array($headers[0])) {
$headers = array($headers);
$headers = [$headers];
}
$this->headers = $headers;
@@ -253,7 +252,7 @@ class Table
public function setRows(array $rows)
{
$this->rows = array();
$this->rows = [];
return $this->addRows($rows);
}
@@ -324,6 +323,13 @@ class Table
return $this;
}
public function setHorizontal(bool $horizontal = true): self
{
$this->horizontal = $horizontal;
return $this;
}
/**
* Renders table to output.
*
@@ -339,40 +345,84 @@ class Table
*/
public function render()
{
$rows = array_merge($this->headers, array($divider = new TableSeparator()), $this->rows);
$this->calculateNumberOfColumns($rows);
$rows = $this->buildTableRows($rows);
$this->calculateColumnsWidth($rows);
$isHeader = true;
$isFirstRow = false;
foreach ($rows as $row) {
if ($divider === $row) {
$isHeader = false;
$isFirstRow = true;
continue;
}
if ($row instanceof TableSeparator) {
$this->renderRowSeparator();
continue;
}
if (!$row) {
continue;
}
if ($isHeader || $isFirstRow) {
if ($isFirstRow) {
$this->renderRowSeparator(self::SEPARATOR_TOP_BOTTOM);
$isFirstRow = false;
} else {
$this->renderRowSeparator(self::SEPARATOR_TOP, $this->headerTitle, $this->style->getHeaderTitleFormat());
$divider = new TableSeparator();
if ($this->horizontal) {
$rows = [];
foreach ($this->headers[0] ?? [] as $i => $header) {
$rows[$i] = [$header];
foreach ($this->rows as $row) {
if ($row instanceof TableSeparator) {
continue;
}
if (isset($row[$i])) {
$rows[$i][] = $row[$i];
} elseif ($rows[$i][0] instanceof TableCell && $rows[$i][0]->getColspan() >= 2) {
// Noop, there is a "title"
} else {
$rows[$i][] = null;
}
}
}
} else {
$rows = array_merge($this->headers, [$divider], $this->rows);
}
$this->renderRow($row, $isHeader ? $this->style->getCellHeaderFormat() : $this->style->getCellRowFormat());
$this->calculateNumberOfColumns($rows);
$rowGroups = $this->buildTableRows($rows);
$this->calculateColumnsWidth($rowGroups);
$isHeader = !$this->horizontal;
$isFirstRow = $this->horizontal;
$hasTitle = (bool) $this->headerTitle;
foreach ($rowGroups as $rowGroup) {
$isHeaderSeparatorRendered = false;
foreach ($rowGroup as $row) {
if ($divider === $row) {
$isHeader = false;
$isFirstRow = true;
continue;
}
if ($row instanceof TableSeparator) {
$this->renderRowSeparator();
continue;
}
if (!$row) {
continue;
}
if ($isHeader && !$isHeaderSeparatorRendered) {
$this->renderRowSeparator(
$isHeader ? self::SEPARATOR_TOP : self::SEPARATOR_TOP_BOTTOM,
$hasTitle ? $this->headerTitle : null,
$hasTitle ? $this->style->getHeaderTitleFormat() : null
);
$hasTitle = false;
$isHeaderSeparatorRendered = true;
}
if ($isFirstRow) {
$this->renderRowSeparator(
$isHeader ? self::SEPARATOR_TOP : self::SEPARATOR_TOP_BOTTOM,
$hasTitle ? $this->headerTitle : null,
$hasTitle ? $this->style->getHeaderTitleFormat() : null
);
$isFirstRow = false;
$hasTitle = false;
}
if ($this->horizontal) {
$this->renderRow($row, $this->style->getCellRowFormat(), $this->style->getCellHeaderFormat());
} else {
$this->renderRow($row, $isHeader ? $this->style->getCellHeaderFormat() : $this->style->getCellRowFormat());
}
}
}
$this->renderRowSeparator(self::SEPARATOR_BOTTOM, $this->footerTitle, $this->style->getFooterTitleFormat());
@@ -400,13 +450,13 @@ class Table
$crossings = $this->style->getCrossingChars();
if (self::SEPARATOR_MID === $type) {
list($horizontal, $leftChar, $midChar, $rightChar) = array($borders[2], $crossings[8], $crossings[0], $crossings[4]);
[$horizontal, $leftChar, $midChar, $rightChar] = [$borders[2], $crossings[8], $crossings[0], $crossings[4]];
} elseif (self::SEPARATOR_TOP === $type) {
list($horizontal, $leftChar, $midChar, $rightChar) = array($borders[0], $crossings[1], $crossings[2], $crossings[3]);
[$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[1], $crossings[2], $crossings[3]];
} elseif (self::SEPARATOR_TOP_BOTTOM === $type) {
list($horizontal, $leftChar, $midChar, $rightChar) = array($borders[0], $crossings[9], $crossings[10], $crossings[11]);
[$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[9], $crossings[10], $crossings[11]];
} else {
list($horizontal, $leftChar, $midChar, $rightChar) = array($borders[0], $crossings[7], $crossings[6], $crossings[5]);
[$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[7], $crossings[6], $crossings[5]];
}
$markup = $leftChar;
@@ -424,7 +474,7 @@ class Table
$formattedTitle = sprintf($titleFormat, Helper::substr($title, 0, $limit - $formatLength - 3).'...');
}
$titleStart = ($markupLength - $titleLength) / 2;
$titleStart = intdiv($markupLength - $titleLength, 2);
if (false === mb_detect_encoding($markup, null, true)) {
$markup = substr_replace($markup, $formattedTitle, $titleStart, $titleLength);
} else {
@@ -438,7 +488,7 @@ class Table
/**
* Renders vertical column separator.
*/
private function renderColumnSeparator($type = self::BORDER_OUTSIDE)
private function renderColumnSeparator(int $type = self::BORDER_OUTSIDE): string
{
$borders = $this->style->getBorderChars();
@@ -452,13 +502,17 @@ class Table
*
* | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
*/
private function renderRow(array $row, string $cellFormat)
private function renderRow(array $row, string $cellFormat, string $firstCellFormat = null)
{
$rowContent = $this->renderColumnSeparator(self::BORDER_OUTSIDE);
$columns = $this->getRowColumns($row);
$last = \count($columns) - 1;
foreach ($columns as $i => $column) {
$rowContent .= $this->renderCell($row, $column, $cellFormat);
if ($firstCellFormat && 0 === $i) {
$rowContent .= $this->renderCell($row, $column, $firstCellFormat);
} else {
$rowContent .= $this->renderCell($row, $column, $cellFormat);
}
$rowContent .= $this->renderColumnSeparator($last === $i ? self::BORDER_OUTSIDE : self::BORDER_INSIDE);
}
$this->output->writeln($rowContent);
@@ -467,9 +521,9 @@ class Table
/**
* Renders table cell with padding.
*/
private function renderCell(array $row, int $column, string $cellFormat)
private function renderCell(array $row, int $column, string $cellFormat): string
{
$cell = isset($row[$column]) ? $row[$column] : '';
$cell = $row[$column] ?? '';
$width = $this->effectiveColumnWidths[$column];
if ($cell instanceof TableCell && $cell->getColspan() > 1) {
// add the width of the following columns(numbers of colspan).
@@ -498,9 +552,9 @@ class Table
/**
* Calculate number of columns for this table.
*/
private function calculateNumberOfColumns($rows)
private function calculateNumberOfColumns(array $rows)
{
$columns = array(0);
$columns = [0];
foreach ($rows as $row) {
if ($row instanceof TableSeparator) {
continue;
@@ -512,58 +566,68 @@ class Table
$this->numberOfColumns = max($columns);
}
private function buildTableRows($rows)
private function buildTableRows(array $rows): TableRows
{
/** @var WrappableOutputFormatterInterface $formatter */
$formatter = $this->output->getFormatter();
$unmergedRows = array();
$unmergedRows = [];
for ($rowKey = 0; $rowKey < \count($rows); ++$rowKey) {
$rows = $this->fillNextRows($rows, $rowKey);
// Remove any new line breaks and replace it with a new line
foreach ($rows[$rowKey] as $column => $cell) {
$colspan = $cell instanceof TableCell ? $cell->getColspan() : 1;
if (isset($this->columnMaxWidths[$column]) && Helper::strlenWithoutDecoration($formatter, $cell) > $this->columnMaxWidths[$column]) {
$cell = $formatter->formatAndWrap($cell, $this->columnMaxWidths[$column]);
$cell = $formatter->formatAndWrap($cell, $this->columnMaxWidths[$column] * $colspan);
}
if (!strstr($cell, "\n")) {
if (!strstr($cell ?? '', "\n")) {
continue;
}
$lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell));
$escaped = implode("\n", array_map([OutputFormatter::class, '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) {
if ($cell instanceof TableCell) {
$line = new TableCell($line, array('colspan' => $cell->getColspan()));
if ($colspan > 1) {
$line = new TableCell($line, ['colspan' => $colspan]);
}
if (0 === $lineKey) {
$rows[$rowKey][$column] = $line;
} else {
if (!\array_key_exists($rowKey, $unmergedRows) || !\array_key_exists($lineKey, $unmergedRows[$rowKey])) {
$unmergedRows[$rowKey][$lineKey] = $this->copyRow($rows, $rowKey);
}
$unmergedRows[$rowKey][$lineKey][$column] = $line;
}
}
}
}
return new TableRows(function () use ($rows, $unmergedRows) {
return new TableRows(function () use ($rows, $unmergedRows): \Traversable {
foreach ($rows as $rowKey => $row) {
yield $this->fillCells($row);
$rowGroup = [$row instanceof TableSeparator ? $row : $this->fillCells($row)];
if (isset($unmergedRows[$rowKey])) {
foreach ($unmergedRows[$rowKey] as $row) {
yield $row;
$rowGroup[] = $row instanceof TableSeparator ? $row : $this->fillCells($row);
}
}
yield $rowGroup;
}
});
}
private function calculateRowCount(): int
{
$numberOfRows = \count(iterator_to_array($this->buildTableRows(array_merge($this->headers, array(new TableSeparator()), $this->rows))));
$numberOfRows = \count(iterator_to_array($this->buildTableRows(array_merge($this->headers, [new TableSeparator()], $this->rows))));
if ($this->headers) {
++$numberOfRows; // Add row for header separator
}
++$numberOfRows; // Add row for footer separator
if (\count($this->rows) > 0) {
++$numberOfRows; // Add row for footer separator
}
return $numberOfRows;
}
@@ -575,27 +639,27 @@ class Table
*/
private function fillNextRows(array $rows, int $line): array
{
$unmergedRows = array();
$unmergedRows = [];
foreach ($rows[$line] as $column => $cell) {
if (null !== $cell && !$cell instanceof TableCell && !is_scalar($cell) && !(\is_object($cell) && method_exists($cell, '__toString'))) {
throw new InvalidArgumentException(sprintf('A cell must be a TableCell, a scalar or an object implementing __toString, %s given.', \gettype($cell)));
if (null !== $cell && !$cell instanceof TableCell && !\is_scalar($cell) && !(\is_object($cell) && method_exists($cell, '__toString'))) {
throw new InvalidArgumentException(sprintf('A cell must be a TableCell, a scalar or an object implementing "__toString()", "%s" given.', \gettype($cell)));
}
if ($cell instanceof TableCell && $cell->getRowspan() > 1) {
$nbLines = $cell->getRowspan() - 1;
$lines = array($cell);
$lines = [$cell];
if (strstr($cell, "\n")) {
$lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell));
$nbLines = \count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines;
$rows[$line][$column] = new TableCell($lines[0], array('colspan' => $cell->getColspan()));
$rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan()]);
unset($lines[0]);
}
// create a two dimensional array (rowspan x colspan)
$unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, array()), $unmergedRows);
$unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, []), $unmergedRows);
foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
$value = isset($lines[$unmergedRowKey - $line]) ? $lines[$unmergedRowKey - $line] : '';
$unmergedRows[$unmergedRowKey][$column] = new TableCell($value, array('colspan' => $cell->getColspan()));
$value = $lines[$unmergedRowKey - $line] ?? '';
$unmergedRows[$unmergedRowKey][$column] = new TableCell($value, ['colspan' => $cell->getColspan()]);
if ($nbLines === $unmergedRowKey - $line) {
break;
}
@@ -608,7 +672,7 @@ class Table
if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) {
foreach ($unmergedRow as $cellKey => $cell) {
// insert cell into row at cellKey position
array_splice($rows[$unmergedRowKey], $cellKey, 0, array($cell));
array_splice($rows[$unmergedRowKey], $cellKey, 0, [$cell]);
}
} else {
$row = $this->copyRow($rows, $unmergedRowKey - 1);
@@ -617,7 +681,7 @@ class Table
$row[$column] = $unmergedRow[$column];
}
}
array_splice($rows, $unmergedRowKey, 0, array($row));
array_splice($rows, $unmergedRowKey, 0, [$row]);
}
}
@@ -627,9 +691,10 @@ class Table
/**
* fill cells for a row that contains colspan > 1.
*/
private function fillCells($row)
private function fillCells(iterable $row)
{
$newRow = array();
$newRow = [];
foreach ($row as $column => $cell) {
$newRow[] = $cell;
if ($cell instanceof TableCell && $cell->getColspan() > 1) {
@@ -649,7 +714,7 @@ class Table
foreach ($row as $cellKey => $cellValue) {
$row[$cellKey] = '';
if ($cellValue instanceof TableCell) {
$row[$cellKey] = new TableCell('', array('colspan' => $cellValue->getColspan()));
$row[$cellKey] = new TableCell('', ['colspan' => $cellValue->getColspan()]);
}
}
@@ -688,29 +753,31 @@ class Table
/**
* Calculates columns widths.
*/
private function calculateColumnsWidth(iterable $rows)
private function calculateColumnsWidth(iterable $groups)
{
for ($column = 0; $column < $this->numberOfColumns; ++$column) {
$lengths = array();
foreach ($rows as $row) {
if ($row instanceof TableSeparator) {
continue;
}
$lengths = [];
foreach ($groups as $group) {
foreach ($group as $row) {
if ($row instanceof TableSeparator) {
continue;
}
foreach ($row as $i => $cell) {
if ($cell instanceof TableCell) {
$textContent = Helper::removeDecoration($this->output->getFormatter(), $cell);
$textLength = Helper::strlen($textContent);
if ($textLength > 0) {
$contentColumns = str_split($textContent, ceil($textLength / $cell->getColspan()));
foreach ($contentColumns as $position => $content) {
$row[$i + $position] = $content;
foreach ($row as $i => $cell) {
if ($cell instanceof TableCell) {
$textContent = Helper::removeDecoration($this->output->getFormatter(), $cell);
$textLength = Helper::strlen($textContent);
if ($textLength > 0) {
$contentColumns = str_split($textContent, ceil($textLength / $cell->getColspan()));
foreach ($contentColumns as $position => $content) {
$row[$i + $position] = $content;
}
}
}
}
}
$lengths[] = $this->getCellWidth($row, $column);
$lengths[] = $this->getCellWidth($row, $column);
}
}
$this->effectiveColumnWidths[$column] = max($lengths) + Helper::strlen($this->style->getCellRowContentFormat()) - 2;
@@ -731,7 +798,7 @@ class Table
$cellWidth = Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell);
}
$columnWidth = isset($this->columnWidths[$column]) ? $this->columnWidths[$column] : 0;
$columnWidth = $this->columnWidths[$column] ?? 0;
$cellWidth = max($cellWidth, $columnWidth);
return isset($this->columnMaxWidths[$column]) ? min($this->columnMaxWidths[$column], $cellWidth) : $cellWidth;
@@ -742,11 +809,11 @@ class Table
*/
private function cleanup()
{
$this->effectiveColumnWidths = array();
$this->effectiveColumnWidths = [];
$this->numberOfColumns = null;
}
private static function initStyles()
private static function initStyles(): array
{
$borderless = new TableStyle();
$borderless
@@ -758,9 +825,9 @@ class Table
$compact = new TableStyle();
$compact
->setHorizontalBorderChars('')
->setVerticalBorderChars(' ')
->setVerticalBorderChars('')
->setDefaultCrossingChar('')
->setCellRowContentFormat('%s')
->setCellRowContentFormat('%s ')
;
$styleGuide = new TableStyle();
@@ -783,17 +850,17 @@ class Table
->setCrossingChars('┼', '╔', '╤', '╗', '╢', '╝', '╧', '╚', '╟', '╠', '╪', '╣')
;
return array(
return [
'default' => new TableStyle(),
'borderless' => $borderless,
'compact' => $compact,
'symfony-style-guide' => $styleGuide,
'box' => $box,
'box-double' => $boxDouble,
);
];
}
private function resolveStyle($name)
private function resolveStyle($name): TableStyle
{
if ($name instanceof TableStyle) {
return $name;

View File

@@ -19,12 +19,12 @@ use Symfony\Component\Console\Exception\InvalidArgumentException;
class TableCell
{
private $value;
private $options = array(
private $options = [
'rowspan' => 1,
'colspan' => 1,
);
];
public function __construct(string $value = '', array $options = array())
public function __construct(string $value = '', array $options = [])
{
$this->value = $value;

View File

@@ -23,7 +23,7 @@ class TableRows implements \IteratorAggregate
$this->generator = $generator;
}
public function getIterator()
public function getIterator(): \Traversable
{
$g = $this->generator;

View File

@@ -18,7 +18,7 @@ namespace Symfony\Component\Console\Helper;
*/
class TableSeparator extends TableCell
{
public function __construct(array $options = array())
public function __construct(array $options = [])
{
parent::__construct('', $options);
}

View File

@@ -46,7 +46,7 @@ class TableStyle
private $cellRowFormat = '%s';
private $cellRowContentFormat = ' %s ';
private $borderFormat = '%s';
private $padType = STR_PAD_RIGHT;
private $padType = \STR_PAD_RIGHT;
/**
* Sets padding character, used for cell padding.
@@ -58,7 +58,7 @@ class TableStyle
public function setPaddingChar($paddingChar)
{
if (!$paddingChar) {
throw new LogicException('The padding char must not be empty');
throw new LogicException('The padding char must not be empty.');
}
$this->paddingChar = $paddingChar;
@@ -112,7 +112,7 @@ class TableStyle
*/
public function setHorizontalBorderChar($horizontalBorderChar)
{
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1, use setHorizontalBorderChars() instead.', __METHOD__), E_USER_DEPRECATED);
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1, use setHorizontalBorderChars() instead.', __METHOD__), \E_USER_DEPRECATED);
return $this->setHorizontalBorderChars($horizontalBorderChar, $horizontalBorderChar);
}
@@ -126,7 +126,7 @@ class TableStyle
*/
public function getHorizontalBorderChar()
{
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1, use getBorderChars() instead.', __METHOD__), E_USER_DEPRECATED);
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1, use getBorderChars() instead.', __METHOD__), \E_USER_DEPRECATED);
return $this->horizontalOutsideBorderChar;
}
@@ -168,7 +168,7 @@ class TableStyle
*/
public function setVerticalBorderChar($verticalBorderChar)
{
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1, use setVerticalBorderChars() instead.', __METHOD__), E_USER_DEPRECATED);
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1, use setVerticalBorderChars() instead.', __METHOD__), \E_USER_DEPRECATED);
return $this->setVerticalBorderChars($verticalBorderChar, $verticalBorderChar);
}
@@ -182,7 +182,7 @@ class TableStyle
*/
public function getVerticalBorderChar()
{
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1, use getBorderChars() instead.', __METHOD__), E_USER_DEPRECATED);
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1, use getBorderChars() instead.', __METHOD__), \E_USER_DEPRECATED);
return $this->verticalOutsideBorderChar;
}
@@ -192,14 +192,14 @@ class TableStyle
*
* @internal
*/
public function getBorderChars()
public function getBorderChars(): array
{
return array(
return [
$this->horizontalOutsideBorderChar,
$this->verticalOutsideBorderChar,
$this->horizontalInsideBorderChar,
$this->verticalInsideBorderChar,
);
];
}
/**
@@ -270,7 +270,7 @@ class TableStyle
*/
public function setCrossingChar($crossingChar)
{
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1. Use setDefaultCrossingChar() instead.', __METHOD__), E_USER_DEPRECATED);
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1. Use setDefaultCrossingChar() instead.', __METHOD__), \E_USER_DEPRECATED);
return $this->setDefaultCrossingChar($crossingChar);
}
@@ -292,7 +292,7 @@ class TableStyle
*/
public function getCrossingChars(): array
{
return array(
return [
$this->crossingChar,
$this->crossingTopLeftChar,
$this->crossingTopMidChar,
@@ -305,7 +305,7 @@ class TableStyle
$this->crossingTopLeftBottomChar,
$this->crossingTopMidBottomChar,
$this->crossingTopRightBottomChar,
);
];
}
/**
@@ -413,7 +413,7 @@ class TableStyle
*/
public function setPadType($padType)
{
if (!\in_array($padType, array(STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH), true)) {
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).');
}

View File

@@ -44,14 +44,11 @@ class ArgvInput extends Input
private $parsed;
/**
* @param array|null $argv An array of parameters from the CLI (in the argv format)
* @param InputDefinition|null $definition A InputDefinition instance
* @param array|null $argv An array of parameters from the CLI (in the argv format)
*/
public function __construct(array $argv = null, InputDefinition $definition = null)
{
if (null === $argv) {
$argv = $_SERVER['argv'];
}
$argv = $argv ?? $_SERVER['argv'] ?? [];
// strip the application name
array_shift($argv);
@@ -78,7 +75,7 @@ class ArgvInput extends Input
$this->parseArgument($token);
} elseif ($parseOptions && '--' == $token) {
$parseOptions = false;
} elseif ($parseOptions && 0 === strpos($token, '--')) {
} elseif ($parseOptions && str_starts_with($token, '--')) {
$this->parseLongOption($token);
} elseif ($parseOptions && '-' === $token[0] && '-' !== $token) {
$this->parseShortOption($token);
@@ -90,10 +87,8 @@ class ArgvInput extends Input
/**
* Parses a short option.
*
* @param string $token The current token
*/
private function parseShortOption($token)
private function parseShortOption(string $token)
{
$name = substr($token, 1);
@@ -112,11 +107,9 @@ class ArgvInput extends Input
/**
* Parses a short option set.
*
* @param string $name The current token
*
* @throws RuntimeException When option given doesn't exist
*/
private function parseShortOptionSet($name)
private function parseShortOptionSet(string $name)
{
$len = \strlen($name);
for ($i = 0; $i < $len; ++$i) {
@@ -138,15 +131,13 @@ class ArgvInput extends Input
/**
* Parses a long option.
*
* @param string $token The current token
*/
private function parseLongOption($token)
private function parseLongOption(string $token)
{
$name = substr($token, 2);
if (false !== $pos = strpos($name, '=')) {
if (0 === \strlen($value = substr($name, $pos + 1))) {
if ('' === $value = substr($name, $pos + 1)) {
array_unshift($this->parsed, $value);
}
$this->addLongOption(substr($name, 0, $pos), $value);
@@ -158,18 +149,16 @@ class ArgvInput extends Input
/**
* Parses an argument.
*
* @param string $token The current token
*
* @throws RuntimeException When too many arguments are given
*/
private function parseArgument($token)
private function parseArgument(string $token)
{
$c = \count($this->arguments);
// if input is expecting another argument, add it
if ($this->definition->hasArgument($c)) {
$arg = $this->definition->getArgument($c);
$this->arguments[$arg->getName()] = $arg->isArray() ? array($token) : $token;
$this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token;
// if last argument isArray(), append token to last argument
} elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) {
@@ -190,12 +179,9 @@ class ArgvInput extends Input
/**
* Adds a short option value.
*
* @param string $shortcut The short option key
* @param mixed $value The value for the option
*
* @throws RuntimeException When option given doesn't exist
*/
private function addShortOption($shortcut, $value)
private function addShortOption(string $shortcut, $value)
{
if (!$this->definition->hasShortcut($shortcut)) {
throw new RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut));
@@ -207,12 +193,9 @@ class ArgvInput extends Input
/**
* Adds a long option value.
*
* @param string $name The long option key
* @param mixed $value The value for the option
*
* @throws RuntimeException When option given doesn't exist
*/
private function addLongOption($name, $value)
private function addLongOption(string $name, $value)
{
if (!$this->definition->hasOption($name)) {
throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name));
@@ -224,11 +207,11 @@ class ArgvInput extends Input
throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name));
}
if (\in_array($value, array('', null), true) && $option->acceptValue() && \count($this->parsed)) {
if (\in_array($value, ['', null], true) && $option->acceptValue() && \count($this->parsed)) {
// if option accepts an optional or mandatory argument
// let's see if there is one provided
$next = array_shift($this->parsed);
if ((isset($next[0]) && '-' !== $next[0]) || \in_array($next, array('', null), true)) {
if ((isset($next[0]) && '-' !== $next[0]) || \in_array($next, ['', null], true)) {
$value = $next;
} else {
array_unshift($this->parsed, $next);
@@ -257,13 +240,34 @@ class ArgvInput extends Input
*/
public function getFirstArgument()
{
foreach ($this->tokens as $token) {
$isOption = false;
foreach ($this->tokens as $i => $token) {
if ($token && '-' === $token[0]) {
if (str_contains($token, '=') || !isset($this->tokens[$i + 1])) {
continue;
}
// If it's a long option, consider that everything after "--" is the option name.
// Otherwise, use the last char (if it's a short option set, only the last one can take a value with space separator)
$name = '-' === $token[1] ? substr($token, 2) : substr($token, -1);
if (!isset($this->options[$name]) && !$this->definition->hasShortcut($name)) {
// noop
} elseif ((isset($this->options[$name]) || isset($this->options[$name = $this->definition->shortcutToName($name)])) && $this->tokens[$i + 1] === $this->options[$name]) {
$isOption = true;
}
continue;
}
if ($isOption) {
$isOption = false;
continue;
}
return $token;
}
return null;
}
/**
@@ -281,8 +285,8 @@ class ArgvInput extends Input
// Options with values:
// For long options, test for '--option=' at beginning
// For short options, test for '-o' at beginning
$leading = 0 === strpos($value, '--') ? $value.'=' : $value;
if ($token === $value || '' !== $leading && 0 === strpos($token, $leading)) {
$leading = str_starts_with($value, '--') ? $value.'=' : $value;
if ($token === $value || '' !== $leading && str_starts_with($token, $leading)) {
return true;
}
}
@@ -312,8 +316,8 @@ class ArgvInput extends Input
// Options with values:
// For long options, test for '--option=' at beginning
// For short options, test for '-o' at beginning
$leading = 0 === strpos($value, '--') ? $value.'=' : $value;
if ('' !== $leading && 0 === strpos($token, $leading)) {
$leading = str_starts_with($value, '--') ? $value.'=' : $value;
if ('' !== $leading && str_starts_with($token, $leading)) {
return substr($token, \strlen($leading));
}
}

View File

@@ -19,7 +19,7 @@ use Symfony\Component\Console\Exception\InvalidOptionException;
*
* Usage:
*
* $input = new ArrayInput(array('name' => 'foo', '--bar' => 'foobar'));
* $input = new ArrayInput(['command' => 'foo:bar', 'foo' => 'bar', '--bar' => 'foobar']);
*
* @author Fabien Potencier <fabien@symfony.com>
*/
@@ -39,13 +39,15 @@ class ArrayInput extends Input
*/
public function getFirstArgument()
{
foreach ($this->parameters as $key => $value) {
if ($key && '-' === $key[0]) {
foreach ($this->parameters as $param => $value) {
if ($param && \is_string($param) && '-' === $param[0]) {
continue;
}
return $value;
}
return null;
}
/**
@@ -103,18 +105,19 @@ class ArrayInput extends Input
*/
public function __toString()
{
$params = array();
$params = [];
foreach ($this->parameters as $param => $val) {
if ($param && '-' === $param[0]) {
if ($param && \is_string($param) && '-' === $param[0]) {
$glue = ('-' === $param[1]) ? '=' : ' ';
if (\is_array($val)) {
foreach ($val as $v) {
$params[] = $param.('' != $v ? '='.$this->escapeToken($v) : '');
$params[] = $param.('' != $v ? $glue.$this->escapeToken($v) : '');
}
} else {
$params[] = $param.('' != $val ? '='.$this->escapeToken($val) : '');
$params[] = $param.('' != $val ? $glue.$this->escapeToken($val) : '');
}
} else {
$params[] = \is_array($val) ? implode(' ', array_map(array($this, 'escapeToken'), $val)) : $this->escapeToken($val);
$params[] = \is_array($val) ? implode(' ', array_map([$this, 'escapeToken'], $val)) : $this->escapeToken($val);
}
}
@@ -130,9 +133,9 @@ class ArrayInput extends Input
if ('--' === $key) {
return;
}
if (0 === strpos($key, '--')) {
if (str_starts_with($key, '--')) {
$this->addLongOption(substr($key, 2), $value);
} elseif ('-' === $key[0]) {
} elseif (str_starts_with($key, '-')) {
$this->addShortOption(substr($key, 1), $value);
} else {
$this->addArgument($key, $value);
@@ -143,12 +146,9 @@ class ArrayInput extends Input
/**
* Adds a short option value.
*
* @param string $shortcut The short option key
* @param mixed $value The value for the option
*
* @throws InvalidOptionException When option given doesn't exist
*/
private function addShortOption($shortcut, $value)
private function addShortOption(string $shortcut, $value)
{
if (!$this->definition->hasShortcut($shortcut)) {
throw new InvalidOptionException(sprintf('The "-%s" option does not exist.', $shortcut));
@@ -160,13 +160,10 @@ class ArrayInput extends Input
/**
* Adds a long option value.
*
* @param string $name The long option key
* @param mixed $value The value for the option
*
* @throws InvalidOptionException When option given doesn't exist
* @throws InvalidOptionException When a required value is missing
*/
private function addLongOption($name, $value)
private function addLongOption(string $name, $value)
{
if (!$this->definition->hasOption($name)) {
throw new InvalidOptionException(sprintf('The "--%s" option does not exist.', $name));
@@ -190,8 +187,8 @@ class ArrayInput extends Input
/**
* Adds an argument value.
*
* @param string $name The argument name
* @param mixed $value The value for the argument
* @param string|int $name The argument name
* @param mixed $value The value for the argument
*
* @throws InvalidArgumentException When argument given doesn't exist
*/

View File

@@ -29,8 +29,8 @@ abstract class Input implements InputInterface, StreamableInputInterface
{
protected $definition;
protected $stream;
protected $options = array();
protected $arguments = array();
protected $options = [];
protected $arguments = [];
protected $interactive = true;
public function __construct(InputDefinition $definition = null)
@@ -48,8 +48,8 @@ abstract class Input implements InputInterface, StreamableInputInterface
*/
public function bind(InputDefinition $definition)
{
$this->arguments = array();
$this->options = array();
$this->arguments = [];
$this->options = [];
$this->definition = $definition;
$this->parse();
@@ -69,7 +69,7 @@ abstract class Input implements InputInterface, StreamableInputInterface
$givenArguments = $this->arguments;
$missingArguments = array_filter(array_keys($definition->getArguments()), function ($argument) use ($definition, $givenArguments) {
return !array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired();
return !\array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired();
});
if (\count($missingArguments) > 0) {
@@ -106,11 +106,11 @@ abstract class Input implements InputInterface, StreamableInputInterface
*/
public function getArgument($name)
{
if (!$this->definition->hasArgument($name)) {
if (!$this->definition->hasArgument((string) $name)) {
throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name)->getDefault();
return $this->arguments[$name] ?? $this->definition->getArgument($name)->getDefault();
}
/**
@@ -118,7 +118,7 @@ abstract class Input implements InputInterface, StreamableInputInterface
*/
public function setArgument($name, $value)
{
if (!$this->definition->hasArgument($name)) {
if (!$this->definition->hasArgument((string) $name)) {
throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
@@ -130,7 +130,7 @@ abstract class Input implements InputInterface, StreamableInputInterface
*/
public function hasArgument($name)
{
return $this->definition->hasArgument($name);
return $this->definition->hasArgument((string) $name);
}
/**
@@ -150,7 +150,7 @@ abstract class Input implements InputInterface, StreamableInputInterface
throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
}
return array_key_exists($name, $this->options) ? $this->options[$name] : $this->definition->getOption($name)->getDefault();
return \array_key_exists($name, $this->options) ? $this->options[$name] : $this->definition->getOption($name)->getDefault();
}
/**

View File

@@ -21,9 +21,9 @@ use Symfony\Component\Console\Exception\LogicException;
*/
class InputArgument
{
const REQUIRED = 1;
const OPTIONAL = 2;
const IS_ARRAY = 4;
public const REQUIRED = 1;
public const OPTIONAL = 2;
public const IS_ARRAY = 4;
private $name;
private $mode;
@@ -31,10 +31,10 @@ class InputArgument
private $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|string[]|null $default The default value (for self::OPTIONAL mode only)
* @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)
*
* @throws InvalidArgumentException When argument mode is not valid
*/
@@ -86,19 +86,19 @@ class InputArgument
/**
* Sets the default value.
*
* @param string|string[]|null $default The default value
* @param string|bool|int|float|array|null $default
*
* @throws LogicException When incorrect default value is given
*/
public function setDefault($default = null)
{
if (self::REQUIRED === $this->mode && null !== $default) {
if ($this->isRequired() && null !== $default) {
throw new LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.');
}
if ($this->isArray()) {
if (null === $default) {
$default = array();
$default = [];
} elseif (!\is_array($default)) {
throw new LogicException('A default value for an array argument must be an array.');
}
@@ -110,7 +110,7 @@ class InputArgument
/**
* Returns the default value.
*
* @return string|string[]|null The default value
* @return string|bool|int|float|array|null
*/
public function getDefault()
{

View File

@@ -19,10 +19,10 @@ use Symfony\Component\Console\Exception\LogicException;
*
* Usage:
*
* $definition = new InputDefinition(array(
* $definition = new InputDefinition([
* new InputArgument('name', InputArgument::REQUIRED),
* new InputOption('foo', 'f', InputOption::VALUE_REQUIRED),
* ));
* ]);
*
* @author Fabien Potencier <fabien@symfony.com>
*/
@@ -38,7 +38,7 @@ class InputDefinition
/**
* @param array $definition An array of InputArgument and InputOption instance
*/
public function __construct(array $definition = array())
public function __construct(array $definition = [])
{
$this->setDefinition($definition);
}
@@ -48,8 +48,8 @@ class InputDefinition
*/
public function setDefinition(array $definition)
{
$arguments = array();
$options = array();
$arguments = [];
$options = [];
foreach ($definition as $item) {
if ($item instanceof InputOption) {
$options[] = $item;
@@ -67,9 +67,9 @@ class InputDefinition
*
* @param InputArgument[] $arguments An array of InputArgument objects
*/
public function setArguments($arguments = array())
public function setArguments($arguments = [])
{
$this->arguments = array();
$this->arguments = [];
$this->requiredCount = 0;
$this->hasOptional = false;
$this->hasAnArrayArgument = false;
@@ -81,7 +81,7 @@ class InputDefinition
*
* @param InputArgument[] $arguments An array of InputArgument objects
*/
public function addArguments($arguments = array())
public function addArguments($arguments = [])
{
if (null !== $arguments) {
foreach ($arguments as $argument) {
@@ -171,7 +171,7 @@ class InputDefinition
*/
public function getArgumentCount()
{
return $this->hasAnArrayArgument ? PHP_INT_MAX : \count($this->arguments);
return $this->hasAnArrayArgument ? \PHP_INT_MAX : \count($this->arguments);
}
/**
@@ -185,13 +185,11 @@ class InputDefinition
}
/**
* Gets the default values.
*
* @return array An array of default values
* @return array<string|bool|int|float|array|null>
*/
public function getArgumentDefaults()
{
$values = array();
$values = [];
foreach ($this->arguments as $argument) {
$values[$argument->getName()] = $argument->getDefault();
}
@@ -204,10 +202,10 @@ class InputDefinition
*
* @param InputOption[] $options An array of InputOption objects
*/
public function setOptions($options = array())
public function setOptions($options = [])
{
$this->options = array();
$this->shortcuts = array();
$this->options = [];
$this->shortcuts = [];
$this->addOptions($options);
}
@@ -216,7 +214,7 @@ class InputDefinition
*
* @param InputOption[] $options An array of InputOption objects
*/
public function addOptions($options = array())
public function addOptions($options = [])
{
foreach ($options as $option) {
$this->addOption($option);
@@ -316,13 +314,11 @@ class InputDefinition
}
/**
* Gets an array of default values.
*
* @return array An array of all default values
* @return array<string|bool|int|float|array|null>
*/
public function getOptionDefaults()
{
$values = array();
$values = [];
foreach ($this->options as $option) {
$values[$option->getName()] = $option->getDefault();
}
@@ -333,13 +329,11 @@ class InputDefinition
/**
* Returns the InputOption name given a shortcut.
*
* @param string $shortcut The shortcut
*
* @return string The InputOption name
*
* @throws InvalidArgumentException When option given does not exist
*
* @internal
*/
private function shortcutToName($shortcut)
public function shortcutToName(string $shortcut): string
{
if (!isset($this->shortcuts[$shortcut])) {
throw new InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut));
@@ -357,7 +351,7 @@ class InputDefinition
*/
public function getSynopsis($short = false)
{
$elements = array();
$elements = [];
if ($short && $this->getOptions()) {
$elements[] = '[options]';

View File

@@ -51,9 +51,9 @@ interface InputInterface
* Does not necessarily return the correct result for short options
* when multiple flags are combined in the same option.
*
* @param string|array $values The value(s) to look for in the raw parameters (can be an array)
* @param mixed $default The default value to return if no result is found
* @param bool $onlyParams Only check real parameters, skip those following an end of options (--) signal
* @param string|array $values The value(s) to look for in the raw parameters (can be an array)
* @param string|bool|int|float|array|null $default The default value to return if no result is found
* @param bool $onlyParams Only check real parameters, skip those following an end of options (--) signal
*
* @return mixed The option value
*/
@@ -76,7 +76,7 @@ interface InputInterface
/**
* Returns all the given arguments merged with the default values.
*
* @return array
* @return array<string|bool|int|float|array|null>
*/
public function getArguments();
@@ -85,7 +85,7 @@ interface InputInterface
*
* @param string $name The argument name
*
* @return string|string[]|null The argument value
* @return mixed
*
* @throws InvalidArgumentException When argument given doesn't exist
*/
@@ -94,8 +94,8 @@ interface InputInterface
/**
* Sets an argument value by name.
*
* @param string $name The argument name
* @param string|string[]|null $value The argument value
* @param string $name The argument name
* @param mixed $value The argument value
*
* @throws InvalidArgumentException When argument given doesn't exist
*/
@@ -104,7 +104,7 @@ interface InputInterface
/**
* Returns true if an InputArgument object exists by name or position.
*
* @param string|int $name The InputArgument name or position
* @param string $name The argument name
*
* @return bool true if the InputArgument object exists, false otherwise
*/
@@ -113,7 +113,7 @@ interface InputInterface
/**
* Returns all the given options merged with the default values.
*
* @return array
* @return array<string|bool|int|float|array|null>
*/
public function getOptions();
@@ -122,7 +122,7 @@ interface InputInterface
*
* @param string $name The option name
*
* @return string|string[]|bool|null The option value
* @return mixed
*
* @throws InvalidArgumentException When option given doesn't exist
*/
@@ -131,8 +131,8 @@ interface InputInterface
/**
* Sets an option value by name.
*
* @param string $name The option name
* @param string|string[]|bool|null $value The option value
* @param string $name The option name
* @param mixed $value The option value
*
* @throws InvalidArgumentException When option given doesn't exist
*/

View File

@@ -21,10 +21,25 @@ use Symfony\Component\Console\Exception\LogicException;
*/
class InputOption
{
const VALUE_NONE = 1;
const VALUE_REQUIRED = 2;
const VALUE_OPTIONAL = 4;
const VALUE_IS_ARRAY = 8;
/**
* Do not accept input for the option (e.g. --yell). This is the default behavior of options.
*/
public const VALUE_NONE = 1;
/**
* A value must be passed when the option is used (e.g. --iterations=5 or -i5).
*/
public const VALUE_REQUIRED = 2;
/**
* The option may or may not have a value (e.g. --yell or --yell=loud).
*/
public const VALUE_OPTIONAL = 4;
/**
* The option accepts multiple values (e.g. --dir=/foo --dir=/bar).
*/
public const VALUE_IS_ARRAY = 8;
private $name;
private $shortcut;
@@ -33,17 +48,17 @@ class InputOption
private $description;
/**
* @param string $name The option name
* @param string|array $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 $description A description text
* @param string|string[]|int|bool|null $default The default value (must be null for self::VALUE_NONE)
* @param string $name The option name
* @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 $description A description text
* @param string|bool|int|float|array|null $default The default value (must be null for self::VALUE_NONE)
*
* @throws InvalidArgumentException If option mode is invalid or incompatible
*/
public function __construct(string $name, $shortcut = null, int $mode = null, string $description = '', $default = null)
{
if (0 === strpos($name, '--')) {
if (str_starts_with($name, '--')) {
$name = substr($name, 2);
}
@@ -89,7 +104,7 @@ class InputOption
/**
* Returns the option shortcut.
*
* @return string The shortcut
* @return string|null The shortcut
*/
public function getShortcut()
{
@@ -147,11 +162,7 @@ class InputOption
}
/**
* Sets the default value.
*
* @param string|string[]|int|bool|null $default The default value
*
* @throws LogicException When incorrect default value is given
* @param string|bool|int|float|array|null $default
*/
public function setDefault($default = null)
{
@@ -161,7 +172,7 @@ class InputOption
if ($this->isArray()) {
if (null === $default) {
$default = array();
$default = [];
} elseif (!\is_array($default)) {
throw new LogicException('A default value for an array option must be an array.');
}
@@ -173,7 +184,7 @@ class InputOption
/**
* Returns the default value.
*
* @return string|string[]|int|bool|null The default value
* @return string|bool|int|float|array|null
*/
public function getDefault()
{

View File

@@ -24,15 +24,16 @@ use Symfony\Component\Console\Exception\InvalidArgumentException;
*/
class StringInput extends ArgvInput
{
const REGEX_STRING = '([^\s]+?)(?:\s|(?<!\\\\)"|(?<!\\\\)\'|$)';
const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')';
public const REGEX_STRING = '([^\s]+?)(?:\s|(?<!\\\\)"|(?<!\\\\)\'|$)';
public const REGEX_UNQUOTED_STRING = '([^\s\\\\]+?)';
public const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')';
/**
* @param string $input A string representing the parameters from the CLI
*/
public function __construct(string $input)
{
parent::__construct(array());
parent::__construct([]);
$this->setTokens($this->tokenize($input));
}
@@ -40,33 +41,44 @@ class StringInput extends ArgvInput
/**
* Tokenizes a string.
*
* @param string $input The input to tokenize
*
* @return array An array of tokens
*
* @throws InvalidArgumentException When unable to parse input (should never happen)
*/
private function tokenize($input)
private function tokenize(string $input): array
{
$tokens = array();
$tokens = [];
$length = \strlen($input);
$cursor = 0;
$token = null;
while ($cursor < $length) {
if (preg_match('/\s+/A', $input, $match, null, $cursor)) {
} elseif (preg_match('/([^="\'\s]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, null, $cursor)) {
$tokens[] = $match[1].$match[2].stripcslashes(str_replace(array('"\'', '\'"', '\'\'', '""'), '', substr($match[3], 1, \strlen($match[3]) - 2)));
} elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, null, $cursor)) {
$tokens[] = stripcslashes(substr($match[0], 1, \strlen($match[0]) - 2));
} elseif (preg_match('/'.self::REGEX_STRING.'/A', $input, $match, null, $cursor)) {
$tokens[] = stripcslashes($match[1]);
if ('\\' === $input[$cursor]) {
$token .= $input[++$cursor] ?? '';
++$cursor;
continue;
}
if (preg_match('/\s+/A', $input, $match, 0, $cursor)) {
if (null !== $token) {
$tokens[] = $token;
$token = null;
}
} elseif (preg_match('/([^="\'\s]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, 0, $cursor)) {
$token .= $match[1].$match[2].stripcslashes(str_replace(['"\'', '\'"', '\'\'', '""'], '', substr($match[3], 1, -1)));
} elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, 0, $cursor)) {
$token .= stripcslashes(substr($match[0], 1, -1));
} elseif (preg_match('/'.self::REGEX_UNQUOTED_STRING.'/A', $input, $match, 0, $cursor)) {
$token .= $match[1];
} else {
// should never happen
throw new InvalidArgumentException(sprintf('Unable to parse input near "... %s ..."', substr($input, $cursor, 10)));
throw new InvalidArgumentException(sprintf('Unable to parse input near "... %s ...".', substr($input, $cursor, 10)));
}
$cursor += \strlen($match[0]);
}
if (null !== $token) {
$tokens[] = $token;
}
return $tokens;
}
}

View File

@@ -1,4 +1,4 @@
Copyright (c) 2004-2018 Fabien Potencier
Copyright (c) 2004-2022 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -22,15 +22,15 @@ use Symfony\Component\Console\Output\OutputInterface;
*
* @author Kévin Dunglas <dunglas@gmail.com>
*
* @see http://www.php-fig.org/psr/psr-3/
* @see https://www.php-fig.org/psr/psr-3/
*/
class ConsoleLogger extends AbstractLogger
{
const INFO = 'info';
const ERROR = 'error';
public const INFO = 'info';
public const ERROR = 'error';
private $output;
private $verbosityLevelMap = array(
private $verbosityLevelMap = [
LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL,
LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL,
LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL,
@@ -39,8 +39,8 @@ class ConsoleLogger extends AbstractLogger
LogLevel::NOTICE => OutputInterface::VERBOSITY_VERBOSE,
LogLevel::INFO => OutputInterface::VERBOSITY_VERY_VERBOSE,
LogLevel::DEBUG => OutputInterface::VERBOSITY_DEBUG,
);
private $formatLevelMap = array(
];
private $formatLevelMap = [
LogLevel::EMERGENCY => self::ERROR,
LogLevel::ALERT => self::ERROR,
LogLevel::CRITICAL => self::ERROR,
@@ -49,10 +49,10 @@ class ConsoleLogger extends AbstractLogger
LogLevel::NOTICE => self::INFO,
LogLevel::INFO => self::INFO,
LogLevel::DEBUG => self::INFO,
);
];
private $errored = false;
public function __construct(OutputInterface $output, array $verbosityLevelMap = array(), array $formatLevelMap = array())
public function __construct(OutputInterface $output, array $verbosityLevelMap = [], array $formatLevelMap = [])
{
$this->output = $output;
$this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap;
@@ -61,8 +61,10 @@ class ConsoleLogger extends AbstractLogger
/**
* {@inheritdoc}
*
* @return void
*/
public function log($level, $message, array $context = array())
public function log($level, $message, array $context = [])
{
if (!isset($this->verbosityLevelMap[$level])) {
throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level));
@@ -102,13 +104,13 @@ class ConsoleLogger extends AbstractLogger
*/
private function interpolate(string $message, array $context): string
{
if (false === strpos($message, '{')) {
if (!str_contains($message, '{')) {
return $message;
}
$replacements = array();
$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) || (\is_object($val) && method_exists($val, '__toString'))) {
$replacements["{{$key}}"] = $val;
} elseif ($val instanceof \DateTimeInterface) {
$replacements["{{$key}}"] = $val->format(\DateTime::RFC3339);

View File

@@ -39,7 +39,7 @@ class BufferedOutput extends Output
$this->buffer .= $message;
if ($newline) {
$this->buffer .= PHP_EOL;
$this->buffer .= \PHP_EOL;
}
}
}

View File

@@ -30,7 +30,7 @@ use Symfony\Component\Console\Formatter\OutputFormatterInterface;
class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
{
private $stderr;
private $consoleSectionOutputs = array();
private $consoleSectionOutputs = [];
/**
* @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
@@ -41,6 +41,13 @@ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
{
parent::__construct($this->openOutputStream(), $verbosity, $decorated, $formatter);
if (null === $formatter) {
// for BC reasons, stdErr has it own Formatter only when user don't inject a specific formatter.
$this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated);
return;
}
$actualDecorated = $this->isDecorated();
$this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated, $this->getFormatter());
@@ -125,16 +132,14 @@ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
/**
* Checks if current executing environment is IBM iSeries (OS400), which
* doesn't properly convert character-encodings between ASCII to EBCDIC.
*
* @return bool
*/
private function isRunningOS400()
private function isRunningOS400(): bool
{
$checks = array(
$checks = [
\function_exists('php_uname') ? php_uname('s') : '',
getenv('OSTYPE'),
PHP_OS,
);
\PHP_OS,
];
return false !== stripos(implode(';', $checks), 'OS400');
}
@@ -148,7 +153,8 @@ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
return fopen('php://output', 'w');
}
return @fopen('php://stdout', 'w') ?: fopen('php://output', 'w');
// Use STDOUT when possible to prevent from opening too many file descriptors
return \defined('STDOUT') ? \STDOUT : (@fopen('php://stdout', 'w') ?: fopen('php://output', 'w'));
}
/**
@@ -156,6 +162,11 @@ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
*/
private function openErrorStream()
{
return fopen($this->hasStderrSupport() ? 'php://stderr' : 'php://output', 'w');
if (!$this->hasStderrSupport()) {
return fopen('php://output', 'w');
}
// Use STDERR when possible to prevent from opening too many file descriptors
return \defined('STDERR') ? \STDERR : (@fopen('php://stderr', 'w') ?: fopen('php://output', 'w'));
}
}

View File

@@ -21,7 +21,7 @@ use Symfony\Component\Console\Terminal;
*/
class ConsoleSectionOutput extends StreamOutput
{
private $content = array();
private $content = [];
private $lines = 0;
private $sections;
private $terminal;
@@ -50,10 +50,10 @@ 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 * 2)); // Multiply lines by 2 to cater for each new line added between content
} else {
$lines = $this->lines;
$this->content = array();
$this->content = [];
}
$this->lines -= $lines;
@@ -82,10 +82,10 @@ class ConsoleSectionOutput extends StreamOutput
*/
public function addContent(string $input)
{
foreach (explode(PHP_EOL, $input) as $lineContent) {
foreach (explode(\PHP_EOL, $input) as $lineContent) {
$this->lines += ceil($this->getDisplayLength($lineContent) / $this->terminal->getWidth()) ?: 1;
$this->content[] = $lineContent;
$this->content[] = PHP_EOL;
$this->content[] = \PHP_EOL;
}
}
@@ -95,7 +95,9 @@ class ConsoleSectionOutput extends StreamOutput
protected function doWrite($message, $newline)
{
if (!$this->isDecorated()) {
return parent::doWrite($message, $newline);
parent::doWrite($message, $newline);
return;
}
$erasedContent = $this->popStreamContentUntilCurrentSection();
@@ -113,7 +115,7 @@ class ConsoleSectionOutput extends StreamOutput
private function popStreamContentUntilCurrentSection(int $numberOfLinesToClearFromCurrentSection = 0): string
{
$numberOfLinesToClear = $numberOfLinesToClearFromCurrentSection;
$erasedContent = array();
$erasedContent = [];
foreach ($this->sections as $section) {
if ($section === $this) {

View File

@@ -40,7 +40,7 @@ abstract class Output implements OutputInterface
public function __construct(?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, OutputFormatterInterface $formatter = null)
{
$this->verbosity = null === $verbosity ? self::VERBOSITY_NORMAL : $verbosity;
$this->formatter = $formatter ?: new OutputFormatter();
$this->formatter = $formatter ?? new OutputFormatter();
$this->formatter->setDecorated($decorated);
}
@@ -138,7 +138,7 @@ abstract class Output implements OutputInterface
public function write($messages, $newline = false, $options = self::OUTPUT_NORMAL)
{
if (!is_iterable($messages)) {
$messages = array($messages);
$messages = [$messages];
}
$types = self::OUTPUT_NORMAL | self::OUTPUT_RAW | self::OUTPUT_PLAIN;
@@ -163,7 +163,7 @@ abstract class Output implements OutputInterface
break;
}
$this->doWrite($message, $newline);
$this->doWrite($message ?? '', $newline);
}
}

View File

@@ -20,15 +20,15 @@ use Symfony\Component\Console\Formatter\OutputFormatterInterface;
*/
interface OutputInterface
{
const VERBOSITY_QUIET = 16;
const VERBOSITY_NORMAL = 32;
const VERBOSITY_VERBOSE = 64;
const VERBOSITY_VERY_VERBOSE = 128;
const VERBOSITY_DEBUG = 256;
public const VERBOSITY_QUIET = 16;
public const VERBOSITY_NORMAL = 32;
public const VERBOSITY_VERBOSE = 64;
public const VERBOSITY_VERY_VERBOSE = 128;
public const VERBOSITY_DEBUG = 256;
const OUTPUT_NORMAL = 1;
const OUTPUT_RAW = 2;
const OUTPUT_PLAIN = 4;
public const OUTPUT_NORMAL = 1;
public const OUTPUT_RAW = 2;
public const OUTPUT_PLAIN = 4;
/**
* Writes a message to the output.

View File

@@ -12,7 +12,6 @@
namespace Symfony\Component\Console\Output;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
/**
@@ -71,13 +70,10 @@ class StreamOutput extends Output
protected function doWrite($message, $newline)
{
if ($newline) {
$message .= PHP_EOL;
$message .= \PHP_EOL;
}
if (false === @fwrite($this->stream, $message)) {
// should never happen
throw new RuntimeException('Unable to write output.');
}
@fwrite($this->stream, $message);
fflush($this->stream);
}
@@ -97,6 +93,11 @@ class StreamOutput extends Output
*/
protected function hasColorSupport()
{
// Follow https://no-color.org/
if (isset($_SERVER['NO_COLOR']) || false !== getenv('NO_COLOR')) {
return false;
}
if ('Hyper' === getenv('TERM_PROGRAM')) {
return true;
}

View File

@@ -0,0 +1,63 @@
<?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;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
/**
* A BufferedOutput that keeps only the last N chars.
*
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class TrimmedBufferOutput extends Output
{
private $maxLength;
private $buffer = '';
public function __construct(int $maxLength, ?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, OutputFormatterInterface $formatter = null)
{
if ($maxLength <= 0) {
throw new InvalidArgumentException(sprintf('"%s()" expects a strictly positive maxLength. Got %d.', __METHOD__, $maxLength));
}
parent::__construct($verbosity, $decorated, $formatter);
$this->maxLength = $maxLength;
}
/**
* Empties buffer and returns its content.
*
* @return string
*/
public function fetch()
{
$content = $this->buffer;
$this->buffer = '';
return $content;
}
/**
* {@inheritdoc}
*/
protected function doWrite($message, $newline)
{
$this->buffer .= $message;
if ($newline) {
$this->buffer .= \PHP_EOL;
}
$this->buffer = substr($this->buffer, 0 - $this->maxLength);
}
}

View File

@@ -129,22 +129,26 @@ class ChoiceQuestion extends Question
$isAssoc = $this->isAssoc($choices);
return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) {
// Collapse all spaces.
$selectedChoices = str_replace(' ', '', $selected);
if ($multiselect) {
// Check for a separated comma values
if (!preg_match('/^[^,]+(?:,[^,]+)*$/', $selectedChoices, $matches)) {
if (!preg_match('/^[^,]+(?:,[^,]+)*$/', (string) $selected, $matches)) {
throw new InvalidArgumentException(sprintf($errorMessage, $selected));
}
$selectedChoices = explode(',', $selectedChoices);
$selectedChoices = explode(',', (string) $selected);
} else {
$selectedChoices = array($selected);
$selectedChoices = [$selected];
}
$multiselectChoices = array();
if ($this->isTrimmable()) {
foreach ($selectedChoices as $k => $v) {
$selectedChoices[$k] = trim((string) $v);
}
}
$multiselectChoices = [];
foreach ($selectedChoices as $value) {
$results = array();
$results = [];
foreach ($choices as $key => $choice) {
if ($choice === $value) {
$results[] = $key;
@@ -152,7 +156,7 @@ class ChoiceQuestion extends Question
}
if (\count($results) > 1) {
throw new InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of %s.', implode(' or ', $results)));
throw new InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of "%s".', implode('" or "', $results)));
}
$result = array_search($value, $choices);

View File

@@ -35,10 +35,8 @@ class ConfirmationQuestion extends Question
/**
* Returns the default answer normalizer.
*
* @return callable
*/
private function getDefaultNormalizer()
private function getDefaultNormalizer(): callable
{
$default = $this->getDefault();
$regex = $this->trueAnswerRegex;
@@ -53,7 +51,7 @@ class ConfirmationQuestion extends Question
return $answer && $answerIsTrue;
}
return !$answer || $answerIsTrue;
return '' === $answer || $answerIsTrue;
};
}
}

View File

@@ -25,14 +25,15 @@ class Question
private $attempts;
private $hidden = false;
private $hiddenFallback = true;
private $autocompleterValues;
private $autocompleterCallback;
private $validator;
private $default;
private $normalizer;
private $trimmable = true;
/**
* @param string $question The question to ask to the user
* @param mixed $default The default answer to return if the user enters nothing
* @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)
{
@@ -53,7 +54,7 @@ class Question
/**
* Returns the default answer.
*
* @return mixed
* @return string|bool|int|float|null
*/
public function getDefault()
{
@@ -81,7 +82,7 @@ class Question
*/
public function setHidden($hidden)
{
if ($this->autocompleterValues) {
if ($this->autocompleterCallback) {
throw new LogicException('A hidden question cannot use the autocompleter.');
}
@@ -121,7 +122,9 @@ class Question
*/
public function getAutocompleterValues()
{
return $this->autocompleterValues;
$callback = $this->getAutocompleterCallback();
return $callback ? $callback('') : null;
}
/**
@@ -138,17 +141,46 @@ class Question
{
if (\is_array($values)) {
$values = $this->isAssoc($values) ? array_merge(array_keys($values), array_values($values)) : array_values($values);
}
if (null !== $values && !\is_array($values) && !$values instanceof \Traversable) {
$callback = static function () use ($values) {
return $values;
};
} elseif ($values instanceof \Traversable) {
$valueCache = null;
$callback = static function () use ($values, &$valueCache) {
return $valueCache ?? $valueCache = iterator_to_array($values, false);
};
} elseif (null === $values) {
$callback = null;
} else {
throw new InvalidArgumentException('Autocompleter values can be either an array, "null" or a "Traversable" object.');
}
if ($this->hidden) {
return $this->setAutocompleterCallback($callback);
}
/**
* Gets the callback function used for the autocompleter.
*/
public function getAutocompleterCallback(): ?callable
{
return $this->autocompleterCallback;
}
/**
* Sets the callback function used for the autocompleter.
*
* The callback is passed the user input as argument and should return an iterable of corresponding suggestions.
*
* @return $this
*/
public function setAutocompleterCallback(callable $callback = null): self
{
if ($this->hidden && null !== $callback) {
throw new LogicException('A hidden question cannot use the autocompleter.');
}
$this->autocompleterValues = $values;
$this->autocompleterCallback = $callback;
return $this;
}
@@ -156,8 +188,6 @@ class Question
/**
* Sets a validator for the question.
*
* @param callable|null $validator
*
* @return $this
*/
public function setValidator(callable $validator = null)
@@ -190,8 +220,11 @@ class Question
*/
public function setMaxAttempts($attempts)
{
if (null !== $attempts && $attempts < 1) {
throw new InvalidArgumentException('Maximum number of attempts must be a positive value.');
if (null !== $attempts) {
$attempts = (int) $attempts;
if ($attempts < 1) {
throw new InvalidArgumentException('Maximum number of attempts must be a positive value.');
}
}
$this->attempts = $attempts;
@@ -216,8 +249,6 @@ class Question
*
* The normalizer can be a callable (a string), a closure or a class implementing __invoke.
*
* @param callable $normalizer
*
* @return $this
*/
public function setNormalizer(callable $normalizer)
@@ -232,7 +263,7 @@ class Question
*
* The normalizer can ba a callable (a string), a closure or a class implementing __invoke.
*
* @return callable
* @return callable|null
*/
public function getNormalizer()
{
@@ -243,4 +274,19 @@ class Question
{
return (bool) \count(array_filter(array_keys($array), 'is_string'));
}
public function isTrimmable(): bool
{
return $this->trimmable;
}
/**
* @return $this
*/
public function setTrimmable(bool $trimmable): self
{
$this->trimmable = $trimmable;
return $this;
}
}

View File

@@ -7,11 +7,11 @@ interfaces.
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/console/index.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)
* [Documentation](https://symfony.com/doc/current/components/console.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)
Credits
-------

View File

@@ -35,7 +35,7 @@ abstract class OutputStyle implements OutputInterface, StyleInterface
*/
public function newLine($count = 1)
{
$this->output->write(str_repeat(PHP_EOL, $count));
$this->output->write(str_repeat(\PHP_EOL, $count));
}
/**

View File

@@ -119,7 +119,6 @@ interface StyleInterface
* Asks a choice question.
*
* @param string $question
* @param array $choices
* @param string|int|null $default
*
* @return mixed

View File

@@ -11,15 +11,18 @@
namespace Symfony\Component\Console\Style;
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\ProgressBar;
use Symfony\Component\Console\Helper\SymfonyQuestionHelper;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableCell;
use Symfony\Component\Console\Helper\TableSeparator;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\TrimmedBufferOutput;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
@@ -32,7 +35,7 @@ use Symfony\Component\Console\Terminal;
*/
class SymfonyStyle extends OutputStyle
{
const MAX_LINE_LENGTH = 120;
public const MAX_LINE_LENGTH = 120;
private $input;
private $questionHelper;
@@ -43,7 +46,7 @@ class SymfonyStyle extends OutputStyle
public function __construct(InputInterface $input, OutputInterface $output)
{
$this->input = $input;
$this->bufferedOutput = new BufferedOutput($output->getVerbosity(), false, clone $output->getFormatter());
$this->bufferedOutput = new TrimmedBufferOutput(\DIRECTORY_SEPARATOR === '\\' ? 4 : 2, $output->getVerbosity(), false, clone $output->getFormatter());
// Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not.
$width = (new Terminal())->getWidth() ?: self::MAX_LINE_LENGTH;
$this->lineLength = min($width - (int) (\DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH);
@@ -63,7 +66,7 @@ class SymfonyStyle extends OutputStyle
*/
public function block($messages, $type = null, $style = null, $prefix = ' ', $padding = false, $escape = true)
{
$messages = \is_array($messages) ? array_values($messages) : array($messages);
$messages = \is_array($messages) ? array_values($messages) : [$messages];
$this->autoPrependBlock();
$this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding, $escape));
@@ -76,10 +79,10 @@ class SymfonyStyle extends OutputStyle
public function title($message)
{
$this->autoPrependBlock();
$this->writeln(array(
$this->writeln([
sprintf('<comment>%s</>', OutputFormatter::escapeTrailingBackslash($message)),
sprintf('<comment>%s</>', str_repeat('=', Helper::strlenWithoutDecoration($this->getFormatter(), $message))),
));
]);
$this->newLine();
}
@@ -89,10 +92,10 @@ class SymfonyStyle extends OutputStyle
public function section($message)
{
$this->autoPrependBlock();
$this->writeln(array(
$this->writeln([
sprintf('<comment>%s</>', OutputFormatter::escapeTrailingBackslash($message)),
sprintf('<comment>%s</>', str_repeat('-', Helper::strlenWithoutDecoration($this->getFormatter(), $message))),
));
]);
$this->newLine();
}
@@ -117,7 +120,7 @@ class SymfonyStyle extends OutputStyle
{
$this->autoPrependText();
$messages = \is_array($message) ? array_values($message) : array($message);
$messages = \is_array($message) ? array_values($message) : [$message];
foreach ($messages as $message) {
$this->writeln(sprintf(' %s', $message));
}
@@ -154,7 +157,7 @@ class SymfonyStyle extends OutputStyle
*/
public function warning($message)
{
$this->block($message, 'WARNING', 'fg=white;bg=red', ' ', true);
$this->block($message, 'WARNING', 'fg=black;bg=yellow', ' ', true);
}
/**
@@ -190,6 +193,69 @@ class SymfonyStyle extends OutputStyle
$this->newLine();
}
/**
* Formats a horizontal table.
*/
public function horizontalTable(array $headers, array $rows)
{
$style = clone Table::getStyleDefinition('symfony-style-guide');
$style->setCellHeaderFormat('<info>%s</info>');
$table = new Table($this);
$table->setHeaders($headers);
$table->setRows($rows);
$table->setStyle($style);
$table->setHorizontal(true);
$table->render();
$this->newLine();
}
/**
* Formats a list of key/value horizontally.
*
* Each row can be one of:
* * 'A title'
* * ['key' => 'value']
* * new TableSeparator()
*
* @param string|array|TableSeparator ...$list
*/
public function definitionList(...$list)
{
$style = clone Table::getStyleDefinition('symfony-style-guide');
$style->setCellHeaderFormat('<info>%s</info>');
$table = new Table($this);
$headers = [];
$row = [];
foreach ($list as $value) {
if ($value instanceof TableSeparator) {
$headers[] = $value;
$row[] = $value;
continue;
}
if (\is_string($value)) {
$headers[] = new TableCell($value, ['colspan' => 2]);
$row[] = null;
continue;
}
if (!\is_array($value)) {
throw new InvalidArgumentException('Value should be an array, string, or an instance of TableSeparator.');
}
$headers[] = key($value);
$row[] = current($value);
}
$table->setHeaders($headers);
$table->setRows([$row]);
$table->setHorizontal();
$table->setStyle($style);
$table->render();
$this->newLine();
}
/**
* {@inheritdoc}
*/
@@ -229,7 +295,7 @@ class SymfonyStyle extends OutputStyle
{
if (null !== $default) {
$values = array_flip($choices);
$default = $values[$default];
$default = $values[$default] ?? $default;
}
return $this->askQuestion(new ChoiceQuestion($question, $choices, $default));
@@ -307,7 +373,7 @@ class SymfonyStyle extends OutputStyle
public function writeln($messages, $type = self::OUTPUT_NORMAL)
{
if (!is_iterable($messages)) {
$messages = array($messages);
$messages = [$messages];
}
foreach ($messages as $message) {
@@ -322,7 +388,7 @@ class SymfonyStyle extends OutputStyle
public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL)
{
if (!is_iterable($messages)) {
$messages = array($messages);
$messages = [$messages];
}
foreach ($messages as $message) {
@@ -361,38 +427,37 @@ class SymfonyStyle extends OutputStyle
private function autoPrependBlock(): void
{
$chars = substr(str_replace(PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2);
$chars = substr(str_replace(\PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2);
if (!isset($chars[0])) {
$this->newLine(); //empty history, so we should start with a new line.
$this->newLine(); // empty history, so we should start with a new line.
return;
}
//Prepend new line for each non LF chars (This means no blank line was output before)
// Prepend new line for each non LF chars (This means no blank line was output before)
$this->newLine(2 - substr_count($chars, "\n"));
}
private function autoPrependText(): void
{
$fetched = $this->bufferedOutput->fetch();
//Prepend new line if last char isn't EOL:
if ("\n" !== substr($fetched, -1)) {
// Prepend new line if last char isn't EOL:
if (!str_ends_with($fetched, "\n")) {
$this->newLine();
}
}
private function writeBuffer(string $message, bool $newLine, int $type): void
{
// We need to know if the two last chars are PHP_EOL
// Preserve the last 4 chars inserted (PHP_EOL on windows is two chars) in the history buffer
$this->bufferedOutput->write(substr($message, -4), $newLine, $type);
// We need to know if the last chars are PHP_EOL
$this->bufferedOutput->write($message, $newLine, $type);
}
private function createBlock(iterable $messages, string $type = null, string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = false)
private function createBlock(iterable $messages, string $type = null, string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = false): array
{
$indentLength = 0;
$prefixLength = Helper::strlenWithoutDecoration($this->getFormatter(), $prefix);
$lines = array();
$lines = [];
if (null !== $type) {
$type = sprintf('[%s] ', $type);
@@ -406,7 +471,12 @@ class SymfonyStyle extends OutputStyle
$message = OutputFormatter::escape($message);
}
$lines = array_merge($lines, explode(PHP_EOL, wordwrap($message, $this->lineLength - $prefixLength - $indentLength, PHP_EOL, true)));
$decorationLength = Helper::strlen($message) - Helper::strlenWithoutDecoration($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;
}
if (\count($messages) > 1 && $key < \count($messages) - 1) {
$lines[] = '';
@@ -426,7 +496,7 @@ class SymfonyStyle extends OutputStyle
}
$line = $prefix.$line;
$line .= str_repeat(' ', $this->lineLength - Helper::strlenWithoutDecoration($this->getFormatter(), $line));
$line .= str_repeat(' ', max($this->lineLength - Helper::strlenWithoutDecoration($this->getFormatter(), $line), 0));
if ($style) {
$line = sprintf('<%s>%s</>', $style, $line);

View File

@@ -15,6 +15,7 @@ class Terminal
{
private static $width;
private static $height;
private static $stty;
/**
* Gets the terminal width.
@@ -54,6 +55,27 @@ class Terminal
return self::$height ?: 50;
}
/**
* @internal
*
* @return bool
*/
public static function hasSttyAvailable()
{
if (null !== self::$stty) {
return self::$stty;
}
// skip check if exec function is disabled
if (!\function_exists('exec')) {
return false;
}
exec('stty 2>&1', $output, $exitcode);
return self::$stty = 0 === $exitcode;
}
private static function initDimensions()
{
if ('\\' === \DIRECTORY_SEPARATOR) {
@@ -62,12 +84,34 @@ class Terminal
// or [w, h] from "wxh"
self::$width = (int) $matches[1];
self::$height = isset($matches[4]) ? (int) $matches[4] : (int) $matches[2];
} elseif (!self::hasVt100Support() && self::hasSttyAvailable()) {
// only use stty on Windows if the terminal does not support vt100 (e.g. Windows 7 + git-bash)
// testing for stty in a Windows 10 vt100-enabled console will implicitly disable vt100 support on STDOUT
self::initDimensionsUsingStty();
} elseif (null !== $dimensions = self::getConsoleMode()) {
// extract [w, h] from "wxh"
self::$width = (int) $dimensions[0];
self::$height = (int) $dimensions[1];
}
} elseif ($sttyString = self::getSttyColumns()) {
} else {
self::initDimensionsUsingStty();
}
}
/**
* Returns whether STDOUT has vt100 support (some Windows 10+ configurations).
*/
private static function hasVt100Support(): bool
{
return \function_exists('sapi_windows_vt100_support') && sapi_windows_vt100_support(fopen('php://stdout', 'w'));
}
/**
* Initializes dimensions using the output of an stty columns line.
*/
private static function initDimensionsUsingStty()
{
if ($sttyString = self::getSttyColumns()) {
if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) {
// extract [w, h] from "rows h; columns w;"
self::$width = (int) $matches[2];
@@ -85,53 +129,46 @@ class Terminal
*
* @return int[]|null An array composed of the width and the height or null if it could not be parsed
*/
private static function getConsoleMode()
private static function getConsoleMode(): ?array
{
if (!\function_exists('proc_open')) {
return;
$info = self::readFromProcess('mode CON');
if (null === $info || !preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) {
return null;
}
$descriptorspec = array(
1 => array('pipe', 'w'),
2 => array('pipe', 'w'),
);
$process = proc_open('mode CON', $descriptorspec, $pipes, null, null, array('suppress_errors' => true));
if (\is_resource($process)) {
$info = stream_get_contents($pipes[1]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) {
return array((int) $matches[2], (int) $matches[1]);
}
}
return [(int) $matches[2], (int) $matches[1]];
}
/**
* Runs and parses stty -a if it's available, suppressing any error output.
*
* @return string|null
*/
private static function getSttyColumns()
private static function getSttyColumns(): ?string
{
return self::readFromProcess('stty -a | grep columns');
}
private static function readFromProcess(string $command): ?string
{
if (!\function_exists('proc_open')) {
return;
return null;
}
$descriptorspec = array(
1 => array('pipe', 'w'),
2 => array('pipe', 'w'),
);
$descriptorspec = [
1 => ['pipe', 'w'],
2 => ['pipe', 'w'],
];
$process = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, array('suppress_errors' => true));
if (\is_resource($process)) {
$info = stream_get_contents($pipes[1]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
return $info;
$process = proc_open($command, $descriptorspec, $pipes, null, null, ['suppress_errors' => true]);
if (!\is_resource($process)) {
return null;
}
$info = stream_get_contents($pipes[1]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
return $info;
}
}

View File

@@ -52,26 +52,39 @@ class ApplicationTester
*
* @return int The command exit code
*/
public function run(array $input, $options = array())
public function run(array $input, $options = [])
{
$this->input = new ArrayInput($input);
if (isset($options['interactive'])) {
$this->input->setInteractive($options['interactive']);
$prevShellVerbosity = getenv('SHELL_VERBOSITY');
try {
$this->input = new ArrayInput($input);
if (isset($options['interactive'])) {
$this->input->setInteractive($options['interactive']);
}
if ($this->inputs) {
$this->input->setStream(self::createStream($this->inputs));
}
$this->initOutput($options);
return $this->statusCode = $this->application->run($this->input, $this->output);
} finally {
// SHELL_VERBOSITY is set by Application::configureIO so we need to unset/reset it
// to its previous value to avoid one test's verbosity to spread to the following tests
if (false === $prevShellVerbosity) {
if (\function_exists('putenv')) {
@putenv('SHELL_VERBOSITY');
}
unset($_ENV['SHELL_VERBOSITY']);
unset($_SERVER['SHELL_VERBOSITY']);
} else {
if (\function_exists('putenv')) {
@putenv('SHELL_VERBOSITY='.$prevShellVerbosity);
}
$_ENV['SHELL_VERBOSITY'] = $prevShellVerbosity;
$_SERVER['SHELL_VERBOSITY'] = $prevShellVerbosity;
}
}
$shellInteractive = getenv('SHELL_INTERACTIVE');
if ($this->inputs) {
$this->input->setStream(self::createStream($this->inputs));
putenv('SHELL_INTERACTIVE=1');
}
$this->initOutput($options);
$this->statusCode = $this->application->run($this->input, $this->output);
putenv($shellInteractive ? "SHELL_INTERACTIVE=$shellInteractive" : 'SHELL_INTERACTIVE');
return $this->statusCode;
}
}

View File

@@ -48,7 +48,7 @@ class CommandTester
*
* @return int The command exit code
*/
public function execute(array $input, array $options = array())
public function execute(array $input, array $options = [])
{
// set the command name automatically if the application requires
// this argument and no command name was passed
@@ -56,13 +56,12 @@ class CommandTester
&& (null !== $application = $this->command->getApplication())
&& $application->getDefinition()->hasArgument('command')
) {
$input = array_merge(array('command' => $this->command->getName()), $input);
$input = array_merge(['command' => $this->command->getName()], $input);
}
$this->input = new ArrayInput($input);
if ($this->inputs) {
$this->input->setStream(self::createStream($this->inputs));
}
// Use an in-memory input stream even if no inputs are set so that QuestionHelper::ask() does not rely on the blocking STDIN.
$this->input->setStream(self::createStream($this->inputs));
if (isset($options['interactive'])) {
$this->input->setInteractive($options['interactive']);

View File

@@ -23,7 +23,7 @@ trait TesterTrait
{
/** @var StreamOutput */
private $output;
private $inputs = array();
private $inputs = [];
private $captureStreamsIndependently = false;
/**
@@ -35,12 +35,16 @@ trait TesterTrait
*/
public function getDisplay($normalize = false)
{
if (null === $this->output) {
throw new \RuntimeException('Output not initialized, did you execute the command before requesting the display?');
}
rewind($this->output->getStream());
$display = stream_get_contents($this->output->getStream());
if ($normalize) {
$display = str_replace(PHP_EOL, "\n", $display);
$display = str_replace(\PHP_EOL, "\n", $display);
}
return $display;
@@ -64,7 +68,7 @@ trait TesterTrait
$display = stream_get_contents($this->output->getErrorOutput()->getStream());
if ($normalize) {
$display = str_replace(PHP_EOL, "\n", $display);
$display = str_replace(\PHP_EOL, "\n", $display);
}
return $display;
@@ -106,7 +110,7 @@ trait TesterTrait
* @param array $inputs An array of strings representing each input
* passed to the command input stream
*
* @return self
* @return $this
*/
public function setInputs(array $inputs)
{
@@ -126,7 +130,7 @@ trait TesterTrait
*/
private function initOutput(array $options)
{
$this->captureStreamsIndependently = array_key_exists('capture_stderr_separately', $options) && $options['capture_stderr_separately'];
$this->captureStreamsIndependently = \array_key_exists('capture_stderr_separately', $options) && $options['capture_stderr_separately'];
if (!$this->captureStreamsIndependently) {
$this->output = new StreamOutput(fopen('php://memory', 'w', false));
if (isset($options['decorated'])) {
@@ -137,8 +141,8 @@ trait TesterTrait
}
} else {
$this->output = new ConsoleOutput(
isset($options['verbosity']) ? $options['verbosity'] : ConsoleOutput::VERBOSITY_NORMAL,
isset($options['decorated']) ? $options['decorated'] : null
$options['verbosity'] ?? ConsoleOutput::VERBOSITY_NORMAL,
$options['decorated'] ?? null
);
$errorOutput = new StreamOutput(fopen('php://memory', 'w', false));
@@ -158,11 +162,17 @@ trait TesterTrait
}
}
/**
* @return resource
*/
private static function createStream(array $inputs)
{
$stream = fopen('php://memory', 'r+', false);
fwrite($stream, implode(PHP_EOL, $inputs));
foreach ($inputs as $input) {
fwrite($stream, $input.\PHP_EOL);
}
rewind($stream);
return $stream;

File diff suppressed because it is too large Load Diff

View File

@@ -1,428 +0,0 @@
<?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\Tests\Command;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Tester\CommandTester;
class CommandTest extends TestCase
{
protected static $fixturesPath;
public static function setUpBeforeClass()
{
self::$fixturesPath = __DIR__.'/../Fixtures/';
require_once self::$fixturesPath.'/TestCommand.php';
}
public function testConstructor()
{
$command = new Command('foo:bar');
$this->assertEquals('foo:bar', $command->getName(), '__construct() takes the command name as its first argument');
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage The command defined in "Symfony\Component\Console\Command\Command" cannot have an empty name.
*/
public function testCommandNameCannotBeEmpty()
{
(new Application())->add(new Command());
}
public function testSetApplication()
{
$application = new Application();
$command = new \TestCommand();
$command->setApplication($application);
$this->assertEquals($application, $command->getApplication(), '->setApplication() sets the current application');
$this->assertEquals($application->getHelperSet(), $command->getHelperSet());
}
public function testSetApplicationNull()
{
$command = new \TestCommand();
$command->setApplication(null);
$this->assertNull($command->getHelperSet());
}
public function testSetGetDefinition()
{
$command = new \TestCommand();
$ret = $command->setDefinition($definition = new InputDefinition());
$this->assertEquals($command, $ret, '->setDefinition() implements a fluent interface');
$this->assertEquals($definition, $command->getDefinition(), '->setDefinition() sets the current InputDefinition instance');
$command->setDefinition(array(new InputArgument('foo'), new InputOption('bar')));
$this->assertTrue($command->getDefinition()->hasArgument('foo'), '->setDefinition() also takes an array of InputArguments and InputOptions as an argument');
$this->assertTrue($command->getDefinition()->hasOption('bar'), '->setDefinition() also takes an array of InputArguments and InputOptions as an argument');
$command->setDefinition(new InputDefinition());
}
public function testAddArgument()
{
$command = new \TestCommand();
$ret = $command->addArgument('foo');
$this->assertEquals($command, $ret, '->addArgument() implements a fluent interface');
$this->assertTrue($command->getDefinition()->hasArgument('foo'), '->addArgument() adds an argument to the command');
}
public function testAddOption()
{
$command = new \TestCommand();
$ret = $command->addOption('foo');
$this->assertEquals($command, $ret, '->addOption() implements a fluent interface');
$this->assertTrue($command->getDefinition()->hasOption('foo'), '->addOption() adds an option to the command');
}
public function testSetHidden()
{
$command = new \TestCommand();
$command->setHidden(true);
$this->assertTrue($command->isHidden());
}
public function testGetNamespaceGetNameSetName()
{
$command = new \TestCommand();
$this->assertEquals('namespace:name', $command->getName(), '->getName() returns the command name');
$command->setName('foo');
$this->assertEquals('foo', $command->getName(), '->setName() sets the command name');
$ret = $command->setName('foobar:bar');
$this->assertEquals($command, $ret, '->setName() implements a fluent interface');
$this->assertEquals('foobar:bar', $command->getName(), '->setName() sets the command name');
}
/**
* @dataProvider provideInvalidCommandNames
*/
public function testInvalidCommandNames($name)
{
if (method_exists($this, 'expectException')) {
$this->expectException('InvalidArgumentException');
$this->expectExceptionMessage(sprintf('Command name "%s" is invalid.', $name));
} else {
$this->setExpectedException('InvalidArgumentException', sprintf('Command name "%s" is invalid.', $name));
}
$command = new \TestCommand();
$command->setName($name);
}
public function provideInvalidCommandNames()
{
return array(
array(''),
array('foo:'),
);
}
public function testGetSetDescription()
{
$command = new \TestCommand();
$this->assertEquals('description', $command->getDescription(), '->getDescription() returns the description');
$ret = $command->setDescription('description1');
$this->assertEquals($command, $ret, '->setDescription() implements a fluent interface');
$this->assertEquals('description1', $command->getDescription(), '->setDescription() sets the description');
}
public function testGetSetHelp()
{
$command = new \TestCommand();
$this->assertEquals('help', $command->getHelp(), '->getHelp() returns the help');
$ret = $command->setHelp('help1');
$this->assertEquals($command, $ret, '->setHelp() implements a fluent interface');
$this->assertEquals('help1', $command->getHelp(), '->setHelp() sets the help');
$command->setHelp('');
$this->assertEquals('', $command->getHelp(), '->getHelp() does not fall back to the description');
}
public function testGetProcessedHelp()
{
$command = new \TestCommand();
$command->setHelp('The %command.name% command does... Example: php %command.full_name%.');
$this->assertContains('The namespace:name command does...', $command->getProcessedHelp(), '->getProcessedHelp() replaces %command.name% correctly');
$this->assertNotContains('%command.full_name%', $command->getProcessedHelp(), '->getProcessedHelp() replaces %command.full_name%');
$command = new \TestCommand();
$command->setHelp('');
$this->assertContains('description', $command->getProcessedHelp(), '->getProcessedHelp() falls back to the description');
}
public function testGetSetAliases()
{
$command = new \TestCommand();
$this->assertEquals(array('name'), $command->getAliases(), '->getAliases() returns the aliases');
$ret = $command->setAliases(array('name1'));
$this->assertEquals($command, $ret, '->setAliases() implements a fluent interface');
$this->assertEquals(array('name1'), $command->getAliases(), '->setAliases() sets the aliases');
}
public function testSetAliasesNull()
{
$command = new \TestCommand();
$this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('InvalidArgumentException');
$command->setAliases(null);
}
public function testGetSynopsis()
{
$command = new \TestCommand();
$command->addOption('foo');
$command->addArgument('bar');
$this->assertEquals('namespace:name [--foo] [--] [<bar>]', $command->getSynopsis(), '->getSynopsis() returns the synopsis');
}
public function testAddGetUsages()
{
$command = new \TestCommand();
$command->addUsage('foo1');
$command->addUsage('foo2');
$this->assertContains('namespace:name foo1', $command->getUsages());
$this->assertContains('namespace:name foo2', $command->getUsages());
}
public function testGetHelper()
{
$application = new Application();
$command = new \TestCommand();
$command->setApplication($application);
$formatterHelper = new FormatterHelper();
$this->assertEquals($formatterHelper->getName(), $command->getHelper('formatter')->getName(), '->getHelper() returns the correct helper');
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage Cannot retrieve helper "formatter" because there is no HelperSet defined.
*/
public function testGetHelperWithoutHelperSet()
{
$command = new \TestCommand();
$command->getHelper('formatter');
}
public function testMergeApplicationDefinition()
{
$application1 = new Application();
$application1->getDefinition()->addArguments(array(new InputArgument('foo')));
$application1->getDefinition()->addOptions(array(new InputOption('bar')));
$command = new \TestCommand();
$command->setApplication($application1);
$command->setDefinition($definition = new InputDefinition(array(new InputArgument('bar'), new InputOption('foo'))));
$r = new \ReflectionObject($command);
$m = $r->getMethod('mergeApplicationDefinition');
$m->setAccessible(true);
$m->invoke($command);
$this->assertTrue($command->getDefinition()->hasArgument('foo'), '->mergeApplicationDefinition() merges the application arguments and the command arguments');
$this->assertTrue($command->getDefinition()->hasArgument('bar'), '->mergeApplicationDefinition() merges the application arguments and the command arguments');
$this->assertTrue($command->getDefinition()->hasOption('foo'), '->mergeApplicationDefinition() merges the application options and the command options');
$this->assertTrue($command->getDefinition()->hasOption('bar'), '->mergeApplicationDefinition() merges the application options and the command options');
$m->invoke($command);
$this->assertEquals(3, $command->getDefinition()->getArgumentCount(), '->mergeApplicationDefinition() does not try to merge twice the application arguments and options');
}
public function testMergeApplicationDefinitionWithoutArgsThenWithArgsAddsArgs()
{
$application1 = new Application();
$application1->getDefinition()->addArguments(array(new InputArgument('foo')));
$application1->getDefinition()->addOptions(array(new InputOption('bar')));
$command = new \TestCommand();
$command->setApplication($application1);
$command->setDefinition($definition = new InputDefinition(array()));
$r = new \ReflectionObject($command);
$m = $r->getMethod('mergeApplicationDefinition');
$m->setAccessible(true);
$m->invoke($command, false);
$this->assertTrue($command->getDefinition()->hasOption('bar'), '->mergeApplicationDefinition(false) merges the application and the command options');
$this->assertFalse($command->getDefinition()->hasArgument('foo'), '->mergeApplicationDefinition(false) does not merge the application arguments');
$m->invoke($command, true);
$this->assertTrue($command->getDefinition()->hasArgument('foo'), '->mergeApplicationDefinition(true) merges the application arguments and the command arguments');
$m->invoke($command);
$this->assertEquals(2, $command->getDefinition()->getArgumentCount(), '->mergeApplicationDefinition() does not try to merge twice the application arguments');
}
public function testRunInteractive()
{
$tester = new CommandTester(new \TestCommand());
$tester->execute(array(), array('interactive' => true));
$this->assertEquals('interact called'.PHP_EOL.'execute called'.PHP_EOL, $tester->getDisplay(), '->run() calls the interact() method if the input is interactive');
}
public function testRunNonInteractive()
{
$tester = new CommandTester(new \TestCommand());
$tester->execute(array(), array('interactive' => false));
$this->assertEquals('execute called'.PHP_EOL, $tester->getDisplay(), '->run() does not call the interact() method if the input is not interactive');
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage You must override the execute() method in the concrete command class.
*/
public function testExecuteMethodNeedsToBeOverridden()
{
$command = new Command('foo');
$command->run(new StringInput(''), new NullOutput());
}
/**
* @expectedException \Symfony\Component\Console\Exception\InvalidOptionException
* @expectedExceptionMessage The "--bar" option does not exist.
*/
public function testRunWithInvalidOption()
{
$command = new \TestCommand();
$tester = new CommandTester($command);
$tester->execute(array('--bar' => true));
}
public function testRunReturnsIntegerExitCode()
{
$command = new \TestCommand();
$exitCode = $command->run(new StringInput(''), new NullOutput());
$this->assertSame(0, $exitCode, '->run() returns integer exit code (treats null as 0)');
$command = $this->getMockBuilder('TestCommand')->setMethods(array('execute'))->getMock();
$command->expects($this->once())
->method('execute')
->will($this->returnValue('2.3'));
$exitCode = $command->run(new StringInput(''), new NullOutput());
$this->assertSame(2, $exitCode, '->run() returns integer exit code (casts numeric to int)');
}
public function testRunWithApplication()
{
$command = new \TestCommand();
$command->setApplication(new Application());
$exitCode = $command->run(new StringInput(''), new NullOutput());
$this->assertSame(0, $exitCode, '->run() returns an integer exit code');
}
public function testRunReturnsAlwaysInteger()
{
$command = new \TestCommand();
$this->assertSame(0, $command->run(new StringInput(''), new NullOutput()));
}
public function testRunWithProcessTitle()
{
$command = new \TestCommand();
$command->setApplication(new Application());
$command->setProcessTitle('foo');
$this->assertSame(0, $command->run(new StringInput(''), new NullOutput()));
if (\function_exists('cli_set_process_title')) {
if (null === @cli_get_process_title() && 'Darwin' === PHP_OS) {
$this->markTestSkipped('Running "cli_get_process_title" as an unprivileged user is not supported on MacOS.');
}
$this->assertEquals('foo', cli_get_process_title());
}
}
public function testSetCode()
{
$command = new \TestCommand();
$ret = $command->setCode(function (InputInterface $input, OutputInterface $output) {
$output->writeln('from the code...');
});
$this->assertEquals($command, $ret, '->setCode() implements a fluent interface');
$tester = new CommandTester($command);
$tester->execute(array());
$this->assertEquals('interact called'.PHP_EOL.'from the code...'.PHP_EOL, $tester->getDisplay());
}
public function getSetCodeBindToClosureTests()
{
return array(
array(true, 'not bound to the command'),
array(false, 'bound to the command'),
);
}
/**
* @dataProvider getSetCodeBindToClosureTests
*/
public function testSetCodeBindToClosure($previouslyBound, $expected)
{
$code = createClosure();
if ($previouslyBound) {
$code = $code->bindTo($this);
}
$command = new \TestCommand();
$command->setCode($code);
$tester = new CommandTester($command);
$tester->execute(array());
$this->assertEquals('interact called'.PHP_EOL.$expected.PHP_EOL, $tester->getDisplay());
}
public function testSetCodeWithStaticClosure()
{
$command = new \TestCommand();
$command->setCode(self::createClosure());
$tester = new CommandTester($command);
$tester->execute(array());
$this->assertEquals('interact called'.PHP_EOL.'bound'.PHP_EOL, $tester->getDisplay());
}
private static function createClosure()
{
return function (InputInterface $input, OutputInterface $output) {
$output->writeln(isset($this) ? 'bound' : 'not bound');
};
}
public function testSetCodeWithNonClosureCallable()
{
$command = new \TestCommand();
$ret = $command->setCode(array($this, 'callableMethodCommand'));
$this->assertEquals($command, $ret, '->setCode() implements a fluent interface');
$tester = new CommandTester($command);
$tester->execute(array());
$this->assertEquals('interact called'.PHP_EOL.'from the code...'.PHP_EOL, $tester->getDisplay());
}
public function callableMethodCommand(InputInterface $input, OutputInterface $output)
{
$output->writeln('from the code...');
}
}
// In order to get an unbound closure, we should create it outside a class
// scope.
function createClosure()
{
return function (InputInterface $input, OutputInterface $output) {
$output->writeln($this instanceof Command ? 'bound to the command' : 'not bound to the command');
};
}

View File

@@ -1,71 +0,0 @@
<?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\Tests\Command;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\HelpCommand;
use Symfony\Component\Console\Command\ListCommand;
use Symfony\Component\Console\Tester\CommandTester;
class HelpCommandTest extends TestCase
{
public function testExecuteForCommandAlias()
{
$command = new HelpCommand();
$command->setApplication(new Application());
$commandTester = new CommandTester($command);
$commandTester->execute(array('command_name' => 'li'), array('decorated' => false));
$this->assertContains('list [options] [--] [<namespace>]', $commandTester->getDisplay(), '->execute() returns a text help for the given command alias');
$this->assertContains('format=FORMAT', $commandTester->getDisplay(), '->execute() returns a text help for the given command alias');
$this->assertContains('raw', $commandTester->getDisplay(), '->execute() returns a text help for the given command alias');
}
public function testExecuteForCommand()
{
$command = new HelpCommand();
$commandTester = new CommandTester($command);
$command->setCommand(new ListCommand());
$commandTester->execute(array(), array('decorated' => false));
$this->assertContains('list [options] [--] [<namespace>]', $commandTester->getDisplay(), '->execute() returns a text help for the given command');
$this->assertContains('format=FORMAT', $commandTester->getDisplay(), '->execute() returns a text help for the given command');
$this->assertContains('raw', $commandTester->getDisplay(), '->execute() returns a text help for the given command');
}
public function testExecuteForCommandWithXmlOption()
{
$command = new HelpCommand();
$commandTester = new CommandTester($command);
$command->setCommand(new ListCommand());
$commandTester->execute(array('--format' => 'xml'));
$this->assertContains('<command', $commandTester->getDisplay(), '->execute() returns an XML help text if --xml is passed');
}
public function testExecuteForApplicationCommand()
{
$application = new Application();
$commandTester = new CommandTester($application->get('help'));
$commandTester->execute(array('command_name' => 'list'));
$this->assertContains('list [options] [--] [<namespace>]', $commandTester->getDisplay(), '->execute() returns a text help for the given command');
$this->assertContains('format=FORMAT', $commandTester->getDisplay(), '->execute() returns a text help for the given command');
$this->assertContains('raw', $commandTester->getDisplay(), '->execute() returns a text help for the given command');
}
public function testExecuteForApplicationCommandWithXmlOption()
{
$application = new Application();
$commandTester = new CommandTester($application->get('help'));
$commandTester->execute(array('command_name' => 'list', '--format' => 'xml'));
$this->assertContains('list [--raw] [--format FORMAT] [--] [&lt;namespace&gt;]', $commandTester->getDisplay(), '->execute() returns a text help for the given command');
$this->assertContains('<command', $commandTester->getDisplay(), '->execute() returns an XML help text if --format=xml is passed');
}
}

View File

@@ -1,113 +0,0 @@
<?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\Tests\Command;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;
class ListCommandTest extends TestCase
{
public function testExecuteListsCommands()
{
$application = new Application();
$commandTester = new CommandTester($command = $application->get('list'));
$commandTester->execute(array('command' => $command->getName()), array('decorated' => false));
$this->assertRegExp('/help\s{2,}Displays help for a command/', $commandTester->getDisplay(), '->execute() returns a list of available commands');
}
public function testExecuteListsCommandsWithXmlOption()
{
$application = new Application();
$commandTester = new CommandTester($command = $application->get('list'));
$commandTester->execute(array('command' => $command->getName(), '--format' => 'xml'));
$this->assertRegExp('/<command id="list" name="list" hidden="0">/', $commandTester->getDisplay(), '->execute() returns a list of available commands in XML if --xml is passed');
}
public function testExecuteListsCommandsWithRawOption()
{
$application = new Application();
$commandTester = new CommandTester($command = $application->get('list'));
$commandTester->execute(array('command' => $command->getName(), '--raw' => true));
$output = <<<'EOF'
help Displays help for a command
list Lists commands
EOF;
$this->assertEquals($output, $commandTester->getDisplay(true));
}
public function testExecuteListsCommandsWithNamespaceArgument()
{
require_once realpath(__DIR__.'/../Fixtures/FooCommand.php');
$application = new Application();
$application->add(new \FooCommand());
$commandTester = new CommandTester($command = $application->get('list'));
$commandTester->execute(array('command' => $command->getName(), 'namespace' => 'foo', '--raw' => true));
$output = <<<'EOF'
foo:bar The foo:bar command
EOF;
$this->assertEquals($output, $commandTester->getDisplay(true));
}
public function testExecuteListsCommandsOrder()
{
require_once realpath(__DIR__.'/../Fixtures/Foo6Command.php');
$application = new Application();
$application->add(new \Foo6Command());
$commandTester = new CommandTester($command = $application->get('list'));
$commandTester->execute(array('command' => $command->getName()), array('decorated' => false));
$output = <<<'EOF'
Console Tool
Usage:
command [options] [arguments]
Options:
-h, --help Display this help message
-q, --quiet Do not output any message
-V, --version Display this application version
--ansi Force ANSI output
--no-ansi Disable ANSI output
-n, --no-interaction Do not ask any interactive question
-v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
Available commands:
help Displays help for a command
list Lists commands
0foo
0foo:bar 0foo:bar command
EOF;
$this->assertEquals($output, trim($commandTester->getDisplay(true)));
}
public function testExecuteListsCommandsOrderRaw()
{
require_once realpath(__DIR__.'/../Fixtures/Foo6Command.php');
$application = new Application();
$application->add(new \Foo6Command());
$commandTester = new CommandTester($command = $application->get('list'));
$commandTester->execute(array('command' => $command->getName(), '--raw' => true));
$output = <<<'EOF'
help Displays help for a command
list Lists commands
0foo:bar 0foo:bar command
EOF;
$this->assertEquals($output, trim($commandTester->getDisplay(true)));
}
}

View File

@@ -1,67 +0,0 @@
<?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\Tests\Command;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Component\Lock\Factory;
use Symfony\Component\Lock\Store\FlockStore;
use Symfony\Component\Lock\Store\SemaphoreStore;
class LockableTraitTest extends TestCase
{
protected static $fixturesPath;
public static function setUpBeforeClass()
{
self::$fixturesPath = __DIR__.'/../Fixtures/';
require_once self::$fixturesPath.'/FooLockCommand.php';
require_once self::$fixturesPath.'/FooLock2Command.php';
}
public function testLockIsReleased()
{
$command = new \FooLockCommand();
$tester = new CommandTester($command);
$this->assertSame(2, $tester->execute(array()));
$this->assertSame(2, $tester->execute(array()));
}
public function testLockReturnsFalseIfAlreadyLockedByAnotherCommand()
{
$command = new \FooLockCommand();
if (SemaphoreStore::isSupported()) {
$store = new SemaphoreStore();
} else {
$store = new FlockStore();
}
$lock = (new Factory($store))->createLock($command->getName());
$lock->acquire();
$tester = new CommandTester($command);
$this->assertSame(1, $tester->execute(array()));
$lock->release();
$this->assertSame(2, $tester->execute(array()));
}
public function testMultipleLockCallsThrowLogicException()
{
$command = new \FooLock2Command();
$tester = new CommandTester($command);
$this->assertSame(1, $tester->execute(array()));
}
}

View File

@@ -1,61 +0,0 @@
<?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\Tests\CommandLoader;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
use Symfony\Component\DependencyInjection\ServiceLocator;
class ContainerCommandLoaderTest extends TestCase
{
public function testHas()
{
$loader = new ContainerCommandLoader(new ServiceLocator(array(
'foo-service' => function () { return new Command('foo'); },
'bar-service' => function () { return new Command('bar'); },
)), array('foo' => 'foo-service', 'bar' => 'bar-service'));
$this->assertTrue($loader->has('foo'));
$this->assertTrue($loader->has('bar'));
$this->assertFalse($loader->has('baz'));
}
public function testGet()
{
$loader = new ContainerCommandLoader(new ServiceLocator(array(
'foo-service' => function () { return new Command('foo'); },
'bar-service' => function () { return new Command('bar'); },
)), array('foo' => 'foo-service', 'bar' => 'bar-service'));
$this->assertInstanceOf(Command::class, $loader->get('foo'));
$this->assertInstanceOf(Command::class, $loader->get('bar'));
}
/**
* @expectedException \Symfony\Component\Console\Exception\CommandNotFoundException
*/
public function testGetUnknownCommandThrows()
{
(new ContainerCommandLoader(new ServiceLocator(array()), array()))->get('unknown');
}
public function testGetCommandNames()
{
$loader = new ContainerCommandLoader(new ServiceLocator(array(
'foo-service' => function () { return new Command('foo'); },
'bar-service' => function () { return new Command('bar'); },
)), array('foo' => 'foo-service', 'bar' => 'bar-service'));
$this->assertSame(array('foo', 'bar'), $loader->getNames());
}
}

View File

@@ -1,60 +0,0 @@
<?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\Tests\CommandLoader;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\CommandLoader\FactoryCommandLoader;
class FactoryCommandLoaderTest extends TestCase
{
public function testHas()
{
$loader = new FactoryCommandLoader(array(
'foo' => function () { return new Command('foo'); },
'bar' => function () { return new Command('bar'); },
));
$this->assertTrue($loader->has('foo'));
$this->assertTrue($loader->has('bar'));
$this->assertFalse($loader->has('baz'));
}
public function testGet()
{
$loader = new FactoryCommandLoader(array(
'foo' => function () { return new Command('foo'); },
'bar' => function () { return new Command('bar'); },
));
$this->assertInstanceOf(Command::class, $loader->get('foo'));
$this->assertInstanceOf(Command::class, $loader->get('bar'));
}
/**
* @expectedException \Symfony\Component\Console\Exception\CommandNotFoundException
*/
public function testGetUnknownCommandThrows()
{
(new FactoryCommandLoader(array()))->get('unknown');
}
public function testGetCommandNames()
{
$loader = new FactoryCommandLoader(array(
'foo' => function () { return new Command('foo'); },
'bar' => function () { return new Command('bar'); },
));
$this->assertSame(array('foo', 'bar'), $loader->getNames());
}
}

View File

@@ -1,258 +0,0 @@
<?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\Tests\DependencyInjection;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\TypedReference;
class AddConsoleCommandPassTest extends TestCase
{
/**
* @dataProvider visibilityProvider
*/
public function testProcess($public)
{
$container = new ContainerBuilder();
$container->addCompilerPass(new AddConsoleCommandPass(), PassConfig::TYPE_BEFORE_REMOVING);
$container->setParameter('my-command.class', 'Symfony\Component\Console\Tests\DependencyInjection\MyCommand');
$id = 'my-command';
$definition = new Definition('%my-command.class%');
$definition->setPublic($public);
$definition->addTag('console.command');
$container->setDefinition($id, $definition);
$container->compile();
$alias = 'console.command.public_alias.my-command';
if ($public) {
$this->assertFalse($container->hasAlias($alias));
} else {
// The alias is replaced by a Definition by the ReplaceAliasByActualDefinitionPass
// in case the original service is private
$this->assertFalse($container->hasDefinition($id));
$this->assertTrue($container->hasDefinition($alias));
}
$this->assertTrue($container->hasParameter('console.command.ids'));
$this->assertSame(array($public ? $id : $alias), $container->getParameter('console.command.ids'));
}
public function testProcessRegistersLazyCommands()
{
$container = new ContainerBuilder();
$command = $container
->register('my-command', MyCommand::class)
->setPublic(false)
->addTag('console.command', array('command' => 'my:command'))
->addTag('console.command', array('command' => 'my:alias'))
;
(new AddConsoleCommandPass())->process($container);
$commandLoader = $container->getDefinition('console.command_loader');
$commandLocator = $container->getDefinition((string) $commandLoader->getArgument(0));
$this->assertSame(ContainerCommandLoader::class, $commandLoader->getClass());
$this->assertSame(array('my:command' => 'my-command', 'my:alias' => 'my-command'), $commandLoader->getArgument(1));
$this->assertEquals(array(array('my-command' => new ServiceClosureArgument(new TypedReference('my-command', MyCommand::class)))), $commandLocator->getArguments());
$this->assertSame(array(), $container->getParameter('console.command.ids'));
$this->assertSame(array(array('setName', array('my:command')), array('setAliases', array(array('my:alias')))), $command->getMethodCalls());
}
public function testProcessFallsBackToDefaultName()
{
$container = new ContainerBuilder();
$container
->register('with-default-name', NamedCommand::class)
->setPublic(false)
->addTag('console.command')
;
$pass = new AddConsoleCommandPass();
$pass->process($container);
$commandLoader = $container->getDefinition('console.command_loader');
$commandLocator = $container->getDefinition((string) $commandLoader->getArgument(0));
$this->assertSame(ContainerCommandLoader::class, $commandLoader->getClass());
$this->assertSame(array('default' => 'with-default-name'), $commandLoader->getArgument(1));
$this->assertEquals(array(array('with-default-name' => new ServiceClosureArgument(new TypedReference('with-default-name', NamedCommand::class)))), $commandLocator->getArguments());
$this->assertSame(array(), $container->getParameter('console.command.ids'));
$container = new ContainerBuilder();
$container
->register('with-default-name', NamedCommand::class)
->setPublic(false)
->addTag('console.command', array('command' => 'new-name'))
;
$pass->process($container);
$this->assertSame(array('new-name' => 'with-default-name'), $container->getDefinition('console.command_loader')->getArgument(1));
}
public function visibilityProvider()
{
return array(
array(true),
array(false),
);
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The service "my-command" tagged "console.command" must not be abstract.
*/
public function testProcessThrowAnExceptionIfTheServiceIsAbstract()
{
$container = new ContainerBuilder();
$container->setResourceTracking(false);
$container->addCompilerPass(new AddConsoleCommandPass(), PassConfig::TYPE_BEFORE_REMOVING);
$definition = new Definition('Symfony\Component\Console\Tests\DependencyInjection\MyCommand');
$definition->addTag('console.command');
$definition->setAbstract(true);
$container->setDefinition('my-command', $definition);
$container->compile();
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The service "my-command" tagged "console.command" must be a subclass of "Symfony\Component\Console\Command\Command".
*/
public function testProcessThrowAnExceptionIfTheServiceIsNotASubclassOfCommand()
{
$container = new ContainerBuilder();
$container->setResourceTracking(false);
$container->addCompilerPass(new AddConsoleCommandPass(), PassConfig::TYPE_BEFORE_REMOVING);
$definition = new Definition('SplObjectStorage');
$definition->addTag('console.command');
$container->setDefinition('my-command', $definition);
$container->compile();
}
public function testProcessPrivateServicesWithSameCommand()
{
$container = new ContainerBuilder();
$className = 'Symfony\Component\Console\Tests\DependencyInjection\MyCommand';
$definition1 = new Definition($className);
$definition1->addTag('console.command')->setPublic(false);
$definition2 = new Definition($className);
$definition2->addTag('console.command')->setPublic(false);
$container->setDefinition('my-command1', $definition1);
$container->setDefinition('my-command2', $definition2);
(new AddConsoleCommandPass())->process($container);
$aliasPrefix = 'console.command.public_alias.';
$this->assertTrue($container->hasAlias($aliasPrefix.'my-command1'));
$this->assertTrue($container->hasAlias($aliasPrefix.'my-command2'));
}
public function testProcessOnChildDefinitionWithClass()
{
$container = new ContainerBuilder();
$container->addCompilerPass(new AddConsoleCommandPass(), PassConfig::TYPE_BEFORE_REMOVING);
$className = 'Symfony\Component\Console\Tests\DependencyInjection\MyCommand';
$parentId = 'my-parent-command';
$childId = 'my-child-command';
$parentDefinition = new Definition(/* no class */);
$parentDefinition->setAbstract(true)->setPublic(false);
$childDefinition = new ChildDefinition($parentId);
$childDefinition->addTag('console.command')->setPublic(true);
$childDefinition->setClass($className);
$container->setDefinition($parentId, $parentDefinition);
$container->setDefinition($childId, $childDefinition);
$container->compile();
$command = $container->get($childId);
$this->assertInstanceOf($className, $command);
}
public function testProcessOnChildDefinitionWithParentClass()
{
$container = new ContainerBuilder();
$container->addCompilerPass(new AddConsoleCommandPass(), PassConfig::TYPE_BEFORE_REMOVING);
$className = 'Symfony\Component\Console\Tests\DependencyInjection\MyCommand';
$parentId = 'my-parent-command';
$childId = 'my-child-command';
$parentDefinition = new Definition($className);
$parentDefinition->setAbstract(true)->setPublic(false);
$childDefinition = new ChildDefinition($parentId);
$childDefinition->addTag('console.command')->setPublic(true);
$container->setDefinition($parentId, $parentDefinition);
$container->setDefinition($childId, $childDefinition);
$container->compile();
$command = $container->get($childId);
$this->assertInstanceOf($className, $command);
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage The definition for "my-child-command" has no class.
*/
public function testProcessOnChildDefinitionWithoutClass()
{
$container = new ContainerBuilder();
$container->addCompilerPass(new AddConsoleCommandPass(), PassConfig::TYPE_BEFORE_REMOVING);
$parentId = 'my-parent-command';
$childId = 'my-child-command';
$parentDefinition = new Definition();
$parentDefinition->setAbstract(true)->setPublic(false);
$childDefinition = new ChildDefinition($parentId);
$childDefinition->addTag('console.command')->setPublic(true);
$container->setDefinition($parentId, $parentDefinition);
$container->setDefinition($childId, $childDefinition);
$container->compile();
}
}
class MyCommand extends Command
{
}
class NamedCommand extends Command
{
protected static $defaultName = 'default';
}

View File

@@ -1,107 +0,0 @@
<?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\Tests\Descriptor;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\BufferedOutput;
abstract class AbstractDescriptorTest extends TestCase
{
/** @dataProvider getDescribeInputArgumentTestData */
public function testDescribeInputArgument(InputArgument $argument, $expectedDescription)
{
$this->assertDescription($expectedDescription, $argument);
}
/** @dataProvider getDescribeInputOptionTestData */
public function testDescribeInputOption(InputOption $option, $expectedDescription)
{
$this->assertDescription($expectedDescription, $option);
}
/** @dataProvider getDescribeInputDefinitionTestData */
public function testDescribeInputDefinition(InputDefinition $definition, $expectedDescription)
{
$this->assertDescription($expectedDescription, $definition);
}
/** @dataProvider getDescribeCommandTestData */
public function testDescribeCommand(Command $command, $expectedDescription)
{
$this->assertDescription($expectedDescription, $command);
}
/** @dataProvider getDescribeApplicationTestData */
public function testDescribeApplication(Application $application, $expectedDescription)
{
// Replaces the dynamic placeholders of the command help text with a static version.
// The placeholder %command.full_name% includes the script path that is not predictable
// and can not be tested against.
foreach ($application->all() as $command) {
$command->setHelp(str_replace('%command.full_name%', 'app/console %command.name%', $command->getHelp()));
}
$this->assertDescription($expectedDescription, $application);
}
public function getDescribeInputArgumentTestData()
{
return $this->getDescriptionTestData(ObjectsProvider::getInputArguments());
}
public function getDescribeInputOptionTestData()
{
return $this->getDescriptionTestData(ObjectsProvider::getInputOptions());
}
public function getDescribeInputDefinitionTestData()
{
return $this->getDescriptionTestData(ObjectsProvider::getInputDefinitions());
}
public function getDescribeCommandTestData()
{
return $this->getDescriptionTestData(ObjectsProvider::getCommands());
}
public function getDescribeApplicationTestData()
{
return $this->getDescriptionTestData(ObjectsProvider::getApplications());
}
abstract protected function getDescriptor();
abstract protected function getFormat();
protected function getDescriptionTestData(array $objects)
{
$data = array();
foreach ($objects as $name => $object) {
$description = file_get_contents(sprintf('%s/../Fixtures/%s.%s', __DIR__, $name, $this->getFormat()));
$data[] = array($object, $description);
}
return $data;
}
protected function assertDescription($expectedDescription, $describedObject, array $options = array())
{
$output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, true);
$this->getDescriptor()->describe($output, $describedObject, $options + array('raw_output' => true));
$this->assertEquals(trim($expectedDescription), trim(str_replace(PHP_EOL, "\n", $output->fetch())));
}
}

View File

@@ -1,35 +0,0 @@
<?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\Tests\Descriptor;
use Symfony\Component\Console\Descriptor\JsonDescriptor;
use Symfony\Component\Console\Output\BufferedOutput;
class JsonDescriptorTest extends AbstractDescriptorTest
{
protected function getDescriptor()
{
return new JsonDescriptor();
}
protected function getFormat()
{
return 'json';
}
protected function assertDescription($expectedDescription, $describedObject, array $options = array())
{
$output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, true);
$this->getDescriptor()->describe($output, $describedObject, $options + array('raw_output' => true));
$this->assertEquals(json_decode(trim($expectedDescription), true), json_decode(trim(str_replace(PHP_EOL, "\n", $output->fetch())), true));
}
}

View File

@@ -1,45 +0,0 @@
<?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\Tests\Descriptor;
use Symfony\Component\Console\Descriptor\MarkdownDescriptor;
use Symfony\Component\Console\Tests\Fixtures\DescriptorApplicationMbString;
use Symfony\Component\Console\Tests\Fixtures\DescriptorCommandMbString;
class MarkdownDescriptorTest extends AbstractDescriptorTest
{
public function getDescribeCommandTestData()
{
return $this->getDescriptionTestData(array_merge(
ObjectsProvider::getCommands(),
array('command_mbstring' => new DescriptorCommandMbString())
));
}
public function getDescribeApplicationTestData()
{
return $this->getDescriptionTestData(array_merge(
ObjectsProvider::getApplications(),
array('application_mbstring' => new DescriptorApplicationMbString())
));
}
protected function getDescriptor()
{
return new MarkdownDescriptor();
}
protected function getFormat()
{
return 'md';
}
}

View File

@@ -1,82 +0,0 @@
<?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\Tests\Descriptor;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Tests\Fixtures\DescriptorApplication1;
use Symfony\Component\Console\Tests\Fixtures\DescriptorApplication2;
use Symfony\Component\Console\Tests\Fixtures\DescriptorCommand1;
use Symfony\Component\Console\Tests\Fixtures\DescriptorCommand2;
/**
* @author Jean-François Simon <contact@jfsimon.fr>
*/
class ObjectsProvider
{
public static function getInputArguments()
{
return array(
'input_argument_1' => new InputArgument('argument_name', InputArgument::REQUIRED),
'input_argument_2' => new InputArgument('argument_name', InputArgument::IS_ARRAY, 'argument description'),
'input_argument_3' => new InputArgument('argument_name', InputArgument::OPTIONAL, 'argument description', 'default_value'),
'input_argument_4' => new InputArgument('argument_name', InputArgument::REQUIRED, "multiline\nargument description"),
'input_argument_with_style' => new InputArgument('argument_name', InputArgument::OPTIONAL, 'argument description', '<comment>style</>'),
'input_argument_with_default_inf_value' => new InputArgument('argument_name', InputArgument::OPTIONAL, 'argument description', INF),
);
}
public static function getInputOptions()
{
return array(
'input_option_1' => new InputOption('option_name', 'o', InputOption::VALUE_NONE),
'input_option_2' => new InputOption('option_name', 'o', InputOption::VALUE_OPTIONAL, 'option description', 'default_value'),
'input_option_3' => new InputOption('option_name', 'o', InputOption::VALUE_REQUIRED, 'option description'),
'input_option_4' => new InputOption('option_name', 'o', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'option description', array()),
'input_option_5' => new InputOption('option_name', 'o', InputOption::VALUE_REQUIRED, "multiline\noption description"),
'input_option_6' => new InputOption('option_name', array('o', 'O'), InputOption::VALUE_REQUIRED, 'option with multiple shortcuts'),
'input_option_with_style' => new InputOption('option_name', 'o', InputOption::VALUE_REQUIRED, 'option description', '<comment>style</>'),
'input_option_with_style_array' => new InputOption('option_name', 'o', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'option description', array('<comment>Hello</comment>', '<info>world</info>')),
'input_option_with_default_inf_value' => new InputOption('option_name', 'o', InputOption::VALUE_OPTIONAL, 'option description', INF),
);
}
public static function getInputDefinitions()
{
return array(
'input_definition_1' => new InputDefinition(),
'input_definition_2' => new InputDefinition(array(new InputArgument('argument_name', InputArgument::REQUIRED))),
'input_definition_3' => new InputDefinition(array(new InputOption('option_name', 'o', InputOption::VALUE_NONE))),
'input_definition_4' => new InputDefinition(array(
new InputArgument('argument_name', InputArgument::REQUIRED),
new InputOption('option_name', 'o', InputOption::VALUE_NONE),
)),
);
}
public static function getCommands()
{
return array(
'command_1' => new DescriptorCommand1(),
'command_2' => new DescriptorCommand2(),
);
}
public static function getApplications()
{
return array(
'application_1' => new DescriptorApplication1(),
'application_2' => new DescriptorApplication2(),
);
}
}

View File

@@ -1,53 +0,0 @@
<?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\Tests\Descriptor;
use Symfony\Component\Console\Descriptor\TextDescriptor;
use Symfony\Component\Console\Tests\Fixtures\DescriptorApplication2;
use Symfony\Component\Console\Tests\Fixtures\DescriptorApplicationMbString;
use Symfony\Component\Console\Tests\Fixtures\DescriptorCommandMbString;
class TextDescriptorTest extends AbstractDescriptorTest
{
public function getDescribeCommandTestData()
{
return $this->getDescriptionTestData(array_merge(
ObjectsProvider::getCommands(),
array('command_mbstring' => new DescriptorCommandMbString())
));
}
public function getDescribeApplicationTestData()
{
return $this->getDescriptionTestData(array_merge(
ObjectsProvider::getApplications(),
array('application_mbstring' => new DescriptorApplicationMbString())
));
}
public function testDescribeApplicationWithFilteredNamespace()
{
$application = new DescriptorApplication2();
$this->assertDescription(file_get_contents(__DIR__.'/../Fixtures/application_filtered_namespace.txt'), $application, array('namespace' => 'command4'));
}
protected function getDescriptor()
{
return new TextDescriptor();
}
protected function getFormat()
{
return 'txt';
}
}

View File

@@ -1,27 +0,0 @@
<?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\Tests\Descriptor;
use Symfony\Component\Console\Descriptor\XmlDescriptor;
class XmlDescriptorTest extends AbstractDescriptorTest
{
protected function getDescriptor()
{
return new XmlDescriptor();
}
protected function getFormat()
{
return 'xml';
}
}

View File

@@ -1,156 +0,0 @@
<?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\Tests\EventListener;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\Console\EventListener\ErrorListener;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\Input;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\OutputInterface;
class ErrorListenerTest extends TestCase
{
public function testOnConsoleError()
{
$error = new \TypeError('An error occurred');
$logger = $this->getLogger();
$logger
->expects($this->once())
->method('error')
->with('Error thrown while running command "{command}". Message: "{message}"', array('exception' => $error, 'command' => 'test:run --foo=baz buzz', 'message' => 'An error occurred'))
;
$listener = new ErrorListener($logger);
$listener->onConsoleError(new ConsoleErrorEvent(new ArgvInput(array('console.php', 'test:run', '--foo=baz', 'buzz')), $this->getOutput(), $error, new Command('test:run')));
}
public function testOnConsoleErrorWithNoCommandAndNoInputString()
{
$error = new \RuntimeException('An error occurred');
$logger = $this->getLogger();
$logger
->expects($this->once())
->method('error')
->with('An error occurred while using the console. Message: "{message}"', array('exception' => $error, 'message' => 'An error occurred'))
;
$listener = new ErrorListener($logger);
$listener->onConsoleError(new ConsoleErrorEvent(new NonStringInput(), $this->getOutput(), $error));
}
public function testOnConsoleTerminateForNonZeroExitCodeWritesToLog()
{
$logger = $this->getLogger();
$logger
->expects($this->once())
->method('debug')
->with('Command "{command}" exited with code "{code}"', array('command' => 'test:run', 'code' => 255))
;
$listener = new ErrorListener($logger);
$listener->onConsoleTerminate($this->getConsoleTerminateEvent(new ArgvInput(array('console.php', 'test:run')), 255));
}
public function testOnConsoleTerminateForZeroExitCodeDoesNotWriteToLog()
{
$logger = $this->getLogger();
$logger
->expects($this->never())
->method('debug')
;
$listener = new ErrorListener($logger);
$listener->onConsoleTerminate($this->getConsoleTerminateEvent(new ArgvInput(array('console.php', 'test:run')), 0));
}
public function testGetSubscribedEvents()
{
$this->assertEquals(
array(
'console.error' => array('onConsoleError', -128),
'console.terminate' => array('onConsoleTerminate', -128),
),
ErrorListener::getSubscribedEvents()
);
}
public function testAllKindsOfInputCanBeLogged()
{
$logger = $this->getLogger();
$logger
->expects($this->exactly(3))
->method('debug')
->with('Command "{command}" exited with code "{code}"', array('command' => 'test:run --foo=bar', 'code' => 255))
;
$listener = new ErrorListener($logger);
$listener->onConsoleTerminate($this->getConsoleTerminateEvent(new ArgvInput(array('console.php', 'test:run', '--foo=bar')), 255));
$listener->onConsoleTerminate($this->getConsoleTerminateEvent(new ArrayInput(array('name' => 'test:run', '--foo' => 'bar')), 255));
$listener->onConsoleTerminate($this->getConsoleTerminateEvent(new StringInput('test:run --foo=bar'), 255));
}
public function testCommandNameIsDisplayedForNonStringableInput()
{
$logger = $this->getLogger();
$logger
->expects($this->once())
->method('debug')
->with('Command "{command}" exited with code "{code}"', array('command' => 'test:run', 'code' => 255))
;
$listener = new ErrorListener($logger);
$listener->onConsoleTerminate($this->getConsoleTerminateEvent($this->getMockBuilder(InputInterface::class)->getMock(), 255));
}
private function getLogger()
{
return $this->getMockForAbstractClass(LoggerInterface::class);
}
private function getConsoleTerminateEvent(InputInterface $input, $exitCode)
{
return new ConsoleTerminateEvent(new Command('test:run'), $input, $this->getOutput(), $exitCode);
}
private function getOutput()
{
return $this->getMockBuilder(OutputInterface::class)->getMock();
}
}
class NonStringInput extends Input
{
public function getFirstArgument()
{
}
public function hasParameterOption($values, $onlyParams = false)
{
}
public function getParameterOption($values, $default = false, $onlyParams = false)
{
}
public function parse()
{
}
}

View File

@@ -1,11 +0,0 @@
<?php
use Symfony\Component\Console\Command\Command;
class BarBucCommand extends Command
{
protected function configure()
{
$this->setName('bar:buc');
}
}

View File

@@ -1,26 +0,0 @@
<?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\Tests\Fixtures;
use Symfony\Component\Console\Application;
class DescriptorApplication2 extends Application
{
public function __construct()
{
parent::__construct('My Symfony application', 'v1.0');
$this->add(new DescriptorCommand1());
$this->add(new DescriptorCommand2());
$this->add(new DescriptorCommand3());
$this->add(new DescriptorCommand4());
}
}

View File

@@ -1,24 +0,0 @@
<?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\Tests\Fixtures;
use Symfony\Component\Console\Application;
class DescriptorApplicationMbString extends Application
{
public function __construct()
{
parent::__construct('MbString åpplicätion');
$this->add(new DescriptorCommandMbString());
}
}

View File

@@ -1,27 +0,0 @@
<?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\Tests\Fixtures;
use Symfony\Component\Console\Command\Command;
class DescriptorCommand1 extends Command
{
protected function configure()
{
$this
->setName('descriptor:command1')
->setAliases(array('alias1', 'alias2'))
->setDescription('command 1 description')
->setHelp('command 1 help')
;
}
}

View File

@@ -1,32 +0,0 @@
<?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\Tests\Fixtures;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
class DescriptorCommand2 extends Command
{
protected function configure()
{
$this
->setName('descriptor:command2')
->setDescription('command 2 description')
->setHelp('command 2 help')
->addUsage('-o|--option_name <argument_name>')
->addUsage('<argument_name>')
->addArgument('argument_name', InputArgument::REQUIRED)
->addOption('option_name', 'o', InputOption::VALUE_NONE)
;
}
}

View File

@@ -1,27 +0,0 @@
<?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\Tests\Fixtures;
use Symfony\Component\Console\Command\Command;
class DescriptorCommand3 extends Command
{
protected function configure()
{
$this
->setName('descriptor:command3')
->setDescription('command 3 description')
->setHelp('command 3 help')
->setHidden(true)
;
}
}

View File

@@ -1,25 +0,0 @@
<?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\Tests\Fixtures;
use Symfony\Component\Console\Command\Command;
class DescriptorCommand4 extends Command
{
protected function configure()
{
$this
->setName('descriptor:command4')
->setAliases(array('descriptor:alias_command4', 'command4:descriptor'))
;
}
}

View File

@@ -1,32 +0,0 @@
<?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\Tests\Fixtures;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
class DescriptorCommandMbString extends Command
{
protected function configure()
{
$this
->setName('descriptor:åèä')
->setDescription('command åèä description')
->setHelp('command åèä help')
->addUsage('-o|--option_name <argument_name>')
->addUsage('<argument_name>')
->addArgument('argument_åèä', InputArgument::REQUIRED)
->addOption('option_åèä', 'o', InputOption::VALUE_NONE)
;
}
}

View File

@@ -1,36 +0,0 @@
<?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\Tests\Fixtures;
use Symfony\Component\Console\Output\BufferedOutput;
/**
* Dummy output.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class DummyOutput extends BufferedOutput
{
/**
* @return array
*/
public function getLogs()
{
$logs = array();
foreach (explode(PHP_EOL, trim($this->fetch())) as $message) {
preg_match('/^\[(.*)\] (.*)/', $message, $matches);
$logs[] = sprintf('%s %s', $matches[1], $matches[2]);
}
return $logs;
}
}

View File

@@ -1,26 +0,0 @@
<?php
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class Foo1Command extends Command
{
public $input;
public $output;
protected function configure()
{
$this
->setName('foo:bar1')
->setDescription('The foo:bar1 command')
->setAliases(array('afoobar1'))
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->input = $input;
$this->output = $output;
}
}

View File

@@ -1,21 +0,0 @@
<?php
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class Foo2Command extends Command
{
protected function configure()
{
$this
->setName('foo1:bar')
->setDescription('The foo1:bar command')
->setAliases(array('afoobar2'))
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
}
}

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