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