comparison vendor/symfony/console/Application.php @ 14:1fec387a4317

Update Drupal core to 8.5.2 via Composer
author Chris Cannam
date Mon, 23 Apr 2018 09:46:53 +0100
parents 7a779792577d
children 129ea1e6d783
comparison
equal deleted inserted replaced
13:5fb285c0d0e3 14:1fec387a4317
9 * file that was distributed with this source code. 9 * file that was distributed with this source code.
10 */ 10 */
11 11
12 namespace Symfony\Component\Console; 12 namespace Symfony\Component\Console;
13 13
14 use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
14 use Symfony\Component\Console\Exception\ExceptionInterface; 15 use Symfony\Component\Console\Exception\ExceptionInterface;
15 use Symfony\Component\Console\Formatter\OutputFormatter; 16 use Symfony\Component\Console\Formatter\OutputFormatter;
16 use Symfony\Component\Console\Helper\DebugFormatterHelper; 17 use Symfony\Component\Console\Helper\DebugFormatterHelper;
17 use Symfony\Component\Console\Helper\Helper; 18 use Symfony\Component\Console\Helper\Helper;
18 use Symfony\Component\Console\Helper\ProcessHelper; 19 use Symfony\Component\Console\Helper\ProcessHelper;
32 use Symfony\Component\Console\Command\HelpCommand; 33 use Symfony\Component\Console\Command\HelpCommand;
33 use Symfony\Component\Console\Command\ListCommand; 34 use Symfony\Component\Console\Command\ListCommand;
34 use Symfony\Component\Console\Helper\HelperSet; 35 use Symfony\Component\Console\Helper\HelperSet;
35 use Symfony\Component\Console\Helper\FormatterHelper; 36 use Symfony\Component\Console\Helper\FormatterHelper;
36 use Symfony\Component\Console\Event\ConsoleCommandEvent; 37 use Symfony\Component\Console\Event\ConsoleCommandEvent;
38 use Symfony\Component\Console\Event\ConsoleErrorEvent;
37 use Symfony\Component\Console\Event\ConsoleExceptionEvent; 39 use Symfony\Component\Console\Event\ConsoleExceptionEvent;
38 use Symfony\Component\Console\Event\ConsoleTerminateEvent; 40 use Symfony\Component\Console\Event\ConsoleTerminateEvent;
39 use Symfony\Component\Console\Exception\CommandNotFoundException; 41 use Symfony\Component\Console\Exception\CommandNotFoundException;
40 use Symfony\Component\Console\Exception\LogicException; 42 use Symfony\Component\Console\Exception\LogicException;
43 use Symfony\Component\Debug\ErrorHandler;
41 use Symfony\Component\Debug\Exception\FatalThrowableError; 44 use Symfony\Component\Debug\Exception\FatalThrowableError;
42 use Symfony\Component\EventDispatcher\EventDispatcherInterface; 45 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
43 46
44 /** 47 /**
45 * An Application is the container for a collection of commands. 48 * An Application is the container for a collection of commands.
61 private $commands = array(); 64 private $commands = array();
62 private $wantHelps = false; 65 private $wantHelps = false;
63 private $runningCommand; 66 private $runningCommand;
64 private $name; 67 private $name;
65 private $version; 68 private $version;
69 private $commandLoader;
66 private $catchExceptions = true; 70 private $catchExceptions = true;
67 private $autoExit = true; 71 private $autoExit = true;
68 private $definition; 72 private $definition;
69 private $helperSet; 73 private $helperSet;
70 private $dispatcher; 74 private $dispatcher;
71 private $terminal; 75 private $terminal;
72 private $defaultCommand; 76 private $defaultCommand;
73 private $singleCommand; 77 private $singleCommand;
78 private $initialized;
74 79
75 /** 80 /**
76 * @param string $name The name of the application 81 * @param string $name The name of the application
77 * @param string $version The version of the application 82 * @param string $version The version of the application
78 */ 83 */
80 { 85 {
81 $this->name = $name; 86 $this->name = $name;
82 $this->version = $version; 87 $this->version = $version;
83 $this->terminal = new Terminal(); 88 $this->terminal = new Terminal();
84 $this->defaultCommand = 'list'; 89 $this->defaultCommand = 'list';
85 $this->helperSet = $this->getDefaultHelperSet();
86 $this->definition = $this->getDefaultInputDefinition();
87
88 foreach ($this->getDefaultCommands() as $command) {
89 $this->add($command);
90 }
91 } 90 }
92 91
93 public function setDispatcher(EventDispatcherInterface $dispatcher) 92 public function setDispatcher(EventDispatcherInterface $dispatcher)
94 { 93 {
95 $this->dispatcher = $dispatcher; 94 $this->dispatcher = $dispatcher;
96 } 95 }
97 96
97 public function setCommandLoader(CommandLoaderInterface $commandLoader)
98 {
99 $this->commandLoader = $commandLoader;
100 }
101
98 /** 102 /**
99 * Runs the current application. 103 * Runs the current application.
100 *
101 * @param InputInterface $input An Input instance
102 * @param OutputInterface $output An Output instance
103 * 104 *
104 * @return int 0 if everything went fine, or an error code 105 * @return int 0 if everything went fine, or an error code
105 * 106 *
106 * @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}. 107 * @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}.
107 */ 108 */
116 117
117 if (null === $output) { 118 if (null === $output) {
118 $output = new ConsoleOutput(); 119 $output = new ConsoleOutput();
119 } 120 }
120 121
121 $this->configureIO($input, $output); 122 $renderException = function ($e) use ($output) {
122 123 if (!$e instanceof \Exception) {
123 try { 124 $e = class_exists(FatalThrowableError::class) ? new FatalThrowableError($e) : new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
124 $e = null; 125 }
125 $exitCode = $this->doRun($input, $output);
126 } catch (\Exception $x) {
127 $e = $x;
128 } catch (\Throwable $x) {
129 $e = new FatalThrowableError($x);
130 }
131
132 if (null !== $e) {
133 if (!$this->catchExceptions || !$x instanceof \Exception) {
134 throw $x;
135 }
136
137 if ($output instanceof ConsoleOutputInterface) { 126 if ($output instanceof ConsoleOutputInterface) {
138 $this->renderException($e, $output->getErrorOutput()); 127 $this->renderException($e, $output->getErrorOutput());
139 } else { 128 } else {
140 $this->renderException($e, $output); 129 $this->renderException($e, $output);
141 } 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);
142 155
143 $exitCode = $e->getCode(); 156 $exitCode = $e->getCode();
144 if (is_numeric($exitCode)) { 157 if (is_numeric($exitCode)) {
145 $exitCode = (int) $exitCode; 158 $exitCode = (int) $exitCode;
146 if (0 === $exitCode) { 159 if (0 === $exitCode) {
147 $exitCode = 1; 160 $exitCode = 1;
148 } 161 }
149 } else { 162 } else {
150 $exitCode = 1; 163 $exitCode = 1;
151 } 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 }
152 } 179 }
153 180
154 if ($this->autoExit) { 181 if ($this->autoExit) {
155 if ($exitCode > 255) { 182 if ($exitCode > 255) {
156 $exitCode = 255; 183 $exitCode = 255;
162 return $exitCode; 189 return $exitCode;
163 } 190 }
164 191
165 /** 192 /**
166 * Runs the current application. 193 * Runs the current application.
167 *
168 * @param InputInterface $input An Input instance
169 * @param OutputInterface $output An Output instance
170 * 194 *
171 * @return int 0 if everything went fine, or an error code 195 * @return int 0 if everything went fine, or an error code
172 */ 196 */
173 public function doRun(InputInterface $input, OutputInterface $output) 197 public function doRun(InputInterface $input, OutputInterface $output)
174 { 198 {
188 } 212 }
189 } 213 }
190 214
191 if (!$name) { 215 if (!$name) {
192 $name = $this->defaultCommand; 216 $name = $this->defaultCommand;
193 $this->definition->setArguments(array_merge( 217 $definition = $this->getDefinition();
194 $this->definition->getArguments(), 218 $definition->setArguments(array_merge(
219 $definition->getArguments(),
195 array( 220 array(
196 'command' => new InputArgument('command', InputArgument::OPTIONAL, $this->definition->getArgument('command')->getDescription(), $name), 221 'command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name),
197 ) 222 )
198 )); 223 ));
199 } 224 }
200 225
201 $this->runningCommand = null; 226 try {
202 // the command name MUST be the first element of the input 227 $e = $this->runningCommand = null;
203 $command = $this->find($name); 228 // the command name MUST be the first element of the input
229 $command = $this->find($name);
230 } catch (\Exception $e) {
231 } catch (\Throwable $e) {
232 }
233 if (null !== $e) {
234 if (null !== $this->dispatcher) {
235 $event = new ConsoleErrorEvent($input, $output, $e);
236 $this->dispatcher->dispatch(ConsoleEvents::ERROR, $event);
237 $e = $event->getError();
238
239 if (0 === $event->getExitCode()) {
240 return 0;
241 }
242 }
243
244 throw $e;
245 }
204 246
205 $this->runningCommand = $command; 247 $this->runningCommand = $command;
206 $exitCode = $this->doRunCommand($command, $input, $output); 248 $exitCode = $this->doRunCommand($command, $input, $output);
207 $this->runningCommand = null; 249 $this->runningCommand = null;
208 250
209 return $exitCode; 251 return $exitCode;
210 } 252 }
211 253
212 /**
213 * Set a helper set to be used with the command.
214 *
215 * @param HelperSet $helperSet The helper set
216 */
217 public function setHelperSet(HelperSet $helperSet) 254 public function setHelperSet(HelperSet $helperSet)
218 { 255 {
219 $this->helperSet = $helperSet; 256 $this->helperSet = $helperSet;
220 } 257 }
221 258
224 * 261 *
225 * @return HelperSet The HelperSet instance associated with this command 262 * @return HelperSet The HelperSet instance associated with this command
226 */ 263 */
227 public function getHelperSet() 264 public function getHelperSet()
228 { 265 {
266 if (!$this->helperSet) {
267 $this->helperSet = $this->getDefaultHelperSet();
268 }
269
229 return $this->helperSet; 270 return $this->helperSet;
230 } 271 }
231 272
232 /**
233 * Set an input definition to be used with this application.
234 *
235 * @param InputDefinition $definition The input definition
236 */
237 public function setDefinition(InputDefinition $definition) 273 public function setDefinition(InputDefinition $definition)
238 { 274 {
239 $this->definition = $definition; 275 $this->definition = $definition;
240 } 276 }
241 277
244 * 280 *
245 * @return InputDefinition The InputDefinition instance 281 * @return InputDefinition The InputDefinition instance
246 */ 282 */
247 public function getDefinition() 283 public function getDefinition()
248 { 284 {
285 if (!$this->definition) {
286 $this->definition = $this->getDefaultInputDefinition();
287 }
288
249 if ($this->singleCommand) { 289 if ($this->singleCommand) {
250 $inputDefinition = $this->definition; 290 $inputDefinition = $this->definition;
251 $inputDefinition->setArguments(); 291 $inputDefinition->setArguments();
252 292
253 return $inputDefinition; 293 return $inputDefinition;
394 * Adds a command object. 434 * Adds a command object.
395 * 435 *
396 * If a command with the same name already exists, it will be overridden. 436 * If a command with the same name already exists, it will be overridden.
397 * If the command is not enabled it will not be added. 437 * If the command is not enabled it will not be added.
398 * 438 *
399 * @param Command $command A Command object
400 *
401 * @return Command|null The registered command if enabled or null 439 * @return Command|null The registered command if enabled or null
402 */ 440 */
403 public function add(Command $command) 441 public function add(Command $command)
404 { 442 {
443 $this->init();
444
405 $command->setApplication($this); 445 $command->setApplication($this);
406 446
407 if (!$command->isEnabled()) { 447 if (!$command->isEnabled()) {
408 $command->setApplication(null); 448 $command->setApplication(null);
409 449
412 452
413 if (null === $command->getDefinition()) { 453 if (null === $command->getDefinition()) {
414 throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command))); 454 throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command)));
415 } 455 }
416 456
457 if (!$command->getName()) {
458 throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($command)));
459 }
460
417 $this->commands[$command->getName()] = $command; 461 $this->commands[$command->getName()] = $command;
418 462
419 foreach ($command->getAliases() as $alias) { 463 foreach ($command->getAliases() as $alias) {
420 $this->commands[$alias] = $command; 464 $this->commands[$alias] = $command;
421 } 465 }
432 * 476 *
433 * @throws CommandNotFoundException When given command name does not exist 477 * @throws CommandNotFoundException When given command name does not exist
434 */ 478 */
435 public function get($name) 479 public function get($name)
436 { 480 {
437 if (!isset($this->commands[$name])) { 481 $this->init();
482
483 if (!$this->has($name)) {
438 throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name)); 484 throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name));
439 } 485 }
440 486
441 $command = $this->commands[$name]; 487 $command = $this->commands[$name];
442 488
459 * 505 *
460 * @return bool true if the command exists, false otherwise 506 * @return bool true if the command exists, false otherwise
461 */ 507 */
462 public function has($name) 508 public function has($name)
463 { 509 {
464 return isset($this->commands[$name]); 510 $this->init();
511
512 return isset($this->commands[$name]) || ($this->commandLoader && $this->commandLoader->has($name) && $this->add($this->commandLoader->get($name)));
465 } 513 }
466 514
467 /** 515 /**
468 * Returns an array of all unique namespaces used by currently registered commands. 516 * Returns an array of all unique namespaces used by currently registered commands.
469 * 517 *
516 throw new CommandNotFoundException($message, $alternatives); 564 throw new CommandNotFoundException($message, $alternatives);
517 } 565 }
518 566
519 $exact = in_array($namespace, $namespaces, true); 567 $exact = in_array($namespace, $namespaces, true);
520 if (count($namespaces) > 1 && !$exact) { 568 if (count($namespaces) > 1 && !$exact) {
521 throw new CommandNotFoundException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))), array_values($namespaces)); 569 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));
522 } 570 }
523 571
524 return $exact ? $namespace : reset($namespaces); 572 return $exact ? $namespace : reset($namespaces);
525 } 573 }
526 574
536 * 584 *
537 * @throws CommandNotFoundException When command name is incorrect or ambiguous 585 * @throws CommandNotFoundException When command name is incorrect or ambiguous
538 */ 586 */
539 public function find($name) 587 public function find($name)
540 { 588 {
541 $allCommands = array_keys($this->commands); 589 $this->init();
590
591 $aliases = array();
592 $allCommands = $this->commandLoader ? array_merge($this->commandLoader->getNames(), array_keys($this->commands)) : array_keys($this->commands);
542 $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name); 593 $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name);
543 $commands = preg_grep('{^'.$expr.'}', $allCommands); 594 $commands = preg_grep('{^'.$expr.'}', $allCommands);
544 595
545 if (empty($commands) || count(preg_grep('{^'.$expr.'$}', $commands)) < 1) { 596 if (empty($commands)) {
597 $commands = preg_grep('{^'.$expr.'}i', $allCommands);
598 }
599
600 // if no commands matched or we just matched namespaces
601 if (empty($commands) || count(preg_grep('{^'.$expr.'$}i', $commands)) < 1) {
546 if (false !== $pos = strrpos($name, ':')) { 602 if (false !== $pos = strrpos($name, ':')) {
547 // check if a namespace exists and contains commands 603 // check if a namespace exists and contains commands
548 $this->findNamespace(substr($name, 0, $pos)); 604 $this->findNamespace(substr($name, 0, $pos));
549 } 605 }
550 606
562 throw new CommandNotFoundException($message, $alternatives); 618 throw new CommandNotFoundException($message, $alternatives);
563 } 619 }
564 620
565 // filter out aliases for commands which are already on the list 621 // filter out aliases for commands which are already on the list
566 if (count($commands) > 1) { 622 if (count($commands) > 1) {
567 $commandList = $this->commands; 623 $commandList = $this->commandLoader ? array_merge(array_flip($this->commandLoader->getNames()), $this->commands) : $this->commands;
568 $commands = array_filter($commands, function ($nameOrAlias) use ($commandList, $commands) { 624 $commands = array_unique(array_filter($commands, function ($nameOrAlias) use ($commandList, $commands, &$aliases) {
569 $commandName = $commandList[$nameOrAlias]->getName(); 625 $commandName = $commandList[$nameOrAlias] instanceof Command ? $commandList[$nameOrAlias]->getName() : $nameOrAlias;
626 $aliases[$nameOrAlias] = $commandName;
570 627
571 return $commandName === $nameOrAlias || !in_array($commandName, $commands); 628 return $commandName === $nameOrAlias || !in_array($commandName, $commands);
572 }); 629 }));
573 } 630 }
574 631
575 $exact = in_array($name, $commands, true); 632 $exact = in_array($name, $commands, true) || isset($aliases[$name]);
576 if (count($commands) > 1 && !$exact) { 633 if (count($commands) > 1 && !$exact) {
577 $suggestions = $this->getAbbreviationSuggestions(array_values($commands)); 634 $usableWidth = $this->terminal->getWidth() - 10;
578 635 $abbrevs = array_values($commands);
579 throw new CommandNotFoundException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions), array_values($commands)); 636 $maxLen = 0;
637 foreach ($abbrevs as $abbrev) {
638 $maxLen = max(Helper::strlen($abbrev), $maxLen);
639 }
640 $abbrevs = array_map(function ($cmd) use ($commandList, $usableWidth, $maxLen) {
641 if (!$commandList[$cmd] instanceof Command) {
642 return $cmd;
643 }
644 $abbrev = str_pad($cmd, $maxLen, ' ').' '.$commandList[$cmd]->getDescription();
645
646 return Helper::strlen($abbrev) > $usableWidth ? Helper::substr($abbrev, 0, $usableWidth - 3).'...' : $abbrev;
647 }, array_values($commands));
648 $suggestions = $this->getAbbreviationSuggestions($abbrevs);
649
650 throw new CommandNotFoundException(sprintf("Command \"%s\" is ambiguous.\nDid you mean one of these?\n%s", $name, $suggestions), array_values($commands));
580 } 651 }
581 652
582 return $this->get($exact ? $name : reset($commands)); 653 return $this->get($exact ? $name : reset($commands));
583 } 654 }
584 655
591 * 662 *
592 * @return Command[] An array of Command instances 663 * @return Command[] An array of Command instances
593 */ 664 */
594 public function all($namespace = null) 665 public function all($namespace = null)
595 { 666 {
667 $this->init();
668
596 if (null === $namespace) { 669 if (null === $namespace) {
597 return $this->commands; 670 if (!$this->commandLoader) {
671 return $this->commands;
672 }
673
674 $commands = $this->commands;
675 foreach ($this->commandLoader->getNames() as $name) {
676 if (!isset($commands[$name]) && $this->has($name)) {
677 $commands[$name] = $this->get($name);
678 }
679 }
680
681 return $commands;
598 } 682 }
599 683
600 $commands = array(); 684 $commands = array();
601 foreach ($this->commands as $name => $command) { 685 foreach ($this->commands as $name => $command) {
602 if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) { 686 if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) {
603 $commands[$name] = $command; 687 $commands[$name] = $command;
688 }
689 }
690
691 if ($this->commandLoader) {
692 foreach ($this->commandLoader->getNames() as $name) {
693 if (!isset($commands[$name]) && $namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1) && $this->has($name)) {
694 $commands[$name] = $this->get($name);
695 }
604 } 696 }
605 } 697 }
606 698
607 return $commands; 699 return $commands;
608 } 700 }
627 return $abbrevs; 719 return $abbrevs;
628 } 720 }
629 721
630 /** 722 /**
631 * Renders a caught exception. 723 * Renders a caught exception.
632 *
633 * @param \Exception $e An exception instance
634 * @param OutputInterface $output An OutputInterface instance
635 */ 724 */
636 public function renderException(\Exception $e, OutputInterface $output) 725 public function renderException(\Exception $e, OutputInterface $output)
637 { 726 {
638 $output->writeln('', OutputInterface::VERBOSITY_QUIET); 727 $output->writeln('', OutputInterface::VERBOSITY_QUIET);
639 728
729 $this->doRenderException($e, $output);
730
731 if (null !== $this->runningCommand) {
732 $output->writeln(sprintf('<info>%s</info>', sprintf($this->runningCommand->getSynopsis(), $this->getName())), OutputInterface::VERBOSITY_QUIET);
733 $output->writeln('', OutputInterface::VERBOSITY_QUIET);
734 }
735 }
736
737 protected function doRenderException(\Exception $e, OutputInterface $output)
738 {
640 do { 739 do {
641 $title = sprintf( 740 $message = trim($e->getMessage());
642 ' [%s%s] ', 741 if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
643 get_class($e), 742 $title = sprintf(' [%s%s] ', get_class($e), 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : '');
644 $output->isVerbose() && 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : '' 743 $len = Helper::strlen($title);
645 ); 744 } else {
646 745 $len = 0;
647 $len = Helper::strlen($title); 746 }
648 747
649 $width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : PHP_INT_MAX; 748 $width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : PHP_INT_MAX;
650 // 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 749 // 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
651 if (defined('HHVM_VERSION') && $width > 1 << 31) { 750 if (defined('HHVM_VERSION') && $width > 1 << 31) {
652 $width = 1 << 31; 751 $width = 1 << 31;
653 } 752 }
654 $lines = array(); 753 $lines = array();
655 foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) { 754 foreach ('' !== $message ? preg_split('/\r?\n/', $message) : array() as $line) {
656 foreach ($this->splitStringByWidth($line, $width - 4) as $line) { 755 foreach ($this->splitStringByWidth($line, $width - 4) as $line) {
657 // pre-format lines to get the right string length 756 // pre-format lines to get the right string length
658 $lineLength = Helper::strlen($line) + 4; 757 $lineLength = Helper::strlen($line) + 4;
659 $lines[] = array($line, $lineLength); 758 $lines[] = array($line, $lineLength);
660 759
661 $len = max($lineLength, $len); 760 $len = max($lineLength, $len);
662 } 761 }
663 } 762 }
664 763
665 $messages = array(); 764 $messages = array();
765 if (!$e instanceof ExceptionInterface || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
766 $messages[] = sprintf('<comment>%s</comment>', OutputFormatter::escape(sprintf('In %s line %s:', basename($e->getFile()) ?: 'n/a', $e->getLine() ?: 'n/a')));
767 }
666 $messages[] = $emptyLine = sprintf('<error>%s</error>', str_repeat(' ', $len)); 768 $messages[] = $emptyLine = sprintf('<error>%s</error>', str_repeat(' ', $len));
667 $messages[] = sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - Helper::strlen($title)))); 769 if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
770 $messages[] = sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - Helper::strlen($title))));
771 }
668 foreach ($lines as $line) { 772 foreach ($lines as $line) {
669 $messages[] = sprintf('<error> %s %s</error>', OutputFormatter::escape($line[0]), str_repeat(' ', $len - $line[1])); 773 $messages[] = sprintf('<error> %s %s</error>', OutputFormatter::escape($line[0]), str_repeat(' ', $len - $line[1]));
670 } 774 }
671 $messages[] = $emptyLine; 775 $messages[] = $emptyLine;
672 $messages[] = ''; 776 $messages[] = '';
676 if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { 780 if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
677 $output->writeln('<comment>Exception trace:</comment>', OutputInterface::VERBOSITY_QUIET); 781 $output->writeln('<comment>Exception trace:</comment>', OutputInterface::VERBOSITY_QUIET);
678 782
679 // exception related properties 783 // exception related properties
680 $trace = $e->getTrace(); 784 $trace = $e->getTrace();
681 array_unshift($trace, array(
682 'function' => '',
683 'file' => $e->getFile() !== null ? $e->getFile() : 'n/a',
684 'line' => $e->getLine() !== null ? $e->getLine() : 'n/a',
685 'args' => array(),
686 ));
687 785
688 for ($i = 0, $count = count($trace); $i < $count; ++$i) { 786 for ($i = 0, $count = count($trace); $i < $count; ++$i) {
689 $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : ''; 787 $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : '';
690 $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : ''; 788 $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : '';
691 $function = $trace[$i]['function']; 789 $function = $trace[$i]['function'];
696 } 794 }
697 795
698 $output->writeln('', OutputInterface::VERBOSITY_QUIET); 796 $output->writeln('', OutputInterface::VERBOSITY_QUIET);
699 } 797 }
700 } while ($e = $e->getPrevious()); 798 } while ($e = $e->getPrevious());
701
702 if (null !== $this->runningCommand) {
703 $output->writeln(sprintf('<info>%s</info>', sprintf($this->runningCommand->getSynopsis(), $this->getName())), OutputInterface::VERBOSITY_QUIET);
704 $output->writeln('', OutputInterface::VERBOSITY_QUIET);
705 }
706 } 799 }
707 800
708 /** 801 /**
709 * Tries to figure out the terminal width in which this application runs. 802 * Tries to figure out the terminal width in which this application runs.
710 * 803 *
769 return $this; 862 return $this;
770 } 863 }
771 864
772 /** 865 /**
773 * Configures the input and output instances based on the user arguments and options. 866 * Configures the input and output instances based on the user arguments and options.
774 *
775 * @param InputInterface $input An InputInterface instance
776 * @param OutputInterface $output An OutputInterface instance
777 */ 867 */
778 protected function configureIO(InputInterface $input, OutputInterface $output) 868 protected function configureIO(InputInterface $input, OutputInterface $output)
779 { 869 {
780 if (true === $input->hasParameterOption(array('--ansi'), true)) { 870 if (true === $input->hasParameterOption(array('--ansi'), true)) {
781 $output->setDecorated(true); 871 $output->setDecorated(true);
801 if (!@posix_isatty($inputStream) && false === getenv('SHELL_INTERACTIVE')) { 891 if (!@posix_isatty($inputStream) && false === getenv('SHELL_INTERACTIVE')) {
802 $input->setInteractive(false); 892 $input->setInteractive(false);
803 } 893 }
804 } 894 }
805 895
896 switch ($shellVerbosity = (int) getenv('SHELL_VERBOSITY')) {
897 case -1: $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); break;
898 case 1: $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); break;
899 case 2: $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); break;
900 case 3: $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); break;
901 default: $shellVerbosity = 0; break;
902 }
903
806 if (true === $input->hasParameterOption(array('--quiet', '-q'), true)) { 904 if (true === $input->hasParameterOption(array('--quiet', '-q'), true)) {
807 $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); 905 $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
808 $input->setInteractive(false); 906 $shellVerbosity = -1;
809 } else { 907 } else {
810 if ($input->hasParameterOption('-vvv', true) || $input->hasParameterOption('--verbose=3', true) || $input->getParameterOption('--verbose', false, true) === 3) { 908 if ($input->hasParameterOption('-vvv', true) || $input->hasParameterOption('--verbose=3', true) || 3 === $input->getParameterOption('--verbose', false, true)) {
811 $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); 909 $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
812 } elseif ($input->hasParameterOption('-vv', true) || $input->hasParameterOption('--verbose=2', true) || $input->getParameterOption('--verbose', false, true) === 2) { 910 $shellVerbosity = 3;
911 } elseif ($input->hasParameterOption('-vv', true) || $input->hasParameterOption('--verbose=2', true) || 2 === $input->getParameterOption('--verbose', false, true)) {
813 $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); 912 $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
913 $shellVerbosity = 2;
814 } elseif ($input->hasParameterOption('-v', true) || $input->hasParameterOption('--verbose=1', true) || $input->hasParameterOption('--verbose', true) || $input->getParameterOption('--verbose', false, true)) { 914 } elseif ($input->hasParameterOption('-v', true) || $input->hasParameterOption('--verbose=1', true) || $input->hasParameterOption('--verbose', true) || $input->getParameterOption('--verbose', false, true)) {
815 $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); 915 $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
816 } 916 $shellVerbosity = 1;
817 } 917 }
918 }
919
920 if (-1 === $shellVerbosity) {
921 $input->setInteractive(false);
922 }
923
924 putenv('SHELL_VERBOSITY='.$shellVerbosity);
925 $_ENV['SHELL_VERBOSITY'] = $shellVerbosity;
926 $_SERVER['SHELL_VERBOSITY'] = $shellVerbosity;
818 } 927 }
819 928
820 /** 929 /**
821 * Runs the current command. 930 * Runs the current command.
822 * 931 *
823 * If an event dispatcher has been attached to the application, 932 * If an event dispatcher has been attached to the application,
824 * events are also dispatched during the life-cycle of the command. 933 * events are also dispatched during the life-cycle of the command.
825 *
826 * @param Command $command A Command instance
827 * @param InputInterface $input An Input instance
828 * @param OutputInterface $output An Output instance
829 * 934 *
830 * @return int 0 if everything went fine, or an error code 935 * @return int 0 if everything went fine, or an error code
831 */ 936 */
832 protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) 937 protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)
833 { 938 {
862 } 967 }
863 } catch (\Exception $e) { 968 } catch (\Exception $e) {
864 } catch (\Throwable $e) { 969 } catch (\Throwable $e) {
865 } 970 }
866 if (null !== $e) { 971 if (null !== $e) {
867 $x = $e instanceof \Exception ? $e : new FatalThrowableError($e); 972 if ($this->dispatcher->hasListeners(ConsoleEvents::EXCEPTION)) {
868 $event = new ConsoleExceptionEvent($command, $input, $output, $x, $x->getCode()); 973 $x = $e instanceof \Exception ? $e : new FatalThrowableError($e);
869 $this->dispatcher->dispatch(ConsoleEvents::EXCEPTION, $event); 974 $event = new ConsoleExceptionEvent($command, $input, $output, $x, $x->getCode());
870 975 $this->dispatcher->dispatch(ConsoleEvents::EXCEPTION, $event);
871 if ($x !== $event->getException()) { 976
872 $e = $event->getException(); 977 if ($x !== $event->getException()) {
873 } 978 $e = $event->getException();
874 $exitCode = $e->getCode(); 979 }
980 }
981 $event = new ConsoleErrorEvent($input, $output, $e, $command);
982 $this->dispatcher->dispatch(ConsoleEvents::ERROR, $event);
983 $e = $event->getError();
984
985 if (0 === $exitCode = $event->getExitCode()) {
986 $e = null;
987 }
875 } 988 }
876 989
877 $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode); 990 $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode);
878 $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event); 991 $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event);
879 992
884 return $event->getExitCode(); 997 return $event->getExitCode();
885 } 998 }
886 999
887 /** 1000 /**
888 * Gets the name of the command based on input. 1001 * Gets the name of the command based on input.
889 *
890 * @param InputInterface $input The input interface
891 * 1002 *
892 * @return string The command name 1003 * @return string The command name
893 */ 1004 */
894 protected function getCommandName(InputInterface $input) 1005 protected function getCommandName(InputInterface $input)
895 { 1006 {
948 * 1059 *
949 * @return string A formatted string of abbreviated suggestions 1060 * @return string A formatted string of abbreviated suggestions
950 */ 1061 */
951 private function getAbbreviationSuggestions($abbrevs) 1062 private function getAbbreviationSuggestions($abbrevs)
952 { 1063 {
953 return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : ''); 1064 return ' '.implode("\n ", $abbrevs);
954 } 1065 }
955 1066
956 /** 1067 /**
957 * Returns the namespace part of the command name. 1068 * Returns the namespace part of the command name.
958 * 1069 *
973 1084
974 /** 1085 /**
975 * Finds alternative of $name among $collection, 1086 * Finds alternative of $name among $collection,
976 * if nothing is found in $collection, try in $abbrevs. 1087 * if nothing is found in $collection, try in $abbrevs.
977 * 1088 *
978 * @param string $name The string 1089 * @param string $name The string
979 * @param array|\Traversable $collection The collection 1090 * @param iterable $collection The collection
980 * 1091 *
981 * @return string[] A sorted array of similar string 1092 * @return string[] A sorted array of similar string
982 */ 1093 */
983 private function findAlternatives($name, $collection) 1094 private function findAlternatives($name, $collection)
984 { 1095 {
1015 $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev; 1126 $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
1016 } 1127 }
1017 } 1128 }
1018 1129
1019 $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; }); 1130 $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; });
1020 asort($alternatives); 1131 ksort($alternatives, SORT_NATURAL | SORT_FLAG_CASE);
1021 1132
1022 return array_keys($alternatives); 1133 return array_keys($alternatives);
1023 } 1134 }
1024 1135
1025 /** 1136 /**
1064 } 1175 }
1065 // if not, push current line to array and make new line 1176 // if not, push current line to array and make new line
1066 $lines[] = str_pad($line, $width); 1177 $lines[] = str_pad($line, $width);
1067 $line = $char; 1178 $line = $char;
1068 } 1179 }
1069 if ('' !== $line) { 1180
1070 $lines[] = count($lines) ? str_pad($line, $width) : $line; 1181 $lines[] = count($lines) ? str_pad($line, $width) : $line;
1071 }
1072 1182
1073 mb_convert_variables($encoding, 'utf8', $lines); 1183 mb_convert_variables($encoding, 'utf8', $lines);
1074 1184
1075 return $lines; 1185 return $lines;
1076 } 1186 }
1096 } 1206 }
1097 } 1207 }
1098 1208
1099 return $namespaces; 1209 return $namespaces;
1100 } 1210 }
1211
1212 private function init()
1213 {
1214 if ($this->initialized) {
1215 return;
1216 }
1217 $this->initialized = true;
1218
1219 foreach ($this->getDefaultCommands() as $command) {
1220 $this->add($command);
1221 }
1222 }
1101 } 1223 }