[ Index ] |
PHP Cross Reference of phpBB-3.3.12-deutsch |
[Summary view] [Print] [Text view]
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\ConsoleExceptionEvent; 21 use Symfony\Component\Console\Event\ConsoleTerminateEvent; 22 use Symfony\Component\Console\Exception\CommandNotFoundException; 23 use Symfony\Component\Console\Exception\ExceptionInterface; 24 use Symfony\Component\Console\Exception\LogicException; 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\Input\StreamableInputInterface; 40 use Symfony\Component\Console\Output\ConsoleOutput; 41 use Symfony\Component\Console\Output\ConsoleOutputInterface; 42 use Symfony\Component\Console\Output\OutputInterface; 43 use Symfony\Component\Debug\ErrorHandler; 44 use Symfony\Component\Debug\Exception\FatalThrowableError; 45 use Symfony\Component\EventDispatcher\EventDispatcherInterface; 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 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 /** 81 * @param string $name The name of the application 82 * @param string $version The version of the application 83 */ 84 public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN') 85 { 86 $this->name = $name; 87 $this->version = $version; 88 $this->terminal = new Terminal(); 89 $this->defaultCommand = 'list'; 90 } 91 92 public function setDispatcher(EventDispatcherInterface $dispatcher) 93 { 94 $this->dispatcher = $dispatcher; 95 } 96 97 public function setCommandLoader(CommandLoaderInterface $commandLoader) 98 { 99 $this->commandLoader = $commandLoader; 100 } 101 102 /** 103 * Runs the current application. 104 * 105 * @return int 0 if everything went fine, or an error code 106 * 107 * @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}. 108 */ 109 public function run(InputInterface $input = null, OutputInterface $output = null) 110 { 111 putenv('LINES='.$this->terminal->getHeight()); 112 putenv('COLUMNS='.$this->terminal->getWidth()); 113 114 if (null === $input) { 115 $input = new ArgvInput(); 116 } 117 118 if (null === $output) { 119 $output = new ConsoleOutput(); 120 } 121 122 $renderException = function ($e) use ($output) { 123 if (!$e instanceof \Exception) { 124 $e = class_exists(FatalThrowableError::class) ? new FatalThrowableError($e) : new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine()); 125 } 126 if ($output instanceof ConsoleOutputInterface) { 127 $this->renderException($e, $output->getErrorOutput()); 128 } else { 129 $this->renderException($e, $output); 130 } 131 }; 132 if ($phpHandler = set_exception_handler($renderException)) { 133 restore_exception_handler(); 134 if (!\is_array($phpHandler) || !$phpHandler[0] instanceof ErrorHandler) { 135 $debugHandler = true; 136 } elseif ($debugHandler = $phpHandler[0]->setExceptionHandler($renderException)) { 137 $phpHandler[0]->setExceptionHandler($debugHandler); 138 } 139 } 140 141 if (null !== $this->dispatcher && $this->dispatcher->hasListeners(ConsoleEvents::EXCEPTION)) { 142 @trigger_error(sprintf('The "ConsoleEvents::EXCEPTION" event is deprecated since Symfony 3.3 and will be removed in 4.0. Listen to the "ConsoleEvents::ERROR" event instead.'), \E_USER_DEPRECATED); 143 } 144 145 $this->configureIO($input, $output); 146 147 try { 148 $exitCode = $this->doRun($input, $output); 149 } catch (\Exception $e) { 150 if (!$this->catchExceptions) { 151 throw $e; 152 } 153 154 $renderException($e); 155 156 $exitCode = $e->getCode(); 157 if (is_numeric($exitCode)) { 158 $exitCode = (int) $exitCode; 159 if (0 === $exitCode) { 160 $exitCode = 1; 161 } 162 } else { 163 $exitCode = 1; 164 } 165 } finally { 166 // if the exception handler changed, keep it 167 // otherwise, unregister $renderException 168 if (!$phpHandler) { 169 if (set_exception_handler($renderException) === $renderException) { 170 restore_exception_handler(); 171 } 172 restore_exception_handler(); 173 } elseif (!$debugHandler) { 174 $finalHandler = $phpHandler[0]->setExceptionHandler(null); 175 if ($finalHandler !== $renderException) { 176 $phpHandler[0]->setExceptionHandler($finalHandler); 177 } 178 } 179 } 180 181 if ($this->autoExit) { 182 if ($exitCode > 255) { 183 $exitCode = 255; 184 } 185 186 exit($exitCode); 187 } 188 189 return $exitCode; 190 } 191 192 /** 193 * Runs the current application. 194 * 195 * @return int 0 if everything went fine, or an error code 196 */ 197 public function doRun(InputInterface $input, OutputInterface $output) 198 { 199 if (true === $input->hasParameterOption(['--version', '-V'], true)) { 200 $output->writeln($this->getLongVersion()); 201 202 return 0; 203 } 204 205 try { 206 // Makes ArgvInput::getFirstArgument() able to distinguish an option from an argument. 207 $input->bind($this->getDefinition()); 208 } catch (ExceptionInterface $e) { 209 // Errors must be ignored, full binding/validation happens later when the command is known. 210 } 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 222 if (!$name) { 223 $name = $this->defaultCommand; 224 $definition = $this->getDefinition(); 225 $definition->setArguments(array_merge( 226 $definition->getArguments(), 227 [ 228 'command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name), 229 ] 230 )); 231 } 232 233 try { 234 $e = $this->runningCommand = null; 235 // the command name MUST be the first element of the input 236 $command = $this->find($name); 237 } catch (\Exception $e) { 238 } catch (\Throwable $e) { 239 } 240 if (null !== $e) { 241 if (null !== $this->dispatcher) { 242 $event = new ConsoleErrorEvent($input, $output, $e); 243 $this->dispatcher->dispatch(ConsoleEvents::ERROR, $event); 244 $e = $event->getError(); 245 246 if (0 === $event->getExitCode()) { 247 return 0; 248 } 249 } 250 251 throw $e; 252 } 253 254 $this->runningCommand = $command; 255 $exitCode = $this->doRunCommand($command, $input, $output); 256 $this->runningCommand = null; 257 258 return $exitCode; 259 } 260 261 public function setHelperSet(HelperSet $helperSet) 262 { 263 $this->helperSet = $helperSet; 264 } 265 266 /** 267 * Get the helper set associated with the command. 268 * 269 * @return HelperSet The HelperSet instance associated with this command 270 */ 271 public function getHelperSet() 272 { 273 if (!$this->helperSet) { 274 $this->helperSet = $this->getDefaultHelperSet(); 275 } 276 277 return $this->helperSet; 278 } 279 280 public function setDefinition(InputDefinition $definition) 281 { 282 $this->definition = $definition; 283 } 284 285 /** 286 * Gets the InputDefinition related to this Application. 287 * 288 * @return InputDefinition The InputDefinition instance 289 */ 290 public function getDefinition() 291 { 292 if (!$this->definition) { 293 $this->definition = $this->getDefaultInputDefinition(); 294 } 295 296 if ($this->singleCommand) { 297 $inputDefinition = $this->definition; 298 $inputDefinition->setArguments(); 299 300 return $inputDefinition; 301 } 302 303 return $this->definition; 304 } 305 306 /** 307 * Gets the help message. 308 * 309 * @return string A help message 310 */ 311 public function getHelp() 312 { 313 return $this->getLongVersion(); 314 } 315 316 /** 317 * Gets whether to catch exceptions or not during commands execution. 318 * 319 * @return bool Whether to catch exceptions or not during commands execution 320 */ 321 public function areExceptionsCaught() 322 { 323 return $this->catchExceptions; 324 } 325 326 /** 327 * Sets whether to catch exceptions or not during commands execution. 328 * 329 * @param bool $boolean Whether to catch exceptions or not during commands execution 330 */ 331 public function setCatchExceptions($boolean) 332 { 333 $this->catchExceptions = (bool) $boolean; 334 } 335 336 /** 337 * Gets whether to automatically exit after a command execution or not. 338 * 339 * @return bool Whether to automatically exit after a command execution or not 340 */ 341 public function isAutoExitEnabled() 342 { 343 return $this->autoExit; 344 } 345 346 /** 347 * Sets whether to automatically exit after a command execution or not. 348 * 349 * @param bool $boolean Whether to automatically exit after a command execution or not 350 */ 351 public function setAutoExit($boolean) 352 { 353 $this->autoExit = (bool) $boolean; 354 } 355 356 /** 357 * Gets the name of the application. 358 * 359 * @return string The application name 360 */ 361 public function getName() 362 { 363 return $this->name; 364 } 365 366 /** 367 * Sets the application name. 368 * 369 * @param string $name The application name 370 */ 371 public function setName($name) 372 { 373 $this->name = $name; 374 } 375 376 /** 377 * Gets the application version. 378 * 379 * @return string The application version 380 */ 381 public function getVersion() 382 { 383 return $this->version; 384 } 385 386 /** 387 * Sets the application version. 388 * 389 * @param string $version The application version 390 */ 391 public function setVersion($version) 392 { 393 $this->version = $version; 394 } 395 396 /** 397 * Returns the long version of the application. 398 * 399 * @return string The long application version 400 */ 401 public function getLongVersion() 402 { 403 if ('UNKNOWN' !== $this->getName()) { 404 if ('UNKNOWN' !== $this->getVersion()) { 405 return sprintf('%s <info>%s</info>', $this->getName(), $this->getVersion()); 406 } 407 408 return $this->getName(); 409 } 410 411 return 'Console Tool'; 412 } 413 414 /** 415 * Registers a new command. 416 * 417 * @param string $name The command name 418 * 419 * @return Command The newly created command 420 */ 421 public function register($name) 422 { 423 return $this->add(new Command($name)); 424 } 425 426 /** 427 * Adds an array of command objects. 428 * 429 * If a Command is not enabled it will not be added. 430 * 431 * @param Command[] $commands An array of commands 432 */ 433 public function addCommands(array $commands) 434 { 435 foreach ($commands as $command) { 436 $this->add($command); 437 } 438 } 439 440 /** 441 * Adds a command object. 442 * 443 * If a command with the same name already exists, it will be overridden. 444 * If the command is not enabled it will not be added. 445 * 446 * @return Command|null The registered command if enabled or null 447 */ 448 public function add(Command $command) 449 { 450 $this->init(); 451 452 $command->setApplication($this); 453 454 if (!$command->isEnabled()) { 455 $command->setApplication(null); 456 457 return null; 458 } 459 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_class($command))); 465 } 466 467 $this->commands[$command->getName()] = $command; 468 469 foreach ($command->getAliases() as $alias) { 470 $this->commands[$alias] = $command; 471 } 472 473 return $command; 474 } 475 476 /** 477 * Returns a registered command by name or alias. 478 * 479 * @param string $name The command name or alias 480 * 481 * @return Command A Command object 482 * 483 * @throws CommandNotFoundException When given command name does not exist 484 */ 485 public function get($name) 486 { 487 $this->init(); 488 489 if (!$this->has($name)) { 490 throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name)); 491 } 492 493 // When the command has a different name than the one used at the command loader level 494 if (!isset($this->commands[$name])) { 495 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)); 496 } 497 498 $command = $this->commands[$name]; 499 500 if ($this->wantHelps) { 501 $this->wantHelps = false; 502 503 $helpCommand = $this->get('help'); 504 $helpCommand->setCommand($command); 505 506 return $helpCommand; 507 } 508 509 return $command; 510 } 511 512 /** 513 * Returns true if the command exists, false otherwise. 514 * 515 * @param string $name The command name or alias 516 * 517 * @return bool true if the command exists, false otherwise 518 */ 519 public function has($name) 520 { 521 $this->init(); 522 523 return isset($this->commands[$name]) || ($this->commandLoader && $this->commandLoader->has($name) && $this->add($this->commandLoader->get($name))); 524 } 525 526 /** 527 * Returns an array of all unique namespaces used by currently registered commands. 528 * 529 * It does not return the global namespace which always exists. 530 * 531 * @return string[] An array of namespaces 532 */ 533 public function getNamespaces() 534 { 535 $namespaces = []; 536 foreach ($this->all() as $command) { 537 if ($command->isHidden()) { 538 continue; 539 } 540 541 $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName())); 542 543 foreach ($command->getAliases() as $alias) { 544 $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias)); 545 } 546 } 547 548 return array_values(array_unique(array_filter($namespaces))); 549 } 550 551 /** 552 * Finds a registered namespace by a name or an abbreviation. 553 * 554 * @param string $namespace A namespace or abbreviation to search for 555 * 556 * @return string A registered namespace 557 * 558 * @throws CommandNotFoundException When namespace is incorrect or ambiguous 559 */ 560 public function findNamespace($namespace) 561 { 562 $allNamespaces = $this->getNamespaces(); 563 $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $namespace); 564 $namespaces = preg_grep('{^'.$expr.'}', $allNamespaces); 565 566 if (empty($namespaces)) { 567 $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace); 568 569 if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) { 570 if (1 == \count($alternatives)) { 571 $message .= "\n\nDid you mean this?\n "; 572 } else { 573 $message .= "\n\nDid you mean one of these?\n "; 574 } 575 576 $message .= implode("\n ", $alternatives); 577 } 578 579 throw new CommandNotFoundException($message, $alternatives); 580 } 581 582 $exact = \in_array($namespace, $namespaces, true); 583 if (\count($namespaces) > 1 && !$exact) { 584 throw new CommandNotFoundException(sprintf("The namespace \"%s\" is ambiguous.\nDid you mean one of these?\n%s.", $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))), array_values($namespaces)); 585 } 586 587 return $exact ? $namespace : reset($namespaces); 588 } 589 590 /** 591 * Finds a command by name or alias. 592 * 593 * Contrary to get, this command tries to find the best 594 * match if you give it an abbreviation of a name or alias. 595 * 596 * @param string $name A command name or a command alias 597 * 598 * @return Command A Command instance 599 * 600 * @throws CommandNotFoundException When command name is incorrect or ambiguous 601 */ 602 public function find($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 $exact = \in_array($name, $commands, true) || isset($aliases[$name]); 671 if (\count($commands) > 1 && !$exact) { 672 $usableWidth = $this->terminal->getWidth() - 10; 673 $abbrevs = array_values($commands); 674 $maxLen = 0; 675 foreach ($abbrevs as $abbrev) { 676 $maxLen = max(Helper::strlen($abbrev), $maxLen); 677 } 678 $abbrevs = array_map(function ($cmd) use ($commandList, $usableWidth, $maxLen) { 679 if ($commandList[$cmd]->isHidden()) { 680 return false; 681 } 682 683 $abbrev = str_pad($cmd, $maxLen, ' ').' '.$commandList[$cmd]->getDescription(); 684 685 return Helper::strlen($abbrev) > $usableWidth ? Helper::substr($abbrev, 0, $usableWidth - 3).'...' : $abbrev; 686 }, array_values($commands)); 687 $suggestions = $this->getAbbreviationSuggestions(array_filter($abbrevs)); 688 689 throw new CommandNotFoundException(sprintf("Command \"%s\" is ambiguous.\nDid you mean one of these?\n%s.", $name, $suggestions), array_values($commands)); 690 } 691 692 return $this->get($exact ? $name : reset($commands)); 693 } 694 695 /** 696 * Gets the commands (registered in the given namespace if provided). 697 * 698 * The array keys are the full names and the values the command instances. 699 * 700 * @param string $namespace A namespace name 701 * 702 * @return Command[] An array of Command instances 703 */ 704 public function all($namespace = null) 705 { 706 $this->init(); 707 708 if (null === $namespace) { 709 if (!$this->commandLoader) { 710 return $this->commands; 711 } 712 713 $commands = $this->commands; 714 foreach ($this->commandLoader->getNames() as $name) { 715 if (!isset($commands[$name]) && $this->has($name)) { 716 $commands[$name] = $this->get($name); 717 } 718 } 719 720 return $commands; 721 } 722 723 $commands = []; 724 foreach ($this->commands as $name => $command) { 725 if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) { 726 $commands[$name] = $command; 727 } 728 } 729 730 if ($this->commandLoader) { 731 foreach ($this->commandLoader->getNames() as $name) { 732 if (!isset($commands[$name]) && $namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1) && $this->has($name)) { 733 $commands[$name] = $this->get($name); 734 } 735 } 736 } 737 738 return $commands; 739 } 740 741 /** 742 * Returns an array of possible abbreviations given a set of names. 743 * 744 * @param array $names An array of names 745 * 746 * @return array An array of abbreviations 747 */ 748 public static function getAbbreviations($names) 749 { 750 $abbrevs = []; 751 foreach ($names as $name) { 752 for ($len = \strlen($name); $len > 0; --$len) { 753 $abbrev = substr($name, 0, $len); 754 $abbrevs[$abbrev][] = $name; 755 } 756 } 757 758 return $abbrevs; 759 } 760 761 /** 762 * Renders a caught exception. 763 */ 764 public function renderException(\Exception $e, OutputInterface $output) 765 { 766 $output->writeln('', OutputInterface::VERBOSITY_QUIET); 767 768 $this->doRenderException($e, $output); 769 770 if (null !== $this->runningCommand) { 771 $output->writeln(sprintf('<info>%s</info>', sprintf($this->runningCommand->getSynopsis(), $this->getName())), OutputInterface::VERBOSITY_QUIET); 772 $output->writeln('', OutputInterface::VERBOSITY_QUIET); 773 } 774 } 775 776 protected function doRenderException(\Exception $e, OutputInterface $output) 777 { 778 do { 779 $message = trim($e->getMessage()); 780 if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { 781 $title = sprintf(' [%s%s] ', \get_class($e), 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : ''); 782 $len = Helper::strlen($title); 783 } else { 784 $len = 0; 785 } 786 787 $width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : \PHP_INT_MAX; 788 // HHVM only accepts 32 bits integer in str_split, even when PHP_INT_MAX is a 64 bit integer: https://github.com/facebook/hhvm/issues/1327 789 if (\defined('HHVM_VERSION') && $width > 1 << 31) { 790 $width = 1 << 31; 791 } 792 $lines = []; 793 foreach ('' !== $message ? preg_split('/\r?\n/', $message) : [] as $line) { 794 foreach ($this->splitStringByWidth($line, $width - 4) as $line) { 795 // pre-format lines to get the right string length 796 $lineLength = Helper::strlen($line) + 4; 797 $lines[] = [$line, $lineLength]; 798 799 $len = max($lineLength, $len); 800 } 801 } 802 803 $messages = []; 804 if (!$e instanceof ExceptionInterface || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { 805 $messages[] = sprintf('<comment>%s</comment>', OutputFormatter::escape(sprintf('In %s line %s:', basename($e->getFile()) ?: 'n/a', $e->getLine() ?: 'n/a'))); 806 } 807 $messages[] = $emptyLine = sprintf('<error>%s</error>', str_repeat(' ', $len)); 808 if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { 809 $messages[] = sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - Helper::strlen($title)))); 810 } 811 foreach ($lines as $line) { 812 $messages[] = sprintf('<error> %s %s</error>', OutputFormatter::escape($line[0]), str_repeat(' ', $len - $line[1])); 813 } 814 $messages[] = $emptyLine; 815 $messages[] = ''; 816 817 $output->writeln($messages, OutputInterface::VERBOSITY_QUIET); 818 819 if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { 820 $output->writeln('<comment>Exception trace:</comment>', OutputInterface::VERBOSITY_QUIET); 821 822 // exception related properties 823 $trace = $e->getTrace(); 824 825 array_unshift($trace, [ 826 'function' => '', 827 'file' => $e->getFile() ?: 'n/a', 828 'line' => $e->getLine() ?: 'n/a', 829 'args' => [], 830 ]); 831 832 for ($i = 0, $count = \count($trace); $i < $count; ++$i) { 833 $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : ''; 834 $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : ''; 835 $function = isset($trace[$i]['function']) ? $trace[$i]['function'] : ''; 836 $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a'; 837 $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a'; 838 839 $output->writeln(sprintf(' %s%s at <info>%s:%s</info>', $class, $function ? $type.$function.'()' : '', $file, $line), OutputInterface::VERBOSITY_QUIET); 840 } 841 842 $output->writeln('', OutputInterface::VERBOSITY_QUIET); 843 } 844 } while ($e = $e->getPrevious()); 845 } 846 847 /** 848 * Tries to figure out the terminal width in which this application runs. 849 * 850 * @return int|null 851 * 852 * @deprecated since version 3.2, to be removed in 4.0. Create a Terminal instance instead. 853 */ 854 protected function getTerminalWidth() 855 { 856 @trigger_error(sprintf('The "%s()" method is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.', __METHOD__), \E_USER_DEPRECATED); 857 858 return $this->terminal->getWidth(); 859 } 860 861 /** 862 * Tries to figure out the terminal height in which this application runs. 863 * 864 * @return int|null 865 * 866 * @deprecated since version 3.2, to be removed in 4.0. Create a Terminal instance instead. 867 */ 868 protected function getTerminalHeight() 869 { 870 @trigger_error(sprintf('The "%s()" method is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.', __METHOD__), \E_USER_DEPRECATED); 871 872 return $this->terminal->getHeight(); 873 } 874 875 /** 876 * Tries to figure out the terminal dimensions based on the current environment. 877 * 878 * @return array Array containing width and height 879 * 880 * @deprecated since version 3.2, to be removed in 4.0. Create a Terminal instance instead. 881 */ 882 public function getTerminalDimensions() 883 { 884 @trigger_error(sprintf('The "%s()" method is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.', __METHOD__), \E_USER_DEPRECATED); 885 886 return [$this->terminal->getWidth(), $this->terminal->getHeight()]; 887 } 888 889 /** 890 * Sets terminal dimensions. 891 * 892 * Can be useful to force terminal dimensions for functional tests. 893 * 894 * @param int $width The width 895 * @param int $height The height 896 * 897 * @return $this 898 * 899 * @deprecated since version 3.2, to be removed in 4.0. Set the COLUMNS and LINES env vars instead. 900 */ 901 public function setTerminalDimensions($width, $height) 902 { 903 @trigger_error(sprintf('The "%s()" method is deprecated as of 3.2 and will be removed in 4.0. Set the COLUMNS and LINES env vars instead.', __METHOD__), \E_USER_DEPRECATED); 904 905 putenv('COLUMNS='.$width); 906 putenv('LINES='.$height); 907 908 return $this; 909 } 910 911 /** 912 * Configures the input and output instances based on the user arguments and options. 913 */ 914 protected function configureIO(InputInterface $input, OutputInterface $output) 915 { 916 if (true === $input->hasParameterOption(['--ansi'], true)) { 917 $output->setDecorated(true); 918 } elseif (true === $input->hasParameterOption(['--no-ansi'], true)) { 919 $output->setDecorated(false); 920 } 921 922 if (true === $input->hasParameterOption(['--no-interaction', '-n'], true)) { 923 $input->setInteractive(false); 924 } elseif (\function_exists('posix_isatty')) { 925 $inputStream = null; 926 927 if ($input instanceof StreamableInputInterface) { 928 $inputStream = $input->getStream(); 929 } 930 931 // This check ensures that calling QuestionHelper::setInputStream() works 932 // To be removed in 4.0 (in the same time as QuestionHelper::setInputStream) 933 if (!$inputStream && $this->getHelperSet()->has('question')) { 934 $inputStream = $this->getHelperSet()->get('question')->getInputStream(false); 935 } 936 937 if (!@posix_isatty($inputStream) && false === getenv('SHELL_INTERACTIVE')) { 938 $input->setInteractive(false); 939 } 940 } 941 942 switch ($shellVerbosity = (int) getenv('SHELL_VERBOSITY')) { 943 case -1: $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); break; 944 case 1: $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); break; 945 case 2: $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); break; 946 case 3: $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); break; 947 default: $shellVerbosity = 0; break; 948 } 949 950 if (true === $input->hasParameterOption(['--quiet', '-q'], true)) { 951 $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); 952 $shellVerbosity = -1; 953 } else { 954 if ($input->hasParameterOption('-vvv', true) || $input->hasParameterOption('--verbose=3', true) || 3 === $input->getParameterOption('--verbose', false, true)) { 955 $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); 956 $shellVerbosity = 3; 957 } elseif ($input->hasParameterOption('-vv', true) || $input->hasParameterOption('--verbose=2', true) || 2 === $input->getParameterOption('--verbose', false, true)) { 958 $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); 959 $shellVerbosity = 2; 960 } elseif ($input->hasParameterOption('-v', true) || $input->hasParameterOption('--verbose=1', true) || $input->hasParameterOption('--verbose', true) || $input->getParameterOption('--verbose', false, true)) { 961 $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); 962 $shellVerbosity = 1; 963 } 964 } 965 966 if (-1 === $shellVerbosity) { 967 $input->setInteractive(false); 968 } 969 970 putenv('SHELL_VERBOSITY='.$shellVerbosity); 971 $_ENV['SHELL_VERBOSITY'] = $shellVerbosity; 972 $_SERVER['SHELL_VERBOSITY'] = $shellVerbosity; 973 } 974 975 /** 976 * Runs the current command. 977 * 978 * If an event dispatcher has been attached to the application, 979 * events are also dispatched during the life-cycle of the command. 980 * 981 * @return int 0 if everything went fine, or an error code 982 */ 983 protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) 984 { 985 foreach ($command->getHelperSet() as $helper) { 986 if ($helper instanceof InputAwareInterface) { 987 $helper->setInput($input); 988 } 989 } 990 991 if (null === $this->dispatcher) { 992 return $command->run($input, $output); 993 } 994 995 // bind before the console.command event, so the listeners have access to input options/arguments 996 try { 997 $command->mergeApplicationDefinition(); 998 $input->bind($command->getDefinition()); 999 } catch (ExceptionInterface $e) { 1000 // ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition 1001 } 1002 1003 $event = new ConsoleCommandEvent($command, $input, $output); 1004 $e = null; 1005 1006 try { 1007 $this->dispatcher->dispatch(ConsoleEvents::COMMAND, $event); 1008 1009 if ($event->commandShouldRun()) { 1010 $exitCode = $command->run($input, $output); 1011 } else { 1012 $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED; 1013 } 1014 } catch (\Exception $e) { 1015 } catch (\Throwable $e) { 1016 } 1017 if (null !== $e) { 1018 if ($this->dispatcher->hasListeners(ConsoleEvents::EXCEPTION)) { 1019 $x = $e instanceof \Exception ? $e : new FatalThrowableError($e); 1020 $event = new ConsoleExceptionEvent($command, $input, $output, $x, $x->getCode()); 1021 $this->dispatcher->dispatch(ConsoleEvents::EXCEPTION, $event); 1022 1023 if ($x !== $event->getException()) { 1024 $e = $event->getException(); 1025 } 1026 } 1027 $event = new ConsoleErrorEvent($input, $output, $e, $command); 1028 $this->dispatcher->dispatch(ConsoleEvents::ERROR, $event); 1029 $e = $event->getError(); 1030 1031 if (0 === $exitCode = $event->getExitCode()) { 1032 $e = null; 1033 } 1034 } 1035 1036 $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode); 1037 $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event); 1038 1039 if (null !== $e) { 1040 throw $e; 1041 } 1042 1043 return $event->getExitCode(); 1044 } 1045 1046 /** 1047 * Gets the name of the command based on input. 1048 * 1049 * @return string|null 1050 */ 1051 protected function getCommandName(InputInterface $input) 1052 { 1053 return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument(); 1054 } 1055 1056 /** 1057 * Gets the default input definition. 1058 * 1059 * @return InputDefinition An InputDefinition instance 1060 */ 1061 protected function getDefaultInputDefinition() 1062 { 1063 return new InputDefinition([ 1064 new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), 1065 1066 new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'), 1067 new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'), 1068 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'), 1069 new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'), 1070 new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'), 1071 new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'), 1072 new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'), 1073 ]); 1074 } 1075 1076 /** 1077 * Gets the default commands that should always be available. 1078 * 1079 * @return Command[] An array of default Command instances 1080 */ 1081 protected function getDefaultCommands() 1082 { 1083 return [new HelpCommand(), new ListCommand()]; 1084 } 1085 1086 /** 1087 * Gets the default helper set with the helpers that should always be available. 1088 * 1089 * @return HelperSet A HelperSet instance 1090 */ 1091 protected function getDefaultHelperSet() 1092 { 1093 return new HelperSet([ 1094 new FormatterHelper(), 1095 new DebugFormatterHelper(), 1096 new ProcessHelper(), 1097 new QuestionHelper(), 1098 ]); 1099 } 1100 1101 /** 1102 * Returns abbreviated suggestions in string format. 1103 * 1104 * @param array $abbrevs Abbreviated suggestions to convert 1105 * 1106 * @return string A formatted string of abbreviated suggestions 1107 */ 1108 private function getAbbreviationSuggestions($abbrevs) 1109 { 1110 return ' '.implode("\n ", $abbrevs); 1111 } 1112 1113 /** 1114 * Returns the namespace part of the command name. 1115 * 1116 * This method is not part of public API and should not be used directly. 1117 * 1118 * @param string $name The full name of the command 1119 * @param string $limit The maximum number of parts of the namespace 1120 * 1121 * @return string The namespace of the command 1122 */ 1123 public function extractNamespace($name, $limit = null) 1124 { 1125 $parts = explode(':', $name, -1); 1126 1127 return implode(':', null === $limit ? $parts : \array_slice($parts, 0, $limit)); 1128 } 1129 1130 /** 1131 * Finds alternative of $name among $collection, 1132 * if nothing is found in $collection, try in $abbrevs. 1133 * 1134 * @param string $name The string 1135 * @param iterable $collection The collection 1136 * 1137 * @return string[] A sorted array of similar string 1138 */ 1139 private function findAlternatives($name, $collection) 1140 { 1141 $threshold = 1e3; 1142 $alternatives = []; 1143 1144 $collectionParts = []; 1145 foreach ($collection as $item) { 1146 $collectionParts[$item] = explode(':', $item); 1147 } 1148 1149 foreach (explode(':', $name) as $i => $subname) { 1150 foreach ($collectionParts as $collectionName => $parts) { 1151 $exists = isset($alternatives[$collectionName]); 1152 if (!isset($parts[$i]) && $exists) { 1153 $alternatives[$collectionName] += $threshold; 1154 continue; 1155 } elseif (!isset($parts[$i])) { 1156 continue; 1157 } 1158 1159 $lev = levenshtein($subname, $parts[$i]); 1160 if ($lev <= \strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) { 1161 $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev; 1162 } elseif ($exists) { 1163 $alternatives[$collectionName] += $threshold; 1164 } 1165 } 1166 } 1167 1168 foreach ($collection as $item) { 1169 $lev = levenshtein($name, $item); 1170 if ($lev <= \strlen($name) / 3 || false !== strpos($item, $name)) { 1171 $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev; 1172 } 1173 } 1174 1175 $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; }); 1176 ksort($alternatives, \SORT_NATURAL | \SORT_FLAG_CASE); 1177 1178 return array_keys($alternatives); 1179 } 1180 1181 /** 1182 * Sets the default Command name. 1183 * 1184 * @param string $commandName The Command name 1185 * @param bool $isSingleCommand Set to true if there is only one command in this application 1186 * 1187 * @return self 1188 */ 1189 public function setDefaultCommand($commandName, $isSingleCommand = false) 1190 { 1191 $this->defaultCommand = $commandName; 1192 1193 if ($isSingleCommand) { 1194 // Ensure the command exist 1195 $this->find($commandName); 1196 1197 $this->singleCommand = true; 1198 } 1199 1200 return $this; 1201 } 1202 1203 /** 1204 * @internal 1205 */ 1206 public function isSingleCommand() 1207 { 1208 return $this->singleCommand; 1209 } 1210 1211 private function splitStringByWidth($string, $width) 1212 { 1213 // str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly. 1214 // additionally, array_slice() is not enough as some character has doubled width. 1215 // we need a function to split string not by character count but by string width 1216 if (false === $encoding = mb_detect_encoding($string, null, true)) { 1217 return str_split($string, $width); 1218 } 1219 1220 $utf8String = mb_convert_encoding($string, 'utf8', $encoding); 1221 $lines = []; 1222 $line = ''; 1223 foreach (preg_split('//u', $utf8String) as $char) { 1224 // test if $char could be appended to current line 1225 if (mb_strwidth($line.$char, 'utf8') <= $width) { 1226 $line .= $char; 1227 continue; 1228 } 1229 // if not, push current line to array and make new line 1230 $lines[] = str_pad($line, $width); 1231 $line = $char; 1232 } 1233 1234 $lines[] = \count($lines) ? str_pad($line, $width) : $line; 1235 1236 mb_convert_variables($encoding, 'utf8', $lines); 1237 1238 return $lines; 1239 } 1240 1241 /** 1242 * Returns all namespaces of the command name. 1243 * 1244 * @param string $name The full name of the command 1245 * 1246 * @return string[] The namespaces of the command 1247 */ 1248 private function extractAllNamespaces($name) 1249 { 1250 // -1 as third argument is needed to skip the command short name when exploding 1251 $parts = explode(':', $name, -1); 1252 $namespaces = []; 1253 1254 foreach ($parts as $part) { 1255 if (\count($namespaces)) { 1256 $namespaces[] = end($namespaces).':'.$part; 1257 } else { 1258 $namespaces[] = $part; 1259 } 1260 } 1261 1262 return $namespaces; 1263 } 1264 1265 private function init() 1266 { 1267 if ($this->initialized) { 1268 return; 1269 } 1270 $this->initialized = true; 1271 1272 foreach ($this->getDefaultCommands() as $command) { 1273 $this->add($command); 1274 } 1275 } 1276 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Jun 23 12:25:44 2024 | Cross-referenced by PHPXref 0.7.1 |