Zur aktuellen Revision | Blame | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed
<?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;use Symfony\Component\Console\Command\Command;use Symfony\Component\Console\Command\CompleteCommand;use Symfony\Component\Console\Command\DumpCompletionCommand;use Symfony\Component\Console\Command\HelpCommand;use Symfony\Component\Console\Command\LazyCommand;use Symfony\Component\Console\Command\ListCommand;use Symfony\Component\Console\Command\SignalableCommandInterface;use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;use Symfony\Component\Console\Completion\CompletionInput;use Symfony\Component\Console\Completion\CompletionSuggestions;use Symfony\Component\Console\Completion\Suggestion;use Symfony\Component\Console\Event\ConsoleCommandEvent;use Symfony\Component\Console\Event\ConsoleErrorEvent;use Symfony\Component\Console\Event\ConsoleSignalEvent;use Symfony\Component\Console\Event\ConsoleTerminateEvent;use Symfony\Component\Console\Exception\CommandNotFoundException;use Symfony\Component\Console\Exception\ExceptionInterface;use Symfony\Component\Console\Exception\LogicException;use Symfony\Component\Console\Exception\NamespaceNotFoundException;use Symfony\Component\Console\Exception\RuntimeException;use Symfony\Component\Console\Formatter\OutputFormatter;use Symfony\Component\Console\Helper\DebugFormatterHelper;use Symfony\Component\Console\Helper\DescriptorHelper;use Symfony\Component\Console\Helper\FormatterHelper;use Symfony\Component\Console\Helper\Helper;use Symfony\Component\Console\Helper\HelperSet;use Symfony\Component\Console\Helper\ProcessHelper;use Symfony\Component\Console\Helper\QuestionHelper;use Symfony\Component\Console\Input\ArgvInput;use Symfony\Component\Console\Input\ArrayInput;use Symfony\Component\Console\Input\InputArgument;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\Output\ConsoleOutput;use Symfony\Component\Console\Output\ConsoleOutputInterface;use Symfony\Component\Console\Output\OutputInterface;use Symfony\Component\Console\SignalRegistry\SignalRegistry;use Symfony\Component\Console\Style\SymfonyStyle;use Symfony\Component\ErrorHandler\ErrorHandler;use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;use Symfony\Contracts\Service\ResetInterface;/*** An Application is the container for a collection of commands.** It is the main entry point of a Console application.** This class is optimized for a standard CLI environment.** Usage:** $app = new Application('myapp', '1.0 (stable)');* $app->add(new SimpleCommand());* $app->run();** @author Fabien Potencier <fabien@symfony.com>*/class Application implements ResetInterface{private array $commands = [];private bool $wantHelps = false;private ?Command $runningCommand = null;private string $name;private string $version;private ?CommandLoaderInterface $commandLoader = null;private bool $catchExceptions = true;private bool $autoExit = true;private InputDefinition $definition;private HelperSet $helperSet;private ?EventDispatcherInterface $dispatcher = null;private Terminal $terminal;private string $defaultCommand;private bool $singleCommand = false;private bool $initialized = false;private SignalRegistry $signalRegistry;private array $signalsToDispatchEvent = [];public function __construct(string $name = 'UNKNOWN', string $version = 'UNKNOWN'){$this->name = $name;$this->version = $version;$this->terminal = new Terminal();$this->defaultCommand = 'list';if (\defined('SIGINT') && SignalRegistry::isSupported()) {$this->signalRegistry = new SignalRegistry();$this->signalsToDispatchEvent = [\SIGINT, \SIGTERM, \SIGUSR1, \SIGUSR2];}}/*** @final*/public function setDispatcher(EventDispatcherInterface $dispatcher){$this->dispatcher = $dispatcher;}public function setCommandLoader(CommandLoaderInterface $commandLoader){$this->commandLoader = $commandLoader;}public function getSignalRegistry(): SignalRegistry{if (!$this->signalRegistry) {throw new RuntimeException('Signals are not supported. Make sure that the `pcntl` extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.');}return $this->signalRegistry;}public function setSignalsToDispatchEvent(int ...$signalsToDispatchEvent){$this->signalsToDispatchEvent = $signalsToDispatchEvent;}/*** Runs the current application.** @return int 0 if everything went fine, or an error code** @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}.*/public function run(InputInterface $input = null, OutputInterface $output = null): int{if (\function_exists('putenv')) {@putenv('LINES='.$this->terminal->getHeight());@putenv('COLUMNS='.$this->terminal->getWidth());}$input ??= new ArgvInput();$output ??= new ConsoleOutput();$renderException = function (\Throwable $e) use ($output) {if ($output instanceof ConsoleOutputInterface) {$this->renderThrowable($e, $output->getErrorOutput());} else {$this->renderThrowable($e, $output);}};if ($phpHandler = set_exception_handler($renderException)) {restore_exception_handler();if (!\is_array($phpHandler) || !$phpHandler[0] instanceof ErrorHandler) {$errorHandler = true;} elseif ($errorHandler = $phpHandler[0]->setExceptionHandler($renderException)) {$phpHandler[0]->setExceptionHandler($errorHandler);}}$this->configureIO($input, $output);try {$exitCode = $this->doRun($input, $output);} catch (\Exception $e) {if (!$this->catchExceptions) {throw $e;}$renderException($e);$exitCode = $e->getCode();if (is_numeric($exitCode)) {$exitCode = (int) $exitCode;if ($exitCode <= 0) {$exitCode = 1;}} else {$exitCode = 1;}} finally {// if the exception handler changed, keep it// otherwise, unregister $renderExceptionif (!$phpHandler) {if (set_exception_handler($renderException) === $renderException) {restore_exception_handler();}restore_exception_handler();} elseif (!$errorHandler) {$finalHandler = $phpHandler[0]->setExceptionHandler(null);if ($finalHandler !== $renderException) {$phpHandler[0]->setExceptionHandler($finalHandler);}}}if ($this->autoExit) {if ($exitCode > 255) {$exitCode = 255;}exit($exitCode);}return $exitCode;}/*** Runs the current application.** @return int 0 if everything went fine, or an error code*/public function doRun(InputInterface $input, OutputInterface $output){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) {// Errors must be ignored, full binding/validation happens later when the command is known.}$name = $this->getCommandName($input);if (true === $input->hasParameterOption(['--help', '-h'], true)) {if (!$name) {$name = 'help';$input = new ArrayInput(['command_name' => $this->defaultCommand]);} else {$this->wantHelps = true;}}if (!$name) {$name = $this->defaultCommand;$definition = $this->getDefinition();$definition->setArguments(array_merge($definition->getArguments(),['command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name),]));}try {$this->runningCommand = null;// the command name MUST be the first element of the input$command = $this->find($name);} catch (\Throwable $e) {if (($e instanceof CommandNotFoundException && !$e instanceof NamespaceNotFoundException) && 1 === \count($alternatives = $e->getAlternatives()) && $input->isInteractive()) {$alternative = $alternatives[0];$style = new SymfonyStyle($input, $output);$output->writeln('');$formattedBlock = (new FormatterHelper())->formatBlock(sprintf('Command "%s" is not defined.', $name), 'error', true);$output->writeln($formattedBlock);if (!$style->confirm(sprintf('Do you want to run "%s" instead? ', $alternative), false)) {if (null !== $this->dispatcher) {$event = new ConsoleErrorEvent($input, $output, $e);$this->dispatcher->dispatch($event, ConsoleEvents::ERROR);return $event->getExitCode();}return 1;}$command = $this->find($alternative);} else {if (null !== $this->dispatcher) {$event = new ConsoleErrorEvent($input, $output, $e);$this->dispatcher->dispatch($event, ConsoleEvents::ERROR);if (0 === $event->getExitCode()) {return 0;}$e = $event->getError();}try {if ($e instanceof CommandNotFoundException && $namespace = $this->findNamespace($name)) {$helper = new DescriptorHelper();$helper->describe($output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output, $this, ['format' => 'txt','raw_text' => false,'namespace' => $namespace,'short' => false,]);return isset($event) ? $event->getExitCode() : 1;}throw $e;} catch (NamespaceNotFoundException) {throw $e;}}}if ($command instanceof LazyCommand) {$command = $command->getCommand();}$this->runningCommand = $command;$exitCode = $this->doRunCommand($command, $input, $output);$this->runningCommand = null;return $exitCode;}public function reset(){}public function setHelperSet(HelperSet $helperSet){$this->helperSet = $helperSet;}/*** Get the helper set associated with the command.*/public function getHelperSet(): HelperSet{return $this->helperSet ??= $this->getDefaultHelperSet();}public function setDefinition(InputDefinition $definition){$this->definition = $definition;}/*** Gets the InputDefinition related to this Application.*/public function getDefinition(): InputDefinition{$this->definition ??= $this->getDefaultInputDefinition();if ($this->singleCommand) {$inputDefinition = $this->definition;$inputDefinition->setArguments();return $inputDefinition;}return $this->definition;}/*** Adds suggestions to $suggestions for the current completion input (e.g. option or argument).*/public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void{if (CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType()&& 'command' === $input->getCompletionName()) {foreach ($this->all() as $name => $command) {// skip hidden commands and aliased commands as they already get added belowif ($command->isHidden() || $command->getName() !== $name) {continue;}$suggestions->suggestValue(new Suggestion($command->getName(), $command->getDescription()));foreach ($command->getAliases() as $name) {$suggestions->suggestValue(new Suggestion($name, $command->getDescription()));}}return;}if (CompletionInput::TYPE_OPTION_NAME === $input->getCompletionType()) {$suggestions->suggestOptions($this->getDefinition()->getOptions());return;}}/*** Gets the help message.*/public function getHelp(): string{return $this->getLongVersion();}/*** Gets whether to catch exceptions or not during commands execution.*/public function areExceptionsCaught(): bool{return $this->catchExceptions;}/*** Sets whether to catch exceptions or not during commands execution.*/public function setCatchExceptions(bool $boolean){$this->catchExceptions = $boolean;}/*** Gets whether to automatically exit after a command execution or not.*/public function isAutoExitEnabled(): bool{return $this->autoExit;}/*** Sets whether to automatically exit after a command execution or not.*/public function setAutoExit(bool $boolean){$this->autoExit = $boolean;}/*** Gets the name of the application.*/public function getName(): string{return $this->name;}/*** Sets the application name.**/public function setName(string $name){$this->name = $name;}/*** Gets the application version.*/public function getVersion(): string{return $this->version;}/*** Sets the application version.*/public function setVersion(string $version){$this->version = $version;}/*** Returns the long version of the application.** @return string*/public function getLongVersion(){if ('UNKNOWN' !== $this->getName()) {if ('UNKNOWN' !== $this->getVersion()) {return sprintf('%s <info>%s</info>', $this->getName(), $this->getVersion());}return $this->getName();}return 'Console Tool';}/*** Registers a new command.*/public function register(string $name): Command{return $this->add(new Command($name));}/*** Adds an array of command objects.** If a Command is not enabled it will not be added.** @param Command[] $commands An array of commands*/public function addCommands(array $commands){foreach ($commands as $command) {$this->add($command);}}/*** Adds a command object.** If a command with the same name already exists, it will be overridden.* If the command is not enabled it will not be added.** @return Command|null*/public function add(Command $command){$this->init();$command->setApplication($this);if (!$command->isEnabled()) {$command->setApplication(null);return null;}if (!$command instanceof LazyCommand) {// 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_debug_type($command)));}$this->commands[$command->getName()] = $command;foreach ($command->getAliases() as $alias) {$this->commands[$alias] = $command;}return $command;}/*** Returns a registered command by name or alias.** @return Command** @throws CommandNotFoundException When given command name does not exist*/public function get(string $name){$this->init();if (!$this->has($name)) {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 levelif (!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) {$this->wantHelps = false;$helpCommand = $this->get('help');$helpCommand->setCommand($command);return $helpCommand;}return $command;}/*** Returns true if the command exists, false otherwise.*/public function has(string $name): bool{$this->init();return isset($this->commands[$name]) || ($this->commandLoader?->has($name) && $this->add($this->commandLoader->get($name)));}/*** Returns an array of all unique namespaces used by currently registered commands.** It does not return the global namespace which always exists.** @return string[]*/public function getNamespaces(): array{$namespaces = [];foreach ($this->all() as $command) {if ($command->isHidden()) {continue;}$namespaces[] = $this->extractAllNamespaces($command->getName());foreach ($command->getAliases() as $alias) {$namespaces[] = $this->extractAllNamespaces($alias);}}return array_values(array_unique(array_filter(array_merge([], ...$namespaces))));}/*** Finds a registered namespace by a name or an abbreviation.** @throws NamespaceNotFoundException When namespace is incorrect or ambiguous*/public function findNamespace(string $namespace): string{$allNamespaces = $this->getNamespaces();$expr = implode('[^:]*:', array_map('preg_quote', explode(':', $namespace))).'[^:]*';$namespaces = preg_grep('{^'.$expr.'}', $allNamespaces);if (empty($namespaces)) {$message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) {if (1 == \count($alternatives)) {$message .= "\n\nDid you mean this?\n ";} else {$message .= "\n\nDid you mean one of these?\n ";}$message .= implode("\n ", $alternatives);}throw new NamespaceNotFoundException($message, $alternatives);}$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));}return $exact ? $namespace : reset($namespaces);}/*** Finds a command by name or alias.** Contrary to get, this command tries to find the best* match if you give it an abbreviation of a name or alias.** @return Command** @throws CommandNotFoundException When command name is incorrect or ambiguous*/public function find(string $name){$this->init();$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 = implode('[^:]*:', array_map('preg_quote', explode(':', $name))).'[^:]*';$commands = preg_grep('{^'.$expr.'}', $allCommands);if (empty($commands)) {$commands = preg_grep('{^'.$expr.'}i', $allCommands);}// if no commands matched or we just matched namespacesif (empty($commands) || \count(preg_grep('{^'.$expr.'$}i', $commands)) < 1) {if (false !== $pos = strrpos($name, ':')) {// check if a namespace exists and contains commands$this->findNamespace(substr($name, 0, $pos));}$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 {$message .= "\n\nDid you mean one of these?\n ";}$message .= implode("\n ", $alternatives);}throw new CommandNotFoundException($message, array_values($alternatives));}// filter out aliases for commands which are already on the listif (\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) {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);}));}if (\count($commands) > 1) {$usableWidth = $this->terminal->getWidth() - 10;$abbrevs = array_values($commands);$maxLen = 0;foreach ($abbrevs as $abbrev) {$maxLen = max(Helper::width($abbrev), $maxLen);}$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::width($abbrev) > $usableWidth ? Helper::substr($abbrev, 0, $usableWidth - 3).'...' : $abbrev;}, 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));}}$command = $this->get(reset($commands));if ($command->isHidden()) {throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name));}return $command;}/*** Gets the commands (registered in the given namespace if provided).** The array keys are the full names and the values the command instances.** @return Command[]*/public function all(string $namespace = null){$this->init();if (null === $namespace) {if (!$this->commandLoader) {return $this->commands;}$commands = $this->commands;foreach ($this->commandLoader->getNames() as $name) {if (!isset($commands[$name]) && $this->has($name)) {$commands[$name] = $this->get($name);}}return $commands;}$commands = [];foreach ($this->commands as $name => $command) {if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) {$commands[$name] = $command;}}if ($this->commandLoader) {foreach ($this->commandLoader->getNames() as $name) {if (!isset($commands[$name]) && $namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1) && $this->has($name)) {$commands[$name] = $this->get($name);}}}return $commands;}/*** Returns an array of possible abbreviations given a set of names.** @return string[][]*/public static function getAbbreviations(array $names): array{$abbrevs = [];foreach ($names as $name) {for ($len = \strlen($name); $len > 0; --$len) {$abbrev = substr($name, 0, $len);$abbrevs[$abbrev][] = $name;}}return $abbrevs;}public function renderThrowable(\Throwable $e, OutputInterface $output): void{$output->writeln('', OutputInterface::VERBOSITY_QUIET);$this->doRenderThrowable($e, $output);if (null !== $this->runningCommand) {$output->writeln(sprintf('<info>%s</info>', OutputFormatter::escape(sprintf($this->runningCommand->getSynopsis(), $this->getName()))), OutputInterface::VERBOSITY_QUIET);$output->writeln('', OutputInterface::VERBOSITY_QUIET);}}protected function doRenderThrowable(\Throwable $e, OutputInterface $output): void{do {$message = trim($e->getMessage());if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {$class = get_debug_type($e);$title = sprintf(' [%s%s] ', $class, 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : '');$len = Helper::width($title);} else {$len = 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 = [];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::width($line) + 4;$lines[] = [$line, $lineLength];$len = max($lineLength, $len);}}$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')));}$messages[] = $emptyLine = sprintf('<error>%s</error>', str_repeat(' ', $len));if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {$messages[] = sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - Helper::width($title))));}foreach ($lines as $line) {$messages[] = sprintf('<error> %s %s</error>', OutputFormatter::escape($line[0]), str_repeat(' ', $len - $line[1]));}$messages[] = $emptyLine;$messages[] = '';$output->writeln($messages, OutputInterface::VERBOSITY_QUIET);if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {$output->writeln('<comment>Exception trace:</comment>', OutputInterface::VERBOSITY_QUIET);// exception related properties$trace = $e->getTrace();array_unshift($trace, ['function' => '','file' => $e->getFile() ?: 'n/a','line' => $e->getLine() ?: 'n/a','args' => [],]);for ($i = 0, $count = \count($trace); $i < $count; ++$i) {$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 at <info>%s:%s</info>', $class, $function ? $type.$function.'()' : '', $file, $line), OutputInterface::VERBOSITY_QUIET);}$output->writeln('', OutputInterface::VERBOSITY_QUIET);}} while ($e = $e->getPrevious());}/*** Configures the input and output instances based on the user arguments and options.*/protected function configureIO(InputInterface $input, OutputInterface $output){if (true === $input->hasParameterOption(['--ansi'], true)) {$output->setDecorated(true);} elseif (true === $input->hasParameterOption(['--no-ansi'], true)) {$output->setDecorated(false);}if (true === $input->hasParameterOption(['--no-interaction', '-n'], true)) {$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;}if (true === $input->hasParameterOption(['--quiet', '-q'], true)) {$output->setVerbosity(OutputInterface::VERBOSITY_QUIET);$shellVerbosity = -1;} else {if ($input->hasParameterOption('-vvv', true) || $input->hasParameterOption('--verbose=3', true) || 3 === $input->getParameterOption('--verbose', false, true)) {$output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);$shellVerbosity = 3;} elseif ($input->hasParameterOption('-vv', true) || $input->hasParameterOption('--verbose=2', true) || 2 === $input->getParameterOption('--verbose', false, true)) {$output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);$shellVerbosity = 2;} elseif ($input->hasParameterOption('-v', true) || $input->hasParameterOption('--verbose=1', true) || $input->hasParameterOption('--verbose', true) || $input->getParameterOption('--verbose', false, true)) {$output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);$shellVerbosity = 1;}}if (-1 === $shellVerbosity) {$input->setInteractive(false);}if (\function_exists('putenv')) {@putenv('SHELL_VERBOSITY='.$shellVerbosity);}$_ENV['SHELL_VERBOSITY'] = $shellVerbosity;$_SERVER['SHELL_VERBOSITY'] = $shellVerbosity;}/*** Runs the current command.** If an event dispatcher has been attached to the application,* events are also dispatched during the life-cycle of the command.** @return int 0 if everything went fine, or an error code*/protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output){foreach ($command->getHelperSet() as $helper) {if ($helper instanceof InputAwareInterface) {$helper->setInput($input);}}if ($this->signalsToDispatchEvent) {$commandSignals = $command instanceof SignalableCommandInterface ? $command->getSubscribedSignals() : [];if ($commandSignals || null !== $this->dispatcher) {if (!$this->signalRegistry) {throw new RuntimeException('Unable to subscribe to signal events. Make sure that the `pcntl` extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.');}if (Terminal::hasSttyAvailable()) {$sttyMode = shell_exec('stty -g');foreach ([\SIGINT, \SIGTERM] as $signal) {$this->signalRegistry->register($signal, static function () use ($sttyMode) {shell_exec('stty '.$sttyMode);});}}}if (null !== $this->dispatcher) {foreach ($this->signalsToDispatchEvent as $signal) {$event = new ConsoleSignalEvent($command, $input, $output, $signal);$this->signalRegistry->register($signal, function ($signal, $hasNext) use ($event) {$this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL);// No more handlers, we try to simulate PHP default behaviorif (!$hasNext) {if (!\in_array($signal, [\SIGUSR1, \SIGUSR2], true)) {exit(0);}}});}}foreach ($commandSignals as $signal) {$this->signalRegistry->register($signal, [$command, 'handleSignal']);}}if (null === $this->dispatcher) {return $command->run($input, $output);}// bind before the console.command event, so the listeners have access to input options/argumentstry {$command->mergeApplicationDefinition();$input->bind($command->getDefinition());} catch (ExceptionInterface) {// ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition}$event = new ConsoleCommandEvent($command, $input, $output);$e = null;try {$this->dispatcher->dispatch($event, ConsoleEvents::COMMAND);if ($event->commandShouldRun()) {$exitCode = $command->run($input, $output);} else {$exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED;}} catch (\Throwable $e) {$event = new ConsoleErrorEvent($input, $output, $e, $command);$this->dispatcher->dispatch($event, ConsoleEvents::ERROR);$e = $event->getError();if (0 === $exitCode = $event->getExitCode()) {$e = null;}}$event = new ConsoleTerminateEvent($command, $input, $output, $exitCode);$this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE);if (null !== $e) {throw $e;}return $event->getExitCode();}/*** Gets the name of the command based on input.*/protected function getCommandName(InputInterface $input): ?string{return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument();}/*** Gets the default input definition.*/protected function getDefaultInputDefinition(): InputDefinition{return new InputDefinition([new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display help for the given command. When no command is given display help for the <info>'.$this->defaultCommand.'</info> command'),new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'),new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'),new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'),new InputOption('--ansi', '', InputOption::VALUE_NEGATABLE, 'Force (or disable --no-ansi) ANSI output', null),new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'),]);}/*** Gets the default commands that should always be available.** @return Command[]*/protected function getDefaultCommands(): array{return [new HelpCommand(), new ListCommand(), new CompleteCommand(), new DumpCompletionCommand()];}/*** Gets the default helper set with the helpers that should always be available.*/protected function getDefaultHelperSet(): HelperSet{return new HelperSet([new FormatterHelper(),new DebugFormatterHelper(),new ProcessHelper(),new QuestionHelper(),]);}/*** Returns abbreviated suggestions in string format.*/private function getAbbreviationSuggestions(array $abbrevs): string{return ' '.implode("\n ", $abbrevs);}/*** Returns the namespace part of the command name.** This method is not part of public API and should not be used directly.*/public function extractNamespace(string $name, int $limit = null): string{$parts = explode(':', $name, -1);return implode(':', null === $limit ? $parts : \array_slice($parts, 0, $limit));}/*** Finds alternative of $name among $collection,* if nothing is found in $collection, try in $abbrevs.** @return string[]*/private function findAlternatives(string $name, iterable $collection): array{$threshold = 1e3;$alternatives = [];$collectionParts = [];foreach ($collection as $item) {$collectionParts[$item] = explode(':', $item);}foreach (explode(':', $name) as $i => $subname) {foreach ($collectionParts as $collectionName => $parts) {$exists = isset($alternatives[$collectionName]);if (!isset($parts[$i]) && $exists) {$alternatives[$collectionName] += $threshold;continue;} elseif (!isset($parts[$i])) {continue;}$lev = levenshtein($subname, $parts[$i]);if ($lev <= \strlen($subname) / 3 || '' !== $subname && str_contains($parts[$i], $subname)) {$alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev;} elseif ($exists) {$alternatives[$collectionName] += $threshold;}}}foreach ($collection as $item) {$lev = levenshtein($name, $item);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);return array_keys($alternatives);}/*** Sets the default Command name.** @return $this*/public function setDefaultCommand(string $commandName, bool $isSingleCommand = false): static{$this->defaultCommand = explode('|', ltrim($commandName, '|'))[0];if ($isSingleCommand) {// Ensure the command exist$this->find($commandName);$this->singleCommand = true;}return $this;}/*** @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.// we need a function to split string not by character count but by string widthif (false === $encoding = mb_detect_encoding($string, null, true)) {return str_split($string, $width);}$utf8String = mb_convert_encoding($string, 'utf8', $encoding);$lines = [];$line = '';$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 lineif (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;}}$lines[] = \count($lines) ? str_pad($line, $width) : $line;mb_convert_variables($encoding, 'utf8', $lines);return $lines;}/*** Returns all namespaces of the command name.** @return string[]*/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 = [];foreach ($parts as $part) {if (\count($namespaces)) {$namespaces[] = end($namespaces).':'.$part;} else {$namespaces[] = $part;}}return $namespaces;}private function init(){if ($this->initialized) {return;}$this->initialized = true;foreach ($this->getDefaultCommands() as $command) {$this->add($command);}}}