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 = $replaceDispatcher->getReplacementCommandArguments($commandData); |
151
|
|
|
$result = $this->runCommandCallback($commandCallback, $commandData, $args); |
152
|
|
|
} else { |
153
|
|
|
// Run the command, alter the results, and then handle output and status |
154
|
|
|
$result = $this->runCommandCallback($commandCallback, $commandData); |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
return $this->processResults($names, $result, $commandData); |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
public function processResults($names, $result, CommandData $commandData) |
161
|
|
|
{ |
162
|
|
|
$processDispatcher = new ProcessResultHookDispatcher($this->hookManager(), $names); |
163
|
|
|
return $processDispatcher->process($result, $commandData); |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
/** |
167
|
|
|
* Handle the result output and status code calculation. |
168
|
|
|
*/ |
169
|
|
|
public function handleResults(OutputInterface $output, $names, $result, CommandData $commandData) |
170
|
|
|
{ |
171
|
|
|
$statusCodeDispatcher = new StatusDeterminerHookDispatcher($this->hookManager(), $names); |
172
|
|
|
$status = $statusCodeDispatcher->determineStatusCode($result); |
173
|
|
|
// If the result is an integer and no separate status code was provided, then use the result as the status and do no output. |
174
|
|
|
if (is_integer($result) && !isset($status)) { |
175
|
|
|
return $result; |
176
|
|
|
} |
177
|
|
|
$status = $this->interpretStatusCode($status); |
178
|
|
|
|
179
|
|
|
// Get the structured output, the output stream and the formatter |
180
|
|
|
$extractDispatcher = new ExtracterHookDispatcher($this->hookManager(), $names); |
181
|
|
|
$structuredOutput = $extractDispatcher->extractOutput($result); |
182
|
|
|
$output = $this->chooseOutputStream($output, $status); |
183
|
|
|
if ($status != 0) { |
184
|
|
|
return $this->writeErrorMessage($output, $status, $structuredOutput, $result); |
185
|
|
|
} |
186
|
|
|
if ($this->dataCanBeFormatted($structuredOutput) && isset($this->formatterManager)) { |
187
|
|
|
return $this->writeUsingFormatter($output, $structuredOutput, $commandData); |
188
|
|
|
} |
189
|
|
|
return $this->writeCommandOutput($output, $structuredOutput); |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
protected function dataCanBeFormatted($structuredOutput) |
193
|
|
|
{ |
194
|
|
|
if (!isset($this->formatterManager)) { |
195
|
|
|
return false; |
196
|
|
|
} |
197
|
|
|
return |
198
|
|
|
is_object($structuredOutput) || |
199
|
|
|
is_array($structuredOutput); |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
/** |
203
|
|
|
* Run the main command callback |
204
|
|
|
*/ |
205
|
|
|
protected function runCommandCallback($commandCallback, CommandData $commandData, $args = []) |
206
|
|
|
{ |
207
|
|
|
$result = false; |
208
|
|
|
try { |
209
|
|
|
if (!$args) { |
|
|
|
|
210
|
|
|
$args = $commandData->getArgsAndOptions(); |
211
|
|
|
} |
212
|
|
|
$result = call_user_func_array($commandCallback, $args); |
213
|
|
|
} catch (\Exception $e) { |
214
|
|
|
$result = new CommandError($e->getMessage(), $e->getCode()); |
215
|
|
|
} |
216
|
|
|
return $result; |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
/** |
220
|
|
|
* Determine the formatter that should be used to render |
221
|
|
|
* output. |
222
|
|
|
* |
223
|
|
|
* If the user specified a format via the --format option, |
224
|
|
|
* then always return that. Otherwise, return the default |
225
|
|
|
* format, unless --pipe was specified, in which case |
226
|
|
|
* return the default pipe format, format-pipe. |
227
|
|
|
* |
228
|
|
|
* n.b. --pipe is a handy option introduced in Drush 2 |
229
|
|
|
* (or perhaps even Drush 1) that indicates that the command |
230
|
|
|
* should select the output format that is most appropriate |
231
|
|
|
* for use in scripts (e.g. to pipe to another command). |
232
|
|
|
* |
233
|
|
|
* @return string |
234
|
|
|
*/ |
235
|
|
|
protected function getFormat(FormatterOptions $options) |
236
|
|
|
{ |
237
|
|
|
// In Symfony Console, there is no way for us to differentiate |
238
|
|
|
// between the user specifying '--format=table', and the user |
239
|
|
|
// not specifying --format when the default value is 'table'. |
240
|
|
|
// Therefore, we must make --field always override --format; it |
241
|
|
|
// cannot become the default value for --format. |
242
|
|
|
if ($options->get('field')) { |
243
|
|
|
return 'string'; |
244
|
|
|
} |
245
|
|
|
$defaults = []; |
246
|
|
|
if ($options->get('pipe')) { |
247
|
|
|
return $options->get('pipe-format', [], 'tsv'); |
248
|
|
|
} |
249
|
|
|
return $options->getFormat($defaults); |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
/** |
253
|
|
|
* Determine whether we should use stdout or stderr. |
254
|
|
|
*/ |
255
|
|
|
protected function chooseOutputStream(OutputInterface $output, $status) |
256
|
|
|
{ |
257
|
|
|
// If the status code indicates an error, then print the |
258
|
|
|
// result to stderr rather than stdout |
259
|
|
|
if ($status && ($output instanceof ConsoleOutputInterface)) { |
260
|
|
|
return $output->getErrorOutput(); |
261
|
|
|
} |
262
|
|
|
return $output; |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
/** |
266
|
|
|
* Call the formatter to output the provided data. |
267
|
|
|
*/ |
268
|
|
|
protected function writeUsingFormatter(OutputInterface $output, $structuredOutput, CommandData $commandData) |
269
|
|
|
{ |
270
|
|
|
$formatterOptions = $this->createFormatterOptions($commandData); |
271
|
|
|
$format = $this->getFormat($formatterOptions); |
272
|
|
|
$this->formatterManager->write( |
273
|
|
|
$output, |
274
|
|
|
$format, |
275
|
|
|
$structuredOutput, |
276
|
|
|
$formatterOptions |
277
|
|
|
); |
278
|
|
|
return 0; |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
/** |
282
|
|
|
* Create a FormatterOptions object for use in writing the formatted output. |
283
|
|
|
* @param CommandData $commandData |
284
|
|
|
* @return FormatterOptions |
285
|
|
|
*/ |
286
|
|
|
protected function createFormatterOptions($commandData) |
287
|
|
|
{ |
288
|
|
|
$options = $commandData->input()->getOptions(); |
289
|
|
|
$formatterOptions = new FormatterOptions($commandData->annotationData()->getArrayCopy(), $options); |
290
|
|
|
foreach ($this->prepareOptionsList as $preparer) { |
291
|
|
|
$preparer->prepare($commandData, $formatterOptions); |
292
|
|
|
} |
293
|
|
|
return $formatterOptions; |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
/** |
297
|
|
|
* Description |
298
|
|
|
* @param OutputInterface $output |
299
|
|
|
* @param int $status |
300
|
|
|
* @param string $structuredOutput |
301
|
|
|
* @param mixed $originalResult |
302
|
|
|
* @return type |
303
|
|
|
*/ |
304
|
|
|
protected function writeErrorMessage($output, $status, $structuredOutput, $originalResult) |
305
|
|
|
{ |
306
|
|
|
if (isset($this->displayErrorFunction)) { |
307
|
|
|
call_user_func($this->displayErrorFunction, $output, $structuredOutput, $status, $originalResult); |
308
|
|
|
} else { |
309
|
|
|
$this->writeCommandOutput($output, $structuredOutput); |
310
|
|
|
} |
311
|
|
|
return $status; |
312
|
|
|
} |
313
|
|
|
|
314
|
|
|
/** |
315
|
|
|
* If the result object is a string, then print it. |
316
|
|
|
*/ |
317
|
|
|
protected function writeCommandOutput( |
318
|
|
|
OutputInterface $output, |
319
|
|
|
$structuredOutput |
320
|
|
|
) { |
321
|
|
|
// If there is no formatter, we will print strings, |
322
|
|
|
// but can do no more than that. |
323
|
|
|
if (is_string($structuredOutput)) { |
324
|
|
|
$output->writeln($structuredOutput); |
325
|
|
|
} |
326
|
|
|
return 0; |
327
|
|
|
} |
328
|
|
|
|
329
|
|
|
/** |
330
|
|
|
* If a status code was set, then return it; otherwise, |
331
|
|
|
* presume success. |
332
|
|
|
*/ |
333
|
|
|
protected function interpretStatusCode($status) |
334
|
|
|
{ |
335
|
|
|
if (isset($status)) { |
336
|
|
|
return $status; |
337
|
|
|
} |
338
|
|
|
return 0; |
339
|
|
|
} |
340
|
|
|
} |
341
|
|
|
|
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.