Completed
Pull Request — master (#97)
by
unknown
02:25
created

CommandProcessor::optionsHook()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 8
rs 9.4285
cc 1
eloc 6
nc 1
nop 3
1
<?php
2
namespace Consolidation\AnnotatedCommand;
3
4
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\ReplaceCommandHookDispatcher;
5
use Psr\Log\LoggerAwareInterface;
6
use Psr\Log\LoggerAwareTrait;
7
use Symfony\Component\Console\Input\InputInterface;
8
use Symfony\Component\Console\Output\OutputInterface;
9
use Symfony\Component\Console\Output\ConsoleOutputInterface;
10
11
use Consolidation\OutputFormatters\FormatterManager;
12
use Consolidation\OutputFormatters\Options\FormatterOptions;
13
use Consolidation\AnnotatedCommand\Hooks\HookManager;
14
use Consolidation\AnnotatedCommand\Options\PrepareFormatter;
15
16
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\InitializeHookDispatcher;
17
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\OptionsHookDispatcher;
18
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\InteractHookDispatcher;
19
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\ValidateHookDispatcher;
20
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\ProcessResultHookDispatcher;
21
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\StatusDeterminerHookDispatcher;
22
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\ExtracterHookDispatcher;
23
24
/**
25
 * Process a command, including hooks and other callbacks.
26
 * There should only be one command processor per application.
27
 * Provide your command processor to the AnnotatedCommandFactory
28
 * via AnnotatedCommandFactory::setCommandProcessor().
29
 */
30
class CommandProcessor implements LoggerAwareInterface
31
{
32
    use LoggerAwareTrait;
33
34
    /** var HookManager */
35
    protected $hookManager;
36
    /** var FormatterManager */
37
    protected $formatterManager;
38
    /** var callable */
39
    protected $displayErrorFunction;
40
    /** var PrepareFormatterOptions[] */
41
    protected $prepareOptionsList = [];
42
43
    public function __construct(HookManager $hookManager)
44
    {
45
        $this->hookManager = $hookManager;
46
    }
47
48
    /**
49
     * Return the hook manager
50
     * @return HookManager
51
     */
52
    public function hookManager()
53
    {
54
        return $this->hookManager;
55
    }
56
57
    public function addPrepareFormatter(PrepareFormatter $preparer)
58
    {
59
        $this->prepareOptionsList[] = $preparer;
60
    }
61
62
    public function setFormatterManager(FormatterManager $formatterManager)
63
    {
64
        $this->formatterManager = $formatterManager;
65
        return $this;
66
    }
67
68
    public function setDisplayErrorFunction(callable $fn)
69
    {
70
        $this->displayErrorFunction = $fn;
71
        return $this;
72
    }
73
74
    /**
75
     * Return the formatter manager
76
     * @return FormatterManager
77
     */
78
    public function formatterManager()
79
    {
80
        return $this->formatterManager;
81
    }
82
83
    public function initializeHook(
84
        InputInterface $input,
85
        $names,
86
        AnnotationData $annotationData
87
    ) {
88
        $initializeDispatcher = new InitializeHookDispatcher($this->hookManager(), $names);
89
        return $initializeDispatcher->initialize($input, $annotationData);
90
    }
91
92
    public function optionsHook(
93
        AnnotatedCommand $command,
94
        $names,
95
        AnnotationData $annotationData
96
    ) {
97
        $optionsDispatcher = new OptionsHookDispatcher($this->hookManager(), $names);
98
        $optionsDispatcher->getOptions($command, $annotationData);
99
    }
100
101
    public function interact(
102
        InputInterface $input,
103
        OutputInterface $output,
104
        $names,
105
        AnnotationData $annotationData
106
    ) {
107
        $interactDispatcher = new InteractHookDispatcher($this->hookManager(), $names);
108
        return $interactDispatcher->interact($input, $output, $annotationData);
109
    }
110
111
    public function process(
112
        OutputInterface $output,
113
        $names,
114
        $commandCallback,
115
        CommandData $commandData
116
    ) {
117
        $result = [];
118
        try {
119
            $result = $this->validateRunAndAlter(
120
                $names,
121
                $commandCallback,
122
                $commandData
123
            );
124
            return $this->handleResults($output, $names, $result, $commandData);
125
        } catch (\Exception $e) {
126
            $result = new CommandError($e->getMessage(), $e->getCode());
127
            return $this->handleResults($output, $names, $result, $commandData);
128
        }
129
    }
130
131
    public function validateRunAndAlter(
132
        $names,
133
        $commandCallback,
134
        CommandData $commandData
135
    ) {
136
        // Validators return any object to signal a validation error;
137
        // if the return an array, it replaces the arguments.
138
        $validateDispatcher = new ValidateHookDispatcher($this->hookManager(), $names);
139
        $validated = $validateDispatcher->validate($commandData);
140
        if (is_object($validated)) {
141
            return $validated;
142
        }
143
144
        $replaceDispatcher = new ReplaceCommandHookDispatcher($this->hookManager(), $names);
145
        if ($this->logger) {
146
            $replaceDispatcher->setLogger($this->logger);
147
        }
148
        if ($replaceDispatcher->hasReplaceCommandHook()) {
149
            $commandCallback = $replaceDispatcher->getReplacementCommand($commandData);
150
            $args_and_options = $commandData->getArgsAndOptions();
151
            $args = [ $args_and_options ];
152
            $result = $this->runCommandCallback($commandCallback, $commandData, $args);
153
        }
154
        else {
155
            // Run the command, alter the results, and then handle output and status
156
            $result = $this->runCommandCallback($commandCallback, $commandData);
157
        }
158
159
        return $this->processResults($names, $result, $commandData);
160
    }
161
162
    public function processResults($names, $result, CommandData $commandData)
163
    {
164
        $processDispatcher = new ProcessResultHookDispatcher($this->hookManager(), $names);
165
        return $processDispatcher->process($result, $commandData);
166
    }
167
168
    /**
169
     * Handle the result output and status code calculation.
170
     */
171
    public function handleResults(OutputInterface $output, $names, $result, CommandData $commandData)
172
    {
173
        $statusCodeDispatcher = new StatusDeterminerHookDispatcher($this->hookManager(), $names);
174
        $status = $statusCodeDispatcher->determineStatusCode($result);
175
        // If the result is an integer and no separate status code was provided, then use the result as the status and do no output.
176
        if (is_integer($result) && !isset($status)) {
177
            return $result;
178
        }
179
        $status = $this->interpretStatusCode($status);
180
181
        // Get the structured output, the output stream and the formatter
182
        $extractDispatcher = new ExtracterHookDispatcher($this->hookManager(), $names);
183
        $structuredOutput = $extractDispatcher->extractOutput($result);
184
        $output = $this->chooseOutputStream($output, $status);
185
        if ($status != 0) {
186
            return $this->writeErrorMessage($output, $status, $structuredOutput, $result);
187
        }
188
        if ($this->dataCanBeFormatted($structuredOutput) && isset($this->formatterManager)) {
189
            return $this->writeUsingFormatter($output, $structuredOutput, $commandData);
190
        }
191
        return $this->writeCommandOutput($output, $structuredOutput);
192
    }
193
194
    protected function dataCanBeFormatted($structuredOutput)
195
    {
196
        if (!isset($this->formatterManager)) {
197
            return false;
198
        }
199
        return
200
            is_object($structuredOutput) ||
201
            is_array($structuredOutput);
202
    }
203
204
    /**
205
     * Run the main command callback
206
     */
207
    protected function runCommandCallback($commandCallback, CommandData $commandData, $args = [])
208
    {
209
        $result = false;
210
        try {
211
            if (!$args) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $args of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
212
                $args = $commandData->getArgsAndOptions();
213
            }
214
            $result = call_user_func_array($commandCallback, $args);
215
        } catch (\Exception $e) {
216
            $result = new CommandError($e->getMessage(), $e->getCode());
217
        }
218
        return $result;
219
    }
220
221
    /**
222
     * Determine the formatter that should be used to render
223
     * output.
224
     *
225
     * If the user specified a format via the --format option,
226
     * then always return that.  Otherwise, return the default
227
     * format, unless --pipe was specified, in which case
228
     * return the default pipe format, format-pipe.
229
     *
230
     * n.b. --pipe is a handy option introduced in Drush 2
231
     * (or perhaps even Drush 1) that indicates that the command
232
     * should select the output format that is most appropriate
233
     * for use in scripts (e.g. to pipe to another command).
234
     *
235
     * @return string
236
     */
237
    protected function getFormat(FormatterOptions $options)
238
    {
239
        // In Symfony Console, there is no way for us to differentiate
240
        // between the user specifying '--format=table', and the user
241
        // not specifying --format when the default value is 'table'.
242
        // Therefore, we must make --field always override --format; it
243
        // cannot become the default value for --format.
244
        if ($options->get('field')) {
245
            return 'string';
246
        }
247
        $defaults = [];
248
        if ($options->get('pipe')) {
249
            return $options->get('pipe-format', [], 'tsv');
250
        }
251
        return $options->getFormat($defaults);
252
    }
253
254
    /**
255
     * Determine whether we should use stdout or stderr.
256
     */
257
    protected function chooseOutputStream(OutputInterface $output, $status)
258
    {
259
        // If the status code indicates an error, then print the
260
        // result to stderr rather than stdout
261
        if ($status && ($output instanceof ConsoleOutputInterface)) {
262
            return $output->getErrorOutput();
263
        }
264
        return $output;
265
    }
266
267
    /**
268
     * Call the formatter to output the provided data.
269
     */
270
    protected function writeUsingFormatter(OutputInterface $output, $structuredOutput, CommandData $commandData)
271
    {
272
        $formatterOptions = $this->createFormatterOptions($commandData);
273
        $format = $this->getFormat($formatterOptions);
274
        $this->formatterManager->write(
275
            $output,
276
            $format,
277
            $structuredOutput,
278
            $formatterOptions
279
        );
280
        return 0;
281
    }
282
283
    /**
284
     * Create a FormatterOptions object for use in writing the formatted output.
285
     * @param CommandData $commandData
286
     * @return FormatterOptions
287
     */
288
    protected function createFormatterOptions($commandData)
289
    {
290
        $options = $commandData->input()->getOptions();
291
        $formatterOptions = new FormatterOptions($commandData->annotationData()->getArrayCopy(), $options);
292
        foreach ($this->prepareOptionsList as $preparer) {
293
            $preparer->prepare($commandData, $formatterOptions);
294
        }
295
        return $formatterOptions;
296
    }
297
298
    /**
299
     * Description
300
     * @param OutputInterface $output
301
     * @param int $status
302
     * @param string $structuredOutput
303
     * @param mixed $originalResult
304
     * @return type
305
     */
306
    protected function writeErrorMessage($output, $status, $structuredOutput, $originalResult)
307
    {
308
        if (isset($this->displayErrorFunction)) {
309
            call_user_func($this->displayErrorFunction, $output, $structuredOutput, $status, $originalResult);
310
        } else {
311
            $this->writeCommandOutput($output, $structuredOutput);
312
        }
313
        return $status;
314
    }
315
316
    /**
317
     * If the result object is a string, then print it.
318
     */
319
    protected function writeCommandOutput(
320
        OutputInterface $output,
321
        $structuredOutput
322
    ) {
323
        // If there is no formatter, we will print strings,
324
        // but can do no more than that.
325
        if (is_string($structuredOutput)) {
326
            $output->writeln($structuredOutput);
327
        }
328
        return 0;
329
    }
330
331
    /**
332
     * If a status code was set, then return it; otherwise,
333
     * presume success.
334
     */
335
    protected function interpretStatusCode($status)
336
    {
337
        if (isset($status)) {
338
            return $status;
339
        }
340
        return 0;
341
    }
342
}
343