Current File : /home/pacjaorg/public_html/dnpsom/libraries/vendor/joomla/console/src/Application.php |
<?php
/**
* Part of the Joomla Framework Console Package
*
* @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
namespace Joomla\Console;
use Joomla\Application\AbstractApplication;
use Joomla\Application\ApplicationEvents;
use Joomla\Console\Command\AbstractCommand;
use Joomla\Console\Command\HelpCommand;
use Joomla\Console\Event\ApplicationErrorEvent;
use Joomla\Console\Event\BeforeCommandExecuteEvent;
use Joomla\Console\Event\CommandErrorEvent;
use Joomla\Console\Event\TerminateEvent;
use Joomla\Console\Exception\NamespaceNotFoundException;
use Joomla\Registry\Registry;
use Joomla\String\StringHelper;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\DebugFormatterHelper;
use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\ProcessHelper;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputAwareInterface;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Console\Terminal;
use Symfony\Component\ErrorHandler\ErrorHandler;
/**
* Base application class for a Joomla! command line application.
*
* @since 2.0.0
*/
class Application extends AbstractApplication
{
/**
* Flag indicating the application should automatically exit after the command is run.
*
* @var boolean
* @since 2.0.0
*/
private $autoExit = true;
/**
* Flag indicating the application should catch and handle Throwables.
*
* @var boolean
* @since 2.0.0
*/
private $catchThrowables = true;
/**
* The available commands.
*
* @var AbstractCommand[]
* @since 2.0.0
*/
private $commands = [];
/**
* The command loader.
*
* @var Loader\LoaderInterface|null
* @since 2.0.0
*/
private $commandLoader;
/**
* Console input handler.
*
* @var InputInterface
* @since 2.0.0
*/
private $consoleInput;
/**
* Console output handler.
*
* @var OutputInterface
* @since 2.0.0
*/
private $consoleOutput;
/**
* The default command for the application.
*
* @var string
* @since 2.0.0
*/
private $defaultCommand = 'list';
/**
* The application input definition.
*
* @var InputDefinition|null
* @since 2.0.0
*/
private $definition;
/**
* The application helper set.
*
* @var HelperSet|null
* @since 2.0.0
*/
private $helperSet;
/**
* Internal flag tracking if the command store has been initialised.
*
* @var boolean
* @since 2.0.0
*/
private $initialised = false;
/**
* The name of the application.
*
* @var string
* @since 2.0.0
*/
private $name = '';
/**
* Reference to the currently running command.
*
* @var AbstractCommand|null
* @since 2.0.0
*/
private $runningCommand;
/**
* The console terminal helper.
*
* @var Terminal
* @since 2.0.0
*/
private $terminal;
/**
* The version of the application.
*
* @var string
* @since 2.0.0
*/
private $version = '';
/**
* Internal flag tracking if the user is seeking help for the given command.
*
* @var boolean
* @since 2.0.0
*/
private $wantsHelp = false;
/**
* Class constructor.
*
* @param InputInterface $input An optional argument to provide dependency injection for the application's input object. If the argument is
* an InputInterface object that object will become the application's input object, otherwise a default input
* object is created.
* @param OutputInterface $output An optional argument to provide dependency injection for the application's output object. If the argument
* is an OutputInterface object that object will become the application's output object, otherwise a default
* output object is created.
* @param Registry $config An optional argument to provide dependency injection for the application's config object. If the argument
* is a Registry object that object will become the application's config object, otherwise a default config
* object is created.
*
* @since 2.0.0
*/
public function __construct(?InputInterface $input = null, ?OutputInterface $output = null, ?Registry $config = null)
{
// Close the application if we are not executed from the command line.
if (!\defined('STDOUT') || !\defined('STDIN') || !isset($_SERVER['argv']))
{
$this->close();
}
$this->consoleInput = $input ?: new ArgvInput;
$this->consoleOutput = $output ?: new ConsoleOutput;
$this->terminal = new Terminal;
// Call the constructor as late as possible (it runs `initialise`).
parent::__construct($config);
}
/**
* 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.
*
* @param AbstractCommand $command The command to add to the application.
*
* @return AbstractCommand
*
* @since 2.0.0
* @throws LogicException
*/
public function addCommand(AbstractCommand $command): AbstractCommand
{
$this->initCommands();
if (!$command->isEnabled())
{
return $command;
}
$command->setApplication($this);
try
{
$command->getDefinition();
}
catch (\TypeError $exception)
{
throw new LogicException(sprintf('Command class "%s" is not correctly initialised.', \get_class($command)), 0, $exception);
}
if (!$command->getName())
{
throw new LogicException(sprintf('The command class "%s" does not have a name.', \get_class($command)));
}
$this->commands[$command->getName()] = $command;
foreach ($command->getAliases() as $alias)
{
$this->commands[$alias] = $command;
}
return $command;
}
/**
* Configures the console input and output instances for the process.
*
* @return void
*
* @since 2.0.0
*/
protected function configureIO(): void
{
if ($this->consoleInput->hasParameterOption(['--ansi'], true))
{
$this->consoleOutput->setDecorated(true);
}
elseif ($this->consoleInput->hasParameterOption(['--no-ansi'], true))
{
$this->consoleOutput->setDecorated(false);
}
if ($this->consoleInput->hasParameterOption(['--no-interaction', '-n'], true))
{
$this->consoleInput->setInteractive(false);
}
if ($this->consoleInput->hasParameterOption(['--quiet', '-q'], true))
{
$this->consoleOutput->setVerbosity(OutputInterface::VERBOSITY_QUIET);
$this->consoleInput->setInteractive(false);
}
else
{
if ($this->consoleInput->hasParameterOption('-vvv', true)
|| $this->consoleInput->hasParameterOption('--verbose=3', true)
|| $this->consoleInput->getParameterOption('--verbose', false, true) === 3
)
{
$this->consoleOutput->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
}
elseif ($this->consoleInput->hasParameterOption('-vv', true)
|| $this->consoleInput->hasParameterOption('--verbose=2', true)
|| $this->consoleInput->getParameterOption('--verbose', false, true) === 2
)
{
$this->consoleOutput->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
}
elseif ($this->consoleInput->hasParameterOption('-v', true)
|| $this->consoleInput->hasParameterOption('--verbose=1', true)
|| $this->consoleInput->hasParameterOption('--verbose', true)
|| $this->consoleInput->getParameterOption('--verbose', false, true)
)
{
$this->consoleOutput->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
}
}
}
/**
* Method to run the application routines.
*
* @return integer The exit code for the application
*
* @since 2.0.0
* @throws \Throwable
*/
protected function doExecute(): int
{
$input = $this->consoleInput;
$output = $this->consoleOutput;
// If requesting the version, short circuit the application and send the version data
if ($input->hasParameterOption(['--version', '-V'], true))
{
$output->writeln($this->getLongVersion());
return 0;
}
try
{
// Makes ArgvInput::getFirstArgument() able to distinguish an option from an argument.
$input->bind($this->getDefinition());
}
catch (ExceptionInterface $e)
{
// Errors must be ignored, full binding/validation happens later when the command is known.
}
$name = $this->getCommandName($input);
// Redirect to the help command if requested
if ($input->hasParameterOption(['--help', '-h'], true))
{
// If no command name was given, use the help command with a minimal input; otherwise flag the request for processing later
if (!$name)
{
$name = 'help';
$input = new ArrayInput(['command_name' => $this->defaultCommand]);
}
else
{
$this->wantsHelp = true;
}
}
// If we still do not have a command name, then the user has requested the application's default command
if (!$name)
{
$name = $this->defaultCommand;
$definition = $this->getDefinition();
// Overwrite the default value of the command argument with the default command name
$definition->setArguments(
array_merge(
$definition->getArguments(),
[
'command' => new InputArgument(
'command',
InputArgument::OPTIONAL,
$definition->getArgument('command')->getDescription(),
$name
),
]
)
);
}
try
{
$this->runningCommand = null;
$command = $this->getCommand($name);
}
catch (\Throwable $e)
{
if ($e instanceof CommandNotFoundException && !($e instanceof NamespaceNotFoundException))
{
(new SymfonyStyle($input, $output))->block(sprintf("\nCommand \"%s\" is not defined.\n", $name), null, 'error');
}
$event = new CommandErrorEvent($e, $this);
$this->dispatchEvent(ConsoleEvents::COMMAND_ERROR, $event);
if ($event->getExitCode() === 0)
{
return 0;
}
$e = $event->getError();
throw $e;
}
$this->runningCommand = $command;
$exitCode = $this->runCommand($command, $input, $output);
$this->runningCommand = null;
return $exitCode;
}
/**
* Execute the application.
*
* @return void
*
* @since 2.0.0
* @throws \Throwable
*/
public function execute()
{
putenv('LINES=' . $this->terminal->getHeight());
putenv('COLUMNS=' . $this->terminal->getWidth());
$this->configureIO();
$renderThrowable = function (\Throwable $e)
{
$this->renderThrowable($e);
};
if ($phpHandler = set_exception_handler($renderThrowable))
{
restore_exception_handler();
if (!\is_array($phpHandler) || !$phpHandler[0] instanceof ErrorHandler)
{
$errorHandler = true;
}
elseif ($errorHandler = $phpHandler[0]->setExceptionHandler($renderThrowable))
{
$phpHandler[0]->setExceptionHandler($errorHandler);
}
}
try
{
$this->dispatchEvent(ApplicationEvents::BEFORE_EXECUTE);
// Perform application routines.
$exitCode = $this->doExecute();
$this->dispatchEvent(ApplicationEvents::AFTER_EXECUTE);
}
catch (\Throwable $throwable)
{
if (!$this->shouldCatchThrowables())
{
throw $throwable;
}
$renderThrowable($throwable);
$event = new ApplicationErrorEvent($throwable, $this, $this->runningCommand);
$this->dispatchEvent(ConsoleEvents::APPLICATION_ERROR, $event);
$exitCode = $event->getExitCode();
if (is_numeric($exitCode))
{
$exitCode = (int) $exitCode;
if ($exitCode === 0)
{
$exitCode = 1;
}
}
else
{
$exitCode = 1;
}
}
finally
{
// If the exception handler changed, keep it; otherwise, unregister $renderThrowable
if (!$phpHandler)
{
if (set_exception_handler($renderThrowable) === $renderThrowable)
{
restore_exception_handler();
}
restore_exception_handler();
}
elseif (!$errorHandler)
{
$finalHandler = $phpHandler[0]->setExceptionHandler(null);
if ($finalHandler !== $renderThrowable)
{
$phpHandler[0]->setExceptionHandler($finalHandler);
}
}
if ($this->shouldAutoExit() && isset($exitCode))
{
$exitCode = $exitCode > 255 ? 255 : $exitCode;
$this->close($exitCode);
}
}
}
/**
* Finds a registered namespace by a name.
*
* @param string $namespace A namespace to search for
*
* @return string
*
* @since 2.0.0
* @throws NamespaceNotFoundException When namespace is incorrect or ambiguous
*/
public function findNamespace(string $namespace): string
{
$allNamespaces = $this->getNamespaces();
$expr = preg_replace_callback(
'{([^:]+|)}',
function ($matches)
{
return preg_quote($matches[1]) . '[^:]*';
},
$namespace
);
$namespaces = preg_grep('{^' . $expr . '}', $allNamespaces);
if (empty($namespaces))
{
throw new NamespaceNotFoundException(sprintf('There are no commands defined in the "%s" namespace.', $namespace));
}
$exact = \in_array($namespace, $namespaces, true);
if (\count($namespaces) > 1 && !$exact)
{
throw new NamespaceNotFoundException(sprintf('The namespace "%s" is ambiguous.', $namespace));
}
return $exact ? $namespace : reset($namespaces);
}
/**
* Gets all commands, including those available through a command loader, optionally filtered on a command namespace.
*
* @param string $namespace An optional command namespace to filter by.
*
* @return AbstractCommand[]
*
* @since 2.0.0
*/
public function getAllCommands(string $namespace = ''): array
{
$this->initCommands();
if ($namespace === '')
{
$commands = $this->commands;
if (!$this->commandLoader)
{
return $commands;
}
foreach ($this->commandLoader->getNames() as $name)
{
if (!isset($commands[$name]))
{
$commands[$name] = $this->getCommand($name);
}
}
return $commands;
}
$commands = [];
foreach ($this->commands as $name => $command)
{
if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1))
{
$commands[$name] = $command;
}
}
if ($this->commandLoader)
{
foreach ($this->commandLoader->getNames() as $name)
{
if (!isset($commands[$name]) && $namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1))
{
$commands[$name] = $this->getCommand($name);
}
}
}
return $commands;
}
/**
* Returns a registered command by name or alias.
*
* @param string $name The command name or alias
*
* @return AbstractCommand
*
* @since 2.0.0
* @throws CommandNotFoundException
*/
public function getCommand(string $name): AbstractCommand
{
$this->initCommands();
if (!$this->hasCommand($name))
{
throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name));
}
// If the command isn't registered, pull it from the loader if registered
if (!isset($this->commands[$name]) && $this->commandLoader)
{
$this->addCommand($this->commandLoader->get($name));
}
$command = $this->commands[$name];
// If the user requested help, we'll fetch the help command now and inject the user's command into it
if ($this->wantsHelp)
{
$this->wantsHelp = false;
/** @var HelpCommand $helpCommand */
$helpCommand = $this->getCommand('help');
$helpCommand->setCommand($command);
return $helpCommand;
}
return $command;
}
/**
* Get the name of the command to run.
*
* @param InputInterface $input The input to read the argument from
*
* @return string|null
*
* @since 2.0.0
*/
protected function getCommandName(InputInterface $input): ?string
{
return $input->getFirstArgument();
}
/**
* Get the registered commands.
*
* This method only retrieves commands which have been explicitly registered. To get all commands including those from a
* command loader, use the `getAllCommands()` method.
*
* @return AbstractCommand[]
*
* @since 2.0.0
*/
public function getCommands(): array
{
return $this->commands;
}
/**
* Get the console input handler.
*
* @return InputInterface
*
* @since 2.0.0
*/
public function getConsoleInput(): InputInterface
{
return $this->consoleInput;
}
/**
* Get the console output handler.
*
* @return OutputInterface
*
* @since 2.0.0
*/
public function getConsoleOutput(): OutputInterface
{
return $this->consoleOutput;
}
/**
* Get the commands which should be registered by default to the application.
*
* @return AbstractCommand[]
*
* @since 2.0.0
*/
protected function getDefaultCommands(): array
{
return [
new Command\ListCommand,
new Command\HelpCommand,
];
}
/**
* Builds the default input definition.
*
* @return InputDefinition
*
* @since 2.0.0
*/
protected function getDefaultInputDefinition(): InputDefinition
{
return new InputDefinition(
[
new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display the help information'),
new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Flag indicating that all output should be silenced'),
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, 'Displays the 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, 'Flag to disable interacting with the user'),
]
);
}
/**
* Builds the default helper set.
*
* @return HelperSet
*
* @since 2.0.0
*/
protected function getDefaultHelperSet(): HelperSet
{
return new HelperSet(
[
new FormatterHelper,
new DebugFormatterHelper,
new ProcessHelper,
new QuestionHelper,
]
);
}
/**
* Gets the InputDefinition related to this Application.
*
* @return InputDefinition
*
* @since 2.0.0
*/
public function getDefinition(): InputDefinition
{
if (!$this->definition)
{
$this->definition = $this->getDefaultInputDefinition();
}
return $this->definition;
}
/**
* Get the helper set associated with the application.
*
* @return HelperSet
*/
public function getHelperSet(): HelperSet
{
if (!$this->helperSet)
{
$this->helperSet = $this->getDefaultHelperSet();
}
return $this->helperSet;
}
/**
* Get the long version string for the application.
*
* Typically, this is the application name and version and is used in the application help output.
*
* @return string
*
* @since 2.0.0
*/
public function getLongVersion(): string
{
$name = $this->getName();
if ($name === '')
{
$name = 'Joomla Console Application';
}
if ($this->getVersion() !== '')
{
return sprintf('%s <info>%s</info>', $name, $this->getVersion());
}
return $name;
}
/**
* Get the name of the application.
*
* @return string
*
* @since 2.0.0
*/
public function getName(): string
{
return $this->name;
}
/**
* Returns an array of all unique namespaces used by currently registered commands.
*
* Note that this does not include the global namespace which always exists.
*
* @return string[]
*
* @since 2.0.0
*/
public function getNamespaces(): array
{
$namespaces = [];
foreach ($this->getAllCommands() as $command)
{
$namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName()));
foreach ($command->getAliases() as $alias)
{
$namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias));
}
}
return array_values(array_unique(array_filter($namespaces)));
}
/**
* Get the version of the application.
*
* @return string
*
* @since 2.0.0
*/
public function getVersion(): string
{
return $this->version;
}
/**
* Check if the application has a command with the given name.
*
* @param string $name The name of the command to check for existence.
*
* @return boolean
*
* @since 2.0.0
*/
public function hasCommand(string $name): bool
{
$this->initCommands();
// If command is already registered, we're good
if (isset($this->commands[$name]))
{
return true;
}
// If there is no loader, we can't look for a command there
if (!$this->commandLoader)
{
return false;
}
return $this->commandLoader->has($name);
}
/**
* Custom initialisation method.
*
* @return void
*
* @since 2.0.0
*/
protected function initialise(): void
{
// Set the current directory.
$this->set('cwd', getcwd());
}
/**
* Renders an error message for a Throwable object
*
* @param \Throwable $throwable The Throwable object to render the message for.
*
* @return void
*
* @since 2.0.0
*/
public function renderThrowable(\Throwable $throwable): void
{
$output = $this->consoleOutput instanceof ConsoleOutputInterface ? $this->consoleOutput->getErrorOutput() : $this->consoleOutput;
$output->writeln('', OutputInterface::VERBOSITY_QUIET);
$this->doRenderThrowable($throwable, $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);
}
}
/**
* Handles recursively rendering error messages for a Throwable and all previous Throwables contained within.
*
* @param \Throwable $throwable The Throwable object to render the message for.
* @param OutputInterface $output The output object to send the message to.
*
* @return void
*
* @since 2.0.0
*/
protected function doRenderThrowable(\Throwable $throwable, OutputInterface $output): void
{
do
{
$message = trim($throwable->getMessage());
if ($message === '' || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity())
{
$class = \get_class($throwable);
if ($class[0] === 'c' && strpos($class, "class@anonymous\0") === 0)
{
$class = get_parent_class($class) ?: key(class_implements($class));
}
$title = sprintf(' [%s%s] ', $class, ($code = $throwable->getCode()) !== 0 ? ' (' . $code . ')' : '');
$len = StringHelper::strlen($title);
}
else
{
$len = 0;
}
if (strpos($message, "class@anonymous\0") !== false)
{
$message = preg_replace_callback(
'/class@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/',
function ($m)
{
return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0]))) . '@anonymous' : $m[0];
},
$message
);
}
$width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : PHP_INT_MAX;
$lines = [];
foreach ($message !== '' ? preg_split('/\r?\n/', $message) : [] as $line)
{
foreach ($this->splitStringByWidth($line, $width - 4) as $line)
{
// Pre-format lines to get the right string length
$lineLength = StringHelper::strlen($line) + 4;
$lines[] = [$line, $lineLength];
$len = max($lineLength, $len);
}
}
$messages = [];
if (!$throwable instanceof ExceptionInterface || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity())
{
$messages[] = sprintf(
'<comment>%s</comment>',
OutputFormatter::escape(
sprintf(
'In %s line %s:', basename($throwable->getFile()) ?: 'n/a', $throwable->getLine() ?: 'n/a'
)
)
);
}
$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 - StringHelper::strlen($title))));
}
foreach ($lines as $line)
{
$messages[] = sprintf('<error> %s %s</error>', OutputFormatter::escape($line[0]), str_repeat(' ', $len - $line[1]));
}
$messages[] = $emptyLine;
$messages[] = '';
$output->writeln($messages, OutputInterface::VERBOSITY_QUIET);
if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity())
{
$output->writeln('<comment>Exception trace:</comment>', OutputInterface::VERBOSITY_QUIET);
// Exception related properties
$trace = $throwable->getTrace();
array_unshift(
$trace,
[
'function' => '',
'file' => $throwable->getFile() ?: 'n/a',
'line' => $throwable->getLine() ?: 'n/a',
'args' => [],
]
);
for ($i = 0, $count = \count($trace); $i < $count; ++$i)
{
$class = $trace[$i]['class'] ?? '';
$type = $trace[$i]['type'] ?? '';
$function = $trace[$i]['function'] ?? '';
$file = $trace[$i]['file'] ?? 'n/a';
$line = $trace[$i]['line'] ?? 'n/a';
$output->writeln(
sprintf(
' %s%s at <info>%s:%s</info>', $class, $function ? $type . $function . '()' : '', $file, $line
),
OutputInterface::VERBOSITY_QUIET
);
}
$output->writeln('', OutputInterface::VERBOSITY_QUIET);
}
}
while ($throwable = $throwable->getPrevious());
}
/**
* Splits a string for a specified width for use in an output.
*
* @param string $string The string to split.
* @param integer $width The maximum width of the output.
*
* @return string[]
*
* @since 2.0.0
*/
private function splitStringByWidth(string $string, int $width): array
{
/*
* The str_split function 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
*/
if (false === $encoding = mb_detect_encoding($string, null, true))
{
return str_split($string, $width);
}
$utf8String = mb_convert_encoding($string, 'utf8', $encoding);
$lines = [];
$line = '';
$offset = 0;
while (preg_match('/.{1,10000}/u', $utf8String, $m, 0, $offset))
{
$offset += \strlen($m[0]);
foreach (preg_split('//u', $m[0]) as $char)
{
// Test if $char could be appended to current line
if (mb_strwidth($line . $char, 'utf8') <= $width)
{
$line .= $char;
continue;
}
// If not, push current line to array and make a 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;
}
/**
* Run the given command.
*
* @param AbstractCommand $command The command to run.
* @param InputInterface $input The input to inject into the command.
* @param OutputInterface $output The output to inject into the command.
*
* @return integer
*
* @since 2.0.0
* @throws \Throwable
*/
protected function runCommand(AbstractCommand $command, InputInterface $input, OutputInterface $output): int
{
if ($command->getHelperSet() !== null)
{
foreach ($command->getHelperSet() as $helper)
{
if ($helper instanceof InputAwareInterface)
{
$helper->setInput($input);
}
}
}
// If the application doesn't have an event dispatcher, we can short circuit and just execute the command
try
{
$this->getDispatcher();
}
catch (\UnexpectedValueException $exception)
{
return $command->execute($input, $output);
}
// Bind before dispatching the 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
}
$event = new BeforeCommandExecuteEvent($this, $command);
$exception = null;
try
{
$this->dispatchEvent(ConsoleEvents::BEFORE_COMMAND_EXECUTE, $event);
if ($event->isCommandEnabled())
{
$exitCode = $command->execute($input, $output);
}
else
{
$exitCode = BeforeCommandExecuteEvent::RETURN_CODE_DISABLED;
}
}
catch (\Throwable $exception)
{
$event = new CommandErrorEvent($exception, $this, $command);
$this->dispatchEvent(ConsoleEvents::COMMAND_ERROR, $event);
$exception = $event->getError();
$exitCode = $event->getExitCode();
if ($exitCode === 0)
{
$exception = null;
}
}
$event = new TerminateEvent($exitCode, $this, $command);
$this->dispatchEvent(ConsoleEvents::TERMINATE, $event);
if ($exception !== null)
{
throw $exception;
}
return $event->getExitCode();
}
/**
* Set whether the application should auto exit.
*
* @param boolean $autoExit The auto exit state.
*
* @return void
*
* @since 2.0.0
*/
public function setAutoExit(bool $autoExit): void
{
$this->autoExit = $autoExit;
}
/**
* Set whether the application should catch Throwables.
*
* @param boolean $catchThrowables The catch Throwables state.
*
* @return void
*
* @since 2.0.0
*/
public function setCatchThrowables(bool $catchThrowables): void
{
$this->catchThrowables = $catchThrowables;
}
/**
* Set the command loader.
*
* @param Loader\LoaderInterface $loader The new command loader.
*
* @return void
*
* @since 2.0.0
*/
public function setCommandLoader(Loader\LoaderInterface $loader): void
{
$this->commandLoader = $loader;
}
/**
* Set the application's helper set.
*
* @param HelperSet $helperSet The new HelperSet.
*
* @return void
*
* @since 2.0.0
*/
public function setHelperSet(HelperSet $helperSet): void
{
$this->helperSet = $helperSet;
}
/**
* Set the name of the application.
*
* @param string $name The new application name.
*
* @return void
*
* @since 2.0.0
*/
public function setName(string $name): void
{
$this->name = $name;
}
/**
* Set the version of the application.
*
* @param string $version The new application version.
*
* @return void
*
* @since 2.0.0
*/
public function setVersion(string $version): void
{
$this->version = $version;
}
/**
* Get the application's auto exit state.
*
* @return boolean
*
* @since 2.0.0
*/
public function shouldAutoExit(): bool
{
return $this->autoExit;
}
/**
* Get the application's catch Throwables state.
*
* @return boolean
*
* @since 2.0.0
*/
public function shouldCatchThrowables(): bool
{
return $this->catchThrowables;
}
/**
* Returns all namespaces of the command name.
*
* @param string $name The full name of the command
*
* @return string[]
*
* @since 2.0.0
*/
private function extractAllNamespaces(string $name): array
{
// -1 as third argument is needed to skip the command short name when exploding
$parts = explode(':', $name, -1);
$namespaces = [];
foreach ($parts as $part)
{
if (\count($namespaces))
{
$namespaces[] = end($namespaces) . ':' . $part;
}
else
{
$namespaces[] = $part;
}
}
return $namespaces;
}
/**
* Returns the namespace part of the command name.
*
* @param string $name The command name to process
* @param integer $limit The maximum number of parts of the namespace
*
* @return string
*
* @since 2.0.0
*/
private function extractNamespace(string $name, ?int $limit = null): string
{
$parts = explode(':', $name);
array_pop($parts);
return implode(':', $limit === null ? $parts : \array_slice($parts, 0, $limit));
}
/**
* Internal function to initialise the command store, this allows the store to be lazy loaded only when needed.
*
* @return void
*
* @since 2.0.0
*/
private function initCommands(): void
{
if ($this->initialised)
{
return;
}
$this->initialised = true;
foreach ($this->getDefaultCommands() as $command)
{
$this->addCommand($command);
}
}
}