annotate vendor/consolidation/annotated-command/src/AnnotatedCommand.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 129ea1e6d783
children
rev   line source
Chris@0 1 <?php
Chris@0 2 namespace Consolidation\AnnotatedCommand;
Chris@0 3
Chris@0 4 use Consolidation\AnnotatedCommand\Hooks\HookManager;
Chris@0 5 use Consolidation\AnnotatedCommand\Parser\CommandInfo;
Chris@0 6 use Consolidation\AnnotatedCommand\Help\HelpDocumentAlter;
Chris@0 7 use Symfony\Component\Console\Command\Command;
Chris@0 8 use Symfony\Component\Console\Input\InputArgument;
Chris@0 9 use Symfony\Component\Console\Input\InputInterface;
Chris@0 10 use Symfony\Component\Console\Input\InputOption;
Chris@0 11 use Symfony\Component\Console\Output\OutputInterface;
Chris@17 12 use Consolidation\AnnotatedCommand\Help\HelpDocumentBuilder;
Chris@0 13
Chris@0 14 /**
Chris@0 15 * AnnotatedCommands are created automatically by the
Chris@0 16 * AnnotatedCommandFactory. Each command method in a
Chris@0 17 * command file will produce one AnnotatedCommand. These
Chris@0 18 * are then added to your Symfony Console Application object;
Chris@0 19 * nothing else is needed.
Chris@0 20 *
Chris@0 21 * Optionally, though, you may extend AnnotatedCommand directly
Chris@0 22 * to make a single command. The usage pattern is the same
Chris@0 23 * as for any other Symfony Console command, except that you may
Chris@0 24 * omit the 'Confiure' method, and instead place your annotations
Chris@0 25 * on the execute() method.
Chris@0 26 *
Chris@0 27 * @package Consolidation\AnnotatedCommand
Chris@0 28 */
Chris@0 29 class AnnotatedCommand extends Command implements HelpDocumentAlter
Chris@0 30 {
Chris@0 31 protected $commandCallback;
Chris@0 32 protected $commandProcessor;
Chris@0 33 protected $annotationData;
Chris@0 34 protected $examples = [];
Chris@0 35 protected $topics = [];
Chris@0 36 protected $returnType;
Chris@17 37 protected $injectedClasses = [];
Chris@0 38
Chris@0 39 public function __construct($name = null)
Chris@0 40 {
Chris@0 41 $commandInfo = false;
Chris@0 42
Chris@0 43 // If this is a subclass of AnnotatedCommand, check to see
Chris@0 44 // if the 'execute' method is annotated. We could do this
Chris@0 45 // unconditionally; it is a performance optimization to skip
Chris@0 46 // checking the annotations if $this is an instance of
Chris@0 47 // AnnotatedCommand. Alternately, we break out a new subclass.
Chris@0 48 // The command factory instantiates the subclass.
Chris@0 49 if (get_class($this) != 'Consolidation\AnnotatedCommand\AnnotatedCommand') {
Chris@0 50 $commandInfo = CommandInfo::create($this, 'execute');
Chris@0 51 if (!isset($name)) {
Chris@0 52 $name = $commandInfo->getName();
Chris@0 53 }
Chris@0 54 }
Chris@0 55 parent::__construct($name);
Chris@0 56 if ($commandInfo && $commandInfo->hasAnnotation('command')) {
Chris@0 57 $this->setCommandInfo($commandInfo);
Chris@0 58 $this->setCommandOptions($commandInfo);
Chris@0 59 }
Chris@0 60 }
Chris@0 61
Chris@0 62 public function setCommandCallback($commandCallback)
Chris@0 63 {
Chris@0 64 $this->commandCallback = $commandCallback;
Chris@0 65 return $this;
Chris@0 66 }
Chris@0 67
Chris@0 68 public function setCommandProcessor($commandProcessor)
Chris@0 69 {
Chris@0 70 $this->commandProcessor = $commandProcessor;
Chris@0 71 return $this;
Chris@0 72 }
Chris@0 73
Chris@0 74 public function commandProcessor()
Chris@0 75 {
Chris@0 76 // If someone is using an AnnotatedCommand, and is NOT getting
Chris@0 77 // it from an AnnotatedCommandFactory OR not correctly injecting
Chris@0 78 // a command processor via setCommandProcessor() (ideally via the
Chris@0 79 // DI container), then we'll just give each annotated command its
Chris@0 80 // own command processor. This is not ideal; preferably, there would
Chris@0 81 // only be one instance of the command processor in the application.
Chris@0 82 if (!isset($this->commandProcessor)) {
Chris@0 83 $this->commandProcessor = new CommandProcessor(new HookManager());
Chris@0 84 }
Chris@0 85 return $this->commandProcessor;
Chris@0 86 }
Chris@0 87
Chris@0 88 public function getReturnType()
Chris@0 89 {
Chris@0 90 return $this->returnType;
Chris@0 91 }
Chris@0 92
Chris@0 93 public function setReturnType($returnType)
Chris@0 94 {
Chris@0 95 $this->returnType = $returnType;
Chris@0 96 return $this;
Chris@0 97 }
Chris@0 98
Chris@0 99 public function getAnnotationData()
Chris@0 100 {
Chris@0 101 return $this->annotationData;
Chris@0 102 }
Chris@0 103
Chris@0 104 public function setAnnotationData($annotationData)
Chris@0 105 {
Chris@0 106 $this->annotationData = $annotationData;
Chris@0 107 return $this;
Chris@0 108 }
Chris@0 109
Chris@0 110 public function getTopics()
Chris@0 111 {
Chris@0 112 return $this->topics;
Chris@0 113 }
Chris@0 114
Chris@0 115 public function setTopics($topics)
Chris@0 116 {
Chris@0 117 $this->topics = $topics;
Chris@0 118 return $this;
Chris@0 119 }
Chris@0 120
Chris@0 121 public function setCommandInfo($commandInfo)
Chris@0 122 {
Chris@0 123 $this->setDescription($commandInfo->getDescription());
Chris@0 124 $this->setHelp($commandInfo->getHelp());
Chris@0 125 $this->setAliases($commandInfo->getAliases());
Chris@0 126 $this->setAnnotationData($commandInfo->getAnnotations());
Chris@0 127 $this->setTopics($commandInfo->getTopics());
Chris@0 128 foreach ($commandInfo->getExampleUsages() as $usage => $description) {
Chris@0 129 $this->addUsageOrExample($usage, $description);
Chris@0 130 }
Chris@0 131 $this->setCommandArguments($commandInfo);
Chris@0 132 $this->setReturnType($commandInfo->getReturnType());
Chris@0 133 // Hidden commands available since Symfony 3.2
Chris@0 134 // http://symfony.com/doc/current/console/hide_commands.html
Chris@0 135 if (method_exists($this, 'setHidden')) {
Chris@0 136 $this->setHidden($commandInfo->getHidden());
Chris@0 137 }
Chris@0 138 return $this;
Chris@0 139 }
Chris@0 140
Chris@0 141 public function getExampleUsages()
Chris@0 142 {
Chris@0 143 return $this->examples;
Chris@0 144 }
Chris@0 145
Chris@0 146 protected function addUsageOrExample($usage, $description)
Chris@0 147 {
Chris@0 148 $this->addUsage($usage);
Chris@0 149 if (!empty($description)) {
Chris@0 150 $this->examples[$usage] = $description;
Chris@0 151 }
Chris@0 152 }
Chris@0 153
Chris@0 154 public function helpAlter(\DomDocument $originalDom)
Chris@0 155 {
Chris@17 156 return HelpDocumentBuilder::alter($originalDom, $this);
Chris@0 157 }
Chris@0 158
Chris@0 159 protected function setCommandArguments($commandInfo)
Chris@0 160 {
Chris@17 161 $this->injectedClasses = $commandInfo->getInjectedClasses();
Chris@0 162 $this->setCommandArgumentsFromParameters($commandInfo);
Chris@0 163 return $this;
Chris@0 164 }
Chris@0 165
Chris@0 166 protected function setCommandArgumentsFromParameters($commandInfo)
Chris@0 167 {
Chris@0 168 $args = $commandInfo->arguments()->getValues();
Chris@0 169 foreach ($args as $name => $defaultValue) {
Chris@0 170 $description = $commandInfo->arguments()->getDescription($name);
Chris@0 171 $hasDefault = $commandInfo->arguments()->hasDefault($name);
Chris@0 172 $parameterMode = $this->getCommandArgumentMode($hasDefault, $defaultValue);
Chris@0 173 $this->addArgument($name, $parameterMode, $description, $defaultValue);
Chris@0 174 }
Chris@0 175 return $this;
Chris@0 176 }
Chris@0 177
Chris@0 178 protected function getCommandArgumentMode($hasDefault, $defaultValue)
Chris@0 179 {
Chris@0 180 if (!$hasDefault) {
Chris@0 181 return InputArgument::REQUIRED;
Chris@0 182 }
Chris@0 183 if (is_array($defaultValue)) {
Chris@0 184 return InputArgument::IS_ARRAY;
Chris@0 185 }
Chris@0 186 return InputArgument::OPTIONAL;
Chris@0 187 }
Chris@0 188
Chris@0 189 public function setCommandOptions($commandInfo, $automaticOptions = [])
Chris@0 190 {
Chris@0 191 $inputOptions = $commandInfo->inputOptions();
Chris@0 192
Chris@0 193 $this->addOptions($inputOptions + $automaticOptions, $automaticOptions);
Chris@0 194 return $this;
Chris@0 195 }
Chris@0 196
Chris@0 197 public function addOptions($inputOptions, $automaticOptions = [])
Chris@0 198 {
Chris@0 199 foreach ($inputOptions as $name => $inputOption) {
Chris@0 200 $description = $inputOption->getDescription();
Chris@0 201
Chris@0 202 if (empty($description) && isset($automaticOptions[$name])) {
Chris@0 203 $description = $automaticOptions[$name]->getDescription();
Chris@0 204 $inputOption = static::inputOptionSetDescription($inputOption, $description);
Chris@0 205 }
Chris@0 206 $this->getDefinition()->addOption($inputOption);
Chris@0 207 }
Chris@0 208 }
Chris@0 209
Chris@0 210 protected static function inputOptionSetDescription($inputOption, $description)
Chris@0 211 {
Chris@0 212 // Recover the 'mode' value, because Symfony is stubborn
Chris@0 213 $mode = 0;
Chris@0 214 if ($inputOption->isValueRequired()) {
Chris@0 215 $mode |= InputOption::VALUE_REQUIRED;
Chris@0 216 }
Chris@0 217 if ($inputOption->isValueOptional()) {
Chris@0 218 $mode |= InputOption::VALUE_OPTIONAL;
Chris@0 219 }
Chris@0 220 if ($inputOption->isArray()) {
Chris@0 221 $mode |= InputOption::VALUE_IS_ARRAY;
Chris@0 222 }
Chris@0 223 if (!$mode) {
Chris@0 224 $mode = InputOption::VALUE_NONE;
Chris@0 225 }
Chris@0 226
Chris@0 227 $inputOption = new InputOption(
Chris@0 228 $inputOption->getName(),
Chris@0 229 $inputOption->getShortcut(),
Chris@0 230 $mode,
Chris@0 231 $description,
Chris@0 232 $inputOption->getDefault()
Chris@0 233 );
Chris@0 234 return $inputOption;
Chris@0 235 }
Chris@0 236
Chris@0 237 /**
Chris@0 238 * Returns all of the hook names that may be called for this command.
Chris@0 239 *
Chris@0 240 * @return array
Chris@0 241 */
Chris@0 242 public function getNames()
Chris@0 243 {
Chris@0 244 return HookManager::getNames($this, $this->commandCallback);
Chris@0 245 }
Chris@0 246
Chris@0 247 /**
Chris@0 248 * Add any options to this command that are defined by hook implementations
Chris@0 249 */
Chris@0 250 public function optionsHook()
Chris@0 251 {
Chris@0 252 $this->commandProcessor()->optionsHook(
Chris@0 253 $this,
Chris@0 254 $this->getNames(),
Chris@0 255 $this->annotationData
Chris@0 256 );
Chris@0 257 }
Chris@0 258
Chris@0 259 public function optionsHookForHookAnnotations($commandInfoList)
Chris@0 260 {
Chris@0 261 foreach ($commandInfoList as $commandInfo) {
Chris@0 262 $inputOptions = $commandInfo->inputOptions();
Chris@0 263 $this->addOptions($inputOptions);
Chris@0 264 foreach ($commandInfo->getExampleUsages() as $usage => $description) {
Chris@0 265 if (!in_array($usage, $this->getUsages())) {
Chris@0 266 $this->addUsageOrExample($usage, $description);
Chris@0 267 }
Chris@0 268 }
Chris@0 269 }
Chris@0 270 }
Chris@0 271
Chris@0 272 /**
Chris@0 273 * {@inheritdoc}
Chris@0 274 */
Chris@0 275 protected function interact(InputInterface $input, OutputInterface $output)
Chris@0 276 {
Chris@0 277 $this->commandProcessor()->interact(
Chris@0 278 $input,
Chris@0 279 $output,
Chris@0 280 $this->getNames(),
Chris@0 281 $this->annotationData
Chris@0 282 );
Chris@0 283 }
Chris@0 284
Chris@0 285 protected function initialize(InputInterface $input, OutputInterface $output)
Chris@0 286 {
Chris@0 287 // Allow the hook manager a chance to provide configuration values,
Chris@0 288 // if there are any registered hooks to do that.
Chris@0 289 $this->commandProcessor()->initializeHook($input, $this->getNames(), $this->annotationData);
Chris@0 290 }
Chris@0 291
Chris@0 292 /**
Chris@0 293 * {@inheritdoc}
Chris@0 294 */
Chris@0 295 protected function execute(InputInterface $input, OutputInterface $output)
Chris@0 296 {
Chris@0 297 // Validate, run, process, alter, handle results.
Chris@0 298 return $this->commandProcessor()->process(
Chris@0 299 $output,
Chris@0 300 $this->getNames(),
Chris@0 301 $this->commandCallback,
Chris@0 302 $this->createCommandData($input, $output)
Chris@0 303 );
Chris@0 304 }
Chris@0 305
Chris@0 306 /**
Chris@0 307 * This function is available for use by a class that may
Chris@0 308 * wish to extend this class rather than use annotations to
Chris@0 309 * define commands. Using this technique does allow for the
Chris@0 310 * use of annotations to define hooks.
Chris@0 311 */
Chris@0 312 public function processResults(InputInterface $input, OutputInterface $output, $results)
Chris@0 313 {
Chris@0 314 $commandData = $this->createCommandData($input, $output);
Chris@0 315 $commandProcessor = $this->commandProcessor();
Chris@0 316 $names = $this->getNames();
Chris@0 317 $results = $commandProcessor->processResults(
Chris@0 318 $names,
Chris@0 319 $results,
Chris@0 320 $commandData
Chris@0 321 );
Chris@0 322 return $commandProcessor->handleResults(
Chris@0 323 $output,
Chris@0 324 $names,
Chris@0 325 $results,
Chris@0 326 $commandData
Chris@0 327 );
Chris@0 328 }
Chris@0 329
Chris@0 330 protected function createCommandData(InputInterface $input, OutputInterface $output)
Chris@0 331 {
Chris@0 332 $commandData = new CommandData(
Chris@0 333 $this->annotationData,
Chris@0 334 $input,
Chris@0 335 $output
Chris@0 336 );
Chris@0 337
Chris@17 338 // Fetch any classes (e.g. InputInterface / OutputInterface) that
Chris@17 339 // this command's callback wants passed as a parameter and inject
Chris@17 340 // it into the command data.
Chris@17 341 $this->commandProcessor()->injectIntoCommandData($commandData, $this->injectedClasses);
Chris@0 342
Chris@0 343 // Allow the commandData to cache the list of options with
Chris@0 344 // special default values ('null' and 'true'), as these will
Chris@0 345 // need special handling. @see CommandData::options().
Chris@0 346 $commandData->cacheSpecialDefaults($this->getDefinition());
Chris@0 347
Chris@0 348 return $commandData;
Chris@0 349 }
Chris@0 350 }