Completed
Pull Request — master (#179)
by Greg
01:46
created

CommandProcessor::writeUsingFormatter()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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