Вход Регистрация
Файл: vendor/symfony/console/Application.php
Строк: 2289
<?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 SymfonyComponentConsole;

use 
SymfonyComponentConsoleCommandCommand;
use 
SymfonyComponentConsoleCommandCompleteCommand;
use 
SymfonyComponentConsoleCommandDumpCompletionCommand;
use 
SymfonyComponentConsoleCommandHelpCommand;
use 
SymfonyComponentConsoleCommandLazyCommand;
use 
SymfonyComponentConsoleCommandListCommand;
use 
SymfonyComponentConsoleCommandSignalableCommandInterface;
use 
SymfonyComponentConsoleCommandLoaderCommandLoaderInterface;
use 
SymfonyComponentConsoleCompletionCompletionInput;
use 
SymfonyComponentConsoleCompletionCompletionSuggestions;
use 
SymfonyComponentConsoleCompletionSuggestion;
use 
SymfonyComponentConsoleEventConsoleCommandEvent;
use 
SymfonyComponentConsoleEventConsoleErrorEvent;
use 
SymfonyComponentConsoleEventConsoleSignalEvent;
use 
SymfonyComponentConsoleEventConsoleTerminateEvent;
use 
SymfonyComponentConsoleExceptionCommandNotFoundException;
use 
SymfonyComponentConsoleExceptionExceptionInterface;
use 
SymfonyComponentConsoleExceptionLogicException;
use 
SymfonyComponentConsoleExceptionNamespaceNotFoundException;
use 
SymfonyComponentConsoleExceptionRuntimeException;
use 
SymfonyComponentConsoleFormatterOutputFormatter;
use 
SymfonyComponentConsoleHelperDebugFormatterHelper;
use 
SymfonyComponentConsoleHelperDescriptorHelper;
use 
SymfonyComponentConsoleHelperFormatterHelper;
use 
SymfonyComponentConsoleHelperHelper;
use 
SymfonyComponentConsoleHelperHelperSet;
use 
SymfonyComponentConsoleHelperProcessHelper;
use 
SymfonyComponentConsoleHelperQuestionHelper;
use 
SymfonyComponentConsoleInputArgvInput;
use 
SymfonyComponentConsoleInputArrayInput;
use 
SymfonyComponentConsoleInputInputArgument;
use 
SymfonyComponentConsoleInputInputAwareInterface;
use 
SymfonyComponentConsoleInputInputDefinition;
use 
SymfonyComponentConsoleInputInputInterface;
use 
SymfonyComponentConsoleInputInputOption;
use 
SymfonyComponentConsoleOutputConsoleOutput;
use 
SymfonyComponentConsoleOutputConsoleOutputInterface;
use 
SymfonyComponentConsoleOutputOutputInterface;
use 
SymfonyComponentConsoleSignalRegistrySignalRegistry;
use 
SymfonyComponentConsoleStyleSymfonyStyle;
use 
SymfonyComponentErrorHandlerErrorHandler;
use 
SymfonyContractsEventDispatcherEventDispatcherInterface;
use 
SymfonyContractsServiceResetInterface;

/**
 * An Application is the container for a collection of commands.
 *
 * It is the main entry point of a Console application.
 *
 * This class is optimized for a standard CLI environment.
 *
 * Usage:
 *
 *     $app = new Application('myapp', '1.0 (stable)');
 *     $app->add(new SimpleCommand());
 *     $app->run();
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Application implements ResetInterface
{
    private array 
$commands = [];
    private 
bool $wantHelps false;
    private ?
Command $runningCommand null;
    private 
string $name;
    private 
string $version;
    private ?
CommandLoaderInterface $commandLoader null;
    private 
bool $catchExceptions true;
    private 
bool $catchErrors false;
    private 
bool $autoExit true;
    private 
InputDefinition $definition;
    private 
HelperSet $helperSet;
    private ?
EventDispatcherInterface $dispatcher null;
    private 
Terminal $terminal;
    private 
string $defaultCommand;
    private 
bool $singleCommand false;
    private 
bool $initialized false;
    private ?
SignalRegistry $signalRegistry null;
    private array 
$signalsToDispatchEvent = [];

    public function 
__construct(string $name 'UNKNOWN'string $version 'UNKNOWN')
    {
        
$this->name $name;
        
$this->version $version;
        
$this->terminal = new Terminal();
        
$this->defaultCommand 'list';
        if (
defined('SIGINT') && SignalRegistry::isSupported()) {
            
$this->signalRegistry = new SignalRegistry();
            
$this->signalsToDispatchEvent = [SIGINTSIGTERMSIGUSR1SIGUSR2];
        }
    }

    
/**
     * @final
     */
    
