Completed
Pull Request — master (#14)
by Greg
02:21
created

setCommandArgumentsFromParameters()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 9
rs 9.6666
cc 2
eloc 6
nc 2
nop 1
1
<?php
2
namespace Consolidation\AnnotatedCommand;
3
4
use Symfony\Component\Console\Command\Command;
5
use Symfony\Component\Console\Input\InputInterface;
6
use Symfony\Component\Console\Output\OutputInterface;
7
use Symfony\Component\Console\Input\InputOption;
8
use Symfony\Component\Console\Input\InputArgument;
9
10
class AnnotatedCommand extends Command
11
{
12
    protected $commandCallback;
13
    protected $commandProcessor;
14
    protected $annotationData;
15
    protected $usesInputInterface;
16
    protected $usesOutputInterface;
17
18
    public function __construct($name = null)
19
    {
20
        $commandInfo = false;
21
22
        // If this is a subclass of AnnotatedCommand, check to see
23
        // if the 'execute' method is annotated.  We could do this
24
        // unconditionally; it is a performance optimization to skip
25
        // checking the annotations if $this is an instance of
26
        // AnnotatedCommand.  Alternately, we break out a new subclass.
27
        // The command factory instantiates the subclass.
28
        if (get_class($this) != 'Consolidation\AnnotatedCommand\AnnotatedCommand') {
29
            $commandInfo = new CommandInfo($this, 'execute');
30
            if (!isset($name)) {
31
                $name = $commandInfo->getName();
32
            }
33
        }
34
        parent::__construct($name);
35
        if ($commandInfo && $commandInfo->hasAnnotation('command')) {
36
            $this->setCommandInfo($commandInfo);
37
        }
38
    }
39
40
    public function setCommandCallback($commandCallback)
41
    {
42
        $this->commandCallback = $commandCallback;
43
    }
44
45
    public function setCommandProcessor($commandProcessor)
46
    {
47
        $this->commandProcessor = $commandProcessor;
48
    }
49
50
    public function getCommandProcessor()
51
    {
52
        // If someone is using an AnnotatedCommand, and is NOT getting
53
        // it from an AnnotatedCommandFactory OR not correctly injecting
54
        // a command processor via setCommandProcessor() (ideally via the
55
        // DI container), then we'll just give each annotated command its
56
        // own command processor. This is not ideal; preferably, there would
57
        // only be one instance of the command processor in the application.
58
        if (!isset($this->commandProcessor)) {
59
            $this->commandProcessor = new CommandProcessor(new HookManager());
60
        }
61
        return $this->commandProcessor;
62
    }
63
64
    public function setAnnotationData($annotationData)
65
    {
66
        $this->annotationData = $annotationData;
67
    }
68
69
    public function setCommandInfo($commandInfo)
70
    {
71
        $this->setDescription($commandInfo->getDescription());
72
        $this->setHelp($commandInfo->getHelp());
73
        $this->setAliases($commandInfo->getAliases());
74
        $this->setAnnotationData($commandInfo->getAnnotations());
75
        foreach ($commandInfo->getExampleUsages() as $usage => $description) {
76
            // Symfony Console does not support attaching a description to a usage
77
            $this->addUsage($usage);
78
        }
79
        $this->setCommandArguments($commandInfo);
80
        $this->setCommandOptions($commandInfo);
81
    }
82
83
    protected function setCommandArguments($commandInfo)
84
    {
85
        $this->setUsesInputInterface($commandInfo);
86
        $this->setUsesOutputInterface($commandInfo);
87
        $this->setCommandArgumentsFromParameters($commandInfo);
88
    }
89
90
    /**
91
     * Check whether the first parameter is an InputInterface.
92
     */
93
    protected function checkUsesInputInterface($params)
94
    {
95
        $firstParam = reset($params);
96
        return $firstParam instanceof InputInterface;
97
    }
98
99
    /**
100
     * Determine whether this command wants to get its inputs
101
     * via an InputInterface or via its command parameters
102
     */
103
    protected function setUsesInputInterface($commandInfo)
104
    {
105
        $params = $commandInfo->getParameters();
106
        $this->usesInputInterface = $this->checkUsesInputInterface($params);
107
    }
108
109
    /**
110
     * Determine whether this command wants to send its output directly
111
     * to the provided OutputInterface, or whether it will returned
112
     * structured output to be processed by the command processor.
113
     */
114
    protected function setUsesOutputInterface($commandInfo)
115
    {
116
        $params = $commandInfo->getParameters();
117
        $index = $this->checkUsesInputInterface($params) ? 1 : 0;
118
        $this->usesOutputInterface =
119
            (count($params) > $index) &&
120
            ($params[$index] instanceof OutputInterface);
121
    }
122
123
    protected function setCommandArgumentsFromParameters($commandInfo)
124
    {
125
        $args = $commandInfo->getArguments();
126
        foreach ($args as $name => $defaultValue) {
127
            $description = $commandInfo->getArgumentDescription($name);
128
            $parameterMode = $this->getCommandArgumentMode($defaultValue);
129
            $this->addArgument($name, $parameterMode, $description, $defaultValue);
130
        }
131
    }
132
133
    protected function getCommandArgumentMode($defaultValue)
134
    {
135
        if (!isset($defaultValue)) {
136
            return InputArgument::REQUIRED;
137
        }
138
        if (is_array($defaultValue)) {
139
            return InputArgument::IS_ARRAY;
140
        }
141
        return InputArgument::OPTIONAL;
142
    }
143
144
    protected function setCommandOptions($commandInfo)
145
    {
146
        $opts = $commandInfo->getOptions();
147
        foreach ($opts as $name => $val) {
148
            $description = $commandInfo->getOptionDescription($name);
149
150
            $fullName = $name;
151
            $shortcut = '';
152
            if (strpos($name, '|')) {
153
                list($fullName, $shortcut) = explode('|', $name, 2);
154
            }
155
156
            if (is_bool($val)) {
157
                $this->addOption($fullName, $shortcut, InputOption::VALUE_NONE, $description);
158
            } else {
159
                $this->addOption($fullName, $shortcut, InputOption::VALUE_OPTIONAL, $description, $val);
160
            }
161
        }
162
    }
163
164
    protected function getArgsWithPassThrough($input)
165
    {
166
        $args = $input->getArguments();
167
168
        // When called via the Application, the first argument
169
        // will be the command name. The Application alters the
170
        // input definition to match, adding a 'command' argument
171
        // to the beginning.
172
        array_shift($args);
173
        if ($input instanceof PassThroughArgsInput) {
174
            return $this->appendPassThroughArgs($input, $args);
175
        }
176
        return $args;
177
    }
178
179
    protected function getArgsAndOptions($input)
180
    {
181
        if (!$input) {
182
            return [];
183
        }
184
        // Get passthrough args, and add the options on the end.
185
        $args = $this->getArgsWithPassThrough($input);
186
        $args[] = $input->getOptions();
187
        return $args;
188
    }
189
190
    protected function appendPassThroughArgs($input, $args)
191
    {
192
        $passThrough = $input->getPassThroughArgs();
193
        $definition = $this->getDefinition();
194
        $argumentDefinitions = $definition->getArguments();
195
        $lastParameter = end($argumentDefinitions);
196
        if ($lastParameter && $lastParameter->isArray()) {
197
            $args[$lastParameter->getName()] = array_merge($args[$lastParameter->getName()], $passThrough);
198
        } else {
199
            $args[$lastParameter->getName()] = implode(' ', $passThrough);
200
        }
201
        return $args;
202
    }
203
204
    protected function getNames()
205
    {
206
        return array_merge(
207
            [$this->getName()],
208
            $this->getAliases()
209
        );
210
    }
211
212
    protected function execute(InputInterface $input, OutputInterface $output)
213
    {
214
        // Get passthrough args, and add the options on the end.
215
        $args = $this->getArgsAndOptions($input);
216
217
        if ($this->usesInputInterface) {
218
            array_unshift($args, $input);
219
        }
220
        if ($this->usesOutputInterface) {
221
            array_unshift($args, $output);
222
        }
223
224
        // Validate, run, process, alter, handle results.
225
        return $this->getCommandProcessor()->process(
226
            $output,
227
            $this->getNames(),
228
            $this->commandCallback,
229
            $this->annotationData,
230
            $args
231
        );
232
    }
233
234
    public function processResults(InputInterface $input, OutputInterface $output, $results)
235
    {
236
        $commandProcessor = $this->getCommandProcessor();
237
        $names = $this->getNames();
238
        $args = $this->getArgsAndOptions($input);
239
        $results = $commandProcessor->processResults(
240
            $names,
241
            $results,
242
            $args
243
        );
244
        $options = end($args);
245
        return $commandProcessor->handleResults(
246
            $output,
247
            $names,
248
            $results,
249
            $this->annotationData,
250
            $options
251
        );
252
    }
253
}
254