Application.php 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Console;
  11. use Symfony\Component\Console\Descriptor\TextDescriptor;
  12. use Symfony\Component\Console\Descriptor\XmlDescriptor;
  13. use Symfony\Component\Console\Input\InputInterface;
  14. use Symfony\Component\Console\Input\ArgvInput;
  15. use Symfony\Component\Console\Input\ArrayInput;
  16. use Symfony\Component\Console\Input\InputDefinition;
  17. use Symfony\Component\Console\Input\InputOption;
  18. use Symfony\Component\Console\Input\InputArgument;
  19. use Symfony\Component\Console\Output\OutputInterface;
  20. use Symfony\Component\Console\Output\ConsoleOutput;
  21. use Symfony\Component\Console\Output\ConsoleOutputInterface;
  22. use Symfony\Component\Console\Command\Command;
  23. use Symfony\Component\Console\Command\HelpCommand;
  24. use Symfony\Component\Console\Command\ListCommand;
  25. use Symfony\Component\Console\Helper\HelperSet;
  26. use Symfony\Component\Console\Helper\FormatterHelper;
  27. use Symfony\Component\Console\Helper\DialogHelper;
  28. use Symfony\Component\Console\Helper\ProgressHelper;
  29. use Symfony\Component\Console\Helper\TableHelper;
  30. use Symfony\Component\Console\Event\ConsoleCommandEvent;
  31. use Symfony\Component\Console\Event\ConsoleExceptionEvent;
  32. use Symfony\Component\Console\Event\ConsoleTerminateEvent;
  33. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  34. /**
  35. * An Application is the container for a collection of commands.
  36. *
  37. * It is the main entry point of a Console application.
  38. *
  39. * This class is optimized for a standard CLI environment.
  40. *
  41. * Usage:
  42. *
  43. * $app = new Application('myapp', '1.0 (stable)');
  44. * $app->add(new SimpleCommand());
  45. * $app->run();
  46. *
  47. * @author Fabien Potencier <fabien@symfony.com>
  48. *
  49. * @api
  50. */
  51. class Application
  52. {
  53. private $commands;
  54. private $wantHelps = false;
  55. private $runningCommand;
  56. private $name;
  57. private $version;
  58. private $catchExceptions;
  59. private $autoExit;
  60. private $definition;
  61. private $helperSet;
  62. private $dispatcher;
  63. /**
  64. * Constructor.
  65. *
  66. * @param string $name The name of the application
  67. * @param string $version The version of the application
  68. *
  69. * @api
  70. */
  71. public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN')
  72. {
  73. $this->name = $name;
  74. $this->version = $version;
  75. $this->catchExceptions = true;
  76. $this->autoExit = true;
  77. $this->commands = array();
  78. $this->helperSet = $this->getDefaultHelperSet();
  79. $this->definition = $this->getDefaultInputDefinition();
  80. foreach ($this->getDefaultCommands() as $command) {
  81. $this->add($command);
  82. }
  83. }
  84. public function setDispatcher(EventDispatcherInterface $dispatcher)
  85. {
  86. $this->dispatcher = $dispatcher;
  87. }
  88. /**
  89. * Runs the current application.
  90. *
  91. * @param InputInterface $input An Input instance
  92. * @param OutputInterface $output An Output instance
  93. *
  94. * @return integer 0 if everything went fine, or an error code
  95. *
  96. * @throws \Exception When doRun returns Exception
  97. *
  98. * @api
  99. */
  100. public function run(InputInterface $input = null, OutputInterface $output = null)
  101. {
  102. if (null === $input) {
  103. $input = new ArgvInput();
  104. }
  105. if (null === $output) {
  106. $output = new ConsoleOutput();
  107. }
  108. $this->configureIO($input, $output);
  109. try {
  110. $exitCode = $this->doRun($input, $output);
  111. } catch (\Exception $e) {
  112. if (!$this->catchExceptions) {
  113. throw $e;
  114. }
  115. if ($output instanceof ConsoleOutputInterface) {
  116. $this->renderException($e, $output->getErrorOutput());
  117. } else {
  118. $this->renderException($e, $output);
  119. }
  120. $exitCode = $e->getCode();
  121. if (is_numeric($exitCode)) {
  122. $exitCode = (int) $exitCode;
  123. if (0 === $exitCode) {
  124. $exitCode = 1;
  125. }
  126. } else {
  127. $exitCode = 1;
  128. }
  129. }
  130. if ($this->autoExit) {
  131. if ($exitCode > 255) {
  132. $exitCode = 255;
  133. }
  134. // @codeCoverageIgnoreStart
  135. exit($exitCode);
  136. // @codeCoverageIgnoreEnd
  137. }
  138. return $exitCode;
  139. }
  140. /**
  141. * Runs the current application.
  142. *
  143. * @param InputInterface $input An Input instance
  144. * @param OutputInterface $output An Output instance
  145. *
  146. * @return integer 0 if everything went fine, or an error code
  147. */
  148. public function doRun(InputInterface $input, OutputInterface $output)
  149. {
  150. if (true === $input->hasParameterOption(array('--version', '-V'))) {
  151. $output->writeln($this->getLongVersion());
  152. return 0;
  153. }
  154. $name = $this->getCommandName($input);
  155. if (true === $input->hasParameterOption(array('--help', '-h'))) {
  156. if (!$name) {
  157. $name = 'help';
  158. $input = new ArrayInput(array('command' => 'help'));
  159. } else {
  160. $this->wantHelps = true;
  161. }
  162. }
  163. if (!$name) {
  164. $name = 'list';
  165. $input = new ArrayInput(array('command' => 'list'));
  166. }
  167. // the command name MUST be the first element of the input
  168. $command = $this->find($name);
  169. $this->runningCommand = $command;
  170. $exitCode = $this->doRunCommand($command, $input, $output);
  171. $this->runningCommand = null;
  172. return $exitCode;
  173. }
  174. /**
  175. * Set a helper set to be used with the command.
  176. *
  177. * @param HelperSet $helperSet The helper set
  178. *
  179. * @api
  180. */
  181. public function setHelperSet(HelperSet $helperSet)
  182. {
  183. $this->helperSet = $helperSet;
  184. }
  185. /**
  186. * Get the helper set associated with the command.
  187. *
  188. * @return HelperSet The HelperSet instance associated with this command
  189. *
  190. * @api
  191. */
  192. public function getHelperSet()
  193. {
  194. return $this->helperSet;
  195. }
  196. /**
  197. * Set an input definition set to be used with this application
  198. *
  199. * @param InputDefinition $definition The input definition
  200. *
  201. * @api
  202. */
  203. public function setDefinition(InputDefinition $definition)
  204. {
  205. $this->definition = $definition;
  206. }
  207. /**
  208. * Gets the InputDefinition related to this Application.
  209. *
  210. * @return InputDefinition The InputDefinition instance
  211. */
  212. public function getDefinition()
  213. {
  214. return $this->definition;
  215. }
  216. /**
  217. * Gets the help message.
  218. *
  219. * @return string A help message.
  220. */
  221. public function getHelp()
  222. {
  223. $messages = array(
  224. $this->getLongVersion(),
  225. '',
  226. '<comment>Usage:</comment>',
  227. ' [options] command [arguments]',
  228. '',
  229. '<comment>Options:</comment>',
  230. );
  231. foreach ($this->getDefinition()->getOptions() as $option) {
  232. $messages[] = sprintf(' %-29s %s %s',
  233. '<info>--'.$option->getName().'</info>',
  234. $option->getShortcut() ? '<info>-'.$option->getShortcut().'</info>' : ' ',
  235. $option->getDescription()
  236. );
  237. }
  238. return implode(PHP_EOL, $messages);
  239. }
  240. /**
  241. * Sets whether to catch exceptions or not during commands execution.
  242. *
  243. * @param Boolean $boolean Whether to catch exceptions or not during commands execution
  244. *
  245. * @api
  246. */
  247. public function setCatchExceptions($boolean)
  248. {
  249. $this->catchExceptions = (Boolean) $boolean;
  250. }
  251. /**
  252. * Sets whether to automatically exit after a command execution or not.
  253. *
  254. * @param Boolean $boolean Whether to automatically exit after a command execution or not
  255. *
  256. * @api
  257. */
  258. public function setAutoExit($boolean)
  259. {
  260. $this->autoExit = (Boolean) $boolean;
  261. }
  262. /**
  263. * Gets the name of the application.
  264. *
  265. * @return string The application name
  266. *
  267. * @api
  268. */
  269. public function getName()
  270. {
  271. return $this->name;
  272. }
  273. /**
  274. * Sets the application name.
  275. *
  276. * @param string $name The application name
  277. *
  278. * @api
  279. */
  280. public function setName($name)
  281. {
  282. $this->name = $name;
  283. }
  284. /**
  285. * Gets the application version.
  286. *
  287. * @return string The application version
  288. *
  289. * @api
  290. */
  291. public function getVersion()
  292. {
  293. return $this->version;
  294. }
  295. /**
  296. * Sets the application version.
  297. *
  298. * @param string $version The application version
  299. *
  300. * @api
  301. */
  302. public function setVersion($version)
  303. {
  304. $this->version = $version;
  305. }
  306. /**
  307. * Returns the long version of the application.
  308. *
  309. * @return string The long application version
  310. *
  311. * @api
  312. */
  313. public function getLongVersion()
  314. {
  315. if ('UNKNOWN' !== $this->getName() && 'UNKNOWN' !== $this->getVersion()) {
  316. return sprintf('<info>%s</info> version <comment>%s</comment>', $this->getName(), $this->getVersion());
  317. }
  318. return '<info>Console Tool</info>';
  319. }
  320. /**
  321. * Registers a new command.
  322. *
  323. * @param string $name The command name
  324. *
  325. * @return Command The newly created command
  326. *
  327. * @api
  328. */
  329. public function register($name)
  330. {
  331. return $this->add(new Command($name));
  332. }
  333. /**
  334. * Adds an array of command objects.
  335. *
  336. * @param Command[] $commands An array of commands
  337. *
  338. * @api
  339. */
  340. public function addCommands(array $commands)
  341. {
  342. foreach ($commands as $command) {
  343. $this->add($command);
  344. }
  345. }
  346. /**
  347. * Adds a command object.
  348. *
  349. * If a command with the same name already exists, it will be overridden.
  350. *
  351. * @param Command $command A Command object
  352. *
  353. * @return Command The registered command
  354. *
  355. * @api
  356. */
  357. public function add(Command $command)
  358. {
  359. $command->setApplication($this);
  360. if (!$command->isEnabled()) {
  361. $command->setApplication(null);
  362. return;
  363. }
  364. $this->commands[$command->getName()] = $command;
  365. foreach ($command->getAliases() as $alias) {
  366. $this->commands[$alias] = $command;
  367. }
  368. return $command;
  369. }
  370. /**
  371. * Returns a registered command by name or alias.
  372. *
  373. * @param string $name The command name or alias
  374. *
  375. * @return Command A Command object
  376. *
  377. * @throws \InvalidArgumentException When command name given does not exist
  378. *
  379. * @api
  380. */
  381. public function get($name)
  382. {
  383. if (!isset($this->commands[$name])) {
  384. throw new \InvalidArgumentException(sprintf('The command "%s" does not exist.', $name));
  385. }
  386. $command = $this->commands[$name];
  387. if ($this->wantHelps) {
  388. $this->wantHelps = false;
  389. $helpCommand = $this->get('help');
  390. $helpCommand->setCommand($command);
  391. return $helpCommand;
  392. }
  393. return $command;
  394. }
  395. /**
  396. * Returns true if the command exists, false otherwise.
  397. *
  398. * @param string $name The command name or alias
  399. *
  400. * @return Boolean true if the command exists, false otherwise
  401. *
  402. * @api
  403. */
  404. public function has($name)
  405. {
  406. return isset($this->commands[$name]);
  407. }
  408. /**
  409. * Returns an array of all unique namespaces used by currently registered commands.
  410. *
  411. * It does not returns the global namespace which always exists.
  412. *
  413. * @return array An array of namespaces
  414. */
  415. public function getNamespaces()
  416. {
  417. $namespaces = array();
  418. foreach ($this->commands as $command) {
  419. $namespaces[] = $this->extractNamespace($command->getName());
  420. foreach ($command->getAliases() as $alias) {
  421. $namespaces[] = $this->extractNamespace($alias);
  422. }
  423. }
  424. return array_values(array_unique(array_filter($namespaces)));
  425. }
  426. /**
  427. * Finds a registered namespace by a name or an abbreviation.
  428. *
  429. * @param string $namespace A namespace or abbreviation to search for
  430. *
  431. * @return string A registered namespace
  432. *
  433. * @throws \InvalidArgumentException When namespace is incorrect or ambiguous
  434. */
  435. public function findNamespace($namespace)
  436. {
  437. $allNamespaces = $this->getNamespaces();
  438. $found = '';
  439. foreach (explode(':', $namespace) as $i => $part) {
  440. // select sub-namespaces matching the current namespace we found
  441. $namespaces = array();
  442. foreach ($allNamespaces as $n) {
  443. if ('' === $found || 0 === strpos($n, $found)) {
  444. $namespaces[$n] = explode(':', $n);
  445. }
  446. }
  447. $abbrevs = static::getAbbreviations(array_unique(array_values(array_filter(array_map(function ($p) use ($i) { return isset($p[$i]) ? $p[$i] : ''; }, $namespaces)))));
  448. if (!isset($abbrevs[$part])) {
  449. $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);
  450. if (1 <= $i) {
  451. $part = $found.':'.$part;
  452. }
  453. if ($alternatives = $this->findAlternativeNamespace($part, $abbrevs)) {
  454. if (1 == count($alternatives)) {
  455. $message .= "\n\nDid you mean this?\n ";
  456. } else {
  457. $message .= "\n\nDid you mean one of these?\n ";
  458. }
  459. $message .= implode("\n ", $alternatives);
  460. }
  461. throw new \InvalidArgumentException($message);
  462. }
  463. // there are multiple matches, but $part is an exact match of one of them so we select it
  464. if (in_array($part, $abbrevs[$part])) {
  465. $abbrevs[$part] = array($part);
  466. }
  467. if (count($abbrevs[$part]) > 1) {
  468. throw new \InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions($abbrevs[$part])));
  469. }
  470. $found .= $found ? ':' . $abbrevs[$part][0] : $abbrevs[$part][0];
  471. }
  472. return $found;
  473. }
  474. /**
  475. * Finds a command by name or alias.
  476. *
  477. * Contrary to get, this command tries to find the best
  478. * match if you give it an abbreviation of a name or alias.
  479. *
  480. * @param string $name A command name or a command alias
  481. *
  482. * @return Command A Command instance
  483. *
  484. * @throws \InvalidArgumentException When command name is incorrect or ambiguous
  485. *
  486. * @api
  487. */
  488. public function find($name)
  489. {
  490. // namespace
  491. $namespace = '';
  492. $searchName = $name;
  493. if (false !== $pos = strrpos($name, ':')) {
  494. $namespace = $this->findNamespace(substr($name, 0, $pos));
  495. $searchName = $namespace.substr($name, $pos);
  496. }
  497. // name
  498. $commands = array();
  499. foreach ($this->commands as $command) {
  500. $extractedNamespace = $this->extractNamespace($command->getName());
  501. if ($extractedNamespace === $namespace
  502. || !empty($namespace) && 0 === strpos($extractedNamespace, $namespace)
  503. ) {
  504. $commands[] = $command->getName();
  505. }
  506. }
  507. $abbrevs = static::getAbbreviations(array_unique($commands));
  508. if (isset($abbrevs[$searchName]) && 1 == count($abbrevs[$searchName])) {
  509. return $this->get($abbrevs[$searchName][0]);
  510. }
  511. if (isset($abbrevs[$searchName]) && in_array($searchName, $abbrevs[$searchName])) {
  512. return $this->get($searchName);
  513. }
  514. if (isset($abbrevs[$searchName]) && count($abbrevs[$searchName]) > 1) {
  515. $suggestions = $this->getAbbreviationSuggestions($abbrevs[$searchName]);
  516. throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions));
  517. }
  518. // aliases
  519. $aliases = array();
  520. foreach ($this->commands as $command) {
  521. foreach ($command->getAliases() as $alias) {
  522. $extractedNamespace = $this->extractNamespace($alias);
  523. if ($extractedNamespace === $namespace
  524. || !empty($namespace) && 0 === strpos($extractedNamespace, $namespace)
  525. ) {
  526. $aliases[] = $alias;
  527. }
  528. }
  529. }
  530. $aliases = static::getAbbreviations(array_unique($aliases));
  531. if (!isset($aliases[$searchName])) {
  532. $message = sprintf('Command "%s" is not defined.', $name);
  533. if ($alternatives = $this->findAlternativeCommands($searchName, $abbrevs)) {
  534. if (1 == count($alternatives)) {
  535. $message .= "\n\nDid you mean this?\n ";
  536. } else {
  537. $message .= "\n\nDid you mean one of these?\n ";
  538. }
  539. $message .= implode("\n ", $alternatives);
  540. }
  541. throw new \InvalidArgumentException($message);
  542. }
  543. if (count($aliases[$searchName]) > 1) {
  544. throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $this->getAbbreviationSuggestions($aliases[$searchName])));
  545. }
  546. return $this->get($aliases[$searchName][0]);
  547. }
  548. /**
  549. * Gets the commands (registered in the given namespace if provided).
  550. *
  551. * The array keys are the full names and the values the command instances.
  552. *
  553. * @param string $namespace A namespace name
  554. *
  555. * @return Command[] An array of Command instances
  556. *
  557. * @api
  558. */
  559. public function all($namespace = null)
  560. {
  561. if (null === $namespace) {
  562. return $this->commands;
  563. }
  564. $commands = array();
  565. foreach ($this->commands as $name => $command) {
  566. if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) {
  567. $commands[$name] = $command;
  568. }
  569. }
  570. return $commands;
  571. }
  572. /**
  573. * Returns an array of possible abbreviations given a set of names.
  574. *
  575. * @param array $names An array of names
  576. *
  577. * @return array An array of abbreviations
  578. */
  579. public static function getAbbreviations($names)
  580. {
  581. $abbrevs = array();
  582. foreach ($names as $name) {
  583. for ($len = strlen($name); $len > 0; --$len) {
  584. $abbrev = substr($name, 0, $len);
  585. $abbrevs[$abbrev][] = $name;
  586. }
  587. }
  588. return $abbrevs;
  589. }
  590. /**
  591. * Returns a text representation of the Application.
  592. *
  593. * @param string $namespace An optional namespace name
  594. * @param boolean $raw Whether to return raw command list
  595. *
  596. * @return string A string representing the Application
  597. *
  598. * @deprecated Deprecated since version 2.3, to be removed in 3.0.
  599. */
  600. public function asText($namespace = null, $raw = false)
  601. {
  602. $descriptor = new TextDescriptor();
  603. return $descriptor->describe($this, array('namespace' => $namespace, 'raw_text' => $raw));
  604. }
  605. /**
  606. * Returns an XML representation of the Application.
  607. *
  608. * @param string $namespace An optional namespace name
  609. * @param Boolean $asDom Whether to return a DOM or an XML string
  610. *
  611. * @return string|\DOMDocument An XML string representing the Application
  612. *
  613. * @deprecated Deprecated since version 2.3, to be removed in 3.0.
  614. */
  615. public function asXml($namespace = null, $asDom = false)
  616. {
  617. $descriptor = new XmlDescriptor();
  618. return $descriptor->describe($this, array('namespace' => $namespace, 'as_dom' => $asDom));
  619. }
  620. /**
  621. * Renders a caught exception.
  622. *
  623. * @param Exception $e An exception instance
  624. * @param OutputInterface $output An OutputInterface instance
  625. */
  626. public function renderException($e, $output)
  627. {
  628. $strlen = function ($string) {
  629. if (!function_exists('mb_strlen')) {
  630. return strlen($string);
  631. }
  632. if (false === $encoding = mb_detect_encoding($string)) {
  633. return strlen($string);
  634. }
  635. return mb_strlen($string, $encoding);
  636. };
  637. do {
  638. $title = sprintf(' [%s] ', get_class($e));
  639. $len = $strlen($title);
  640. $width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : PHP_INT_MAX;
  641. $lines = array();
  642. foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) {
  643. foreach (str_split($line, $width - 4) as $line) {
  644. $lines[] = sprintf(' %s ', $line);
  645. $len = max($strlen($line) + 4, $len);
  646. }
  647. }
  648. $messages = array(str_repeat(' ', $len), $title.str_repeat(' ', max(0, $len - $strlen($title))));
  649. foreach ($lines as $line) {
  650. $messages[] = $line.str_repeat(' ', $len - $strlen($line));
  651. }
  652. $messages[] = str_repeat(' ', $len);
  653. $output->writeln("");
  654. $output->writeln("");
  655. foreach ($messages as $message) {
  656. $output->writeln('<error>'.$message.'</error>');
  657. }
  658. $output->writeln("");
  659. $output->writeln("");
  660. if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
  661. $output->writeln('<comment>Exception trace:</comment>');
  662. // exception related properties
  663. $trace = $e->getTrace();
  664. array_unshift($trace, array(
  665. 'function' => '',
  666. 'file' => $e->getFile() != null ? $e->getFile() : 'n/a',
  667. 'line' => $e->getLine() != null ? $e->getLine() : 'n/a',
  668. 'args' => array(),
  669. ));
  670. for ($i = 0, $count = count($trace); $i < $count; $i++) {
  671. $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : '';
  672. $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : '';
  673. $function = $trace[$i]['function'];
  674. $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a';
  675. $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a';
  676. $output->writeln(sprintf(' %s%s%s() at <info>%s:%s</info>', $class, $type, $function, $file, $line));
  677. }
  678. $output->writeln("");
  679. $output->writeln("");
  680. }
  681. } while ($e = $e->getPrevious());
  682. if (null !== $this->runningCommand) {
  683. $output->writeln(sprintf('<info>%s</info>', sprintf($this->runningCommand->getSynopsis(), $this->getName())));
  684. $output->writeln("");
  685. $output->writeln("");
  686. }
  687. }
  688. /**
  689. * Tries to figure out the terminal width in which this application runs
  690. *
  691. * @return int|null
  692. */
  693. protected function getTerminalWidth()
  694. {
  695. $dimensions = $this->getTerminalDimensions();
  696. return $dimensions[0];
  697. }
  698. /**
  699. * Tries to figure out the terminal height in which this application runs
  700. *
  701. * @return int|null
  702. */
  703. protected function getTerminalHeight()
  704. {
  705. $dimensions = $this->getTerminalDimensions();
  706. return $dimensions[1];
  707. }
  708. /**
  709. * Tries to figure out the terminal dimensions based on the current environment
  710. *
  711. * @return array Array containing width and height
  712. */
  713. public function getTerminalDimensions()
  714. {
  715. if (defined('PHP_WINDOWS_VERSION_BUILD')) {
  716. // extract [w, H] from "wxh (WxH)"
  717. if (preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim(getenv('ANSICON')), $matches)) {
  718. return array((int) $matches[1], (int) $matches[2]);
  719. }
  720. // extract [w, h] from "wxh"
  721. if (preg_match('/^(\d+)x(\d+)$/', $this->getConsoleMode(), $matches)) {
  722. return array((int) $matches[1], (int) $matches[2]);
  723. }
  724. }
  725. if ($sttyString = $this->getSttyColumns()) {
  726. // extract [w, h] from "rows h; columns w;"
  727. if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) {
  728. return array((int) $matches[2], (int) $matches[1]);
  729. }
  730. // extract [w, h] from "; h rows; w columns"
  731. if (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) {
  732. return array((int) $matches[2], (int) $matches[1]);
  733. }
  734. }
  735. return array(null, null);
  736. }
  737. /**
  738. * Configures the input and output instances based on the user arguments and options.
  739. *
  740. * @param InputInterface $input An InputInterface instance
  741. * @param OutputInterface $output An OutputInterface instance
  742. */
  743. protected function configureIO(InputInterface $input, OutputInterface $output)
  744. {
  745. if (true === $input->hasParameterOption(array('--ansi'))) {
  746. $output->setDecorated(true);
  747. } elseif (true === $input->hasParameterOption(array('--no-ansi'))) {
  748. $output->setDecorated(false);
  749. }
  750. if (true === $input->hasParameterOption(array('--no-interaction', '-n'))) {
  751. $input->setInteractive(false);
  752. } elseif (function_exists('posix_isatty') && $this->getHelperSet()->has('dialog')) {
  753. $inputStream = $this->getHelperSet()->get('dialog')->getInputStream();
  754. if (!@posix_isatty($inputStream)) {
  755. $input->setInteractive(false);
  756. }
  757. }
  758. if (true === $input->hasParameterOption(array('--quiet', '-q'))) {
  759. $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
  760. } else {
  761. if ($input->hasParameterOption('-vvv') || $input->hasParameterOption('--verbose=3') || $input->getParameterOption('--verbose') === 3) {
  762. $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
  763. } elseif ($input->hasParameterOption('-vv') || $input->hasParameterOption('--verbose=2') || $input->getParameterOption('--verbose') === 2) {
  764. $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
  765. } elseif ($input->hasParameterOption('-v') || $input->hasParameterOption('--verbose=1') || $input->hasParameterOption('--verbose') || $input->getParameterOption('--verbose')) {
  766. $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
  767. }
  768. }
  769. }
  770. /**
  771. * Runs the current command.
  772. *
  773. * If an event dispatcher has been attached to the application,
  774. * events are also dispatched during the life-cycle of the command.
  775. *
  776. * @param Command $command A Command instance
  777. * @param InputInterface $input An Input instance
  778. * @param OutputInterface $output An Output instance
  779. *
  780. * @return integer 0 if everything went fine, or an error code
  781. */
  782. protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)
  783. {
  784. if (null === $this->dispatcher) {
  785. return $command->run($input, $output);
  786. }
  787. $event = new ConsoleCommandEvent($command, $input, $output);
  788. $this->dispatcher->dispatch(ConsoleEvents::COMMAND, $event);
  789. try {
  790. $exitCode = $command->run($input, $output);
  791. } catch (\Exception $e) {
  792. $event = new ConsoleTerminateEvent($command, $input, $output, $e->getCode());
  793. $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event);
  794. $event = new ConsoleExceptionEvent($command, $input, $output, $e, $event->getExitCode());
  795. $this->dispatcher->dispatch(ConsoleEvents::EXCEPTION, $event);
  796. throw $event->getException();
  797. }
  798. $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode);
  799. $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event);
  800. return $event->getExitCode();
  801. }
  802. /**
  803. * Gets the name of the command based on input.
  804. *
  805. * @param InputInterface $input The input interface
  806. *
  807. * @return string The command name
  808. */
  809. protected function getCommandName(InputInterface $input)
  810. {
  811. return $input->getFirstArgument();
  812. }
  813. /**
  814. * Gets the default input definition.
  815. *
  816. * @return InputDefinition An InputDefinition instance
  817. */
  818. protected function getDefaultInputDefinition()
  819. {
  820. return new InputDefinition(array(
  821. new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
  822. new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message.'),
  823. new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message.'),
  824. 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'),
  825. new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version.'),
  826. new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output.'),
  827. new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output.'),
  828. new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question.'),
  829. ));
  830. }
  831. /**
  832. * Gets the default commands that should always be available.
  833. *
  834. * @return Command[] An array of default Command instances
  835. */
  836. protected function getDefaultCommands()
  837. {
  838. return array(new HelpCommand(), new ListCommand());
  839. }
  840. /**
  841. * Gets the default helper set with the helpers that should always be available.
  842. *
  843. * @return HelperSet A HelperSet instance
  844. */
  845. protected function getDefaultHelperSet()
  846. {
  847. return new HelperSet(array(
  848. new FormatterHelper(),
  849. new DialogHelper(),
  850. new ProgressHelper(),
  851. new TableHelper(),
  852. ));
  853. }
  854. /**
  855. * Runs and parses stty -a if it's available, suppressing any error output
  856. *
  857. * @return string
  858. */
  859. private function getSttyColumns()
  860. {
  861. if (!function_exists('proc_open')) {
  862. return;
  863. }
  864. $descriptorspec = array(1 => array('pipe', 'w'), 2 => array('pipe', 'w'));
  865. $process = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, array('suppress_errors' => true));
  866. if (is_resource($process)) {
  867. $info = stream_get_contents($pipes[1]);
  868. fclose($pipes[1]);
  869. fclose($pipes[2]);
  870. proc_close($process);
  871. return $info;
  872. }
  873. }
  874. /**
  875. * Runs and parses mode CON if it's available, suppressing any error output
  876. *
  877. * @return string <width>x<height> or null if it could not be parsed
  878. */
  879. private function getConsoleMode()
  880. {
  881. if (!function_exists('proc_open')) {
  882. return;
  883. }
  884. $descriptorspec = array(1 => array('pipe', 'w'), 2 => array('pipe', 'w'));
  885. $process = proc_open('mode CON', $descriptorspec, $pipes, null, null, array('suppress_errors' => true));
  886. if (is_resource($process)) {
  887. $info = stream_get_contents($pipes[1]);
  888. fclose($pipes[1]);
  889. fclose($pipes[2]);
  890. proc_close($process);
  891. if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) {
  892. return $matches[2].'x'.$matches[1];
  893. }
  894. }
  895. }
  896. /**
  897. * Returns abbreviated suggestions in string format.
  898. *
  899. * @param array $abbrevs Abbreviated suggestions to convert
  900. *
  901. * @return string A formatted string of abbreviated suggestions
  902. */
  903. private function getAbbreviationSuggestions($abbrevs)
  904. {
  905. return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : '');
  906. }
  907. /**
  908. * Returns the namespace part of the command name.
  909. *
  910. * This method is not part of public API and should not be used directly.
  911. *
  912. * @param string $name The full name of the command
  913. * @param string $limit The maximum number of parts of the namespace
  914. *
  915. * @return string The namespace of the command
  916. */
  917. public function extractNamespace($name, $limit = null)
  918. {
  919. $parts = explode(':', $name);
  920. array_pop($parts);
  921. return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit));
  922. }
  923. /**
  924. * Finds alternative commands of $name
  925. *
  926. * @param string $name The full name of the command
  927. * @param array $abbrevs The abbreviations
  928. *
  929. * @return array A sorted array of similar commands
  930. */
  931. private function findAlternativeCommands($name, $abbrevs)
  932. {
  933. $callback = function($item) {
  934. return $item->getName();
  935. };
  936. return $this->findAlternatives($name, $this->commands, $abbrevs, $callback);
  937. }
  938. /**
  939. * Finds alternative namespace of $name
  940. *
  941. * @param string $name The full name of the namespace
  942. * @param array $abbrevs The abbreviations
  943. *
  944. * @return array A sorted array of similar namespace
  945. */
  946. private function findAlternativeNamespace($name, $abbrevs)
  947. {
  948. return $this->findAlternatives($name, $this->getNamespaces(), $abbrevs);
  949. }
  950. /**
  951. * Finds alternative of $name among $collection,
  952. * if nothing is found in $collection, try in $abbrevs
  953. *
  954. * @param string $name The string
  955. * @param array|Traversable $collection The collection
  956. * @param array $abbrevs The abbreviations
  957. * @param Closure|string|array $callback The callable to transform collection item before comparison
  958. *
  959. * @return array A sorted array of similar string
  960. */
  961. private function findAlternatives($name, $collection, $abbrevs, $callback = null)
  962. {
  963. $alternatives = array();
  964. foreach ($collection as $item) {
  965. if (null !== $callback) {
  966. $item = call_user_func($callback, $item);
  967. }
  968. $lev = levenshtein($name, $item);
  969. if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) {
  970. $alternatives[$item] = $lev;
  971. }
  972. }
  973. if (!$alternatives) {
  974. foreach ($abbrevs as $key => $values) {
  975. $lev = levenshtein($name, $key);
  976. if ($lev <= strlen($name) / 3 || false !== strpos($key, $name)) {
  977. foreach ($values as $value) {
  978. $alternatives[$value] = $lev;
  979. }
  980. }
  981. }
  982. }
  983. asort($alternatives);
  984. return array_keys($alternatives);
  985. }
  986. }