Completed
Push — master ( ef8ae5...2c2ae1 )
by Greg
02:18
created

CommandProcessor::optionsHook()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 5
nc 1
nop 3
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\Output\ConsoleOutputInterface;
8
9
use Consolidation\OutputFormatters\FormatterManager;
10
use Consolidation\OutputFormatters\Options\FormatterOptions;
11
use Consolidation\AnnotatedCommand\Hooks\HookManager;
12
13
/**
14
 * Process a command, including hooks and other callbacks.
15
 * There should only be one command processor per application.
16
 * Provide your command processor to the AnnotatedCommandFactory
17
 * via AnnotatedCommandFactory::setCommandProcessor().
18
 */
19
class CommandProcessor
20
{
21
    /** var HookManager */
22
    protected $hookManager;
23
    /** var FormatterManager */
24
    protected $formatterManager;
25
    /** var callable */
26
    protected $displayErrorFunction;
27
28
    public function __construct(HookManager $hookManager)
29
    {
30
        $this->hookManager = $hookManager;
31
    }
32
33
    /**
34
     * Return the hook manager
35
     * @return HookManager
36
     */
37
    public function hookManager()
38
    {
39
        return $this->hookManager;
40
    }
41
42
    public function setFormatterManager(FormatterManager $formatterManager)
43
    {
44
        $this->formatterManager = $formatterManager;
45
    }
46
47
    public function setDisplayErrorFunction(callable $fn)
48
    {
49
        $this->displayErrorFunction = $fn;
50
    }
51
52
    /**
53
     * Return the formatter manager
54
     * @return FormatterManager
55
     */
56
    public function formatterManager()
57
    {
58
        return $this->formatterManager;
59
    }
60
61
    public function initializeHook(
62
        InputInterface $input,
63
        $names,
64
        AnnotationData $annotationData
65
    ) {
66
        return $this->hookManager()->initializeHook($input, $names, $annotationData);
67
    }
68
69
    public function optionsHook(
70
        AnnotatedCommand $command,
71
        $names,
72
        AnnotationData $annotationData
73
    ) {
74
        $this->hookManager()->optionsHook($command, $names, $annotationData);
75
    }
76
77
    public function interact(
78
        InputInterface $input,
79
        OutputInterface $output,
80
        $names,
81
        AnnotationData $annotationData
82
    ) {
83
        return $this->hookManager()->interact($input, $output, $names, $annotationData);
84
    }
85
86
    public function process(
87
        OutputInterface $output,
88
        $names,
89
        $commandCallback,
90
        AnnotationData $annotationData,
91
        $args
92
    ) {
93
        $result = [];
94
        // Recover options from the end of the args
95
        $options = end($args);
96
        try {
97
            $result = $this->validateRunAndAlter(
98
                $names,
99
                $commandCallback,
100
                $args,
101
                $annotationData
102
            );
103
            return $this->handleResults($output, $names, $result, $annotationData, $options);
104
        } catch (\Exception $e) {
105
            $result = new CommandError($e->getMessage(), $e->getCode());
106
            return $this->handleResults($output, $names, $result, $annotationData, $options);
107
        }
108
    }
109
110 View Code Duplication
    public function validateRunAndAlter(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
111
        $names,
112
        $commandCallback,
113
        $args,
114
        AnnotationData $annotationData
115
    ) {
116
        // Validators return any object to signal a validation error;
117
        // if the return an array, it replaces the arguments.
118
        $validated = $this->hookManager()->validateArguments($names, $args, $annotationData);
119
        if (is_object($validated)) {
120
            return $validated;
121
        }
122
        if (is_array($validated)) {
123
            $args = $validated;
124
        }
125
126
        // Run the command, alter the results, and then handle output and status
127
        $result = $this->runCommandCallback($commandCallback, $args);
128
        return $this->processResults($names, $result, $args, $annotationData);
129
    }
130
131
    public function processResults($names, $result, $args, $annotationData)
132
    {
133
        return $this->hookManager()->alterResult($names, $result, $args, $annotationData);
134
    }
135
136
    /**
137
     * Handle the result output and status code calculation.
138
     */
139
    public function handleResults(OutputInterface $output, $names, $result, AnnotationData $annotationData, $options = [])
140
    {
141
        $status = $this->hookManager()->determineStatusCode($names, $result);
142
        // If the result is an integer and no separate status code was provided, then use the result as the status and do no output.
143
        if (is_integer($result) && !isset($status)) {
144
            return $result;
145
        }
146
        $status = $this->interpretStatusCode($status);
147
148
        // Get the structured output, the output stream and the formatter
149
        $structuredOutput = $this->hookManager()->extractOutput($names, $result);
150
        $output = $this->chooseOutputStream($output, $status);
151
        if ($status != 0) {
152
            return $this->writeErrorMessage($output, $status, $structuredOutput, $result);
153
        }
154
        if ($this->dataCanBeFormatted($structuredOutput) && isset($this->formatterManager)) {
155
            return $this->writeUsingFormatter($output, $structuredOutput, $annotationData, $options);
156
        }
157
        return $this->writeCommandOutput($output, $structuredOutput);
158
    }
159
160
    protected function dataCanBeFormatted($structuredOutput)
161
    {
162
        if (!isset($this->formatterManager)) {
163
            return false;
164
        }
165
        return
166
            is_object($structuredOutput) ||
167
            is_array($structuredOutput);
168
    }
169
170
    /**
171
     * Run the main command callback
172
     */
173
    protected function runCommandCallback($commandCallback, $args)
174
    {
175
        $result = false;
176
        try {
177
            $result = call_user_func_array($commandCallback, $args);
178
        } catch (\Exception $e) {
179
            $result = new CommandError($e->getMessage(), $e->getCode());
180
        }
181
        return $result;
182
    }
183
184
    /**
185
     * Determine the formatter that should be used to render
186
     * output.
187
     *
188
     * If the user specified a format via the --format option,
189
     * then always return that.  Otherwise, return the default
190
     * format, unless --pipe was specified, in which case
191
     * return the default pipe format, format-pipe.
192
     *
193
     * n.b. --pipe is a handy option introduced in Drush 2
194
     * (or perhaps even Drush 1) that indicates that the command
195
     * should select the output format that is most appropriate
196
     * for use in scripts (e.g. to pipe to another command).
197
     *
198
     * @return string
199
     */
200
    protected function getFormat($options)
201
    {
202
        // In Symfony Console, there is no way for us to differentiate
203
        // between the user specifying '--format=table', and the user
204
        // not specifying --format when the default value is 'table'.
205
        // Therefore, we must make --field always override --format; it
206
        // cannot become the default value for --format.
207
        if (!empty($options['field'])) {
208
            return 'string';
209
        }
210
        $options += [
211
            'default-format' => false,
212
            'pipe' => false,
213
        ];
214
        $options += [
215
            'format' => $options['default-format'],
216
            'format-pipe' => $options['default-format'],
217
        ];
218
219
        $format = $options['format'];
220
        if ($options['pipe']) {
221
            $format = $options['format-pipe'];
222
        }
223
        return $format;
224
    }
225
226
    /**
227
     * Determine whether we should use stdout or stderr.
228
     */
229
    protected function chooseOutputStream(OutputInterface $output, $status)
230
    {
231
        // If the status code indicates an error, then print the
232
        // result to stderr rather than stdout
233
        if ($status && ($output instanceof ConsoleOutputInterface)) {
234
            return $output->getErrorOutput();
235
        }
236
        return $output;
237
    }
238
239
    /**
240
     * Call the formatter to output the provided data.
241
     */
242
    protected function writeUsingFormatter(OutputInterface $output, $structuredOutput, AnnotationData $annotationData, $options)
243
    {
244
        $format = $this->getFormat($options);
245
        $formatterOptions = new FormatterOptions($annotationData->getArrayCopy(), $options);
246
        $this->formatterManager->write(
247
            $output,
248
            $format,
0 ignored issues
show
Security Bug introduced by
It seems like $format defined by $this->getFormat($options) on line 244 can also be of type false; however, Consolidation\OutputForm...rmatterManager::write() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
249
            $structuredOutput,
250
            $formatterOptions
251
        );
252
        return 0;
253
    }
254
255
    /**
256
     * Description
257
     * @param OutputInterface $output
258
     * @param int $status
259
     * @param string $structuredOutput
260
     * @param mixed $originalResult
261
     * @return type
262
     */
263
    protected function writeErrorMessage($output, $status, $structuredOutput, $originalResult)
264
    {
265
        if (isset($this->displayErrorFunction)) {
266
            call_user_func($this->displayErrorFunction, $output, $structuredOutput, $status, $originalResult);
267
        } else {
268
            $this->writeCommandOutput($output, $structuredOutput);
269
        }
270
        return $status;
271
    }
272
273
    /**
274
     * If the result object is a string, then print it.
275
     */
276
    protected function writeCommandOutput(
277
        OutputInterface $output,
278
        $structuredOutput
279
    ) {
280
        // If there is no formatter, we will print strings,
281
        // but can do no more than that.
282
        if (is_string($structuredOutput)) {
283
            $output->writeln($structuredOutput);
284
        }
285
        return 0;
286
    }
287
288
    /**
289
     * If a status code was set, then return it; otherwise,
290
     * presume success.
291
     */
292
    protected function interpretStatusCode($status)
293
    {
294
        if (isset($status)) {
295
            return $status;
296
        }
297
        return 0;
298
    }
299
}
300