Completed
Pull Request — master (#57)
by Greg
01:59
created

CommandProcessor::setDisplayErrorFunction()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 5
rs 9.4285
cc 1
eloc 3
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' => '',
215
            'pipe' => '',
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,
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