This repository has been archived on 2023-08-20. You can view files and clone it, but cannot push or open issues or pull requests.
symfony/src/Symfony/Component/Console/Application.php

1152 lines
39 KiB
PHP
Raw Normal View History

2010-01-04 14:42:28 +00:00
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console;
2010-01-09 11:57:15 +00:00
2017-07-12 10:59:19 +01:00
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Exception\NamespaceNotFoundException;
2017-03-24 08:57:35 +00:00
use Symfony\Component\Console\Formatter\OutputFormatter;
2014-04-03 09:19:54 +01:00
use Symfony\Component\Console\Helper\DebugFormatterHelper;
use Symfony\Component\Console\Helper\Helper;
2014-04-03 09:19:54 +01:00
use Symfony\Component\Console\Helper\ProcessHelper;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\StreamableInputInterface;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputAwareInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Command\HelpCommand;
use Symfony\Component\Console\Command\ListCommand;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
2016-01-18 16:12:22 +00:00
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Style\SymfonyStyle;
2017-12-01 17:03:34 +00:00
use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\Debug\Exception\FatalThrowableError;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
2010-01-04 14:42:28 +00:00
/**
* An Application is the container for a collection of commands.
2010-01-04 14:42:28 +00:00
*
2010-01-09 11:57:15 +00:00
* It is the main entry point of a Console application.
2010-01-04 14:42:28 +00:00
*
* This class is optimized for a standard CLI environment.
*
* Usage:
*
* $app = new Application('myapp', '1.0 (stable)');
made some method name changes to have a better coherence throughout the framework When an object has a "main" many relation with related "things" (objects, parameters, ...), the method names are normalized: * get() * set() * all() * replace() * remove() * clear() * isEmpty() * add() * register() * count() * keys() The classes below follow this method naming convention: * BrowserKit\CookieJar -> Cookie * BrowserKit\History -> Request * Console\Application -> Command * Console\Application\Helper\HelperSet -> HelperInterface * DependencyInjection\Container -> services * DependencyInjection\ContainerBuilder -> services * DependencyInjection\ParameterBag\ParameterBag -> parameters * DependencyInjection\ParameterBag\FrozenParameterBag -> parameters * DomCrawler\Form -> FormField * EventDispatcher\Event -> parameters * Form\FieldGroup -> Field * HttpFoundation\HeaderBag -> headers * HttpFoundation\ParameterBag -> parameters * HttpFoundation\Session -> attributes * HttpKernel\Profiler\Profiler -> DataCollectorInterface * Routing\RouteCollection -> Route * Security\Authentication\AuthenticationProviderManager -> AuthenticationProviderInterface * Templating\Engine -> HelperInterface * Translation\MessageCatalogue -> messages The usage of these methods are only allowed when it is clear that there is a main relation: * a CookieJar has many Cookies; * a Container has many services and many parameters (as services is the main relation, we use the naming convention for this relation); * a Console Input has many arguments and many options. There is no "main" relation, and so the naming convention does not apply. For many relations where the convention does not apply, the following methods must be used instead (where XXX is the name of the related thing): * get() -> getXXX() * set() -> setXXX() * all() -> getXXXs() * replace() -> setXXXs() * remove() -> removeXXX() * clear() -> clearXXX() * isEmpty() -> isEmptyXXX() * add() -> addXXX() * register() -> registerXXX() * count() -> countXXX() * keys()
2010-11-23 08:42:19 +00:00
* $app->add(new SimpleCommand());
2010-01-04 14:42:28 +00:00
* $app->run();
*
* @author Fabien Potencier <fabien@symfony.com>
2010-01-04 14:42:28 +00:00
*/
class Application
{
private $commands = array();
private $wantHelps = false;
private $runningCommand;
private $name;
private $version;
2017-07-12 10:59:19 +01:00
private $commandLoader;
private $catchExceptions = true;
private $autoExit = true;
private $definition;
private $helperSet;
private $dispatcher;
2016-06-09 12:42:05 +01:00
private $terminal;
private $defaultCommand;
private $singleCommand;
private $initialized;
/**
2016-06-09 12:42:05 +01:00
* @param string $name The name of the application
* @param string $version The version of the application
*/
2017-10-28 19:15:32 +01:00
public function __construct(string $name = 'UNKNOWN', string $version = 'UNKNOWN')
{
$this->name = $name;
$this->version = $version;
2016-06-09 12:42:05 +01:00
$this->terminal = new Terminal();
$this->defaultCommand = 'list';
}
public function setDispatcher(EventDispatcherInterface $dispatcher)
{
$this->dispatcher = $dispatcher;
}
2017-07-12 10:59:19 +01:00
public function setCommandLoader(CommandLoaderInterface $commandLoader)
{
$this->commandLoader = $commandLoader;
}
/**
* Runs the current application.
*
* @return int 0 if everything went fine, or an error code
2017-04-13 22:07:15 +01:00
*
* @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}.
*/
public function run(InputInterface $input = null, OutputInterface $output = null)
{
putenv('LINES='.$this->terminal->getHeight());
putenv('COLUMNS='.$this->terminal->getWidth());
if (null === $input) {
$input = new ArgvInput();
}
2010-01-04 14:42:28 +00:00
if (null === $output) {
$output = new ConsoleOutput();
}
2010-01-04 14:42:28 +00:00
2017-12-01 17:03:34 +00:00
$renderException = function ($e) use ($output) {
if (!$e instanceof \Exception) {
$e = class_exists(FatalThrowableError::class) ? new FatalThrowableError($e) : new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
}
if ($output instanceof ConsoleOutputInterface) {
$this->renderException($e, $output->getErrorOutput());
} else {
$this->renderException($e, $output);
}
};
if ($phpHandler = set_exception_handler($renderException)) {
restore_exception_handler();
if (!is_array($phpHandler) || !$phpHandler[0] instanceof ErrorHandler) {
$debugHandler = true;
} elseif ($debugHandler = $phpHandler[0]->setExceptionHandler($renderException)) {
$phpHandler[0]->setExceptionHandler($debugHandler);
}
}
$this->configureIO($input, $output);
try {
$exitCode = $this->doRun($input, $output);
2017-11-23 09:03:09 +00:00
} catch (\Exception $e) {
if (!$this->catchExceptions) {
2017-11-13 21:35:01 +00:00
throw $e;
}
2017-12-01 17:03:34 +00:00
$renderException($e);
$exitCode = $e->getCode();
if (is_numeric($exitCode)) {
$exitCode = (int) $exitCode;
if (0 === $exitCode) {
$exitCode = 1;
}
} else {
$exitCode = 1;
}
2017-12-01 17:03:34 +00:00
} finally {
// if the exception handler changed, keep it
// otherwise, unregister $renderException
2017-12-01 17:03:34 +00:00
if (!$phpHandler) {
if (set_exception_handler($renderException) === $renderException) {
restore_exception_handler();
}
2017-12-01 17:03:34 +00:00
restore_exception_handler();
} elseif (!$debugHandler) {
$finalHandler = $phpHandler[0]->setExceptionHandler(null);
if ($finalHandler !== $renderException) {
$phpHandler[0]->setExceptionHandler($finalHandler);
}
2017-12-01 17:03:34 +00:00
}
}
if ($this->autoExit) {
if ($exitCode > 255) {
$exitCode = 255;
}
exit($exitCode);
}
2011-02-27 16:48:41 +00:00
return $exitCode;
2010-01-04 14:42:28 +00:00
}
/**
* Runs the current application.
*
* @return int 0 if everything went fine, or an error code
*/
public function doRun(InputInterface $input, OutputInterface $output)
2010-01-04 14:42:28 +00:00
{
if (true === $input->hasParameterOption(array('--version', '-V'), true)) {
$output->writeln($this->getLongVersion());
return 0;
}
$name = $this->getCommandName($input);
if (true === $input->hasParameterOption(array('--help', '-h'), true)) {
if (!$name) {
$name = 'help';
$input = new ArrayInput(array('command_name' => $this->defaultCommand));
} else {
$this->wantHelps = true;
}
}
if (!$name) {
$name = $this->defaultCommand;
$definition = $this->getDefinition();
$definition->setArguments(array_merge(
$definition->getArguments(),
array(
'command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name),
)
));
}
2010-01-04 14:42:28 +00:00
try {
2017-05-22 08:27:42 +01:00
$this->runningCommand = null;
// the command name MUST be the first element of the input
$command = $this->find($name);
} catch (\Throwable $e) {
if (!($e instanceof CommandNotFoundException && !$e instanceof NamespaceNotFoundException) || 1 !== count($alternatives = $e->getAlternatives()) || !$input->isInteractive()) {
if (null !== $this->dispatcher) {
$event = new ConsoleErrorEvent($input, $output, $e);
$this->dispatcher->dispatch(ConsoleEvents::ERROR, $event);
if (0 === $event->getExitCode()) {
return 0;
}
$e = $event->getError();
}
throw $e;
}
$alternative = $alternatives[0];
$style = new SymfonyStyle($input, $output);
$style->block(sprintf("\nCommand \"%s\" is not defined.\n", $name), null, 'error');
if (!$style->confirm(sprintf('Do you want to run "%s" instead? ', $alternative), false)) {
if (null !== $this->dispatcher) {
$event = new ConsoleErrorEvent($input, $output, $e);
$this->dispatcher->dispatch(ConsoleEvents::ERROR, $event);
return $event->getExitCode();
}
return 1;
}
$command = $this->find($alternative);
}
2010-01-04 14:42:28 +00:00
$this->runningCommand = $command;
$exitCode = $this->doRunCommand($command, $input, $output);
$this->runningCommand = null;
return $exitCode;
2010-01-04 14:42:28 +00:00
}
public function setHelperSet(HelperSet $helperSet)
2010-01-04 14:42:28 +00:00
{
$this->helperSet = $helperSet;
2010-01-04 14:42:28 +00:00
}
/**
2011-02-07 00:34:24 +00:00
* Get the helper set associated with the command.
*
* @return HelperSet The HelperSet instance associated with this command
*/
public function getHelperSet()
2010-01-04 14:42:28 +00:00
{
if (!$this->helperSet) {
$this->helperSet = $this->getDefaultHelperSet();
}
return $this->helperSet;
2010-01-04 14:42:28 +00:00
}
public function setDefinition(InputDefinition $definition)
{
$this->definition = $definition;
}
/**
* Gets the InputDefinition related to this Application.
*
* @return InputDefinition The InputDefinition instance
*/
public function getDefinition()
2010-01-04 14:42:28 +00:00
{
if (!$this->definition) {
$this->definition = $this->getDefaultInputDefinition();
}
if ($this->singleCommand) {
$inputDefinition = $this->definition;
$inputDefinition->setArguments();
return $inputDefinition;
}
return $this->definition;
2010-01-04 14:42:28 +00:00
}
/**
* Gets the help message.
*
* @return string A help message
*/
public function getHelp()
2010-01-04 14:42:28 +00:00
{
return $this->getLongVersion();
2010-01-04 14:42:28 +00:00
}
/**
* Gets whether to catch exceptions or not during commands execution.
*
* @return bool Whether to catch exceptions or not during commands execution
*/
public function areExceptionsCaught()
{
return $this->catchExceptions;
}
/**
* Sets whether to catch exceptions or not during commands execution.
*
* @param bool $boolean Whether to catch exceptions or not during commands execution
*/
public function setCatchExceptions($boolean)
2010-01-04 14:42:28 +00:00
{
$this->catchExceptions = (bool) $boolean;
2010-01-04 14:42:28 +00:00
}
/**
* Gets whether to automatically exit after a command execution or not.
*
* @return bool Whether to automatically exit after a command execution or not
*/
public function isAutoExitEnabled()
{
return $this->autoExit;
}
/**
* Sets whether to automatically exit after a command execution or not.
*
* @param bool $boolean Whether to automatically exit after a command execution or not
*/
public function setAutoExit($boolean)
2010-01-04 14:42:28 +00:00
{
$this->autoExit = (bool) $boolean;
2010-01-04 14:42:28 +00:00
}
/**
* Gets the name of the application.
*
* @return string The application name
*/
public function getName()
2010-01-04 14:42:28 +00:00
{
return $this->name;
2010-01-04 14:42:28 +00:00
}
/**
* Sets the application name.
*
* @param string $name The application name
*/
public function setName($name)
2010-01-04 14:42:28 +00:00
{
$this->name = $name;
2010-01-04 14:42:28 +00:00
}
/**
* Gets the application version.
*
* @return string The application version
*/
public function getVersion()
2010-01-04 14:42:28 +00:00
{
return $this->version;
2010-01-04 14:42:28 +00:00
}
/**
* Sets the application version.
*
* @param string $version The application version
*/
public function setVersion($version)
2010-01-04 14:42:28 +00:00
{
$this->version = $version;
2010-01-04 14:42:28 +00:00
}
/**
* Returns the long version of the application.
*
* @return string The long application version
*/
public function getLongVersion()
{
if ('UNKNOWN' !== $this->getName()) {
if ('UNKNOWN' !== $this->getVersion()) {
return sprintf('%s <info>%s</info>', $this->getName(), $this->getVersion());
}
return $this->getName();
}
2011-02-27 16:48:41 +00:00
return 'Console Tool';
}
/**
* Registers a new command.
*
* @param string $name The command name
*
* @return Command The newly created command
*/
public function register($name)
{
made some method name changes to have a better coherence throughout the framework When an object has a "main" many relation with related "things" (objects, parameters, ...), the method names are normalized: * get() * set() * all() * replace() * remove() * clear() * isEmpty() * add() * register() * count() * keys() The classes below follow this method naming convention: * BrowserKit\CookieJar -> Cookie * BrowserKit\History -> Request * Console\Application -> Command * Console\Application\Helper\HelperSet -> HelperInterface * DependencyInjection\Container -> services * DependencyInjection\ContainerBuilder -> services * DependencyInjection\ParameterBag\ParameterBag -> parameters * DependencyInjection\ParameterBag\FrozenParameterBag -> parameters * DomCrawler\Form -> FormField * EventDispatcher\Event -> parameters * Form\FieldGroup -> Field * HttpFoundation\HeaderBag -> headers * HttpFoundation\ParameterBag -> parameters * HttpFoundation\Session -> attributes * HttpKernel\Profiler\Profiler -> DataCollectorInterface * Routing\RouteCollection -> Route * Security\Authentication\AuthenticationProviderManager -> AuthenticationProviderInterface * Templating\Engine -> HelperInterface * Translation\MessageCatalogue -> messages The usage of these methods are only allowed when it is clear that there is a main relation: * a CookieJar has many Cookies; * a Container has many services and many parameters (as services is the main relation, we use the naming convention for this relation); * a Console Input has many arguments and many options. There is no "main" relation, and so the naming convention does not apply. For many relations where the convention does not apply, the following methods must be used instead (where XXX is the name of the related thing): * get() -> getXXX() * set() -> setXXX() * all() -> getXXXs() * replace() -> setXXXs() * remove() -> removeXXX() * clear() -> clearXXX() * isEmpty() -> isEmptyXXX() * add() -> addXXX() * register() -> registerXXX() * count() -> countXXX() * keys()
2010-11-23 08:42:19 +00:00
return $this->add(new Command($name));
}
/**
* Adds an array of command objects.
*
* If a Command is not enabled it will not be added.
*
* @param Command[] $commands An array of commands
*/
public function addCommands(array $commands)
2010-01-04 14:42:28 +00:00
{
foreach ($commands as $command) {
made some method name changes to have a better coherence throughout the framework When an object has a "main" many relation with related "things" (objects, parameters, ...), the method names are normalized: * get() * set() * all() * replace() * remove() * clear() * isEmpty() * add() * register() * count() * keys() The classes below follow this method naming convention: * BrowserKit\CookieJar -> Cookie * BrowserKit\History -> Request * Console\Application -> Command * Console\Application\Helper\HelperSet -> HelperInterface * DependencyInjection\Container -> services * DependencyInjection\ContainerBuilder -> services * DependencyInjection\ParameterBag\ParameterBag -> parameters * DependencyInjection\ParameterBag\FrozenParameterBag -> parameters * DomCrawler\Form -> FormField * EventDispatcher\Event -> parameters * Form\FieldGroup -> Field * HttpFoundation\HeaderBag -> headers * HttpFoundation\ParameterBag -> parameters * HttpFoundation\Session -> attributes * HttpKernel\Profiler\Profiler -> DataCollectorInterface * Routing\RouteCollection -> Route * Security\Authentication\AuthenticationProviderManager -> AuthenticationProviderInterface * Templating\Engine -> HelperInterface * Translation\MessageCatalogue -> messages The usage of these methods are only allowed when it is clear that there is a main relation: * a CookieJar has many Cookies; * a Container has many services and many parameters (as services is the main relation, we use the naming convention for this relation); * a Console Input has many arguments and many options. There is no "main" relation, and so the naming convention does not apply. For many relations where the convention does not apply, the following methods must be used instead (where XXX is the name of the related thing): * get() -> getXXX() * set() -> setXXX() * all() -> getXXXs() * replace() -> setXXXs() * remove() -> removeXXX() * clear() -> clearXXX() * isEmpty() -> isEmptyXXX() * add() -> addXXX() * register() -> registerXXX() * count() -> countXXX() * keys()
2010-11-23 08:42:19 +00:00
$this->add($command);
}
2010-01-04 14:42:28 +00:00
}
/**
* Adds a command object.
*
* If a command with the same name already exists, it will be overridden.
* If the command is not enabled it will not be added.
*
* @return Command|null The registered command if enabled or null
*/
made some method name changes to have a better coherence throughout the framework When an object has a "main" many relation with related "things" (objects, parameters, ...), the method names are normalized: * get() * set() * all() * replace() * remove() * clear() * isEmpty() * add() * register() * count() * keys() The classes below follow this method naming convention: * BrowserKit\CookieJar -> Cookie * BrowserKit\History -> Request * Console\Application -> Command * Console\Application\Helper\HelperSet -> HelperInterface * DependencyInjection\Container -> services * DependencyInjection\ContainerBuilder -> services * DependencyInjection\ParameterBag\ParameterBag -> parameters * DependencyInjection\ParameterBag\FrozenParameterBag -> parameters * DomCrawler\Form -> FormField * EventDispatcher\Event -> parameters * Form\FieldGroup -> Field * HttpFoundation\HeaderBag -> headers * HttpFoundation\ParameterBag -> parameters * HttpFoundation\Session -> attributes * HttpKernel\Profiler\Profiler -> DataCollectorInterface * Routing\RouteCollection -> Route * Security\Authentication\AuthenticationProviderManager -> AuthenticationProviderInterface * Templating\Engine -> HelperInterface * Translation\MessageCatalogue -> messages The usage of these methods are only allowed when it is clear that there is a main relation: * a CookieJar has many Cookies; * a Container has many services and many parameters (as services is the main relation, we use the naming convention for this relation); * a Console Input has many arguments and many options. There is no "main" relation, and so the naming convention does not apply. For many relations where the convention does not apply, the following methods must be used instead (where XXX is the name of the related thing): * get() -> getXXX() * set() -> setXXX() * all() -> getXXXs() * replace() -> setXXXs() * remove() -> removeXXX() * clear() -> clearXXX() * isEmpty() -> isEmptyXXX() * add() -> addXXX() * register() -> registerXXX() * count() -> countXXX() * keys()
2010-11-23 08:42:19 +00:00
public function add(Command $command)
2010-01-04 14:42:28 +00:00
{
$this->init();
$command->setApplication($this);
if (!$command->isEnabled()) {
$command->setApplication(null);
2011-10-29 11:05:45 +01:00
return;
}
if (null === $command->getDefinition()) {
throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command)));
}
2017-07-12 10:59:19 +01:00
if (!$command->getName()) {
throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($command)));
}
$this->commands[$command->getName()] = $command;
foreach ($command->getAliases() as $alias) {
$this->commands[$alias] = $command;
}
return $command;
2010-01-04 14:42:28 +00:00
}
/**
* Returns a registered command by name or alias.
*
* @param string $name The command name or alias
*
* @return Command A Command object
*
* @throws CommandNotFoundException When given command name does not exist
*/
made some method name changes to have a better coherence throughout the framework When an object has a "main" many relation with related "things" (objects, parameters, ...), the method names are normalized: * get() * set() * all() * replace() * remove() * clear() * isEmpty() * add() * register() * count() * keys() The classes below follow this method naming convention: * BrowserKit\CookieJar -> Cookie * BrowserKit\History -> Request * Console\Application -> Command * Console\Application\Helper\HelperSet -> HelperInterface * DependencyInjection\Container -> services * DependencyInjection\ContainerBuilder -> services * DependencyInjection\ParameterBag\ParameterBag -> parameters * DependencyInjection\ParameterBag\FrozenParameterBag -> parameters * DomCrawler\Form -> FormField * EventDispatcher\Event -> parameters * Form\FieldGroup -> Field * HttpFoundation\HeaderBag -> headers * HttpFoundation\ParameterBag -> parameters * HttpFoundation\Session -> attributes * HttpKernel\Profiler\Profiler -> DataCollectorInterface * Routing\RouteCollection -> Route * Security\Authentication\AuthenticationProviderManager -> AuthenticationProviderInterface * Templating\Engine -> HelperInterface * Translation\MessageCatalogue -> messages The usage of these methods are only allowed when it is clear that there is a main relation: * a CookieJar has many Cookies; * a Container has many services and many parameters (as services is the main relation, we use the naming convention for this relation); * a Console Input has many arguments and many options. There is no "main" relation, and so the naming convention does not apply. For many relations where the convention does not apply, the following methods must be used instead (where XXX is the name of the related thing): * get() -> getXXX() * set() -> setXXX() * all() -> getXXXs() * replace() -> setXXXs() * remove() -> removeXXX() * clear() -> clearXXX() * isEmpty() -> isEmptyXXX() * add() -> addXXX() * register() -> registerXXX() * count() -> countXXX() * keys()
2010-11-23 08:42:19 +00:00
public function get($name)
2010-01-04 14:42:28 +00:00
{
$this->init();
2017-11-19 12:43:28 +00:00
if (!$this->has($name)) {
throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name));
}
2010-01-04 14:42:28 +00:00
2017-11-19 12:43:28 +00:00
$command = $this->commands[$name];
if ($this->wantHelps) {
$this->wantHelps = false;
2010-01-04 14:42:28 +00:00
made some method name changes to have a better coherence throughout the framework When an object has a "main" many relation with related "things" (objects, parameters, ...), the method names are normalized: * get() * set() * all() * replace() * remove() * clear() * isEmpty() * add() * register() * count() * keys() The classes below follow this method naming convention: * BrowserKit\CookieJar -> Cookie * BrowserKit\History -> Request * Console\Application -> Command * Console\Application\Helper\HelperSet -> HelperInterface * DependencyInjection\Container -> services * DependencyInjection\ContainerBuilder -> services * DependencyInjection\ParameterBag\ParameterBag -> parameters * DependencyInjection\ParameterBag\FrozenParameterBag -> parameters * DomCrawler\Form -> FormField * EventDispatcher\Event -> parameters * Form\FieldGroup -> Field * HttpFoundation\HeaderBag -> headers * HttpFoundation\ParameterBag -> parameters * HttpFoundation\Session -> attributes * HttpKernel\Profiler\Profiler -> DataCollectorInterface * Routing\RouteCollection -> Route * Security\Authentication\AuthenticationProviderManager -> AuthenticationProviderInterface * Templating\Engine -> HelperInterface * Translation\MessageCatalogue -> messages The usage of these methods are only allowed when it is clear that there is a main relation: * a CookieJar has many Cookies; * a Container has many services and many parameters (as services is the main relation, we use the naming convention for this relation); * a Console Input has many arguments and many options. There is no "main" relation, and so the naming convention does not apply. For many relations where the convention does not apply, the following methods must be used instead (where XXX is the name of the related thing): * get() -> getXXX() * set() -> setXXX() * all() -> getXXXs() * replace() -> setXXXs() * remove() -> removeXXX() * clear() -> clearXXX() * isEmpty() -> isEmptyXXX() * add() -> addXXX() * register() -> registerXXX() * count() -> countXXX() * keys()
2010-11-23 08:42:19 +00:00
$helpCommand = $this->get('help');
$helpCommand->setCommand($command);
2010-01-04 14:42:28 +00:00
return $helpCommand;
}
2010-01-04 14:42:28 +00:00
return $command;
2010-01-04 14:42:28 +00:00
}
/**
2011-02-07 00:34:24 +00:00
* Returns true if the command exists, false otherwise.
*
* @param string $name The command name or alias
*
* @return bool true if the command exists, false otherwise
*/
made some method name changes to have a better coherence throughout the framework When an object has a "main" many relation with related "things" (objects, parameters, ...), the method names are normalized: * get() * set() * all() * replace() * remove() * clear() * isEmpty() * add() * register() * count() * keys() The classes below follow this method naming convention: * BrowserKit\CookieJar -> Cookie * BrowserKit\History -> Request * Console\Application -> Command * Console\Application\Helper\HelperSet -> HelperInterface * DependencyInjection\Container -> services * DependencyInjection\ContainerBuilder -> services * DependencyInjection\ParameterBag\ParameterBag -> parameters * DependencyInjection\ParameterBag\FrozenParameterBag -> parameters * DomCrawler\Form -> FormField * EventDispatcher\Event -> parameters * Form\FieldGroup -> Field * HttpFoundation\HeaderBag -> headers * HttpFoundation\ParameterBag -> parameters * HttpFoundation\Session -> attributes * HttpKernel\Profiler\Profiler -> DataCollectorInterface * Routing\RouteCollection -> Route * Security\Authentication\AuthenticationProviderManager -> AuthenticationProviderInterface * Templating\Engine -> HelperInterface * Translation\MessageCatalogue -> messages The usage of these methods are only allowed when it is clear that there is a main relation: * a CookieJar has many Cookies; * a Container has many services and many parameters (as services is the main relation, we use the naming convention for this relation); * a Console Input has many arguments and many options. There is no "main" relation, and so the naming convention does not apply. For many relations where the convention does not apply, the following methods must be used instead (where XXX is the name of the related thing): * get() -> getXXX() * set() -> setXXX() * all() -> getXXXs() * replace() -> setXXXs() * remove() -> removeXXX() * clear() -> clearXXX() * isEmpty() -> isEmptyXXX() * add() -> addXXX() * register() -> registerXXX() * count() -> countXXX() * keys()
2010-11-23 08:42:19 +00:00
public function has($name)
2010-01-04 14:42:28 +00:00
{
$this->init();
2017-11-19 12:43:28 +00:00
return isset($this->commands[$name]) || ($this->commandLoader && $this->commandLoader->has($name) && $this->add($this->commandLoader->get($name)));
2010-01-04 14:42:28 +00:00
}
/**
* Returns an array of all unique namespaces used by currently registered commands.
*
* It does not return the global namespace which always exists.
*
* @return string[] An array of namespaces
*/
public function getNamespaces()
2010-01-04 14:42:28 +00:00
{
$namespaces = array();
foreach ($this->all() as $command) {
2015-01-07 22:10:19 +00:00
$namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName()));
foreach ($command->getAliases() as $alias) {
2015-01-07 22:10:19 +00:00
$namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias));
}
}
return array_values(array_unique(array_filter($namespaces)));
2010-01-04 14:42:28 +00:00
}
/**
* Finds a registered namespace by a name or an abbreviation.
*
2011-02-07 00:34:24 +00:00
* @param string $namespace A namespace or abbreviation to search for
*
* @return string A registered namespace
*
* @throws NamespaceNotFoundException When namespace is incorrect or ambiguous
*/
public function findNamespace($namespace)
2010-01-04 14:42:28 +00:00
{
$allNamespaces = $this->getNamespaces();
2013-08-07 07:55:13 +01:00
$expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $namespace);
$namespaces = preg_grep('{^'.$expr.'}', $allNamespaces);
2013-08-07 07:55:13 +01:00
if (empty($namespaces)) {
$message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);
if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) {
2013-08-07 07:55:13 +01:00
if (1 == count($alternatives)) {
$message .= "\n\nDid you mean this?\n ";
} else {
$message .= "\n\nDid you mean one of these?\n ";
}
2013-08-07 07:55:13 +01:00
$message .= implode("\n ", $alternatives);
}
throw new NamespaceNotFoundException($message, $alternatives);
2013-08-07 07:55:13 +01:00
}
2013-08-07 07:55:13 +01:00
$exact = in_array($namespace, $namespaces, true);
if (count($namespaces) > 1 && !$exact) {
throw new NamespaceNotFoundException(sprintf("The namespace \"%s\" is ambiguous.\nDid you mean one of these?\n%s", $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))), array_values($namespaces));
}
2010-01-04 14:42:28 +00:00
2013-08-07 07:55:13 +01:00
return $exact ? $namespace : reset($namespaces);
}
/**
* Finds a command by name or alias.
*
made some method name changes to have a better coherence throughout the framework When an object has a "main" many relation with related "things" (objects, parameters, ...), the method names are normalized: * get() * set() * all() * replace() * remove() * clear() * isEmpty() * add() * register() * count() * keys() The classes below follow this method naming convention: * BrowserKit\CookieJar -> Cookie * BrowserKit\History -> Request * Console\Application -> Command * Console\Application\Helper\HelperSet -> HelperInterface * DependencyInjection\Container -> services * DependencyInjection\ContainerBuilder -> services * DependencyInjection\ParameterBag\ParameterBag -> parameters * DependencyInjection\ParameterBag\FrozenParameterBag -> parameters * DomCrawler\Form -> FormField * EventDispatcher\Event -> parameters * Form\FieldGroup -> Field * HttpFoundation\HeaderBag -> headers * HttpFoundation\ParameterBag -> parameters * HttpFoundation\Session -> attributes * HttpKernel\Profiler\Profiler -> DataCollectorInterface * Routing\RouteCollection -> Route * Security\Authentication\AuthenticationProviderManager -> AuthenticationProviderInterface * Templating\Engine -> HelperInterface * Translation\MessageCatalogue -> messages The usage of these methods are only allowed when it is clear that there is a main relation: * a CookieJar has many Cookies; * a Container has many services and many parameters (as services is the main relation, we use the naming convention for this relation); * a Console Input has many arguments and many options. There is no "main" relation, and so the naming convention does not apply. For many relations where the convention does not apply, the following methods must be used instead (where XXX is the name of the related thing): * get() -> getXXX() * set() -> setXXX() * all() -> getXXXs() * replace() -> setXXXs() * remove() -> removeXXX() * clear() -> clearXXX() * isEmpty() -> isEmptyXXX() * add() -> addXXX() * register() -> registerXXX() * count() -> countXXX() * keys()
2010-11-23 08:42:19 +00:00
* Contrary to get, this command tries to find the best
* match if you give it an abbreviation of a name or alias.
*
2012-05-15 21:19:31 +01:00
* @param string $name A command name or a command alias
*
* @return Command A Command instance
*
* @throws CommandNotFoundException When command name is incorrect or ambiguous
*/
made some method name changes to have a better coherence throughout the framework When an object has a "main" many relation with related "things" (objects, parameters, ...), the method names are normalized: * get() * set() * all() * replace() * remove() * clear() * isEmpty() * add() * register() * count() * keys() The classes below follow this method naming convention: * BrowserKit\CookieJar -> Cookie * BrowserKit\History -> Request * Console\Application -> Command * Console\Application\Helper\HelperSet -> HelperInterface * DependencyInjection\Container -> services * DependencyInjection\ContainerBuilder -> services * DependencyInjection\ParameterBag\ParameterBag -> parameters * DependencyInjection\ParameterBag\FrozenParameterBag -> parameters * DomCrawler\Form -> FormField * EventDispatcher\Event -> parameters * Form\FieldGroup -> Field * HttpFoundation\HeaderBag -> headers * HttpFoundation\ParameterBag -> parameters * HttpFoundation\Session -> attributes * HttpKernel\Profiler\Profiler -> DataCollectorInterface * Routing\RouteCollection -> Route * Security\Authentication\AuthenticationProviderManager -> AuthenticationProviderInterface * Templating\Engine -> HelperInterface * Translation\MessageCatalogue -> messages The usage of these methods are only allowed when it is clear that there is a main relation: * a CookieJar has many Cookies; * a Container has many services and many parameters (as services is the main relation, we use the naming convention for this relation); * a Console Input has many arguments and many options. There is no "main" relation, and so the naming convention does not apply. For many relations where the convention does not apply, the following methods must be used instead (where XXX is the name of the related thing): * get() -> getXXX() * set() -> setXXX() * all() -> getXXXs() * replace() -> setXXXs() * remove() -> removeXXX() * clear() -> clearXXX() * isEmpty() -> isEmptyXXX() * add() -> addXXX() * register() -> registerXXX() * count() -> countXXX() * keys()
2010-11-23 08:42:19 +00:00
public function find($name)
{
$this->init();
$aliases = array();
2017-07-12 10:59:19 +01:00
$allCommands = $this->commandLoader ? array_merge($this->commandLoader->getNames(), array_keys($this->commands)) : array_keys($this->commands);
2013-08-07 07:55:13 +01:00
$expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name);
$commands = preg_grep('{^'.$expr.'}', $allCommands);
if (empty($commands)) {
$commands = preg_grep('{^'.$expr.'}i', $allCommands);
}
// if no commands matched or we just matched namespaces
if (empty($commands) || count(preg_grep('{^'.$expr.'$}i', $commands)) < 1) {
2013-08-07 07:55:13 +01:00
if (false !== $pos = strrpos($name, ':')) {
// check if a namespace exists and contains commands
$this->findNamespace(substr($name, 0, $pos));
}
$message = sprintf('Command "%s" is not defined.', $name);
if ($alternatives = $this->findAlternatives($name, $allCommands)) {
if (1 == count($alternatives)) {
$message .= "\n\nDid you mean this?\n ";
} else {
$message .= "\n\nDid you mean one of these?\n ";
}
$message .= implode("\n ", $alternatives);
}
throw new CommandNotFoundException($message, $alternatives);
}
// filter out aliases for commands which are already on the list
if (count($commands) > 1) {
2017-07-12 10:59:19 +01:00
$commandList = $this->commandLoader ? array_merge(array_flip($this->commandLoader->getNames()), $this->commands) : $this->commands;
$commands = array_unique(array_filter($commands, function ($nameOrAlias) use ($commandList, $commands, &$aliases) {
2017-07-12 10:59:19 +01:00
$commandName = $commandList[$nameOrAlias] instanceof Command ? $commandList[$nameOrAlias]->getName() : $nameOrAlias;
$aliases[$nameOrAlias] = $commandName;
return $commandName === $nameOrAlias || !in_array($commandName, $commands);
2017-07-12 10:59:19 +01:00
}));
}
$exact = in_array($name, $commands, true) || isset($aliases[$name]);
2013-08-07 07:55:13 +01:00
if (count($commands) > 1 && !$exact) {
$usableWidth = $this->terminal->getWidth() - 10;
$abbrevs = array_values($commands);
$maxLen = 0;
foreach ($abbrevs as $abbrev) {
$maxLen = max(Helper::strlen($abbrev), $maxLen);
}
$abbrevs = array_map(function ($cmd) use ($commandList, $usableWidth, $maxLen) {
2017-07-12 10:59:19 +01:00
if (!$commandList[$cmd] instanceof Command) {
return $cmd;
}
$abbrev = str_pad($cmd, $maxLen, ' ').' '.$commandList[$cmd]->getDescription();
return Helper::strlen($abbrev) > $usableWidth ? Helper::substr($abbrev, 0, $usableWidth - 3).'...' : $abbrev;
}, array_values($commands));
$suggestions = $this->getAbbreviationSuggestions($abbrevs);
2013-08-07 07:55:13 +01:00
throw new CommandNotFoundException(sprintf("Command \"%s\" is ambiguous.\nDid you mean one of these?\n%s", $name, $suggestions), array_values($commands));
}
2013-08-07 07:55:13 +01:00
return $this->get($exact ? $name : reset($commands));
2010-01-04 14:42:28 +00:00
}
/**
* Gets the commands (registered in the given namespace if provided).
*
* The array keys are the full names and the values the command instances.
*
2012-05-15 21:19:31 +01:00
* @param string $namespace A namespace name
*
* @return Command[] An array of Command instances
*/
made some method name changes to have a better coherence throughout the framework When an object has a "main" many relation with related "things" (objects, parameters, ...), the method names are normalized: * get() * set() * all() * replace() * remove() * clear() * isEmpty() * add() * register() * count() * keys() The classes below follow this method naming convention: * BrowserKit\CookieJar -> Cookie * BrowserKit\History -> Request * Console\Application -> Command * Console\Application\Helper\HelperSet -> HelperInterface * DependencyInjection\Container -> services * DependencyInjection\ContainerBuilder -> services * DependencyInjection\ParameterBag\ParameterBag -> parameters * DependencyInjection\ParameterBag\FrozenParameterBag -> parameters * DomCrawler\Form -> FormField * EventDispatcher\Event -> parameters * Form\FieldGroup -> Field * HttpFoundation\HeaderBag -> headers * HttpFoundation\ParameterBag -> parameters * HttpFoundation\Session -> attributes * HttpKernel\Profiler\Profiler -> DataCollectorInterface * Routing\RouteCollection -> Route * Security\Authentication\AuthenticationProviderManager -> AuthenticationProviderInterface * Templating\Engine -> HelperInterface * Translation\MessageCatalogue -> messages The usage of these methods are only allowed when it is clear that there is a main relation: * a CookieJar has many Cookies; * a Container has many services and many parameters (as services is the main relation, we use the naming convention for this relation); * a Console Input has many arguments and many options. There is no "main" relation, and so the naming convention does not apply. For many relations where the convention does not apply, the following methods must be used instead (where XXX is the name of the related thing): * get() -> getXXX() * set() -> setXXX() * all() -> getXXXs() * replace() -> setXXXs() * remove() -> removeXXX() * clear() -> clearXXX() * isEmpty() -> isEmptyXXX() * add() -> addXXX() * register() -> registerXXX() * count() -> countXXX() * keys()
2010-11-23 08:42:19 +00:00
public function all($namespace = null)
2010-01-04 14:42:28 +00:00
{
$this->init();
if (null === $namespace) {
2017-07-12 10:59:19 +01:00
if (!$this->commandLoader) {
return $this->commands;
}
$commands = $this->commands;
foreach ($this->commandLoader->getNames() as $name) {
2017-11-19 12:43:28 +00:00
if (!isset($commands[$name]) && $this->has($name)) {
2017-07-15 13:12:51 +01:00
$commands[$name] = $this->get($name);
2017-07-12 10:59:19 +01:00
}
}
return $commands;
2010-01-04 14:42:28 +00:00
}
$commands = array();
foreach ($this->commands as $name => $command) {
if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) {
$commands[$name] = $command;
}
2010-01-04 14:42:28 +00:00
}
2017-07-12 10:59:19 +01:00
if ($this->commandLoader) {
foreach ($this->commandLoader->getNames() as $name) {
2017-11-19 12:43:28 +00:00
if (!isset($commands[$name]) && $namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1) && $this->has($name)) {
2017-07-15 13:12:51 +01:00
$commands[$name] = $this->get($name);
2017-07-12 10:59:19 +01:00
}
}
}
return $commands;
2010-01-04 14:42:28 +00:00
}
/**
* Returns an array of possible abbreviations given a set of names.
*
2010-07-01 18:38:34 +01:00
* @param array $names An array of names
*
* @return array An array of abbreviations
*/
2012-07-09 13:50:58 +01:00
public static function getAbbreviations($names)
2010-01-04 14:42:28 +00:00
{
$abbrevs = array();
foreach ($names as $name) {
for ($len = strlen($name); $len > 0; --$len) {
$abbrev = substr($name, 0, $len);
$abbrevs[$abbrev][] = $name;
}
}
2010-01-04 14:42:28 +00:00
return $abbrevs;
2010-01-04 14:42:28 +00:00
}
/**
2012-05-09 17:42:13 +01:00
* Renders a caught exception.
*/
2015-02-21 20:21:10 +00:00
public function renderException(\Exception $e, OutputInterface $output)
2010-01-04 14:42:28 +00:00
{
$output->writeln('', OutputInterface::VERBOSITY_QUIET);
$this->doRenderException($e, $output);
if (null !== $this->runningCommand) {
$output->writeln(sprintf('<info>%s</info>', sprintf($this->runningCommand->getSynopsis(), $this->getName())), OutputInterface::VERBOSITY_QUIET);
$output->writeln('', OutputInterface::VERBOSITY_QUIET);
}
}
protected function doRenderException(\Exception $e, OutputInterface $output)
{
do {
$message = trim($e->getMessage());
if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
$title = sprintf(' [%s%s] ', get_class($e), 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : '');
$len = Helper::strlen($title);
} else {
$len = 0;
}
$width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : PHP_INT_MAX;
$lines = array();
foreach ('' !== $message ? preg_split('/\r?\n/', $message) : array() as $line) {
foreach ($this->splitStringByWidth($line, $width - 4) as $line) {
// pre-format lines to get the right string length
$lineLength = Helper::strlen($line) + 4;
$lines[] = array($line, $lineLength);
$len = max($lineLength, $len);
2012-04-06 13:21:18 +01:00
}
}
2010-01-04 14:42:28 +00:00
$messages = array();
if (!$e instanceof ExceptionInterface || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
$messages[] = sprintf('<comment>%s</comment>', OutputFormatter::escape(sprintf('In %s line %s:', basename($e->getFile()) ?: 'n/a', $e->getLine() ?: 'n/a')));
}
2017-03-27 21:18:06 +01:00
$messages[] = $emptyLine = sprintf('<error>%s</error>', str_repeat(' ', $len));
if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
$messages[] = sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - Helper::strlen($title))));
}
foreach ($lines as $line) {
2017-03-27 21:18:06 +01:00
$messages[] = sprintf('<error> %s %s</error>', OutputFormatter::escape($line[0]), str_repeat(' ', $len - $line[1]));
}
$messages[] = $emptyLine;
$messages[] = '';
2010-01-04 14:42:28 +00:00
$output->writeln($messages, OutputInterface::VERBOSITY_QUIET);
2013-04-25 11:13:01 +01:00
if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
$output->writeln('<comment>Exception trace:</comment>', OutputInterface::VERBOSITY_QUIET);
// exception related properties
$trace = $e->getTrace();
for ($i = 0, $count = count($trace); $i < $count; ++$i) {
$class = isset($trace[$i]['class']) ? $trace[$i]['class'] : '';
$type = isset($trace[$i]['type']) ? $trace[$i]['type'] : '';
$function = $trace[$i]['function'];
$file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a';
$line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a';
$output->writeln(sprintf(' %s%s%s() at <info>%s:%s</info>', $class, $type, $function, $file, $line), OutputInterface::VERBOSITY_QUIET);
}
$output->writeln('', OutputInterface::VERBOSITY_QUIET);
}
} while ($e = $e->getPrevious());
2010-01-04 14:42:28 +00:00
}
/**
* Configures the input and output instances based on the user arguments and options.
*/
protected function configureIO(InputInterface $input, OutputInterface $output)
{
if (true === $input->hasParameterOption(array('--ansi'), true)) {
$output->setDecorated(true);
} elseif (true === $input->hasParameterOption(array('--no-ansi'), true)) {
$output->setDecorated(false);
}
if (true === $input->hasParameterOption(array('--no-interaction', '-n'), true)) {
$input->setInteractive(false);
} elseif (function_exists('posix_isatty')) {
$inputStream = null;
if ($input instanceof StreamableInputInterface) {
$inputStream = $input->getStream();
}
if (!@posix_isatty($inputStream) && false === getenv('SHELL_INTERACTIVE')) {
$input->setInteractive(false);
}
}
switch ($shellVerbosity = (int) getenv('SHELL_VERBOSITY')) {
case -1: $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); break;
case 1: $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); break;
case 2: $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); break;
case 3: $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); break;
default: $shellVerbosity = 0; break;
}
if (true === $input->hasParameterOption(array('--quiet', '-q'), true)) {
$output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
$shellVerbosity = -1;
} else {
if ($input->hasParameterOption('-vvv', true) || $input->hasParameterOption('--verbose=3', true) || 3 === $input->getParameterOption('--verbose', false, true)) {
$output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
$shellVerbosity = 3;
} elseif ($input->hasParameterOption('-vv', true) || $input->hasParameterOption('--verbose=2', true) || 2 === $input->getParameterOption('--verbose', false, true)) {
$output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
$shellVerbosity = 2;
} elseif ($input->hasParameterOption('-v', true) || $input->hasParameterOption('--verbose=1', true) || $input->hasParameterOption('--verbose', true) || $input->getParameterOption('--verbose', false, true)) {
$output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
$shellVerbosity = 1;
}
}
if (-1 === $shellVerbosity) {
$input->setInteractive(false);
}
putenv('SHELL_VERBOSITY='.$shellVerbosity);
$_ENV['SHELL_VERBOSITY'] = $shellVerbosity;
$_SERVER['SHELL_VERBOSITY'] = $shellVerbosity;
}
/**
* Runs the current command.
*
* If an event dispatcher has been attached to the application,
* events are also dispatched during the life-cycle of the command.
*
* @return int 0 if everything went fine, or an error code
*/
protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)
{
foreach ($command->getHelperSet() as $helper) {
if ($helper instanceof InputAwareInterface) {
$helper->setInput($input);
}
}
if (null === $this->dispatcher) {
return $command->run($input, $output);
}
// bind before the console.command event, so the listeners have access to input options/arguments
try {
$command->mergeApplicationDefinition();
$input->bind($command->getDefinition());
} catch (ExceptionInterface $e) {
// ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition
}
$event = new ConsoleCommandEvent($command, $input, $output);
$e = null;
try {
$this->dispatcher->dispatch(ConsoleEvents::COMMAND, $event);
if ($event->commandShouldRun()) {
$exitCode = $command->run($input, $output);
} else {
$exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED;
}
} catch (\Throwable $e) {
$event = new ConsoleErrorEvent($input, $output, $e, $command);
$this->dispatcher->dispatch(ConsoleEvents::ERROR, $event);
$e = $event->getError();
if (0 === $exitCode = $event->getExitCode()) {
$e = null;
}
}
$event = new ConsoleTerminateEvent($command, $input, $output, $exitCode);
$this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event);
if (null !== $e) {
throw $e;
}
return $event->getExitCode();
}
2011-02-07 00:34:24 +00:00
/**
* Gets the name of the command based on input.
*
* @return string The command name
*/
protected function getCommandName(InputInterface $input)
{
return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument();
}
/**
* Gets the default input definition.
*
* @return InputDefinition An InputDefinition instance
*/
protected function getDefaultInputDefinition()
{
return new InputDefinition(array(
new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
2015-05-15 14:45:10 +01:00
new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'),
new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'),
new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'),
new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'),
new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'),
new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'),
new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'),
));
}
/**
* Gets the default commands that should always be available.
*
* @return Command[] An array of default Command instances
*/
protected function getDefaultCommands()
{
return array(new HelpCommand(), new ListCommand());
}
/**
* Gets the default helper set with the helpers that should always be available.
*
* @return HelperSet A HelperSet instance
*/
protected function getDefaultHelperSet()
{
return new HelperSet(array(
new FormatterHelper(),
2014-04-03 09:19:54 +01:00
new DebugFormatterHelper(),
new ProcessHelper(),
new QuestionHelper(),
));
}
2011-02-07 00:34:24 +00:00
/**
* Returns abbreviated suggestions in string format.
*
* @param array $abbrevs Abbreviated suggestions to convert
*
* @return string A formatted string of abbreviated suggestions
*/
private function getAbbreviationSuggestions($abbrevs)
2010-01-04 14:42:28 +00:00
{
return ' '.implode("\n ", $abbrevs);
2010-01-04 14:42:28 +00:00
}
/**
* Returns the namespace part of the command name.
*
* This method is not part of public API and should not be used directly.
*
* @param string $name The full name of the command
* @param string $limit The maximum number of parts of the namespace
*
* @return string The namespace of the command
*/
public function extractNamespace($name, $limit = null)
{
$parts = explode(':', $name);
array_pop($parts);
return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit));
}
/**
* Finds alternative of $name among $collection,
2014-12-21 17:00:50 +00:00
* if nothing is found in $collection, try in $abbrevs.
*
2017-11-14 18:49:30 +00:00
* @param string $name The string
* @param iterable $collection The collection
*
* @return string[] A sorted array of similar string
*/
2013-08-07 07:55:13 +01:00
private function findAlternatives($name, $collection)
2012-05-21 15:06:09 +01:00
{
2013-08-07 07:55:13 +01:00
$threshold = 1e3;
$alternatives = array();
2013-08-07 07:55:13 +01:00
$collectionParts = array();
foreach ($collection as $item) {
2013-08-07 07:55:13 +01:00
$collectionParts[$item] = explode(':', $item);
}
2013-08-07 07:55:13 +01:00
foreach (explode(':', $name) as $i => $subname) {
foreach ($collectionParts as $collectionName => $parts) {
$exists = isset($alternatives[$collectionName]);
if (!isset($parts[$i]) && $exists) {
$alternatives[$collectionName] += $threshold;
continue;
} elseif (!isset($parts[$i])) {
continue;
}
$lev = levenshtein($subname, $parts[$i]);
if ($lev <= strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) {
2013-08-07 07:55:13 +01:00
$alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev;
} elseif ($exists) {
$alternatives[$collectionName] += $threshold;
}
}
}
2013-08-07 07:55:13 +01:00
foreach ($collection as $item) {
$lev = levenshtein($name, $item);
if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) {
$alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
}
}
$alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; });
2016-09-08 10:37:22 +01:00
ksort($alternatives, SORT_NATURAL | SORT_FLAG_CASE);
return array_keys($alternatives);
}
/**
* Sets the default Command name.
*
* @param string $commandName The Command name
* @param bool $isSingleCommand Set to true if there is only one command in this application
*
* @return self
*/
public function setDefaultCommand($commandName, $isSingleCommand = false)
{
$this->defaultCommand = $commandName;
if ($isSingleCommand) {
// Ensure the command exist
$this->find($commandName);
$this->singleCommand = true;
}
return $this;
}
private function splitStringByWidth($string, $width)
{
// str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly.
// additionally, array_slice() is not enough as some character has doubled width.
// we need a function to split string not by character count but by string width
2015-10-14 15:40:43 +01:00
if (false === $encoding = mb_detect_encoding($string, null, true)) {
return str_split($string, $width);
}
$utf8String = mb_convert_encoding($string, 'utf8', $encoding);
$lines = array();
$line = '';
foreach (preg_split('//u', $utf8String) as $char) {
// test if $char could be appended to current line
2014-05-14 14:23:19 +01:00
if (mb_strwidth($line.$char, 'utf8') <= $width) {
$line .= $char;
continue;
}
// if not, push current line to array and make new line
$lines[] = str_pad($line, $width);
$line = $char;
}
$lines[] = count($lines) ? str_pad($line, $width) : $line;
mb_convert_variables($encoding, 'utf8', $lines);
return $lines;
}
2015-01-07 22:10:19 +00:00
/**
* Returns all namespaces of the command name.
*
* @param string $name The full name of the command
*
* @return string[] The namespaces of the command
2015-01-07 22:10:19 +00:00
*/
private function extractAllNamespaces($name)
{
// -1 as third argument is needed to skip the command short name when exploding
$parts = explode(':', $name, -1);
$namespaces = array();
foreach ($parts as $part) {
if (count($namespaces)) {
$namespaces[] = end($namespaces).':'.$part;
} else {
$namespaces[] = $part;
}
}
return $namespaces;
}
private function init()
{
if ($this->initialized) {
return;
}
$this->initialized = true;
foreach ($this->getDefaultCommands() as $command) {
$this->add($command);
}
}
2010-01-04 14:42:28 +00:00
}