getName(); } } parent::__construct($name); if ($commandInfo && $commandInfo->hasAnnotation('command')) { $this->setCommandInfo($commandInfo); $this->setCommandOptions($commandInfo); } } public function setCommandCallback($commandCallback) { $this->commandCallback = $commandCallback; return $this; } public function setCompletionCallback($completionCallback) { $this->completionCallback = $completionCallback; return $this; } public function setCommandProcessor($commandProcessor) { $this->commandProcessor = $commandProcessor; return $this; } public function commandProcessor() { // If someone is using an AnnotatedCommand, and is NOT getting // it from an AnnotatedCommandFactory OR not correctly injecting // a command processor via setCommandProcessor() (ideally via the // DI container), then we'll just give each annotated command its // own command processor. This is not ideal; preferably, there would // only be one instance of the command processor in the application. if (!isset($this->commandProcessor)) { $this->commandProcessor = new CommandProcessor(new HookManager()); } return $this->commandProcessor; } public function getReturnType() { return $this->returnType; } public function setReturnType($returnType) { $this->returnType = $returnType; return $this; } public function getAnnotationData() { return $this->annotationData; } public function setAnnotationData($annotationData) { $this->annotationData = $annotationData; return $this; } public function getTopics() { return $this->topics; } public function setTopics($topics) { $this->topics = $topics; return $this; } public function setCommandInfo($commandInfo) { $this->setDescription($commandInfo->getDescription() ?: ''); $this->setHelp($commandInfo->getHelp() ?: ''); $this->setAliases($commandInfo->getAliases()); $this->setAnnotationData($commandInfo->getAnnotations()); $this->setTopics($commandInfo->getTopics()); foreach ($commandInfo->getExampleUsages() as $usage => $description) { $this->addUsageOrExample($usage, $description); } $this->setCommandArguments($commandInfo); $this->setReturnType($commandInfo->getReturnType()); // Hidden commands available since Symfony 3.2 // http://symfony.com/doc/current/console/hide_commands.html if (method_exists($this, 'setHidden')) { $this->setHidden($commandInfo->getHidden()); } $this->parameterMap = $commandInfo->getParameterMap(); return $this; } public function getExampleUsages() { return $this->examples; } public function addUsageOrExample($usage, $description) { $this->addUsage($usage); if (!empty($description)) { $this->examples[$usage] = $description; } } public function getCompletionCallback() { return $this->completionCallback; } public function helpAlter(\DomDocument $originalDom) { return HelpDocumentBuilder::alter($originalDom, $this); } protected function setCommandArguments($commandInfo) { $this->injectedClasses = $commandInfo->getInjectedClasses(); $this->setCommandArgumentsFromParameters($commandInfo); return $this; } protected function setCommandArgumentsFromParameters($commandInfo) { $args = $commandInfo->arguments()->getValues(); foreach ($args as $name => $defaultValue) { $description = $commandInfo->arguments()->getDescription($name); $hasDefault = $commandInfo->arguments()->hasDefault($name); $parameterMode = $this->getCommandArgumentMode($hasDefault, $defaultValue); $suggestedValues = $commandInfo->arguments()->getSuggestedValues($name); $this->addArgument($name, $parameterMode, $description, $defaultValue, $suggestedValues); } return $this; } protected function getCommandArgumentMode($hasDefault, $defaultValue) { if (!$hasDefault) { return InputArgument::REQUIRED; } if (is_array($defaultValue)) { return InputArgument::IS_ARRAY; } return InputArgument::OPTIONAL; } public function setCommandOptions($commandInfo, $automaticOptions = []) { $inputOptions = $commandInfo->inputOptions(); $this->addOptions($inputOptions + $automaticOptions, $automaticOptions); return $this; } public function addOptions($inputOptions, $automaticOptions = []) { foreach ($inputOptions as $name => $inputOption) { $description = $inputOption->getDescription(); if (empty($description) && isset($automaticOptions[$name])) { // Unfortunately, Console forces us too construct a new InputOption to set a description. $description = $automaticOptions[$name]->getDescription(); $this->addInputOption($inputOption, $description); } else { if ($native = $this->getNativeDefinition()) { $native->addOption($inputOption); } $this->getDefinition()->addOption($inputOption); } } } private function addInputOption($inputOption, $description = null) { $default = $inputOption->getDefault(); // Recover the 'mode' value, because Symfony is stubborn $mode = 0; if ($inputOption->isValueRequired()) { $mode |= InputOption::VALUE_REQUIRED; } if ($inputOption->isValueOptional()) { $mode |= InputOption::VALUE_OPTIONAL; } if ($inputOption->isArray()) { $mode |= InputOption::VALUE_IS_ARRAY; } if (!$mode) { $mode = InputOption::VALUE_NONE; $default = null; } $suggestedValues = []; // Symfony 6.1+ feature https://symfony.com/blog/new-in-symfony-6-1-improved-console-autocompletion#completion-values-in-input-definitions if (property_exists($inputOption, 'suggestedValues')) { // Alas, Symfony provides no accessor. $class = new \ReflectionClass($inputOption); $property = $class->getProperty('suggestedValues'); $property->setAccessible(true); $suggestedValues = $property->getValue($inputOption); } $this->addOption( $inputOption->getName(), $inputOption->getShortcut(), $mode, $description ?? $inputOption->getDescription(), $default, $suggestedValues ); } /** * @deprecated since 4.5.0 */ protected static function inputOptionSetDescription($inputOption, $description) { @\trigger_error( 'Since consolidation/annotated-command 4.5: ' . 'AnnotatedCommand::inputOptionSetDescription method is deprecated and will be removed in 5.0', \E_USER_DEPRECATED ); // Recover the 'mode' value, because Symfony is stubborn $mode = 0; if ($inputOption->isValueRequired()) { $mode |= InputOption::VALUE_REQUIRED; } if ($inputOption->isValueOptional()) { $mode |= InputOption::VALUE_OPTIONAL; } if ($inputOption->isArray()) { $mode |= InputOption::VALUE_IS_ARRAY; } if (!$mode) { $mode = InputOption::VALUE_NONE; } $inputOption = new InputOption( $inputOption->getName(), $inputOption->getShortcut(), $mode, $description, $inputOption->getDefault() ); return $inputOption; } /** * Returns all of the hook names that may be called for this command. * * @return array */ public function getNames() { return HookManager::getNames($this, $this->commandCallback); } /** * Add any options to this command that are defined by hook implementations */ public function optionsHook() { $this->commandProcessor()->optionsHook( $this, $this->getNames(), $this->annotationData ); } /** * Route a completion request to the specified Callable if available. */ public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { parent::complete($input, $suggestions); if (is_callable($this->completionCallback)) { call_user_func($this->completionCallback, $input, $suggestions); } } public function optionsHookForHookAnnotations($commandInfoList) { foreach ($commandInfoList as $commandInfo) { $inputOptions = $commandInfo->inputOptions(); $this->addOptions($inputOptions); foreach ($commandInfo->getExampleUsages() as $usage => $description) { if (!in_array($usage, $this->getUsages())) { $this->addUsageOrExample($usage, $description); } } } } /** * {@inheritdoc} */ protected function interact(InputInterface $input, OutputInterface $output) { $state = $this->injectIntoCommandfileInstance($input, $output); $this->commandProcessor()->interact( $input, $output, $this->getNames(), $this->annotationData ); $state->restore(); } protected function initialize(InputInterface $input, OutputInterface $output) { $state = $this->injectIntoCommandfileInstance($input, $output); // Allow the hook manager a chance to provide configuration values, // if there are any registered hooks to do that. $this->commandProcessor()->initializeHook($input, $this->getNames(), $this->annotationData); $state->restore(); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { $state = $this->injectIntoCommandfileInstance($input, $output); // Validate, run, process, alter, handle results. $result = $this->commandProcessor()->process( $output, $this->getNames(), $this->commandCallback, $this->createCommandData($input, $output) ); $state->restore(); return $result; } /** * This function is available for use by a class that may * wish to extend this class rather than use annotations to * define commands. Using this technique does allow for the * use of annotations to define hooks. */ public function processResults(InputInterface $input, OutputInterface $output, $results) { $state = $this->injectIntoCommandfileInstance($input, $output); $commandData = $this->createCommandData($input, $output); $commandProcessor = $this->commandProcessor(); $names = $this->getNames(); $results = $commandProcessor->processResults( $names, $results, $commandData ); $status = $commandProcessor->handleResults( $output, $names, $results, $commandData ); $state->restore(); return $status; } protected function createCommandData(InputInterface $input, OutputInterface $output) { $commandData = new CommandData( $this->annotationData, $input, $output, $this->parameterMap ); $formatterOptions = new FormatterOptions($commandData->annotationData()->getArrayCopy(), $commandData->input()->getOptions()); $commandData->setFormatterOptions($formatterOptions); // Fetch any classes (e.g. InputInterface / OutputInterface) that // this command's callback wants passed as a parameter and inject // it into the command data. $this->commandProcessor()->injectIntoCommandData($commandData, $this->injectedClasses); // Allow the commandData to cache the list of options with // special default values ('null' and 'true'), as these will // need special handling. @see CommandData::options(). $commandData->cacheSpecialDefaults($this->getDefinition()); return $commandData; } /** * Inject $input and $output into the command instance if it is set up to receive them. * * @param callable $commandCallback * @param CommandData $commandData * @return State */ public function injectIntoCommandfileInstance(InputInterface $input, OutputInterface $output) { return StateHelper::injectIntoCallbackObject($this->commandCallback, $input, $output); } }