Completed
Push — master ( 6d71ae...f255f3 )
by Greg
01:49
created

CommandProcessor::handleResults()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 4
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 PrepareFormatterOptions[] */
39
    protected $prepareOptionsList = [];
40
    /** @var boolean */
41
    protected $passExceptions;
42
    /** @var ResultWriter */
43
    protected $resultWriter;
44
    /** @var ParameterInjection */
45
    protected $parameterInjection;
46
47
    public function __construct(HookManager $hookManager)
48
    {
49
        $this->hookManager = $hookManager;
50
    }
51
52
    /**
53
     * Return the hook manager
54
     * @return HookManager
55
     */
56
    public function hookManager()
57
    {
58
        return $this->hookManager;
59
    }
60
61
    public function resultWriter()
62
    {
63
        if (!$this->resultWriter) {
64
            $this->setResultWriter(new ResultWriter());
65
        }
66
        return $this->resultWriter;
67
    }
68
69
    public function setResultWriter($resultWriter)
70
    {
71
        $this->resultWriter = $resultWriter;
72
    }
73
74
    public function parameterInjection()
75
    {
76
        if (!$this->parameterInjection) {
77
            $this->setParameterInjection(new ParameterInjection());
78
        }
79
        return $this->parameterInjection;
80
    }
81
82
    public function setParameterInjection($parameterInjection)
83
    {
84
        $this->parameterInjection = $parameterInjection;
85
    }
86
87
    public function addPrepareFormatter(PrepareFormatter $preparer)
88
    {
89
        $this->prepareOptionsList[] = $preparer;
90
    }
91
92
    public function setFormatterManager(FormatterManager $formatterManager)
93
    {
94
        $this->formatterManager = $formatterManager;
95
        $this->resultWriter()->setFormatterManager($formatterManager);
96
        return $this;
97
    }
98
99
    public function setDisplayErrorFunction(callable $fn)
100
    {
101
        $this->resultWriter()->setDisplayErrorFunction($fn);
102
    }
103
104
    /**
105
     * Set a mode to make the annotated command library re-throw
106
     * any exception that it catches while processing a command.
107
     *
108
     * The default behavior in the current (2.x) branch is to catch
109
     * the exception and replace it with a CommandError object that
110
     * may be processed by the normal output processing passthrough.
111
     *
112
     * In the 3.x branch, exceptions will never be caught; they will
113
     * be passed through, as if setPassExceptions(true) were called.
114
     * This is the recommended behavior.
115
     */
116
    public function setPassExceptions($passExceptions)
117
    {
118
        $this->passExceptions = $passExceptions;
119
        return $this;
120
    }
121
122
    public function commandErrorForException(\Exception $e)
123
    {
124
        if ($this->passExceptions) {
125
            throw $e;
126
        }
127
        return new CommandError($e->getMessage(), $e->getCode());
128
    }
129
130
    /**
131
     * Return the formatter manager
132
     * @return FormatterManager
133
     */
134
    public function formatterManager()
135
    {
136
        return $this->formatterManager;
137
    }
138
139
    public function initializeHook(
140
        InputInterface $input,
141
        $names,
142
        AnnotationData $annotationData
143
    ) {
144
        $initializeDispatcher = new InitializeHookDispatcher($this->hookManager(), $names);
145
        return $initializeDispatcher->initialize($input, $annotationData);
146
    }
147
148
    public function optionsHook(
149
        AnnotatedCommand $command,
150
        $names,
151
        AnnotationData $annotationData
152
    ) {
153
        $optionsDispatcher = new OptionsHookDispatcher($this->hookManager(), $names);
154
        $optionsDispatcher->getOptions($command, $annotationData);
155
    }
156
157
    public function interact(
158
        InputInterface $input,
159
        OutputInterface $output,
160
        $names,
161
        AnnotationData $annotationData
162
    ) {
163
        $interactDispatcher = new InteractHookDispatcher($this->hookManager(), $names);
164
        return $interactDispatcher->interact($input, $output, $annotationData);
165
    }
