Updated to Drupal 8.5. Core Media not yet in use.
[yaffs-website] / vendor / consolidation / annotated-command / src / AnnotatedCommand.php
1 <?php
2 namespace Consolidation\AnnotatedCommand;
3
4 use Consolidation\AnnotatedCommand\Hooks\HookManager;
5 use Consolidation\AnnotatedCommand\Parser\CommandInfo;
6 use Consolidation\AnnotatedCommand\Help\HelpDocumentAlter;
7 use Symfony\Component\Console\Command\Command;
8 use Symfony\Component\Console\Input\InputArgument;
9 use Symfony\Component\Console\Input\InputInterface;
10 use Symfony\Component\Console\Input\InputOption;
11 use Symfony\Component\Console\Output\OutputInterface;
12
13 /**
14  * AnnotatedCommands are created automatically by the
15  * AnnotatedCommandFactory.  Each command method in a
16  * command file will produce one AnnotatedCommand.  These
17  * are then added to your Symfony Console Application object;
18  * nothing else is needed.
19  *
20  * Optionally, though, you may extend AnnotatedCommand directly
21  * to make a single command.  The usage pattern is the same
22  * as for any other Symfony Console command, except that you may
23  * omit the 'Confiure' method, and instead place your annotations
24  * on the execute() method.
25  *
26  * @package Consolidation\AnnotatedCommand
27  */
28 class AnnotatedCommand extends Command implements HelpDocumentAlter
29 {
30     protected $commandCallback;
31     protected $commandProcessor;
32     protected $annotationData;
33     protected $examples = [];
34     protected $topics = [];
35     protected $usesInputInterface;
36     protected $usesOutputInterface;
37     protected $returnType;
38
39     public function __construct($name = null)
40     {
41         $commandInfo = false;
42
43         // If this is a subclass of AnnotatedCommand, check to see
44         // if the 'execute' method is annotated.  We could do this
45         // unconditionally; it is a performance optimization to skip
46         // checking the annotations if $this is an instance of
47         // AnnotatedCommand.  Alternately, we break out a new subclass.
48         // The command factory instantiates the subclass.
49         if (get_class($this) != 'Consolidation\AnnotatedCommand\AnnotatedCommand') {
50             $commandInfo = CommandInfo::create($this, 'execute');
51             if (!isset($name)) {
52                 $name = $commandInfo->getName();
53             }
54         }
55         parent::__construct($name);
56         if ($commandInfo && $commandInfo->hasAnnotation('command')) {
57             $this->setCommandInfo($commandInfo);
58             $this->setCommandOptions($commandInfo);
59         }
60     }
61
62     public function setCommandCallback($commandCallback)
63     {
64         $this->commandCallback = $commandCallback;
65         return $this;
66     }
67
68     public function setCommandProcessor($commandProcessor)
69     {
70         $this->commandProcessor = $commandProcessor;
71         return $this;
72     }
73
74     public function commandProcessor()
75     {
76         // If someone is using an AnnotatedCommand, and is NOT getting
77         // it from an AnnotatedCommandFactory OR not correctly injecting
78         // a command processor via setCommandProcessor() (ideally via the
79         // DI container), then we'll just give each annotated command its
80         // own command processor. This is not ideal; preferably, there would
81         // only be one instance of the command processor in the application.
82         if (!isset($this->commandProcessor)) {
83             $this->commandProcessor = new CommandProcessor(new HookManager());
84         }
85         return $this->commandProcessor;
86     }
87
88     public function getReturnType()
89     {
90         return $this->returnType;
91     }
92
93     public function setReturnType($returnType)
94     {
95         $this->returnType = $returnType;
96         return $this;
97     }
98
99     public function getAnnotationData()
100     {
101         return $this->annotationData;
102     }
103
104     public function setAnnotationData($annotationData)
105     {
106         $this->annotationData = $annotationData;
107         return $this;
108     }
109
110     public function getTopics()
111     {
112         return $this->topics;
113     }
114
115     public function setTopics($topics)
116     {
117         $this->topics = $topics;
118         return $this;
119     }
120
121     public function setCommandInfo($commandInfo)
122     {
123         $this->setDescription($commandInfo->getDescription());
124         $this->setHelp($commandInfo->getHelp());
125         $this->setAliases($commandInfo->getAliases());
126         $this->setAnnotationData($commandInfo->getAnnotations());
127         $this->setTopics($commandInfo->getTopics());
128         foreach ($commandInfo->getExampleUsages() as $usage => $description) {
129             $this->addUsageOrExample($usage, $description);
130         }
131         $this->setCommandArguments($commandInfo);
132         $this->setReturnType($commandInfo->getReturnType());
133         // Hidden commands available since Symfony 3.2
134         // http://symfony.com/doc/current/console/hide_commands.html
135         if (method_exists($this, 'setHidden')) {
136             $this->setHidden($commandInfo->getHidden());
137         }
138         return $this;
139     }
140
141     public function getExampleUsages()
142     {
143         return $this->examples;
144     }
145
146     protected function addUsageOrExample($usage, $description)
147     {
148         $this->addUsage($usage);
149         if (!empty($description)) {
150             $this->examples[$usage] = $description;
151         }
152     }
153
154     public function helpAlter(\DomDocument $originalDom)
155     {
156         $dom = new \DOMDocument('1.0', 'UTF-8');
157         $dom->appendChild($commandXML = $dom->createElement('command'));
158         $commandXML->setAttribute('id', $this->getName());
159         $commandXML->setAttribute('name', $this->getName());
160
161         // Get the original <command> element and its top-level elements.
162         $originalCommandXML = $this->getSingleElementByTagName($dom, $originalDom, 'command');
163         $originalUsagesXML = $this->getSingleElementByTagName($dom, $originalCommandXML, 'usages');
164         $originalDescriptionXML = $this->getSingleElementByTagName($dom, $originalCommandXML, 'description');
165         $originalHelpXML = $this->getSingleElementByTagName($dom, $originalCommandXML, 'help');
166         $originalArgumentsXML = $this->getSingleElementByTagName($dom, $originalCommandXML, 'arguments');
167         $originalOptionsXML = $this->getSingleElementByTagName($dom, $originalCommandXML, 'options');
168
169         // Keep only the first of the <usage> elements
170         $newUsagesXML = $dom->createElement('usages');
171         $firstUsageXML = $this->getSingleElementByTagName($dom, $originalUsagesXML, 'usage');
172         $newUsagesXML->appendChild($firstUsageXML);
173
174         // Create our own <example> elements
175         $newExamplesXML = $dom->createElement('examples');
176         foreach ($this->examples as $usage => $description) {
177             $newExamplesXML->appendChild($exampleXML = $dom->createElement('example'));
178             $exampleXML->appendChild($usageXML = $dom->createElement('usage', $usage));
179             $exampleXML->appendChild($descriptionXML = $dom->createElement('description', $description));
180         }
181
182         // Create our own <alias> elements
183         $newAliasesXML = $dom->createElement('aliases');
184         foreach ($this->getAliases() as $alias) {
185             $newAliasesXML->appendChild($dom->createElement('alias', $alias));
186         }
187
188         // Create our own <topic> elements
189         $newTopicsXML = $dom->createElement('topics');
190         foreach ($this->getTopics() as $topic) {
191             $newTopicsXML->appendChild($topicXML = $dom->createElement('topic', $topic));
192         }
193
194         // Place the different elements into the <command> element in the desired order
195         $commandXML->appendChild($newUsagesXML);
196         $commandXML->appendChild($newExamplesXML);
197         $commandXML->appendChild($originalDescriptionXML);
198         $commandXML->appendChild($originalArgumentsXML);
199         $commandXML->appendChild($originalOptionsXML);
200         $commandXML->appendChild($originalHelpXML);
201         $commandXML->appendChild($newAliasesXML);
202         $commandXML->appendChild($newTopicsXML);
203
204         return $dom;
205     }
206
207     protected function getSingleElementByTagName($dom, $parent, $tagName)
208     {
209         // There should always be exactly one '<command>' element.
210         $elements = $parent->getElementsByTagName($tagName);
211         $result = $elements->item(0);
212
213         $result = $dom->importNode($result, true);
214
215         return $result;
216     }
217
218     protected function setCommandArguments($commandInfo)
219     {
220         $this->setUsesInputInterface($commandInfo);
221         $this->setUsesOutputInterface($commandInfo);
222         $this->setCommandArgumentsFromParameters($commandInfo);
223         return $this;
224     }
225
226     /**
227      * Check whether the first parameter is an InputInterface.
228      */
229     protected function checkUsesInputInterface($params)
230     {
231         /** @var \ReflectionParameter $firstParam */
232         $firstParam = reset($params);
233         return $firstParam && $firstParam->getClass() && $firstParam->getClass()->implementsInterface(
234             '\\Symfony\\Component\\Console\\Input\\InputInterface'
235         );
236     }
237
238     /**
239      * Determine whether this command wants to get its inputs
240      * via an InputInterface or via its command parameters
241      */
242     protected function setUsesInputInterface($commandInfo)
243     {
244         $params = $commandInfo->getParameters();
245         $this->usesInputInterface = $this->checkUsesInputInterface($params);
246         return $this;
247     }
248
249     /**
250      * Determine whether this command wants to send its output directly
251      * to the provided OutputInterface, or whether it will returned
252      * structured output to be processed by the command processor.
253      */
254     protected function setUsesOutputInterface($commandInfo)
255     {
256         $params = $commandInfo->getParameters();
257         $index = $this->checkUsesInputInterface($params) ? 1 : 0;
258         $this->usesOutputInterface =
259             (count($params) > $index) &&
260             $params[$index]->getClass() &&
261             $params[$index]->getClass()->implementsInterface(
262                 '\\Symfony\\Component\\Console\\Output\\OutputInterface'
263             )
264         ;
265         return $this;
266     }
267
268     protected function setCommandArgumentsFromParameters($commandInfo)
269     {
270         $args = $commandInfo->arguments()->getValues();
271         foreach ($args as $name => $defaultValue) {
272             $description = $commandInfo->arguments()->getDescription($name);
273             $hasDefault = $commandInfo->arguments()->hasDefault($name);
274             $parameterMode = $this->getCommandArgumentMode($hasDefault, $defaultValue);
275             $this->addArgument($name, $parameterMode, $description, $defaultValue);
276         }
277         return $this;
278     }
279
280     protected function getCommandArgumentMode($hasDefault, $defaultValue)
281     {
282         if (!$hasDefault) {
283             return InputArgument::REQUIRED;
284         }
285         if (is_array($defaultValue)) {
286             return InputArgument::IS_ARRAY;
287         }
288         return InputArgument::OPTIONAL;
289     }
290
291     public function setCommandOptions($commandInfo, $automaticOptions = [])
292     {
293         $inputOptions = $commandInfo->inputOptions();
294
295         $this->addOptions($inputOptions + $automaticOptions, $automaticOptions);
296         return $this;
297     }
298
299     public function addOptions($inputOptions, $automaticOptions = [])
300     {
301         foreach ($inputOptions as $name => $inputOption) {
302             $description = $inputOption->getDescription();
303
304             if (empty($description) && isset($automaticOptions[$name])) {
305                 $description = $automaticOptions[$name]->getDescription();
306                 $inputOption = static::inputOptionSetDescription($inputOption, $description);
307             }
308             $this->getDefinition()->addOption($inputOption);
309         }
310     }
311
312     protected static function inputOptionSetDescription($inputOption, $description)
313     {
314         // Recover the 'mode' value, because Symfony is stubborn
315         $mode = 0;
316         if ($inputOption->isValueRequired()) {
317             $mode |= InputOption::VALUE_REQUIRED;
318         }
319         if ($inputOption->isValueOptional()) {
320             $mode |= InputOption::VALUE_OPTIONAL;
321         }
322         if ($inputOption->isArray()) {
323             $mode |= InputOption::VALUE_IS_ARRAY;
324         }
325         if (!$mode) {
326             $mode = InputOption::VALUE_NONE;
327         }
328
329         $inputOption = new InputOption(
330             $inputOption->getName(),
331             $inputOption->getShortcut(),
332             $mode,
333             $description,
334             $inputOption->getDefault()
335         );
336         return $inputOption;
337     }
338
339     /**
340      * Returns all of the hook names that may be called for this command.
341      *
342      * @return array
343      */
344     public function getNames()
345     {
346         return HookManager::getNames($this, $this->commandCallback);
347     }
348
349     /**
350      * Add any options to this command that are defined by hook implementations
351      */
352     public function optionsHook()
353     {
354         $this->commandProcessor()->optionsHook(
355             $this,
356             $this->getNames(),
357             $this->annotationData
358         );
359     }
360
361     public function optionsHookForHookAnnotations($commandInfoList)
362     {
363         foreach ($commandInfoList as $commandInfo) {
364             $inputOptions = $commandInfo->inputOptions();
365             $this->addOptions($inputOptions);
366             foreach ($commandInfo->getExampleUsages() as $usage => $description) {
367                 if (!in_array($usage, $this->getUsages())) {
368                     $this->addUsageOrExample($usage, $description);
369                 }
370             }
371         }
372     }
373
374     /**
375      * {@inheritdoc}
376      */
377     protected function interact(InputInterface $input, OutputInterface $output)
378     {
379         $this->commandProcessor()->interact(
380             $input,
381             $output,
382             $this->getNames(),
383             $this->annotationData
384         );
385     }
386
387     protected function initialize(InputInterface $input, OutputInterface $output)
388     {
389         // Allow the hook manager a chance to provide configuration values,
390         // if there are any registered hooks to do that.
391         $this->commandProcessor()->initializeHook($input, $this->getNames(), $this->annotationData);
392     }
393
394     /**
395      * {@inheritdoc}
396      */
397     protected function execute(InputInterface $input, OutputInterface $output)
398     {
399         // Validate, run, process, alter, handle results.
400         return $this->commandProcessor()->process(
401             $output,
402             $this->getNames(),
403             $this->commandCallback,
404             $this->createCommandData($input, $output)
405         );
406     }
407
408     /**
409      * This function is available for use by a class that may
410      * wish to extend this class rather than use annotations to
411      * define commands. Using this technique does allow for the
412      * use of annotations to define hooks.
413      */
414     public function processResults(InputInterface $input, OutputInterface $output, $results)
415     {
416         $commandData = $this->createCommandData($input, $output);
417         $commandProcessor = $this->commandProcessor();
418         $names = $this->getNames();
419         $results = $commandProcessor->processResults(
420             $names,
421             $results,
422             $commandData
423         );
424         return $commandProcessor->handleResults(
425             $output,
426             $names,
427             $results,
428             $commandData
429         );
430     }
431
432     protected function createCommandData(InputInterface $input, OutputInterface $output)
433     {
434         $commandData = new CommandData(
435             $this->annotationData,
436             $input,
437             $output
438         );
439
440         $commandData->setUseIOInterfaces(
441             $this->usesInputInterface,
442             $this->usesOutputInterface
443         );
444
445         // Allow the commandData to cache the list of options with
446         // special default values ('null' and 'true'), as these will
447         // need special handling. @see CommandData::options().
448         $commandData->cacheSpecialDefaults($this->getDefinition());
449
450         return $commandData;
451     }
452 }