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