AnnotatedCommand::setCommandArguments()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
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
use Consolidation\AnnotatedCommand\Help\HelpDocumentBuilder;
13
14
/**
15
 * AnnotatedCommands are created automatically by the
16
 * AnnotatedCommandFactory.  Each command method in a
17
 * command file will produce one AnnotatedCommand.  These
18
 * are then added to your Symfony Console Application object;
19
 * nothing else is needed.
20
 *
21
 * Optionally, though, you may extend AnnotatedCommand directly
22
 * to make a single command.  The usage pattern is the same
23
 * as for any other Symfony Console command, except that you may
24
 * omit the 'Confiure' method, and instead place your annotations
25
 * on the execute() method.
26
 *
27
 * @package Consolidation\AnnotatedCommand
28
 */
29
class AnnotatedCommand extends Command implements HelpDocumentAlter
30
{
31
    protected $commandCallback;
32
    protected $commandProcessor;
33
    protected $annotationData;
34
    protected $examples = [];
35
    protected $topics = [];
36
    protected $returnType;
37
    protected $injectedClasses = [];
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
        return HelpDocumentBuilder::alter($originalDom, $this);
157
    }
158
159
    protected function setCommandArguments($commandInfo)
160
    {
161
        $this->injectedClasses = $commandInfo->getInjectedClasses();
162
        $this->setCommandArgumentsFromParameters($commandInfo);
163
        return $this;
164
    }
165
166
    protected function setCommandArgumentsFromParameters($commandInfo)
167
    {
168
        $args = $commandInfo->arguments()->getValues();
169
        foreach ($args as $name => $defaultValue) {
170
            $description = $commandInfo->arguments()->getDescription($name);
171
            $hasDefault = $commandInfo->arguments()->hasDefault($name);
172
            $parameterMode = $this->getCommandArgumentMode($hasDefault, $defaultValue);
173
            $this->addArgument($name, $parameterMode, $description, $defaultValue);
174
        }
175
        return $this;
176
    }
177
178
    protected function getCommandArgumentMode($hasDefault, $defaultValue)
179
    {
180
        if (!$hasDefault) {
181
            return InputArgument::REQUIRED;
182
        }
183
        if (is_array($defaultValue)) {
184
            return InputArgument::IS_ARRAY;
185
        }
186
        return InputArgument::OPTIONAL;
187
    }
188
189
    public function setCommandOptions($commandInfo, $automaticOptions = [])
190
    {
191
        $inputOptions = $commandInfo->inputOptions();
192
193
        $this->addOptions($inputOptions + $automaticOptions, $automaticOptions);
194
        return $this;
195
    }
196
197
    public function addOptions($inputOptions, $automaticOptions = [])
198
    {
199
        foreach ($inputOptions as $name => $inputOption) {
200
            $description = $inputOption->getDescription();
201
202
            if (empty($description) && isset($automaticOptions[$name])) {
203
                $description = $automaticOptions[$name]->getDescription();
204
                $inputOption = static::inputOptionSetDescription($inputOption, $description);
205
            }
206
            $this->getDefinition()->addOption($inputOption);
207
        }
208
    }
209
210
    protected static function inputOptionSetDescription($inputOption, $description)
211
    {
212
        // Recover the 'mode' value, because Symfony is stubborn
213
        $mode = 0;
214
        if ($inputOption->isValueRequired()) {
215
            $mode |= InputOption::VALUE_REQUIRED;
216
        }
217
        if ($inputOption->isValueOptional()) {
218
            $mode |= InputOption::VALUE_OPTIONAL;
219
        }
220
        if ($inputOption->isArray()) {
221
            $mode |= InputOption::VALUE_IS_ARRAY;
222
        }
223
        if (!$mode) {
224
            $mode = InputOption::VALUE_NONE;
225
        }
226
227
        $inputOption = new InputOption(
228
            $inputOption->getName(),
229
            $inputOption->getShortcut(),
230
            $mode,
231
            $description,
232
            $inputOption->getDefault()
233
        );
234
        return $inputOption;
235
    }
236
237
    /**
238
     * Returns all of the hook names that may be called for this command.
239
     *
240
     * @return array
241
     */
242
    public function getNames()
243
    {
244
        return HookManager::getNames($this, $this->commandCallback);
245
    }
246
247
    /**
248
     * Add any options to this command that are defined by hook implementations
249
     */
250
    public function optionsHook()
251
    {
252
        $this->commandProcessor()->optionsHook(
253
            $this,
254
            $this->getNames(),
255
            $this->annotationData
256
        );
257
    }
258
259
    public function optionsHookForHookAnnotations($commandInfoList)
260
    {
261
        foreach ($commandInfoList as $commandInfo) {
262
            $inputOptions = $commandInfo->inputOptions();
263
            $this->addOptions($inputOptions);
264
            foreach ($commandInfo->getExampleUsages() as $usage => $description) {
265
                if (!in_array($usage, $this->getUsages())) {
266
                    $this->addUsageOrExample($usage, $description);
267
                }
268
            }
269
        }
270
    }
271
272
    /**
273
     * {@inheritdoc}
274
     */
275
    protected function interact(InputInterface $input, OutputInterface $output)
276
    {
277
        $this->commandProcessor()->interact(
278
            $input,
279
            $output,
280
            $this->getNames(),
281
            $this->annotationData
282
        );
283
    }
284
285
    protected function initialize(InputInterface $input, OutputInterface $output)
286
    {
287
        // Allow the hook manager a chance to provide configuration values,
288
        // if there are any registered hooks to do that.
289
        $this->commandProcessor()->initializeHook($input, $this->getNames(), $this->annotationData);
290
    }
291
292
    /**
293
     * {@inheritdoc}
294
     */
295
    protected function execute(InputInterface $input, OutputInterface $output)
296
    {
297
        // Validate, run, process, alter, handle results.
298
        return $this->commandProcessor()->process(
299
            $output,
300
            $this->getNames(),
301
            $this->commandCallback,
302
            $this->createCommandData($input, $output)
303
        );
304
    }
305
306
    /**
307
     * This function is available for use by a class that may
308
     * wish to extend this class rather than use annotations to
309
     * define commands. Using this technique does allow for the
310
     * use of annotations to define hooks.
311
     */
312
    public function processResults(InputInterface $input, OutputInterface $output, $results)
313
    {
314
        $commandData = $this->createCommandData($input, $output);
315
        $commandProcessor = $this->commandProcessor();
316
        $names = $this->getNames();
317
        $results = $commandProcessor->processResults(
318
            $names,
319
            $results,
320
            $commandData
321
        );
322
        return $commandProcessor->handleResults(
323
            $output,
324
            $names,
325
            $results,
326
            $commandData
327
        );
328
    }
329
330
    protected function createCommandData(InputInterface $input, OutputInterface $output)
331
    {
332
        $commandData = new CommandData(
333
            $this->annotationData,
334
            $input,
335
            $output
336
        );
337
338
        // Fetch any classes (e.g. InputInterface / OutputInterface) that
339
        // this command's callback wants passed as a parameter and inject
340
        // it into the command data.
341
        $this->commandProcessor()->injectIntoCommandData($commandData, $this->injectedClasses);
342
343
        // Allow the commandData to cache the list of options with
344
        // special default values ('null' and 'true'), as these will
345
        // need special handling. @see CommandData::options().
346
        $commandData->cacheSpecialDefaults($this->getDefinition());
347
348
        return $commandData;
349
    }
350
}
351