public function setDispatcher(EventDispatcherInterface $dispatcher): void
    
{
        
$this->dispatcher $dispatcher;
    }

    
/**
     * @return void
     */
    
public function setCommandLoader(CommandLoaderInterface $commandLoader)
    {
        
$this->commandLoader $commandLoader;
    }

    public function 
getSignalRegistry(): SignalRegistry
    
{
        if (!
$this->signalRegistry) {
            throw new 
RuntimeException('Signals are not supported. Make sure that the "pcntl" extension is installed and that "pcntl_*" functions are not disabled by your php.ini'"disable_functions" directive.');
        }

        return $this->signalRegistry;
    }

    /**
     * @return void
     */
    public function setSignalsToDispatchEvent(int ...$signalsToDispatchEvent)
    {
        $this->signalsToDispatchEvent = $signalsToDispatchEvent;
    }

    /**
     * Runs the current application.
     *
     * @return int 0 if everything went fine, or an error code
     *
     * @throws Exception When running fails. Bypass this when {@link setCatchExceptions()}.
     */
    public function run(?InputInterface $input = null, ?OutputInterface $output = null): int
    {
        if (function_exists('
putenv')) {
            @putenv('
LINES='.$this->terminal->getHeight());
            @putenv('
COLUMNS='.$this->terminal->getWidth());
        }

        $input ??= new ArgvInput();
        $output ??= new ConsoleOutput();

        $renderException = function (Throwable $e) use ($output) {
            if ($output instanceof ConsoleOutputInterface) {
                $this->renderThrowable($e, $output->getErrorOutput());
            } else {
                $this->renderThrowable($e, $output);
            }
        };
        if ($phpHandler = set_exception_handler($renderException)) {
            restore_exception_handler();
            if (!is_array($phpHandler) || !$phpHandler[0] instanceof ErrorHandler) {
                $errorHandler = true;
            } elseif ($errorHandler = $phpHandler[0]->setExceptionHandler($renderException)) {
                $phpHandler[0]->setExceptionHandler($errorHandler);
            }
        }

        try {
            $this->configureIO($input, $output);

            $exitCode = $this->doRun($input, $output);
        } catch (Throwable $e) {
            if ($e instanceof Exception && !$this->catchExceptions) {
                throw $e;
            }
            if (!$e instanceof Exception && !$this->catchErrors) {
                throw $e;
            }

            $renderException($e);

            $exitCode = $e->getCode();
            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 $renderException
            if (!$phpHandler) {
                if (set_exception_handler($renderException) === $renderException) {
                    restore_exception_handler();
                }
                restore_exception_handler();
            } elseif (!$errorHandler) {
                $finalHandler = $phpHandler[0]->setExceptionHandler(null);
                if ($finalHandler !== $renderException) {
                    $phpHandler[0]->setExceptionHandler($finalHandler);
                }
            }
        }

        if ($this->autoExit) {
            if ($exitCode > 255) {
                $exitCode = 255;
            }

            exit($exitCode);
        }

        return $exitCode;
    }

    /**
     * Runs the current application.
     *
     * @return int 0 if everything went fine, or an error code
     */
    public function doRun(InputInterface $input, OutputInterface $output)
    {
        if (true === $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) {
            // Errors must be ignored, full binding/validation happens later when the command is known.
        }

        $name = $this->getCommandName($input);
        if (true === $input->hasParameterOption(['
--help', '-h'], true)) {
            if (!$name) {
                $name = '
help';
                $input = new ArrayInput(['
command_name' => $this->defaultCommand]);
            } else {
                $this->wantHelps = true;
            }
        }

        if (!$name) {
            $name = $this->defaultCommand;
            $definition = $this->getDefinition();
            $definition->setArguments(array_merge(
                $definition->getArguments(),
                [
                    '
command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name),
                ]
            ));
        }

        try {
            $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()) {
                $alternative = $alternatives[0];

                $style = new SymfonyStyle($input, $output);
                $output->writeln('');
                $formattedBlock = (new FormatterHelper())->formatBlock(sprintf('
Command "%s" is not defined.', $name), 'error', true);
                $output->writeln($formattedBlock);
                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($event, ConsoleEvents::ERROR);

                        return $event->getExitCode();
                    }

                    return 1;
                }

                $command = $this->find($alternative);
            } else {
                if (null !== $this->dispatcher) {
                    $event = new ConsoleErrorEvent($input, $output, $e);
                    $this->dispatcher->dispatch($event, ConsoleEvents::ERROR);

                    if (0 === $event->getExitCode()) {
                        return 0;
                    }

                    $e = $event->getError();
                }

                try {
                    if ($e instanceof CommandNotFoundException && $namespace = $this->findNamespace($name)) {
                        $helper = new DescriptorHelper();
                        $helper->describe($output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output, $this, [
                            '
format' => 'txt',
                            '
raw_text' => false,
                            '
namespace' => $namespace,
                            '
short' => false,
                        ]);

                        return isset($event) ? $event->getExitCode() : 1;
                    }

                    throw $e;
                } catch (NamespaceNotFoundException) {
                    throw $e;
                }
            }
        }

        if ($command instanceof LazyCommand) {
            $command = $command->getCommand();
        }

        $this->runningCommand = $command;
        $exitCode = $this->doRunCommand($command, $input, $output);
        $this->runningCommand = null;

        return $exitCode;
    }

    /**
     * @return void
     */
    public function reset()
    {
    }

    /**
     * @return void
     */
    public function setHelperSet(HelperSet $helperSet)
    {
        $this->helperSet = $helperSet;
    }

    /**
     * Get the helper set associated with the command.
     */
    public function getHelperSet(): HelperSet
    {
        return $this->helperSet ??= $this->getDefaultHelperSet();
    }

    /**
     * @return void
     */
    public function setDefinition(InputDefinition $definition)
    {
        $this->definition = $definition;
    }

    /**
     * Gets the InputDefinition related to this Application.
     */
    public function getDefinition(): InputDefinition
    {
        $this->definition ??= $this->getDefaultInputDefinition();

        if ($this->singleCommand) {
            $inputDefinition = $this->definition;
            $inputDefinition->setArguments();

            return $inputDefinition;
        }

        return $this->definition;
    }

    /**
     * Adds suggestions to $suggestions for the current completion input (e.g. option or argument).
     */
    public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
    {
        if (
            CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType()
            && '
command' === $input->getCompletionName()
        ) {
            foreach ($this->all() as $name => $command) {
                // skip hidden commands and aliased commands as they already get added below
                if ($command->isHidden() || $command->getName() !== $name) {
                    continue;
                }
                $suggestions->suggestValue(new Suggestion($command->getName(), $command->getDescription()));
                foreach ($command->getAliases() as $name) {
                    $suggestions->suggestValue(new Suggestion($name, $command->getDescription()));
                }
            }

            return;
        }

        if (CompletionInput::TYPE_OPTION_NAME === $input->getCompletionType()) {
            $suggestions->suggestOptions($this->getDefinition()->getOptions());

            return;
        }
    }

    /**
     * Gets the help message.
     */
    public function getHelp(): string
    {
        return $this->getLongVersion();
    }

    /**
     * Gets whether to catch exceptions or not during commands execution.
     */
    public function areExceptionsCaught(): bool
    {
        return $this->catchExceptions;
    }

    /**
     * Sets whether to catch exceptions or not during commands execution.
     *
     * @return void
     */
    public function setCatchExceptions(bool $boolean)
    {
        $this->catchExceptions = $boolean;
    }

    /**
     * Sets whether to catch errors or not during commands execution.
     */
    public function setCatchErrors(bool $catchErrors = true): void
    {
        $this->catchErrors = $catchErrors;
    }

    /**
     * Gets whether to automatically exit after a command execution or not.
     */
    public function isAutoExitEnabled(): bool
    {
        return $this->autoExit;
    }

    /**
     * Sets whether to automatically exit after a command execution or not.
     *
     * @return void
     */
    public function setAutoExit(bool $boolean)
    {
        $this->autoExit = $boolean;
    }

    /**
     * Gets the name of the application.
     */
    public function getName(): string
    {
        return $this->name;
    }

    /**
     * Sets the application name.
     *
     * @return void
     */
    public function setName(string $name)
    {
        $this->name = $name;
    }

    /**
     * Gets the application version.
     */
    public function getVersion(): string
    {
        return $this->version;
    }

    /**
     * Sets the application version.
     *
     * @return void
     */
    public function setVersion(string $version)
    {
        $this->version = $version;
    }

    /**
     * Returns the long version of the application.
     *
     * @return string
     */
    public function getLongVersion()
    {
        if ('
UNKNOWN' !== $this->getName()) {
            if ('
UNKNOWN' !== $this->getVersion()) {
                return sprintf('
%<info>%s</info>', $this->getName(), $this->getVersion());
            }

            return $this->getName();
        }

        return '
Console Tool';
    }

    /**
     * Registers a new command.
     */
    public function register(string $name): Command
    {
        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
     *
     * @return void
     */
    public function addCommands(array $commands)
    {
        foreach ($commands as $command) {
            $this->add($command);
        }
    }

    /**
     * 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
     */
    public function add(Command $command)
    {
        $this->init();

        $command->setApplication($this);

        if (!$command->isEnabled()) {
            $command->setApplication(null);

            return null;
        }

        if (!$command instanceof LazyCommand) {
            // Will throw if the command is not correctly initialized.
            $command->getDefinition();
        }

        if (!$command->getName()) {
            throw new LogicException(sprintf('
The command defined in "%s" cannot have an empty name.', get_debug_type($command)));
        }

        $this->commands[$command->getName()] = $command;

        foreach ($command->getAliases() as $alias) {
            $this->commands[$alias] = $command;
        }

        return $command;
    }

    /**
     * Returns a registered command by name or alias.
     *
     * @return Command
     *
     * @throws CommandNotFoundException When given command name does not exist
     */
    public function get(string $name)
    {
        $this->init();

        if (!$this->has($name)) {
            throw new CommandNotFoundException(sprintf('
The command "%s" does not exist.', $name));
        }

        // When the command has a different name than the one used at the command loader level
        if (!isset($this->commands[$name])) {
            throw new CommandNotFoundException(sprintf('
The "%s" command cannot be found because it is registered under multiple namesMake sure you don't set a different name via constructor or "setName()".'$name));
        }

        
$command $this->commands[$name];

        if (
$this->wantHelps) {
            
$this->wantHelps false;

            
$helpCommand $this->get('help');
            
$helpCommand->setCommand($command);

            return 
$helpCommand;
        }

        return 
$command;
    }

    
/**
     * Returns true if the command exists, false otherwise.
     */
    
public function has(string $name): bool
    
{
        
$this->init();

        return isset(
$this->commands[$name]) || ($this->commandLoader?->has($name) && $this->add($this->commandLoader->get($name)));
    }

    
/**
     * Returns an array of all unique namespaces used by currently registered commands.
     *
     * It does not return the global namespace which always exists.
     *
     * @return string[]
     */
    
public function getNamespaces(): array
    {
        
$namespaces = [];
        foreach (
$this->all() as $command) {
            if (
$command->isHidden()) {
                continue;
            }

            
$namespaces[] = $this->extractAllNamespaces($command->getName());

            foreach (
$command->getAliases() as $alias) {
                
$namespaces[] = $this->extractAllNamespaces($alias);
            }
        }

        return 
array_values(array_unique(array_filter(array_merge([], ...$namespaces))));
    }

    
/**
     * Finds a registered namespace by a name or an abbreviation.
     *
     * @throws NamespaceNotFoundException When namespace is incorrect or ambiguous
     */
    
public function findNamespace(string $namespace): string
    
{
        
$allNamespaces $this->getNamespaces();
        
$expr implode('[^:]*:'array_map('preg_quote'explode(':'$namespace))).'[^:]*';
        
$namespaces preg_grep('{^'.$expr.'}'$allNamespaces);

        if (empty(
$namespaces)) {
            
$message sprintf('There are no commands defined in the "%s" namespace.'$namespace);

            if (
$alternatives $this->findAlternatives($namespace$allNamespaces)) {
                if (
== count($alternatives)) {
                    
$message .= "nnDid you mean this?n    ";
                } else {
                    
$message .= "nnDid you mean one of these?n    ";
                }

                
$message .= implode("n    "$alternatives);
            }

            throw new 
NamespaceNotFoundException($message$alternatives);
        }

        
$exact in_array($namespace$namespacestrue);
        if (
count($namespaces) > && !$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));
        }

        return 
$exact $namespace reset($namespaces);
    }

    
/**
     * Finds a command by name or alias.
     *
     * Contrary to get, this command tries to find the best
     * match if you give it an abbreviation of a name or alias.
     *
     * @return Command
     *
     * @throws CommandNotFoundException When command name is incorrect or ambiguous
     */
    
public function find(string $name)
    {
        
$this->init();

        
$aliases = [];

        foreach (
$this->commands as $command) {
            foreach (
$command->getAliases() as $alias) {
                if (!
$this->has($alias)) {
                    
$this->commands[$alias] = $command;
                }
            }
        }

        if (
$this->has($name)) {
            return 
$this->get($name);
        }

        
$allCommands $this->commandLoader array_merge($this->commandLoader->getNames(), array_keys($this->commands)) : array_keys($this->commands);
        
$expr implode('[^:]*:'array_map('preg_quote'explode(':'$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) {
            if (
false !== $pos strrpos($name':')) {
                
// check if a namespace exists and contains commands
                
$this->findNamespace(substr($name0$pos));
            }

            
$message sprintf('Command "%s" is not defined.'$name);

            if (
$alternatives $this->findAlternatives($name$allCommands)) {
                
// remove hidden commands
                
$alternatives array_filter($alternativesfn ($name) => !$this->get($name)->isHidden());

                if (
== count($alternatives)) {
                    
$message .= "nnDid you mean this?n    ";
                } else {
                    
$message .= "nnDid you mean one of these?n    ";
                }
                
$message .= implode("n    "$alternatives);
            }

            throw new 
CommandNotFoundException($messagearray_values($alternatives));
        }

        
// filter out aliases for commands which are already on the list
        
if (count($commands) > 1) {
            
$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) {
                if (!
$commandList[$nameOrAlias] instanceof Command) {
                    
$commandList[$nameOrAlias] = $this->commandLoader->get($nameOrAlias);
                }

                
$commandName $commandList[$nameOrAlias]->getName();

                
$aliases[$nameOrAlias] = $commandName;

                return 
$commandName === $nameOrAlias || !in_array($commandName$commands);
            }));
        }

        if (
count($commands) > 1) {
            
$usableWidth $this->terminal->getWidth() - 10;
            
$abbrevs array_values($commands);
            
$maxLen 0;
            foreach (
$abbrevs as $abbrev) {
                
$maxLen max(Helper::width($abbrev), $maxLen);
            }
            
$abbrevs array_map(function ($cmd) use ($commandList$usableWidth$maxLen, &$commands) {
                if (
$commandList[$cmd]->isHidden()) {
                    unset(
$commands[array_search($cmd$commands)]);

                    return 
false;
                }

                
$abbrev str_pad($cmd$maxLen' ').' '.$commandList[$cmd]->getDescription();

                return 
Helper::width($abbrev) > $usableWidth Helper::substr($abbrev0$usableWidth 3).'...' $abbrev;
            }, 
array_values($commands));

            if (
count($commands) > 1) {
                
$suggestions $this->getAbbreviationSuggestions(array_filter($abbrevs));

                throw new 
CommandNotFoundException(sprintf("Command "%s" is ambiguous.nDid you mean one of these?n%s."$name$suggestions), array_values($commands));
            }
        }

        
$command $this->get(reset($commands));

        if (
$command->isHidden()) {
            throw new 
CommandNotFoundException(sprintf('The command "%s" does not exist.'$name));
        }

        return 
$command;
    }

    
/**
     * Gets the commands (registered in the given namespace if provided).
     *
     * The array keys are the full names and the values the command instances.
     *
     * @return Command[]
     */
    
public function all(?string $namespace null)
    {
        
$this->init();

        if (
null === $namespace) {
            if (!
$this->commandLoader) {
                return 
$this->commands;
            }

            
$commands $this->commands;
            foreach (
$this->commandLoader->getNames() as $name) {
                if (!isset(
$commands[$name]) && $this->has($name)) {
                    
$commands[$name] = $this->get($name);
                }
            }

            return 
$commands;
        }

        
$commands = [];
        foreach (
$this->commands as $name => $command) {
            if (
$namespace === $this->extractNamespace($namesubstr_count($namespace':') + 1)) {
                
$commands[$name] = $command;
            }
        }

        if (
$this->commandLoader) {
            foreach (
$this->commandLoader->getNames() as $name) {
                if (!isset(
$commands[$name]) && $namespace === $this->extractNamespace($namesubstr_count($namespace':') + 1) && $this->has($name)) {
                    
$commands[$name] = $this->get($name);
                }
            }
        }

        return 
$commands;
    }

    
/**
     * Returns an array of possible abbreviations given a set of names.
     *
     * @return string[][]
     */
    
public static function getAbbreviations(array $names): array
    {
        
$abbrevs = [];
        foreach (
$names as $name) {
            for (
$len strlen($name); $len 0; --$len) {
                
$abbrev substr($name0$len);
                
$abbrevs[$abbrev][] = $name;
            }
        }

        return 
$abbrevs;
    }

    public function 
renderThrowable(Throwable $eOutputInterface $output): void
    
{
        
$output->writeln(''OutputInterface::VERBOSITY_QUIET);

        
$this->doRenderThrowable($e$output);

        if (
null !== $this->runningCommand) {
            
$output->writeln(sprintf('<info>%s</info>'OutputFormatter::escape(sprintf($this->runningCommand->getSynopsis(), $this->getName()))), OutputInterface::VERBOSITY_QUIET);
            
$output->writeln(''OutputInterface::VERBOSITY_QUIET);
        }
    }

    protected function 
doRenderThrowable(Throwable $eOutputInterface $output): void
    
{
        do {
            
$message trim($e->getMessage());
            if (
'' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
                
$class get_debug_type($e);
                
$title sprintf('  [%s%s]  '$class!== ($code $e->getCode()) ? ' ('.$code.')' '');
                
$len Helper::width($title);
            } else {
                
$len 0;
            }

            if (
str_contains($message"@anonymous")) {
                
$message preg_replace_callback('/[a-zA-Z_x7f-xff][\\a-zA-Z0-9_x7f-xff]*+@anonymousx00.*?.php(?:0x?|:[0-9]++$)?[0-9a-fA-F]++/'fn ($m) => class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' $m[0], $message);
            }

            
$width $this->terminal->getWidth() ? $this->terminal->getWidth() - 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 Helper::width($line) + 4;
                    
$lines[] = [$line$lineLength];

                    
$len max($lineLength$len);
                }
            }

            
$messages = [];
            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')));
            }
            
$messages[] = $emptyLine sprintf('<error>%s</error>'str_repeat(' '$len));
            if (
'' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
                
$messages[] = sprintf('<error>%s%s</error>'$titlestr_repeat(' 'max(0$len Helper::width($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($messagesOutputInterface::VERBOSITY_QUIET);

            if (
OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
                
$output->writeln('<comment>Exception trace:</comment>'OutputInterface::VERBOSITY_QUIET);

                
// exception related properties
                
$trace $e->getTrace();

                
array_unshift($trace, [
                    
'function' => '',
                    
'file' => $e->getFile() ?: 'n/a',
                    
'line' => $e->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 (
$e $e->getPrevious());
    }

    
/**
     * Configures the input and output instances based on the user arguments and options.
     *
     * @return void
     */
    
protected function configureIO(InputInterface $inputOutputInterface $output)
    {
        if (
true === $input->hasParameterOption(['--ansi'], true)) {
            
$output->setDecorated(true);
        } elseif (
true === $input->hasParameterOption(['--no-ansi'], true)) {
            
$output->setDecorated(false);
        }

        if (
true === $input->hasParameterOption(['--no-interaction''-n'], true)) {
            
$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(['--quiet''-q'], true)) {
            
$output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
            
$shellVerbosity = -1;
        } else {
            if (
$input->hasParameterOption('-vvv'true) || $input->hasParameterOption('--verbose=3'true) || === $input->getParameterOption('--verbose'falsetrue)) {
                
$output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
                
$shellVerbosity 3;
            } elseif (
$input->hasParameterOption('-vv'true) || $input->hasParameterOption('--verbose=2'true) || === $input->getParameterOption('--verbose'falsetrue)) {
                
$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'falsetrue)) {
                
$output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
                
$shellVerbosity 1;
            }
        }

        if (-
=== $shellVerbosity) {
            
$input->setInteractive(false);
        }

        if (
function_exists('putenv')) {
            @
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 $commandInputInterface $inputOutputInterface $output)
    {
        foreach (
$command->getHelperSet() as $helper) {
            if (
$helper instanceof InputAwareInterface) {
                
$helper->setInput($input);
            }
        }

        
$commandSignals $command instanceof SignalableCommandInterface $command->getSubscribedSignals() : [];
        if (
$commandSignals || $this->dispatcher && $this->signalsToDispatchEvent) {
            if (!
$this->signalRegistry) {
                throw new 
RuntimeException('Unable to subscribe to signal events. Make sure that the "pcntl" extension is installed and that "pcntl_*" functions are not disabled by your php.ini'"disable_functions" directive.');
            }

            if (Terminal::hasSttyAvailable()) {
                $sttyMode = shell_exec('
stty -g');

                foreach ([SIGINT, SIGTERM] as $signal) {
                    $this->signalRegistry->register($signal, static fn () => shell_exec('
stty '.$sttyMode));
                }
            }

            if ($this->dispatcher) {
                // We register application signals, so that we can dispatch the event
                foreach ($this->signalsToDispatchEvent as $signal) {
                    $event = new ConsoleSignalEvent($command, $input, $output, $signal);

                    $this->signalRegistry->register($signal, function ($signal) use ($event, $command, $commandSignals) {
                        $this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL);
                        $exitCode = $event->getExitCode();

                        // If the command is signalable, we call the handleSignal() method
                        if (in_array($signal, $commandSignals, true)) {
                            $exitCode = $command->handleSignal($signal, $exitCode);
                            // BC layer for Symfony <= 5
                            if (null === $exitCode) {
                                trigger_deprecation('
symfony/console', '6.3', 'Not returning an exit code from "%s::handleSignal()" is deprecated, return "false" to keep the command running or "0" to exit successfully.', get_debug_type($command));
                                $exitCode = 0;
                            }
                        }

                        if (false !== $exitCode) {
                            $event = new ConsoleTerminateEvent($command, $event->getInput(), $event->getOutput(), $exitCode, $signal);
                            $this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE);

                            exit($event->getExitCode());
                        }
                    });
                }

                // then we register command signals, but not if already handled after the dispatcher
                $commandSignals = array_diff($commandSignals, $this->signalsToDispatchEvent);
            }

            foreach ($commandSignals as $signal) {
                $this->signalRegistry->register($signal, function (int $signal) use ($command): void {
                    $exitCode = $command->handleSignal($signal);
                    // BC layer for Symfony <= 5
                    if (null === $exitCode) {
                        trigger_deprecation('
symfony/console', '6.3', 'Not returning an exit code from "%s::handleSignal()" is deprecated, return "false" to keep the command running or "0" to exit successfully.', get_debug_type($command));
                        $exitCode = 0;
                    }

                    if (false !== $exitCode) {
                        exit($exitCode);
                    }
                });
            }
        }

        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) {
            // 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($event, ConsoleEvents::COMMAND);

            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($event, ConsoleEvents::ERROR);
            $e = $event->getError();

            if (0 === $exitCode = $event->getExitCode()) {
                $e = null;
            }
        }

        $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode);
        $this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE);

        if (null !== $e) {
            throw $e;
        }

        return $event->getExitCode();
    }

    /**
     * Gets the name of the command based on input.
     */
    protected function getCommandName(InputInterface $input): ?string
    {
        return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument();
    }

    /**
     * Gets the default input definition.
     */
    protected function getDefaultInputDefinition(): InputDefinition
    {
        return new InputDefinition([
            new InputArgument('
command', InputArgument::REQUIRED, 'The command to execute'),
            new InputOption('
--help', '-h', InputOption::VALUE_NONE, 'Display help for the given commandWhen no command is given display help for the <info>'.$this->defaultCommand.'</infocommand'),
            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 messagesfor normal outputfor more verbose output and for debug'),
            new InputOption('
--version', '-V', InputOption::VALUE_NONE, 'Display this application version'),
            new InputOption('
--ansi', '', InputOption::VALUE_NEGATABLE, 'Force (or disable --no-ansiANSI output', null),
            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[]
     */
    protected function getDefaultCommands(): array
    {
        return [new HelpCommand(), new ListCommand(), new CompleteCommand(), new DumpCompletionCommand()];
    }

    /**
     * Gets the default helper set with the helpers that should always be available.
     */
    protected function getDefaultHelperSet(): HelperSet
    {
        return new HelperSet([
            new FormatterHelper(),
            new DebugFormatterHelper(),
            new ProcessHelper(),
            new QuestionHelper(),
        ]);
    }

    /**
     * Returns abbreviated suggestions in string format.
     */
    private function getAbbreviationSuggestions(array $abbrevs): string
    {
        return '    '.implode("n    ", $abbrevs);
    }

    /**
     * Returns the namespace part of the command name.
     *
     * This method is not part of public API and should not be used directly.
     */
    public function extractNamespace(string $name, ?int $limit = null): string
    {
        $parts = explode('
:', $name, -1);

        return implode('
:', null === $limit ? $parts : array_slice($parts, 0, $limit));
    }

    /**
     * Finds alternative of $name among $collection,
     * if nothing is found in $collection, try in $abbrevs.
     *
     * @return string[]
     */
    private function findAlternatives(string $name, iterable $collection): array
    {
        $threshold = 1e3;
        $alternatives = [];

        $collectionParts = [];
        foreach ($collection as $item) {
            $collectionParts[$item] = explode('
:', $item);
        }

        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 && str_contains($parts[$i], $subname)) {
                    $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev;
                } elseif ($exists) {
                    $alternatives[$collectionName] += $threshold;
                }
            }
        }

        foreach ($collection as $item) {
            $lev = levenshtein($name, $item);
            if ($lev <= strlen($name) / 3 || str_contains($item, $name)) {
                $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
            }
        }

        $alternatives = array_filter($alternatives, fn ($lev) => $lev < 2 * $threshold);
        ksort($alternatives, SORT_NATURAL | SORT_FLAG_CASE);

        return array_keys($alternatives);
    }

    /**
     * Sets the default Command name.
     *
     * @return $this
     */
    public function setDefaultCommand(string $commandName, bool $isSingleCommand = false): static
    {
        $this->defaultCommand = explode('
|', ltrim($commandName, '|'))[0];

        if ($isSingleCommand) {
            // Ensure the command exist
            $this->find($commandName);

            $this->singleCommand = true;
        }

        return $this;
    }

    /**
     * @internal
     */
    public function isSingleCommand(): bool
    {
        return $this->singleCommand;
    }

    private function splitStringByWidth(string $string, int $width): array
    {
        // 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
        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 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;
    }

    
/**
     * Returns all namespaces of the command name.
     *
     * @return string[]
     */
    
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;
    }

    private function 
init(): void
    
{
        if (
$this->initialized) {
            return;
        }
        
$this->initialized true;

        foreach (
$this->getDefaultCommands() as $command) {
            
$this->add($command);
        }
    }
}
Онлайн: 3
Реклама