Completed
Pull Request — master (#57)
by Greg
02:10
created

CommandProcessor::addPrepareFormatter()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
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\Output\ConsoleOutputInterface;
8
9
use Consolidation\OutputFormatters\FormatterManager;
10
use Consolidation\OutputFormatters\Options\FormatterOptions;
11
use Consolidation\AnnotatedCommand\Hooks\HookManager;
12
use Consolidation\AnnotatedCommand\Options\PrepareFormatter;
13
14
/**
15
 * Process a command, including hooks and other callbacks.
16
 * There should only be one command processor per application.
17
 * Provide your command processor to the AnnotatedCommandFactory
18
 * via AnnotatedCommandFactory::setCommandProcessor().
19
 */
20
class CommandProcessor
21
{
22
    /** var HookManager */
23
    protected $hookManager;
24
    /** var FormatterManager */
25
    protected $formatterManager;
26
    /** var callable */
27
    protected $displayErrorFunction;
28
    /** var PrepareFormatterOptions[] */
29
    protected $prepareOptionsList = [];
30
31
    public function __construct(HookManager $hookManager)
32
    {
33
        $this->hookManager = $hookManager;
34
    }
35
36
    /**
37
     * Return the hook manager
38
     * @return HookManager
39
     */
40
    public function hookManager()
41
    {
42
        return $this->hookManager;
43
    }
44
45
    public function addPrepareFormatter(PrepareFormatter $preparer)
46
    {
47
        $this->prepareOptionsList[] = $preparer;
48
    }
49
50
    public function setFormatterManager(FormatterManager $formatterManager)
51
    {
52
        $this->formatterManager = $formatterManager;
53
        return $this;
54
    }
55
56
    public function setDisplayErrorFunction(callable $fn)
57
    {
58
        $this->displayErrorFunction = $fn;
59
        return $this;
60
    }
61
62
    /**
63
     * Return the formatter manager
64
     * @return FormatterManager
65
     */
66
    public function formatterManager()
67
    {
68
        return $this->formatterManager;
69
    }
70
71
    public function initializeHook(
72
        InputInterface $input,
73
        $names,
74
        AnnotationData $annotationData
75
    ) {
76
        return $this->hookManager()->initializeHook($input, $names, $annotationData);
77
    }
78
79
    public function optionsHook(
80
        AnnotatedCommand $command,
81
        $names,
82
        AnnotationData $annotationData
83
    ) {
84
        $this->hookManager()->optionsHook($command, $names, $annotationData);
85
    }
86
87
    public function interact(
88
        InputInterface $input,
89
        OutputInterface $output,
90
        $names,
91
        AnnotationData $annotationData
92
    ) {
93
        return $this->hookManager()->interact($input, $output, $names, $annotationData);
94
    }
95
96
    public function process(
97
        OutputInterface $output,
98
        $names,
99
        $commandCallback,
100
        CommandData $commandData
101
    ) {
102
        $result = [];
103
        try {
104
            $result = $this->validateRunAndAlter(
105
                $names,
106
                $commandCallback,
107
                $commandData
108
            );
109
            return $this->handleResults($output, $names, $result, $commandData);
110
        } catch (\Exception $e) {
111
            $result = new CommandError($e->getMessage(), $e->getCode());
112
            return $this->handleResults($output, $names, $result, $commandData);
113
        }
114
    }
115
116
    public function validateRunAndAlter(
117
        $names,
118
        $commandCallback,
119
        CommandData $commandData
120
    ) {
121
        // Validators return any object to signal a validation error;
122
        // if the return an array, it replaces the arguments.
123
        $validated = $this->hookManager()->validateArguments($names, $commandData);
124
        if (is_object($validated)) {
125
            return $validated;
126
        }
127
128
        // Run the command, alter the results, and then handle output and status
129
        $result = $this->runCommandCallback($commandCallback, $commandData);
130
        return $this->processResults($names, $result, $commandData);
131
    }
132
133
    public function processResults($names, $result, CommandData $commandData)
134
    {
135
        return $this->hookManager()->alterResult($names, $result, $commandData);
136
    }
137
138
    /**
139
     * Handle the result output and status code calculation.
140
     */
141
    public function handleResults(OutputInterface $output, $names, $result, CommandData $commandData)
142
    {
143
        $status = $this->hookManager()->determineStatusCode($names, $result);
144
        // If the result is an integer and no separate status code was provided, then use the result as the status and do no output.
145
        if (is_integer($result) && !isset($status)) {
146
            return $result;
147
        }
148
        $status = $this->interpretStatusCode($status);
149
150
        // Get the structured output, the output stream and the formatter
151
        $structuredOutput = $this->hookManager()->extractOutput($names, $result);
152
        $output = $this->chooseOutputStream($output, $status);
153
        if ($status != 0) {
154
            return $this->writeErrorMessage($output, $status, $structuredOutput, $result);
155
        }
156
        if ($this->dataCanBeFormatted($structuredOutput) && isset($this->formatterManager)) {
157
            return $this->writeUsingFormatter($output, $structuredOutput, $commandData);
158
        }
159
        return $this->writeCommandOutput($output, $structuredOutput);
160
    }
161
162
    protected function dataCanBeFormatted($structuredOutput)
163
    {
164
        if (!isset($this->formatterManager)) {
165
            return false;
166
        }
167
        return
168
            is_object($structuredOutput) ||
169
            is_array($structuredOutput);
170
    }
171
172
    /**
173
     * Run the main command callback
174
     */
175
    protected function runCommandCallback($commandCallback, CommandData $commandData)
176
    {
177
        $result = false;
178
        try {
179
            $args = $commandData->getArgsAndOptions();
180
            $result = call_user_func_array($commandCallback, $args);
181
        } catch (\Exception $e) {
182
            $result = new CommandError($e->getMessage(), $e->getCode());
183
        }
184
        return $result;
185
    }
186
187
    /**
188
     * Determine the formatter that should be used to render
189
     * output.
190
     *
191
     * If the user specified a format via the --format option,
192
     * then always return that.  Otherwise, return the default
193
     * format, unless --pipe was specified, in which case
194
     * return the default pipe format, format-pipe.
195
     *
196
     * n.b. --pipe is a handy option introduced in Drush 2
197
     * (or perhaps even Drush 1) that indicates that the command
198
     * should select the output format that is most appropriate
199
     * for use in scripts (e.g. to pipe to another command).
200
     *
201
     * @return string
202
     */
203
    protected function getFormat($options)
204
    {
205
        // In Symfony Console, there is no way for us to differentiate
206
        // between the user specifying '--format=table', and the user
207
        // not specifying --format when the default value is 'table'.
208
        // Therefore, we must make --field always override --format; it
209
        // cannot become the default value for --format.
210
        if (!empty($options['field'])) {
211
            return 'string';
212
        }
213
        $options += [
214
            'default-format' => false,
215
            'pipe' => false,
216
        ];
217
        $options += [
218
            'format' => $options['default-format'],
219
            'format-pipe' => $options['default-format'],
220
        ];
221
222
        $format = $options['format'];
223
        if ($options['pipe']) {
224
            $format = $options['format-pipe'];
225
        }
226
        return $format;
227
    }
228
229
    /**
230
     * Determine whether we should use stdout or stderr.
231
     */
232
    protected function chooseOutputStream(OutputInterface $output, $status)
233
    {
234
        // If the status code indicates an error, then print the
235
        // result to stderr rather than stdout
236
        if ($status && ($output instanceof ConsoleOutputInterface)) {
237
            return $output->getErrorOutput();
238
        }
239
        return $output;
240
    }
241
242
    /**
243
     * Call the formatter to output the provided data.
244
     */
245
    protected function writeUsingFormatter(OutputInterface $output, $structuredOutput, CommandData $commandData)
246
    {
247
        $format = $this->getFormat($commandData->input()->getOptions());
248
        $formatterOptions = $this->createFormatterOptions($commandData);
249
        $this->formatterManager->write(
250
            $output,
251
            $format,
0 ignored issues
show
Security Bug introduced by
It seems like $format defined by $this->getFormat($comman...>input()->getOptions()) on line 247 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...
252
            $structuredOutput,
253
            $formatterOptions
254
        );
255
        return 0;
256
    }
257
258
    /**
259
     * Create a FormatterOptions object for use in writing the formatted output.
260
     * @param CommandData $commandData
261
     * @return FormatterOptions
262
     */
263
    protected function createFormatterOptions($commandData)
264
    {
265
        $options = $commandData->input()->getOptions();
266
        $formatterOptions = new FormatterOptions($commandData->annotationData()->getArrayCopy(), $options);
267
        foreach ($this->prepareOptionsList as $preparer) {
268
            $preparer->prepare($commandData, $formatterOptions);
269
        }
270
        return $formatterOptions;
271
    }
272
273
    /**
274
     * Description
275
     * @param OutputInterface $output
276
     * @param int $status
277
     * @param string $structuredOutput
278
     * @param mixed $originalResult
279
     * @return type
280
     */
281
    protected function writeErrorMessage($output, $status, $structuredOutput, $originalResult)
282
    {
283
        if (isset($this->displayErrorFunction)) {
284
            call_user_func($this->displayErrorFunction, $output, $structuredOutput, $status, $originalResult);
285
        } else {
286
            $this->writeCommandOutput($output, $structuredOutput);
287
        }
288
        return $status;
289
    }
290
291
    /**
292
     * If the result object is a string, then print it.
293
     */
294
    protected function writeCommandOutput(
295
        OutputInterface $output,
296
        $structuredOutput
297
    ) {
298
        // If there is no formatter, we will print strings,
299
        // but can do no more than that.
300
        if (is_string($structuredOutput)) {
301
            $output->writeln($structuredOutput);
302
        }
303
        return 0;
304
    }
305
306
    /**
307
     * If a status code was set, then return it; otherwise,
308
     * presume success.
309
     */
310
    protected function interpretStatusCode($status)
311
    {
312
        if (isset($status)) {
313
            return $status;
314
        }
315
        return 0;
316
    }
317
}
318