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 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()); return $this; } public function getExampleUsages() { return $this->examples; } protected function addUsageOrExample($usage, $description) { $this->addUsage($usage); if (!empty($description)) { $this->examples[$usage] = $description; } } public function helpAlter(\DomDocument $originalDom) { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($commandXML = $dom->createElement('command')); $commandXML->setAttribute('id', $this->getName()); $commandXML->setAttribute('name', $this->getName()); // Get the original element and its top-level elements. $originalCommandXML = $this->getSingleElementByTagName($dom, $originalDom, 'command'); $originalUsagesXML = $this->getSingleElementByTagName($dom, $originalCommandXML, 'usages'); $originalDescriptionXML = $this->getSingleElementByTagName($dom, $originalCommandXML, 'description'); $originalHelpXML = $this->getSingleElementByTagName($dom, $originalCommandXML, 'help'); $originalArgumentsXML = $this->getSingleElementByTagName($dom, $originalCommandXML, 'arguments'); $originalOptionsXML = $this->getSingleElementByTagName($dom, $originalCommandXML, 'options'); // Keep only the first of the elements $newUsagesXML = $dom->createElement('usages'); $firstUsageXML = $this->getSingleElementByTagName($dom, $originalUsagesXML, 'usage'); $newUsagesXML->appendChild($firstUsageXML); // Create our own elements $newExamplesXML = $dom->createElement('examples'); foreach ($this->examples as $usage => $description) { $newExamplesXML->appendChild($exampleXML = $dom->createElement('example')); $exampleXML->appendChild($usageXML = $dom->createElement('usage', $usage)); $exampleXML->appendChild($descriptionXML = $dom->createElement('description', $description)); } // Create our own elements $newAliasesXML = $dom->createElement('aliases'); foreach ($this->getAliases() as $alias) { $newAliasesXML->appendChild($dom->createElement('alias', $alias)); } // Create our own elements $newTopicsXML = $dom->createElement('topics'); foreach ($this->getTopics() as $topic) { $newTopicsXML->appendChild($topicXML = $dom->createElement('topic', $topic)); } // Place the different elements into the element in the desired order $commandXML->appendChild($newUsagesXML); $commandXML->appendChild($newExamplesXML); $commandXML->appendChild($originalDescriptionXML); $commandXML->appendChild($originalArgumentsXML); $commandXML->appendChild($originalOptionsXML); $commandXML->appendChild($originalHelpXML); $commandXML->appendChild($newAliasesXML); $commandXML->appendChild($newTopicsXML); return $dom; } protected function getSingleElementByTagName($dom, $parent, $tagName) { // There should always be exactly one '' element. $elements = $parent->getElementsByTagName($tagName); $result = $elements->item(0); $result = $dom->importNode($result, true); return $result; } protected function setCommandArguments($commandInfo) { $this->setUsesInputInterface($commandInfo); $this->setUsesOutputInterface($commandInfo); $this->setCommandArgumentsFromParameters($commandInfo); return $this; } /** * Check whether the first parameter is an InputInterface. */ protected function checkUsesInputInterface($params) { $firstParam = reset($params); return $firstParam instanceof InputInterface; } /** * Determine whether this command wants to get its inputs * via an InputInterface or via its command parameters */ protected function setUsesInputInterface($commandInfo) { $params = $commandInfo->getParameters(); $this->usesInputInterface = $this->checkUsesInputInterface($params); return $this; } /** * Determine whether this command wants to send its output directly * to the provided OutputInterface, or whether it will returned * structured output to be processed by the command processor. */ protected function setUsesOutputInterface($commandInfo) { $params = $commandInfo->getParameters(); $index = $this->checkUsesInputInterface($params) ? 1 : 0; $this->usesOutputInterface = (count($params) > $index) && ($params[$index] instanceof OutputInterface); 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); $this->addArgument($name, $parameterMode, $description, $defaultValue); } 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])) { $description = $automaticOptions[$name]->getDescription(); $inputOption = static::inputOptionSetDescription($inputOption, $description); } $this->getDefinition()->addOption($inputOption); } } protected static function inputOptionSetDescription($inputOption, $description) { // 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 ); } 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) { $this->commandProcessor()->interact( $input, $output, $this->getNames(), $this->annotationData ); } protected function initialize(InputInterface $input, OutputInterface $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); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { // Validate, run, process, alter, handle results. return $this->commandProcessor()->process( $output, $this->getNames(), $this->commandCallback, $this->createCommandData($input, $output) ); } /** * 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) { $commandData = $this->createCommandData($input, $output); $commandProcessor = $this->commandProcessor(); $names = $this->getNames(); $results = $commandProcessor->processResults( $names, $results, $commandData ); return $commandProcessor->handleResults( $output, $names, $results, $commandData ); } protected function createCommandData(InputInterface $input, OutputInterface $output) { $commandData = new CommandData( $this->annotationData, $input, $output ); $commandData->setUseIOInterfaces( $this->usesOutputInterface, $this->usesInputInterface ); return $commandData; } }