PluginProbe
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.13.0
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.13.0
5.13.0 5.12.1 5.12.0 5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 All 83 releases
matomo / app / vendor / prefixed / symfony / console / Application.php
Application.php
1,082 lines 44.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11 namespace Matomo\Dependencies\Symfony\Component\Console;
12
13 use Matomo\Dependencies\Symfony\Component\Console\Command\Command;
14 use Matomo\Dependencies\Symfony\Component\Console\Command\CompleteCommand;
15 use Matomo\Dependencies\Symfony\Component\Console\Command\DumpCompletionCommand;
16 use Matomo\Dependencies\Symfony\Component\Console\Command\HelpCommand;
17 use Matomo\Dependencies\Symfony\Component\Console\Command\LazyCommand;
18 use Matomo\Dependencies\Symfony\Component\Console\Command\ListCommand;
19 use Matomo\Dependencies\Symfony\Component\Console\Command\SignalableCommandInterface;
20 use Matomo\Dependencies\Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
21 use Matomo\Dependencies\Symfony\Component\Console\Completion\CompletionInput;
22 use Matomo\Dependencies\Symfony\Component\Console\Completion\CompletionSuggestions;
23 use Matomo\Dependencies\Symfony\Component\Console\Event\ConsoleCommandEvent;
24 use Matomo\Dependencies\Symfony\Component\Console\Event\ConsoleErrorEvent;
25 use Matomo\Dependencies\Symfony\Component\Console\Event\ConsoleSignalEvent;
26 use Matomo\Dependencies\Symfony\Component\Console\Event\ConsoleTerminateEvent;
27 use Matomo\Dependencies\Symfony\Component\Console\Exception\CommandNotFoundException;
28 use Matomo\Dependencies\Symfony\Component\Console\Exception\ExceptionInterface;
29 use Matomo\Dependencies\Symfony\Component\Console\Exception\LogicException;
30 use Matomo\Dependencies\Symfony\Component\Console\Exception\NamespaceNotFoundException;
31 use Matomo\Dependencies\Symfony\Component\Console\Exception\RuntimeException;
32 use Matomo\Dependencies\Symfony\Component\Console\Formatter\OutputFormatter;
33 use Matomo\Dependencies\Symfony\Component\Console\Helper\DebugFormatterHelper;
34 use Matomo\Dependencies\Symfony\Component\Console\Helper\FormatterHelper;
35 use Matomo\Dependencies\Symfony\Component\Console\Helper\Helper;
36 use Matomo\Dependencies\Symfony\Component\Console\Helper\HelperSet;
37 use Matomo\Dependencies\Symfony\Component\Console\Helper\ProcessHelper;
38 use Matomo\Dependencies\Symfony\Component\Console\Helper\QuestionHelper;
39 use Matomo\Dependencies\Symfony\Component\Console\Input\ArgvInput;
40 use Matomo\Dependencies\Symfony\Component\Console\Input\ArrayInput;
41 use Matomo\Dependencies\Symfony\Component\Console\Input\InputArgument;
42 use Matomo\Dependencies\Symfony\Component\Console\Input\InputAwareInterface;
43 use Matomo\Dependencies\Symfony\Component\Console\Input\InputDefinition;
44 use Matomo\Dependencies\Symfony\Component\Console\Input\InputInterface;
45 use Matomo\Dependencies\Symfony\Component\Console\Input\InputOption;
46 use Matomo\Dependencies\Symfony\Component\Console\Output\ConsoleOutput;
47 use Matomo\Dependencies\Symfony\Component\Console\Output\ConsoleOutputInterface;
48 use Matomo\Dependencies\Symfony\Component\Console\Output\OutputInterface;
49 use Matomo\Dependencies\Symfony\Component\Console\SignalRegistry\SignalRegistry;
50 use Matomo\Dependencies\Symfony\Component\Console\Style\SymfonyStyle;
51 use Matomo\Dependencies\Symfony\Component\ErrorHandler\ErrorHandler;
52 use Matomo\Dependencies\Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
53 use Matomo\Dependencies\Symfony\Contracts\Service\ResetInterface;
54 /**
55 * An Application is the container for a collection of commands.
56 *
57 * It is the main entry point of a Console application.
58 *
59 * This class is optimized for a standard CLI environment.
60 *
61 * Usage:
62 *
63 * $app = new Application('myapp', '1.0 (stable)');
64 * $app->add(new SimpleCommand());
65 * $app->run();
66 *
67 * @author Fabien Potencier <fabien@symfony.com>
68 */
69 class Application implements ResetInterface
70 {
71 private $commands = [];
72 private $wantHelps = \false;
73 private $runningCommand;
74 private $name;
75 private $version;
76 private $commandLoader;
77 private $catchExceptions = \true;
78 private $autoExit = \true;
79 private $definition;
80 private $helperSet;
81 private $dispatcher;
82 private $terminal;
83 private $defaultCommand;
84 private $singleCommand = \false;
85 private $initialized;
86 private $signalRegistry;
87 private $signalsToDispatchEvent = [];
88 public function __construct(string $name = 'UNKNOWN', string $version = 'UNKNOWN')
89 {
90 $this->name = $name;
91 $this->version = $version;
92 $this->terminal = new Terminal();
93 $this->defaultCommand = 'list';
94 if (\defined('SIGINT') && SignalRegistry::isSupported()) {
95 $this->signalRegistry = new SignalRegistry();
96 $this->signalsToDispatchEvent = [\SIGINT, \SIGTERM, \SIGUSR1, \SIGUSR2];
97 }
98 }
99 /**
100 * @final
101 */
102 public function setDispatcher(EventDispatcherInterface $dispatcher)
103 {
104 $this->dispatcher = $dispatcher;
105 }
106 public function setCommandLoader(CommandLoaderInterface $commandLoader)
107 {
108 $this->commandLoader = $commandLoader;
109 }
110 public function getSignalRegistry() : SignalRegistry
111 {
112 if (!$this->signalRegistry) {
113 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.');
114 }
115 return $this->signalRegistry;
116 }
117 public function setSignalsToDispatchEvent(int ...$signalsToDispatchEvent)
118 {
119 $this->signalsToDispatchEvent = $signalsToDispatchEvent;
120 }
121 /**
122 * Runs the current application.
123 *
124 * @return int 0 if everything went fine, or an error code
125 *
126 * @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}.
127 */
128 public function run(?InputInterface $input = null, ?OutputInterface $output = null)
129 {
130 if (\function_exists('putenv')) {
131 @putenv('LINES=' . $this->terminal->getHeight());
132 @putenv('COLUMNS=' . $this->terminal->getWidth());
133 }
134 if (null === $input) {
135 $input = new ArgvInput();
136 }
137 if (null === $output) {
138 $output = new ConsoleOutput();
139 }
140 $renderException = function (\Throwable $e) use($output) {
141 if ($output instanceof ConsoleOutputInterface) {
142 $this->renderThrowable($e, $output->getErrorOutput());
143 } else {
144 $this->renderThrowable($e, $output);
145 }
146 };
147 if ($phpHandler = set_exception_handler($renderException)) {
148 restore_exception_handler();
149 if (!\is_array($phpHandler) || !$phpHandler[0] instanceof ErrorHandler) {
150 $errorHandler = \true;
151 } elseif ($errorHandler = $phpHandler[0]->setExceptionHandler($renderException)) {
152 $phpHandler[0]->setExceptionHandler($errorHandler);
153 }
154 }
155 try {
156 $this->configureIO($input, $output);
157 $exitCode = $this->doRun($input, $output);
158 } catch (\Exception $e) {
159 if (!$this->catchExceptions) {
160 throw $e;
161 }
162 $renderException($e);
163 $exitCode = $e->getCode();
164 if (is_numeric($exitCode)) {
165 $exitCode = (int) $exitCode;
166 if ($exitCode <= 0) {
167 $exitCode = 1;
168 }
169 } else {
170 $exitCode = 1;
171 }
172 } finally {
173 // if the exception handler changed, keep it
174 // otherwise, unregister $renderException
175 if (!$phpHandler) {
176 if (set_exception_handler($renderException) === $renderException) {
177 restore_exception_handler();
178 }
179 restore_exception_handler();
180 } elseif (!$errorHandler) {
181 $finalHandler = $phpHandler[0]->setExceptionHandler(null);
182 if ($finalHandler !== $renderException) {
183 $phpHandler[0]->setExceptionHandler($finalHandler);
184 }
185 }
186 }
187 if ($this->autoExit) {
188 if ($exitCode > 255) {
189 $exitCode = 255;
190 }
191 exit($exitCode);
192 }
193 return $exitCode;
194 }
195 /**
196 * Runs the current application.
197 *
198 * @return int 0 if everything went fine, or an error code
199 */
200 public function doRun(InputInterface $input, OutputInterface $output)
201 {
202 if (\true === $input->hasParameterOption(['--version', '-V'], \true)) {
203 $output->writeln($this->getLongVersion());
204 return 0;
205 }
206 try {
207 // Makes ArgvInput::getFirstArgument() able to distinguish an option from an argument.
208 $input->bind($this->getDefinition());
209 } catch (ExceptionInterface $e) {
210 // Errors must be ignored, full binding/validation happens later when the command is known.
211 }
212 $name = $this->getCommandName($input);
213 if (\true === $input->hasParameterOption(['--help', '-h'], \true)) {
214 if (!$name) {
215 $name = 'help';
216 $input = new ArrayInput(['command_name' => $this->defaultCommand]);
217 } else {
218 $this->wantHelps = \true;
219 }
220 }
221 if (!$name) {
222 $name = $this->defaultCommand;
223 $definition = $this->getDefinition();
224 $definition->setArguments(array_merge($definition->getArguments(), ['command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name)]));
225 }
226 try {
227 $this->runningCommand = null;
228 // the command name MUST be the first element of the input
229 $command = $this->find($name);
230 } catch (\Throwable $e) {
231 if (!($e instanceof CommandNotFoundException && !$e instanceof NamespaceNotFoundException) || 1 !== \count($alternatives = $e->getAlternatives()) || !$input->isInteractive()) {
232 if (null !== $this->dispatcher) {
233 $event = new ConsoleErrorEvent($input, $output, $e);
234 $this->dispatcher->dispatch($event, ConsoleEvents::ERROR);
235 if (0 === $event->getExitCode()) {
236 return 0;
237 }
238 $e = $event->getError();
239 }
240 throw $e;
241 }
242 $alternative = $alternatives[0];
243 $style = new SymfonyStyle($input, $output);
244 $output->writeln('');
245 $formattedBlock = (new FormatterHelper())->formatBlock(sprintf('Command "%s" is not defined.', $name), 'error', \true);
246 $output->writeln($formattedBlock);
247 if (!$style->confirm(sprintf('Do you want to run "%s" instead? ', $alternative), \false)) {
248 if (null !== $this->dispatcher) {
249 $event = new ConsoleErrorEvent($input, $output, $e);
250 $this->dispatcher->dispatch($event, ConsoleEvents::ERROR);
251 return $event->getExitCode();
252 }
253 return 1;
254 }
255 $command = $this->find($alternative);
256 }
257 if ($command instanceof LazyCommand) {
258 $command = $command->getCommand();
259 }
260 $this->runningCommand = $command;
261 $exitCode = $this->doRunCommand($command, $input, $output);
262 $this->runningCommand = null;
263 return $exitCode;
264 }
265 /**
266 * {@inheritdoc}
267 */
268 public function reset()
269 {
270 }
271 public function setHelperSet(HelperSet $helperSet)
272 {
273 $this->helperSet = $helperSet;
274 }
275 /**
276 * Get the helper set associated with the command.
277 *
278 * @return HelperSet
279 */
280 public function getHelperSet()
281 {
282 if (!$this->helperSet) {
283 $this->helperSet = $this->getDefaultHelperSet();
284 }
285 return $this->helperSet;
286 }
287 public function setDefinition(InputDefinition $definition)
288 {
289 $this->definition = $definition;
290 }
291 /**
292 * Gets the InputDefinition related to this Application.
293 *
294 * @return InputDefinition
295 */
296 public function getDefinition()
297 {
298 if (!$this->definition) {
299 $this->definition = $this->getDefaultInputDefinition();
300 }
301 if ($this->singleCommand) {
302 $inputDefinition = $this->definition;
303 $inputDefinition->setArguments();
304 return $inputDefinition;
305 }
306 return $this->definition;
307 }
308 /**
309 * Adds suggestions to $suggestions for the current completion input (e.g. option or argument).
310 */
311 public function complete(CompletionInput $input, CompletionSuggestions $suggestions) : void
312 {
313 if (CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType() && 'command' === $input->getCompletionName()) {
314 $commandNames = [];
315 foreach ($this->all() as $name => $command) {
316 // skip hidden commands and aliased commands as they already get added below
317 if ($command->isHidden() || $command->getName() !== $name) {
318 continue;
319 }
320 $commandNames[] = $command->getName();
321 foreach ($command->getAliases() as $name) {
322 $commandNames[] = $name;
323 }
324 }
325 $suggestions->suggestValues(array_filter($commandNames));
326 return;
327 }
328 if (CompletionInput::TYPE_OPTION_NAME === $input->getCompletionType()) {
329 $suggestions->suggestOptions($this->getDefinition()->getOptions());
330 return;
331 }
332 }
333 /**
334 * Gets the help message.
335 *
336 * @return string
337 */
338 public function getHelp()
339 {
340 return $this->getLongVersion();
341 }
342 /**
343 * Gets whether to catch exceptions or not during commands execution.
344 *
345 * @return bool
346 */
347 public function areExceptionsCaught()
348 {
349 return $this->catchExceptions;
350 }
351 /**
352 * Sets whether to catch exceptions or not during commands execution.
353 */
354 public function setCatchExceptions(bool $boolean)
355 {
356 $this->catchExceptions = $boolean;
357 }
358 /**
359 * Gets whether to automatically exit after a command execution or not.
360 *
361 * @return bool
362 */
363 public function isAutoExitEnabled()
364 {
365 return $this->autoExit;
366 }
367 /**
368 * Sets whether to automatically exit after a command execution or not.
369 */
370 public function setAutoExit(bool $boolean)
371 {
372 $this->autoExit = $boolean;
373 }
374 /**
375 * Gets the name of the application.
376 *
377 * @return string
378 */
379 public function getName()
380 {
381 return $this->name;
382 }
383 /**
384 * Sets the application name.
385 **/
386 public function setName(string $name)
387 {
388 $this->name = $name;
389 }
390 /**
391 * Gets the application version.
392 *
393 * @return string
394 */
395 public function getVersion()
396 {
397 return $this->version;
398 }
399 /**
400 * Sets the application version.
401 */
402 public function setVersion(string $version)
403 {
404 $this->version = $version;
405 }
406 /**
407 * Returns the long version of the application.
408 *
409 * @return string
410 */
411 public function getLongVersion()
412 {
413 if ('UNKNOWN' !== $this->getName()) {
414 if ('UNKNOWN' !== $this->getVersion()) {
415 return sprintf('%s <info>%s</info>', $this->getName(), $this->getVersion());
416 }
417 return $this->getName();
418 }
419 return 'Console Tool';
420 }
421 /**
422 * Registers a new command.
423 *
424 * @return Command
425 */
426 public function register(string $name)
427 {
428 return $this->add(new Command($name));
429 }
430 /**
431 * Adds an array of command objects.
432 *
433 * If a Command is not enabled it will not be added.
434 *
435 * @param Command[] $commands An array of commands
436 */
437 public function addCommands(array $commands)
438 {
439 foreach ($commands as $command) {
440 $this->add($command);
441 }
442 }
443 /**
444 * Adds a command object.
445 *
446 * If a command with the same name already exists, it will be overridden.
447 * If the command is not enabled it will not be added.
448 *
449 * @return Command|null
450 */
451 public function add(Command $command)
452 {
453 $this->init();
454 $command->setApplication($this);
455 if (!$command->isEnabled()) {
456 $command->setApplication(null);
457 return null;
458 }
459 if (!$command instanceof LazyCommand) {
460 // Will throw if the command is not correctly initialized.
461 $command->getDefinition();
462 }
463 if (!$command->getName()) {
464 throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_debug_type($command)));
465 }
466 $this->commands[$command->getName()] = $command;
467 foreach ($command->getAliases() as $alias) {
468 $this->commands[$alias] = $command;
469 }
470 return $command;
471 }
472 /**
473 * Returns a registered command by name or alias.
474 *
475 * @return Command
476 *
477 * @throws CommandNotFoundException When given command name does not exist
478 */
479 public function get(string $name)
480 {
481 $this->init();
482 if (!$this->has($name)) {
483 throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name));
484 }
485 // When the command has a different name than the one used at the command loader level
486 if (!isset($this->commands[$name])) {
487 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));
488 }
489 $command = $this->commands[$name];
490 if ($this->wantHelps) {
491 $this->wantHelps = \false;
492 $helpCommand = $this->get('help');
493 $helpCommand->setCommand($command);
494 return $helpCommand;
495 }
496 return $command;
497 }
498 /**
499 * Returns true if the command exists, false otherwise.
500 *
501 * @return bool
502 */
503 public function has(string $name)
504 {
505 $this->init();
506 return isset($this->commands[$name]) || $this->commandLoader && $this->commandLoader->has($name) && $this->add($this->commandLoader->get($name));
507 }
508 /**
509 * Returns an array of all unique namespaces used by currently registered commands.
510 *
511 * It does not return the global namespace which always exists.
512 *
513 * @return string[]
514 */
515 public function getNamespaces()
516 {
517 $namespaces = [];
518 foreach ($this->all() as $command) {
519 if ($command->isHidden()) {
520 continue;
521 }
522 $namespaces[] = $this->extractAllNamespaces($command->getName());
523 foreach ($command->getAliases() as $alias) {
524 $namespaces[] = $this->extractAllNamespaces($alias);
525 }
526 }
527 return array_values(array_unique(array_filter(array_merge([], ...$namespaces))));
528 }
529 /**
530 * Finds a registered namespace by a name or an abbreviation.
531 *
532 * @return string
533 *
534 * @throws NamespaceNotFoundException When namespace is incorrect or ambiguous
535 */
536 public function findNamespace(string $namespace)
537 {
538 $allNamespaces = $this->getNamespaces();
539 $expr = implode('[^:]*:', array_map('preg_quote', explode(':', $namespace))) . '[^:]*';
540 $namespaces = preg_grep('{^' . $expr . '}', $allNamespaces);
541 if (empty($namespaces)) {
542 $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);
543 if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) {
544 if (1 == \count($alternatives)) {
545 $message .= "\n\nDid you mean this?\n ";
546 } else {
547 $message .= "\n\nDid you mean one of these?\n ";
548 }
549 $message .= implode("\n ", $alternatives);
550 }
551 throw new NamespaceNotFoundException($message, $alternatives);
552 }
553 $exact = \in_array($namespace, $namespaces, \true);
554 if (\count($namespaces) > 1 && !$exact) {
555 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));
556 }
557 return $exact ? $namespace : reset($namespaces);
558 }
559 /**
560 * Finds a command by name or alias.
561 *
562 * Contrary to get, this command tries to find the best
563 * match if you give it an abbreviation of a name or alias.
564 *
565 * @return Command
566 *
567 * @throws CommandNotFoundException When command name is incorrect or ambiguous
568 */
569 public function find(string $name)
570 {
571 $this->init();
572 $aliases = [];
573 foreach ($this->commands as $command) {
574 foreach ($command->getAliases() as $alias) {
575 if (!$this->has($alias)) {
576 $this->commands[$alias] = $command;
577 }
578 }
579 }
580 if ($this->has($name)) {
581 return $this->get($name);
582 }
583 $allCommands = $this->commandLoader ? array_merge($this->commandLoader->getNames(), array_keys($this->commands)) : array_keys($this->commands);
584 $expr = implode('[^:]*:', array_map('preg_quote', explode(':', $name))) . '[^:]*';
585 $commands = preg_grep('{^' . $expr . '}', $allCommands);
586 if (empty($commands)) {
587 $commands = preg_grep('{^' . $expr . '}i', $allCommands);
588 }
589 // if no commands matched or we just matched namespaces
590 if (empty($commands) || \count(preg_grep('{^' . $expr . '$}i', $commands)) < 1) {
591 if (\false !== ($pos = strrpos($name, ':'))) {
592 // check if a namespace exists and contains commands
593 $this->findNamespace(substr($name, 0, $pos));
594 }
595 $message = sprintf('Command "%s" is not defined.', $name);
596 if ($alternatives = $this->findAlternatives($name, $allCommands)) {
597 // remove hidden commands
598 $alternatives = array_filter($alternatives, function ($name) {
599 return !$this->get($name)->isHidden();
600 });
601 if (1 == \count($alternatives)) {
602 $message .= "\n\nDid you mean this?\n ";
603 } else {
604 $message .= "\n\nDid you mean one of these?\n ";
605 }
606 $message .= implode("\n ", $alternatives);
607 }
608 throw new CommandNotFoundException($message, array_values($alternatives));
609 }
610 // filter out aliases for commands which are already on the list
611 if (\count($commands) > 1) {
612 $commandList = $this->commandLoader ? array_merge(array_flip($this->commandLoader->getNames()), $this->commands) : $this->commands;
613 $commands = array_unique(array_filter($commands, function ($nameOrAlias) use(&$commandList, $commands, &$aliases) {
614 if (!$commandList[$nameOrAlias] instanceof Command) {
615 $commandList[$nameOrAlias] = $this->commandLoader->get($nameOrAlias);
616 }
617 $commandName = $commandList[$nameOrAlias]->getName();
618 $aliases[$nameOrAlias] = $commandName;
619 return $commandName === $nameOrAlias || !\in_array($commandName, $commands);
620 }));
621 }
622 if (\count($commands) > 1) {
623 $usableWidth = $this->terminal->getWidth() - 10;
624 $abbrevs = array_values($commands);
625 $maxLen = 0;
626 foreach ($abbrevs as $abbrev) {
627 $maxLen = max(Helper::width($abbrev), $maxLen);
628 }
629 $abbrevs = array_map(function ($cmd) use($commandList, $usableWidth, $maxLen, &$commands) {
630 if ($commandList[$cmd]->isHidden()) {
631 unset($commands[array_search($cmd, $commands)]);
632 return \false;
633 }
634 $abbrev = str_pad($cmd, $maxLen, ' ') . ' ' . $commandList[$cmd]->getDescription();
635 return Helper::width($abbrev) > $usableWidth ? Helper::substr($abbrev, 0, $usableWidth - 3) . '...' : $abbrev;
636 }, array_values($commands));
637 if (\count($commands) > 1) {
638 $suggestions = $this->getAbbreviationSuggestions(array_filter($abbrevs));
639 throw new CommandNotFoundException(sprintf("Command \"%s\" is ambiguous.\nDid you mean one of these?\n%s.", $name, $suggestions), array_values($commands));
640 }
641 }
642 $command = $this->get(reset($commands));
643 if ($command->isHidden()) {
644 throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name));
645 }
646 return $command;
647 }
648 /**
649 * Gets the commands (registered in the given namespace if provided).
650 *
651 * The array keys are the full names and the values the command instances.
652 *
653 * @return Command[]
654 */
655 public function all(?string $namespace = null)
656 {
657 $this->init();
658 if (null === $namespace) {
659 if (!$this->commandLoader) {
660 return $this->commands;
661 }
662 $commands = $this->commands;
663 foreach ($this->commandLoader->getNames() as $name) {
664 if (!isset($commands[$name]) && $this->has($name)) {
665 $commands[$name] = $this->get($name);
666 }
667 }
668 return $commands;
669 }
670 $commands = [];
671 foreach ($this->commands as $name => $command) {
672 if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) {
673 $commands[$name] = $command;
674 }
675 }
676 if ($this->commandLoader) {
677 foreach ($this->commandLoader->getNames() as $name) {
678 if (!isset($commands[$name]) && $namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1) && $this->has($name)) {
679 $commands[$name] = $this->get($name);
680 }
681 }
682 }
683 return $commands;
684 }
685 /**
686 * Returns an array of possible abbreviations given a set of names.
687 *
688 * @return string[][]
689 */
690 public static function getAbbreviations(array $names)
691 {
692 $abbrevs = [];
693 foreach ($names as $name) {
694 for ($len = \strlen($name); $len > 0; --$len) {
695 $abbrev = substr($name, 0, $len);
696 $abbrevs[$abbrev][] = $name;
697 }
698 }
699 return $abbrevs;
700 }
701 public function renderThrowable(\Throwable $e, OutputInterface $output) : void
702 {
703 $output->writeln('', OutputInterface::VERBOSITY_QUIET);
704 $this->doRenderThrowable($e, $output);
705 if (null !== $this->runningCommand) {
706 $output->writeln(sprintf('<info>%s</info>', OutputFormatter::escape(sprintf($this->runningCommand->getSynopsis(), $this->getName()))), OutputInterface::VERBOSITY_QUIET);
707 $output->writeln('', OutputInterface::VERBOSITY_QUIET);
708 }
709 }
710 protected function doRenderThrowable(\Throwable $e, OutputInterface $output) : void
711 {
712 do {
713 $message = trim($e->getMessage());
714 if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
715 $class = get_debug_type($e);
716 $title = sprintf(' [%s%s] ', $class, 0 !== ($code = $e->getCode()) ? ' (' . $code . ')' : '');
717 $len = Helper::width($title);
718 } else {
719 $len = 0;
720 }
721 if (str_contains($message, "@anonymous\x00")) {
722 $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) {
723 return class_exists($m[0], \false) ? ((get_parent_class($m[0]) ?: key(class_implements($m[0]))) ?: 'class') . '@anonymous' : $m[0];
724 }, $message);
725 }
726 $width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : \PHP_INT_MAX;
727 $lines = [];
728 foreach ('' !== $message ? preg_split('/\\r?\\n/', $message) : [] as $line) {
729 foreach ($this->splitStringByWidth($line, $width - 4) as $line) {
730 // pre-format lines to get the right string length
731 $lineLength = Helper::width($line) + 4;
732 $lines[] = [$line, $lineLength];
733 $len = max($lineLength, $len);
734 }
735 }
736 $messages = [];
737 if (!$e instanceof ExceptionInterface || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
738 $messages[] = sprintf('<comment>%s</comment>', OutputFormatter::escape(sprintf('In %s line %s:', basename($e->getFile()) ?: 'n/a', $e->getLine() ?: 'n/a')));
739 }
740 $messages[] = $emptyLine = sprintf('<error>%s</error>', str_repeat(' ', $len));
741 if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
742 $messages[] = sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - Helper::width($title))));
743 }
744 foreach ($lines as $line) {
745 $messages[] = sprintf('<error> %s %s</error>', OutputFormatter::escape($line[0]), str_repeat(' ', $len - $line[1]));
746 }
747 $messages[] = $emptyLine;
748 $messages[] = '';
749 $output->writeln($messages, OutputInterface::VERBOSITY_QUIET);
750 if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
751 $output->writeln('<comment>Exception trace:</comment>', OutputInterface::VERBOSITY_QUIET);
752 // exception related properties
753 $trace = $e->getTrace();
754 array_unshift($trace, ['function' => '', 'file' => $e->getFile() ?: 'n/a', 'line' => $e->getLine() ?: 'n/a', 'args' => []]);
755 for ($i = 0, $count = \count($trace); $i < $count; ++$i) {
756 $class = $trace[$i]['class'] ?? '';
757 $type = $trace[$i]['type'] ?? '';
758 $function = $trace[$i]['function'] ?? '';
759 $file = $trace[$i]['file'] ?? 'n/a';
760 $line = $trace[$i]['line'] ?? 'n/a';
761 $output->writeln(sprintf(' %s%s at <info>%s:%s</info>', $class, $function ? $type . $function . '()' : '', $file, $line), OutputInterface::VERBOSITY_QUIET);
762 }
763 $output->writeln('', OutputInterface::VERBOSITY_QUIET);
764 }
765 } while ($e = $e->getPrevious());
766 }
767 /**
768 * Configures the input and output instances based on the user arguments and options.
769 */
770 protected function configureIO(InputInterface $input, OutputInterface $output)
771 {
772 if (\true === $input->hasParameterOption(['--ansi'], \true)) {
773 $output->setDecorated(\true);
774 } elseif (\true === $input->hasParameterOption(['--no-ansi'], \true)) {
775 $output->setDecorated(\false);
776 }
777 if (\true === $input->hasParameterOption(['--no-interaction', '-n'], \true)) {
778 $input->setInteractive(\false);
779 }
780 switch ($shellVerbosity = (int) getenv('SHELL_VERBOSITY')) {
781 case -1:
782 $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
783 break;
784 case 1:
785 $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
786 break;
787 case 2:
788 $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
789 break;
790 case 3:
791 $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
792 break;
793 default:
794 $shellVerbosity = 0;
795 break;
796 }
797 if (\true === $input->hasParameterOption(['--quiet', '-q'], \true)) {
798 $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
799 $shellVerbosity = -1;
800 } else {
801 if ($input->hasParameterOption('-vvv', \true) || $input->hasParameterOption('--verbose=3', \true) || 3 === $input->getParameterOption('--verbose', \false, \true)) {
802 $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
803 $shellVerbosity = 3;
804 } elseif ($input->hasParameterOption('-vv', \true) || $input->hasParameterOption('--verbose=2', \true) || 2 === $input->getParameterOption('--verbose', \false, \true)) {
805 $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
806 $shellVerbosity = 2;
807 } elseif ($input->hasParameterOption('-v', \true) || $input->hasParameterOption('--verbose=1', \true) || $input->hasParameterOption('--verbose', \true) || $input->getParameterOption('--verbose', \false, \true)) {
808 $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
809 $shellVerbosity = 1;
810 }
811 }
812 if (-1 === $shellVerbosity) {
813 $input->setInteractive(\false);
814 }
815 if (\function_exists('putenv')) {
816 @putenv('SHELL_VERBOSITY=' . $shellVerbosity);
817 }
818 $_ENV['SHELL_VERBOSITY'] = $shellVerbosity;
819 $_SERVER['SHELL_VERBOSITY'] = $shellVerbosity;
820 }
821 /**
822 * Runs the current command.
823 *
824 * If an event dispatcher has been attached to the application,
825 * events are also dispatched during the life-cycle of the command.
826 *
827 * @return int 0 if everything went fine, or an error code
828 */
829 protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)
830 {
831 foreach ($command->getHelperSet() as $helper) {
832 if ($helper instanceof InputAwareInterface) {
833 $helper->setInput($input);
834 }
835 }
836 if ($this->signalsToDispatchEvent) {
837 $commandSignals = $command instanceof SignalableCommandInterface ? $command->getSubscribedSignals() : [];
838 if ($commandSignals || null !== $this->dispatcher) {
839 if (!$this->signalRegistry) {
840 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.');
841 }
842 if (Terminal::hasSttyAvailable()) {
843 $sttyMode = shell_exec('stty -g');
844 foreach ([\SIGINT, \SIGTERM] as $signal) {
845 $this->signalRegistry->register($signal, static function () use($sttyMode) {
846 shell_exec('stty ' . $sttyMode);
847 });
848 }
849 }
850 }
851 if (null !== $this->dispatcher) {
852 foreach ($this->signalsToDispatchEvent as $signal) {
853 $event = new ConsoleSignalEvent($command, $input, $output, $signal);
854 $this->signalRegistry->register($signal, function ($signal, $hasNext) use($event) {
855 $this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL);
856 // No more handlers, we try to simulate PHP default behavior
857 if (!$hasNext) {
858 if (!\in_array($signal, [\SIGUSR1, \SIGUSR2], \true)) {
859 exit(0);
860 }
861 }
862 });
863 }
864 }
865 foreach ($commandSignals as $signal) {
866 $this->signalRegistry->register($signal, [$command, 'handleSignal']);
867 }
868 }
869 if (null === $this->dispatcher) {
870 return $command->run($input, $output);
871 }
872 // bind before the console.command event, so the listeners have access to input options/arguments
873 try {
874 $command->mergeApplicationDefinition();
875 $input->bind($command->getDefinition());
876 } catch (ExceptionInterface $e) {
877 // ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition
878 }
879 $event = new ConsoleCommandEvent($command, $input, $output);
880 $e = null;
881 try {
882 $this->dispatcher->dispatch($event, ConsoleEvents::COMMAND);
883 if ($event->commandShouldRun()) {
884 $exitCode = $command->run($input, $output);
885 } else {
886 $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED;
887 }
888 } catch (\Throwable $e) {
889 $event = new ConsoleErrorEvent($input, $output, $e, $command);
890 $this->dispatcher->dispatch($event, ConsoleEvents::ERROR);
891 $e = $event->getError();
892 if (0 === ($exitCode = $event->getExitCode())) {
893 $e = null;
894 }
895 }
896 $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode);
897 $this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE);
898 if (null !== $e) {
899 throw $e;
900 }
901 return $event->getExitCode();
902 }
903 /**
904 * Gets the name of the command based on input.
905 *
906 * @return string|null
907 */
908 protected function getCommandName(InputInterface $input)
909 {
910 return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument();
911 }
912 /**
913 * Gets the default input definition.
914 *
915 * @return InputDefinition
916 */
917 protected function getDefaultInputDefinition()
918 {
919 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')]);
920 }
921 /**
922 * Gets the default commands that should always be available.
923 *
924 * @return Command[]
925 */
926 protected function getDefaultCommands()
927 {
928 return [new HelpCommand(), new ListCommand(), new CompleteCommand(), new DumpCompletionCommand()];
929 }
930 /**
931 * Gets the default helper set with the helpers that should always be available.
932 *
933 * @return HelperSet
934 */
935 protected function getDefaultHelperSet()
936 {
937 return new HelperSet([new FormatterHelper(), new DebugFormatterHelper(), new ProcessHelper(), new QuestionHelper()]);
938 }
939 /**
940 * Returns abbreviated suggestions in string format.
941 */
942 private function getAbbreviationSuggestions(array $abbrevs) : string
943 {
944 return ' ' . implode("\n ", $abbrevs);
945 }
946 /**
947 * Returns the namespace part of the command name.
948 *
949 * This method is not part of public API and should not be used directly.
950 *
951 * @return string
952 */
953 public function extractNamespace(string $name, ?int $limit = null)
954 {
955 $parts = explode(':', $name, -1);
956 return implode(':', null === $limit ? $parts : \array_slice($parts, 0, $limit));
957 }
958 /**
959 * Finds alternative of $name among $collection,
960 * if nothing is found in $collection, try in $abbrevs.
961 *
962 * @return string[]
963 */
964 private function findAlternatives(string $name, iterable $collection) : array
965 {
966 $threshold = 1000.0;
967 $alternatives = [];
968 $collectionParts = [];
969 foreach ($collection as $item) {
970 $collectionParts[$item] = explode(':', $item);
971 }
972 foreach (explode(':', $name) as $i => $subname) {
973 foreach ($collectionParts as $collectionName => $parts) {
974 $exists = isset($alternatives[$collectionName]);
975 if (!isset($parts[$i]) && $exists) {
976 $alternatives[$collectionName] += $threshold;
977 continue;
978 } elseif (!isset($parts[$i])) {
979 continue;
980 }
981 $lev = levenshtein($subname, $parts[$i]);
982 if ($lev <= \strlen($subname) / 3 || '' !== $subname && str_contains($parts[$i], $subname)) {
983 $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev;
984 } elseif ($exists) {
985 $alternatives[$collectionName] += $threshold;
986 }
987 }
988 }
989 foreach ($collection as $item) {
990 $lev = levenshtein($name, $item);
991 if ($lev <= \strlen($name) / 3 || str_contains($item, $name)) {
992 $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
993 }
994 }
995 $alternatives = array_filter($alternatives, function ($lev) use($threshold) {
996 return $lev < 2 * $threshold;
997 });
998 ksort($alternatives, \SORT_NATURAL | \SORT_FLAG_CASE);
999 return array_keys($alternatives);
1000 }
1001 /**
1002 * Sets the default Command name.
1003 *
1004 * @return $this
1005 */
1006 public function setDefaultCommand(string $commandName, bool $isSingleCommand = \false)
1007 {
1008 $this->defaultCommand = explode('|', ltrim($commandName, '|'))[0];
1009 if ($isSingleCommand) {
1010 // Ensure the command exist
1011 $this->find($commandName);
1012 $this->singleCommand = \true;
1013 }
1014 return $this;
1015 }
1016 /**
1017 * @internal
1018 */
1019 public function isSingleCommand() : bool
1020 {
1021 return $this->singleCommand;
1022 }
1023 private function splitStringByWidth(string $string, int $width) : array
1024 {
1025 // str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly.
1026 // additionally, array_slice() is not enough as some character has doubled width.
1027 // we need a function to split string not by character count but by string width
1028 if (\false === ($encoding = mb_detect_encoding($string, null, \true))) {
1029 return str_split($string, $width);
1030 }
1031 $utf8String = mb_convert_encoding($string, 'utf8', $encoding);
1032 $lines = [];
1033 $line = '';
1034 $offset = 0;
1035 while (preg_match('/.{1,10000}/u', $utf8String, $m, 0, $offset)) {
1036 $offset += \strlen($m[0]);
1037 foreach (preg_split('//u', $m[0]) as $char) {
1038 // test if $char could be appended to current line
1039 if (mb_strwidth($line . $char, 'utf8') <= $width) {
1040 $line .= $char;
1041 continue;
1042 }
1043 // if not, push current line to array and make new line
1044 $lines[] = str_pad($line, $width);
1045 $line = $char;
1046 }
1047 }
1048 $lines[] = \count($lines) ? str_pad($line, $width) : $line;
1049 mb_convert_variables($encoding, 'utf8', $lines);
1050 return $lines;
1051 }
1052 /**
1053 * Returns all namespaces of the command name.
1054 *
1055 * @return string[]
1056 */
1057 private function extractAllNamespaces(string $name) : array
1058 {
1059 // -1 as third argument is needed to skip the command short name when exploding
1060 $parts = explode(':', $name, -1);
1061 $namespaces = [];
1062 foreach ($parts as $part) {
1063 if (\count($namespaces)) {
1064 $namespaces[] = end($namespaces) . ':' . $part;
1065 } else {
1066 $namespaces[] = $part;
1067 }
1068 }
1069 return $namespaces;
1070 }
1071 private function init()
1072 {
1073 if ($this->initialized) {
1074 return;
1075 }
1076 $this->initialized = \true;
1077 foreach ($this->getDefaultCommands() as $command) {
1078 $this->add($command);
1079 }
1080 }
1081 }
1082