Completed
Pull Request — master (#43)
by Greg
02:22
created

AnnotatedCommand::optionsHookForHookAnnotations()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.2
c 0
b 0
f 0
cc 4
eloc 7
nc 4
nop 1
1
<?php
2
namespace Consolidation\AnnotatedCommand;
3
4
use Consolidation\AnnotatedCommand\Hooks\HookManager;
5
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
6
use Consolidation\OutputFormatters\FormatterManager;
7
use Consolidation\OutputFormatters\Options\FormatterOptions;
8
use Symfony\Component\Console\Command\Command;
9
use Symfony\Component\Console\Input\InputArgument;
10
use Symfony\Component\Console\Input\InputInterface;
11
use Symfony\Component\Console\Input\InputOption;
12
use Symfony\Component\Console\Output\OutputInterface;
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
30
{
31
    protected $commandCallback;
32
    protected $commandProcessor;
33
    protected $annotationData;
34
    protected $usesInputInterface;
35
    protected $usesOutputInterface;
36
    protected $returnType;
37
38
    public function __construct($name = null)
39
    {
40
        $commandInfo = false;
41
42
        // If this is a subclass of AnnotatedCommand, check to see
43
        // if the 'execute' method is annotated.  We could do this
44
        // unconditionally; it is a performance optimization to skip
45
        // checking the annotations if $this is an instance of
46
        // AnnotatedCommand.  Alternately, we break out a new subclass.
47
        // The command factory instantiates the subclass.
48
        if (get_class($this) != 'Consolidation\AnnotatedCommand\AnnotatedCommand') {
49
            $commandInfo = new CommandInfo($this, 'execute');
50
            if (!isset($name)) {
51
                $name = $commandInfo->getName();
52
            }
53
        }
54
        parent::__construct($name);
55
        if ($commandInfo && $commandInfo->hasAnnotation('command')) {
56
            $this->setCommandInfo($commandInfo);
57
            $this->setCommandOptions($commandInfo);
58
        }
59
    }
60
61
    public function setCommandCallback($commandCallback)
62
    {
63
        $this->commandCallback = $commandCallback;
64
    }
65
66
    public function setCommandProcessor($commandProcessor)
67
    {
68
        $this->commandProcessor = $commandProcessor;
69
    }
70
71
    public function commandProcessor()
72
    {
73
        // If someone is using an AnnotatedCommand, and is NOT getting
74
        // it from an AnnotatedCommandFactory OR not correctly injecting
75
        // a command processor via setCommandProcessor() (ideally via the
76
        // DI container), then we'll just give each annotated command its
77
        // own command processor. This is not ideal; preferably, there would
78
        // only be one instance of the command processor in the application.
79
        if (!isset($this->commandProcessor)) {
80
            $this->commandProcessor = new CommandProcessor(new HookManager());
81
        }
82
        return $this->commandProcessor;
83
    }
84
85
    public function getReturnType()
86
    {
87
        return $this->returnType;
88
    }
89
90
    public function setReturnType($returnType)
91
    {
92
        $this->returnType = $returnType;
93
    }
94
95
    public function getAnnotationData()
96
    {
97
        return $this->annotationData;
98
    }
99
100
    public function setAnnotationData($annotationData)
101
    {
102
        $this->annotationData = $annotationData;
103
    }
104
105
    public function setCommandInfo($commandInfo)
106
    {
107
        $this->setDescription($commandInfo->getDescription());
108
        $this->setHelp($commandInfo->getHelp());
109
        $this->setAliases($commandInfo->getAliases());
110
        $this->setAnnotationData($commandInfo->getAnnotationsForCommand());
111
        foreach ($commandInfo->getExampleUsages() as $usage => $description) {
112
            // Symfony Console does not support attaching a description to a usage
113
            $this->addUsage($usage);
114
        }
115
        $this->setCommandArguments($commandInfo);
116
        $this->setReturnType($commandInfo->getReturnType());
117
    }
118
119
    protected function setCommandArguments($commandInfo)
120
    {
121
        $this->setUsesInputInterface($commandInfo);
122
        $this->setUsesOutputInterface($commandInfo);
123
        $this->setCommandArgumentsFromParameters($commandInfo);
124
    }
125
126
    /**
127
     * Check whether the first parameter is an InputInterface.
128
     */
129
    protected function checkUsesInputInterface($params)
130
    {
131
        $firstParam = reset($params);
132
        return $firstParam instanceof InputInterface;
133
    }
134
135
    /**
136
     * Determine whether this command wants to get its inputs
137
     * via an InputInterface or via its command parameters
138
     */
139
    protected function setUsesInputInterface($commandInfo)
140
    {
141
        $params = $commandInfo->getParameters();
142
        $this->usesInputInterface = $this->checkUsesInputInterface($params);
143
    }
144
145
    /**
146
     * Determine whether this command wants to send its output directly
147
     * to the provided OutputInterface, or whether it will returned
148
     * structured output to be processed by the command processor.
149
     */
150
    protected function setUsesOutputInterface($commandInfo)
151
    {
152
        $params = $commandInfo->getParameters();
153
        $index = $this->checkUsesInputInterface($params) ? 1 : 0;
154
        $this->usesOutputInterface =
155
            (count($params) > $index) &&
156
            ($params[$index] instanceof OutputInterface);
157
    }
158
159
    protected function setCommandArgumentsFromParameters($commandInfo)
160
    {
161
        $args = $commandInfo->arguments()->getValues();
162
        foreach ($args as $name => $defaultValue) {
163
            $description = $commandInfo->arguments()->getDescription($name);
164
            $hasDefault = $commandInfo->arguments()->hasDefault($name);
165
            $parameterMode = $this->getCommandArgumentMode($hasDefault, $defaultValue);
166
            $this->addArgument($name, $parameterMode, $description, $defaultValue);
167
        }
168
    }
169
170
    protected function getCommandArgumentMode($hasDefault, $defaultValue)
171
    {
172
        if (!$hasDefault) {
173
            return InputArgument::REQUIRED;
174
        }
175
        if (is_array($defaultValue)) {
176
            return InputArgument::IS_ARRAY;
177
        }
178
        return InputArgument::OPTIONAL;
179
    }
180
181
    public function setCommandOptions($commandInfo, $automaticOptions = [])
182
    {
183
        $inputOptions = $commandInfo->inputOptions();
184
185
        $this->addOptions($inputOptions + $automaticOptions, $automaticOptions);
186
    }
187
188
    public function addOptions($inputOptions, $automaticOptions = [])
189
    {
190
        foreach ($inputOptions as $name => $inputOption) {
191
            $description = $inputOption->getDescription();
192
193
            if (empty($description) && isset($automaticOptions[$name])) {
194
                $description = $automaticOptions[$name]->getDescription();
195
                $inputOption = static::inputOptionSetDescription($inputOption, $description);
196
            }
197
            $this->getDefinition()->addOption($inputOption);
198
        }
199
    }
200
201
    protected static function inputOptionSetDescription($inputOption, $description)
202
    {
203
        // Recover the 'mode' value, because Symfony is stubborn
204
        $mode = 0;
205
        if ($inputOption->isValueRequired()) {
206
            $mode |= InputOption::VALUE_REQUIRED;
207
        }
208
        if ($inputOption->isValueOptional()) {
209
            $mode |= InputOption::VALUE_OPTIONAL;
210
        }
211
        if ($inputOption->isArray()) {
212
            $mode |= InputOption::VALUE_IS_ARRAY;
213
        }
214
        if (!$mode) {
215
            $mode = InputOption::VALUE_NONE;
216
            $default = null;
0 ignored issues
show
Unused Code introduced by
$default is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
217
        }
218
219
        $inputOption = new InputOption(
220
            $inputOption->getName(),
221
            $inputOption->getShortcut(),
222
            $mode,
223
            $description,
224
            $inputOption->getDefault()
225
        );
226
        return $inputOption;
227
    }
228
229
    protected function getArgsWithPassThrough($input)
230
    {
231
        $args = $input->getArguments();
232
233
        // When called via the Application, the first argument
234
        // will be the command name. The Application alters the
235
        // input definition to match, adding a 'command' argument
236
        // to the beginning.
237
        array_shift($args);
238
        if ($input instanceof PassThroughArgsInput) {
239
            return $this->appendPassThroughArgs($input, $args);
240
        }
241
        return $args;
242
    }
243
244
    protected function getArgsAndOptions($input)
245
    {
246
        if (!$input) {
247
            return [];
248
        }
249
        // Get passthrough args, and add the options on the end.
250
        $args = $this->getArgsWithPassThrough($input);
251
        $args['options'] = $input->getOptions();
252
        return $args;
253
    }
254
255
    protected function appendPassThroughArgs($input, $args)
256
    {
257
        $passThrough = $input->getPassThroughArgs();
258
        $definition = $this->getDefinition();
259
        $argumentDefinitions = $definition->getArguments();
260
        $lastParameter = end($argumentDefinitions);
261
        if ($lastParameter && $lastParameter->isArray()) {
262
            $args[$lastParameter->getName()] = array_merge($args[$lastParameter->getName()], $passThrough);
263
        } else {
264
            $args[$lastParameter->getName()] = implode(' ', $passThrough);
265
        }
266
        return $args;
267
    }
268
269
    /**
270
     * Returns all of the hook names that may be called for this command.
271
     *
272
     * @return array
273
     */
274
    protected function getNames()
275
    {
276
        return HookManager::getNames($this, $this->commandCallback);
277
    }
278
279
    /**
280
     * Add any options to this command that are defined by hook implementations
281
     */
282
    public function optionsHook()
283
    {
284
        $this->commandProcessor()->optionsHook(
285
            $this,
286
            $this->getNames(),
287
            $this->annotationData
288
        );
289
    }
290
291
    public function optionsHookForHookAnnotations($commandInfoList)
292
    {
293
        foreach ($commandInfoList as $commandInfo) {
294
            $inputOptions = $commandInfo->inputOptions();
295
            $this->addOptions($inputOptions);
296
            foreach ($commandInfo->getExampleUsages() as $usage => $description) {
297
                if (!in_array($usage, $this->getUsages())) {
298
                    $this->addUsage($usage);
299
                }
300
            }
301
        }
302
    }
303
304
    /**
305
     * {@inheritdoc}
306
     */
307
    protected function interact(InputInterface $input, OutputInterface $output)
308
    {
309
        $this->commandProcessor()->interact(
310
            $input,
311
            $output,
312
            $this->getNames(),
313
            $this->annotationData
314
        );
315
    }
316
317
    protected function initialize(InputInterface $input, OutputInterface $output)
318
    {
319
        // Allow the hook manager a chance to provide configuration values,
320
        // if there are any registered hooks to do that.
321
        $this->commandProcessor()->initializeHook($input, $this->getNames(), $this->annotationData);
322
    }
323
324
    /**
325
     * {@inheritdoc}
326
     */
327
    protected function execute(InputInterface $input, OutputInterface $output)
328
    {
329
        // Get passthrough args, and add the options on the end.
330
        $args = $this->getArgsAndOptions($input);
331
332
        if ($this->usesInputInterface) {
333
            array_unshift($args, $input);
334
        }
335
        if ($this->usesOutputInterface) {
336
            array_unshift($args, $output);
337
        }
338
339
        // Validate, run, process, alter, handle results.
340
        return $this->commandProcessor()->process(
341
            $output,
342
            $this->getNames(),
343
            $this->commandCallback,
344
            $this->annotationData,
345
            $args
346
        );
347
    }
348
349
    public function processResults(InputInterface $input, OutputInterface $output, $results)
350
    {
351
        $commandProcessor = $this->commandProcessor();
352
        $names = $this->getNames();
353
        $args = $this->getArgsAndOptions($input);
354
        $results = $commandProcessor->processResults(
355
            $names,
356
            $results,
357
            $args,
358
            $this->annotationData
359
        );
360
        $options = end($args);
361
        return $commandProcessor->handleResults(
362
            $output,
363
            $names,
364
            $results,
365
            $this->annotationData,
366
            $options
367
        );
368
    }
369
}
370