166
167
    public function process(
168
        OutputInterface $output,
169
        $names,
170
        $commandCallback,
171
        CommandData $commandData
172
    ) {
173
        $result = [];
174
        try {
175
            $result = $this->validateRunAndAlter(
176
                $names,
177
                $commandCallback,
178
                $commandData
179
            );
180
            return $this->handleResults($output, $names, $result, $commandData);
181
        } catch (\Exception $e) {
182
            $result = $this->commandErrorForException($e);
183
            return $this->handleResults($output, $names, $result, $commandData);
184
        }
185
    }
186
187
    public function validateRunAndAlter(
188
        $names,
189
        $commandCallback,
190
        CommandData $commandData
191
    ) {
192
        // Validators return any object to signal a validation error;
193
        // if the return an array, it replaces the arguments.
194
        $validateDispatcher = new ValidateHookDispatcher($this->hookManager(), $names);
195
        $validated = $validateDispatcher->validate($commandData);
196
        if (is_object($validated)) {
197
            return $validated;
198
        }
199
200
        // Once we have validated the optins, create the formatter options.
201
        $this->createFormatterOptions($commandData);
202
203
        $replaceDispatcher = new ReplaceCommandHookDispatcher($this->hookManager(), $names);
204
        if ($this->logger) {
205
            $replaceDispatcher->setLogger($this->logger);
206
        }
207
        if ($replaceDispatcher->hasReplaceCommandHook()) {
208
            $commandCallback = $replaceDispatcher->getReplacementCommand($commandData);
209
        }
210
211
        // Run the command, alter the results, and then handle output and status
212
        $result = $this->runCommandCallback($commandCallback, $commandData);
213
        return $this->processResults($names, $result, $commandData);
214
    }
215
216
    public function processResults($names, $result, CommandData $commandData)
217
    {
218
        $processDispatcher = new ProcessResultHookDispatcher($this->hookManager(), $names);
219
        return $processDispatcher->process($result, $commandData);
220
    }
221
222
    /**
223
     * Create a FormatterOptions object for use in writing the formatted output.
224
     * @param CommandData $commandData
225
     * @return FormatterOptions
226
     */
227
    protected function createFormatterOptions($commandData)
228
    {
229
        $options = $commandData->input()->getOptions();
230
        $formatterOptions = new FormatterOptions($commandData->annotationData()->getArrayCopy(), $options);
231
        foreach ($this->prepareOptionsList as $preparer) {
232
            $preparer->prepare($commandData, $formatterOptions);
233
        }
234
        $commandData->setFormatterOptions($formatterOptions);
235
        return $formatterOptions;
236
    }
237
238
    /**
239
     * Handle the result output and status code calculation.
240
     */
241
    public function handleResults(OutputInterface $output, $names, $result, CommandData $commandData)
242
    {
243
        $statusCodeDispatcher = new StatusDeterminerHookDispatcher($this->hookManager(), $names);
244
        $extractDispatcher = new ExtracterHookDispatcher($this->hookManager(), $names);
245
246
        return $this->resultWriter()->handle($output, $result, $commandData, $statusCodeDispatcher, $extractDispatcher);
247
    }
248
249
    /**
250
     * Run the main command callback
251
     */
252
    protected function runCommandCallback($commandCallback, CommandData $commandData)
253
    {
254
        $result = false;
255
        try {
256
            $args = $this->parameterInjection()->args($commandData);
257
            $result = call_user_func_array($commandCallback, $args);
258
        } catch (\Exception $e) {
259
            $result = $this->commandErrorForException($e);
260
        }
261
        return $result;
262
    }
263
264
    public function injectIntoCommandData($commandData, $injectedClasses)
265
    {
266
        $this->parameterInjection()->injectIntoCommandData($commandData, $injectedClasses);
267
    }
268
}
269