PluginProbe
Depicter — Popup & Slider Builder / 1.3.2
Depicter — Popup & Slider Builder v1.3.2
4.8.1 trunk 1.0.0 1.1.0 1.1.2 1.1.4 1.1.6 1.1.7 1.1.8 1.1.9 1.2.0 1.3.0 1.3.1 1.3.2 1.3.3 1.3.5 1.3.8 1.5.0 1.5.1 1.5.2 1.5.5 1.6.0 1.6.1 1.6.2 1.7.0 All 76 releases
depicter / vendor / symfony / console / Application.php

Application.php in Depicter — Popup & Slider Builder 1.3.2, at vendor/symfony/console/Application.php

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