2 namespace Consolidation\AnnotatedCommand;
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;
11 use Consolidation\OutputFormatters\FormatterManager;
12 use Consolidation\OutputFormatters\Options\FormatterOptions;
13 use Consolidation\AnnotatedCommand\Hooks\HookManager;
14 use Consolidation\AnnotatedCommand\Options\PrepareFormatter;
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;
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().
30 class CommandProcessor implements LoggerAwareInterface
34 /** var HookManager */
35 protected $hookManager;
36 /** var FormatterManager */
37 protected $formatterManager;
39 protected $displayErrorFunction;
40 /** var PrepareFormatterOptions[] */
41 protected $prepareOptionsList = [];
43 protected $passExceptions;
45 public function __construct(HookManager $hookManager)
47 $this->hookManager = $hookManager;
51 * Return the hook manager
54 public function hookManager()
56 return $this->hookManager;
59 public function addPrepareFormatter(PrepareFormatter $preparer)
61 $this->prepareOptionsList[] = $preparer;
64 public function setFormatterManager(FormatterManager $formatterManager)
66 $this->formatterManager = $formatterManager;
70 public function setDisplayErrorFunction(callable $fn)
72 $this->displayErrorFunction = $fn;
77 * Set a mode to make the annotated command library re-throw
78 * any exception that it catches while processing a command.
80 * The default behavior in the current (2.x) branch is to catch
81 * the exception and replace it with a CommandError object that
82 * may be processed by the normal output processing passthrough.
84 * In the 3.x branch, exceptions will never be caught; they will
85 * be passed through, as if setPassExceptions(true) were called.
86 * This is the recommended behavior.
88 public function setPassExceptions($passExceptions)
90 $this->passExceptions = $passExceptions;
94 public function commandErrorForException(\Exception $e)
96 if ($this->passExceptions) {
99 return new CommandError($e->getMessage(), $e->getCode());
103 * Return the formatter manager
104 * @return FormatterManager
106 public function formatterManager()
108 return $this->formatterManager;
111 public function initializeHook(
112 InputInterface $input,
114 AnnotationData $annotationData
116 $initializeDispatcher = new InitializeHookDispatcher($this->hookManager(), $names);
117 return $initializeDispatcher->initialize($input, $annotationData);
120 public function optionsHook(
121 AnnotatedCommand $command,
123 AnnotationData $annotationData
125 $optionsDispatcher = new OptionsHookDispatcher($this->hookManager(), $names);
126 $optionsDispatcher->getOptions($command, $annotationData);
129 public function interact(
130 InputInterface $input,
131 OutputInterface $output,
133 AnnotationData $annotationData
135 $interactDispatcher = new InteractHookDispatcher($this->hookManager(), $names);
136 return $interactDispatcher->interact($input, $output, $annotationData);
139 public function process(
140 OutputInterface $output,
143 CommandData $commandData
147 $result = $this->validateRunAndAlter(
152 return $this->handleResults($output, $names, $result, $commandData);
153 } catch (\Exception $e) {
154 $result = $this->commandErrorForException($e);
155 return $this->handleResults($output, $names, $result, $commandData);
159 public function validateRunAndAlter(
162 CommandData $commandData
164 // Validators return any object to signal a validation error;
165 // if the return an array, it replaces the arguments.
166 $validateDispatcher = new ValidateHookDispatcher($this->hookManager(), $names);
167 $validated = $validateDispatcher->validate($commandData);
168 if (is_object($validated)) {
172 $replaceDispatcher = new ReplaceCommandHookDispatcher($this->hookManager(), $names);
174 $replaceDispatcher->setLogger($this->logger);
176 if ($replaceDispatcher->hasReplaceCommandHook()) {
177 $commandCallback = $replaceDispatcher->getReplacementCommand($commandData);
180 // Run the command, alter the results, and then handle output and status
181 $result = $this->runCommandCallback($commandCallback, $commandData);
182 return $this->processResults($names, $result, $commandData);
185 public function processResults($names, $result, CommandData $commandData)
187 $processDispatcher = new ProcessResultHookDispatcher($this->hookManager(), $names);
188 return $processDispatcher->process($result, $commandData);
192 * Handle the result output and status code calculation.
194 public function handleResults(OutputInterface $output, $names, $result, CommandData $commandData)
196 $statusCodeDispatcher = new StatusDeterminerHookDispatcher($this->hookManager(), $names);
197 $status = $statusCodeDispatcher->determineStatusCode($result);
198 // If the result is an integer and no separate status code was provided, then use the result as the status and do no output.
199 if (is_integer($result) && !isset($status)) {
202 $status = $this->interpretStatusCode($status);
204 // Get the structured output, the output stream and the formatter
205 $extractDispatcher = new ExtracterHookDispatcher($this->hookManager(), $names);
206 $structuredOutput = $extractDispatcher->extractOutput($result);
207 $output = $this->chooseOutputStream($output, $status);
209 return $this->writeErrorMessage($output, $status, $structuredOutput, $result);
211 if ($this->dataCanBeFormatted($structuredOutput) && isset($this->formatterManager)) {
212 return $this->writeUsingFormatter($output, $structuredOutput, $commandData);
214 return $this->writeCommandOutput($output, $structuredOutput);
217 protected function dataCanBeFormatted($structuredOutput)
219 if (!isset($this->formatterManager)) {
223 is_object($structuredOutput) ||
224 is_array($structuredOutput);
228 * Run the main command callback
230 protected function runCommandCallback($commandCallback, CommandData $commandData)
234 $args = $commandData->getArgsAndOptions();
235 $result = call_user_func_array($commandCallback, $args);
236 } catch (\Exception $e) {
237 $result = $this->commandErrorForException($e);
243 * Determine the formatter that should be used to render
246 * If the user specified a format via the --format option,
247 * then always return that. Otherwise, return the default
248 * format, unless --pipe was specified, in which case
249 * return the default pipe format, format-pipe.
251 * n.b. --pipe is a handy option introduced in Drush 2
252 * (or perhaps even Drush 1) that indicates that the command
253 * should select the output format that is most appropriate
254 * for use in scripts (e.g. to pipe to another command).
258 protected function getFormat(FormatterOptions $options)
260 // In Symfony Console, there is no way for us to differentiate
261 // between the user specifying '--format=table', and the user
262 // not specifying --format when the default value is 'table'.
263 // Therefore, we must make --field always override --format; it
264 // cannot become the default value for --format.
265 if ($options->get('field')) {
269 if ($options->get('pipe')) {
270 return $options->get('pipe-format', [], 'tsv');
272 return $options->getFormat($defaults);
276 * Determine whether we should use stdout or stderr.
278 protected function chooseOutputStream(OutputInterface $output, $status)
280 // If the status code indicates an error, then print the
281 // result to stderr rather than stdout
282 if ($status && ($output instanceof ConsoleOutputInterface)) {
283 return $output->getErrorOutput();
289 * Call the formatter to output the provided data.
291 protected function writeUsingFormatter(OutputInterface $output, $structuredOutput, CommandData $commandData)
293 $formatterOptions = $this->createFormatterOptions($commandData);
294 $format = $this->getFormat($formatterOptions);
295 $this->formatterManager->write(
305 * Create a FormatterOptions object for use in writing the formatted output.
306 * @param CommandData $commandData
307 * @return FormatterOptions
309 protected function createFormatterOptions($commandData)
311 $options = $commandData->input()->getOptions();
312 $formatterOptions = new FormatterOptions($commandData->annotationData()->getArrayCopy(), $options);
313 foreach ($this->prepareOptionsList as $preparer) {
314 $preparer->prepare($commandData, $formatterOptions);
316 return $formatterOptions;
321 * @param OutputInterface $output
323 * @param string $structuredOutput
324 * @param mixed $originalResult
327 protected function writeErrorMessage($output, $status, $structuredOutput, $originalResult)
329 if (isset($this->displayErrorFunction)) {
330 call_user_func($this->displayErrorFunction, $output, $structuredOutput, $status, $originalResult);
332 $this->writeCommandOutput($output, $structuredOutput);
338 * If the result object is a string, then print it.
340 protected function writeCommandOutput(
341 OutputInterface $output,
344 // If there is no formatter, we will print strings,
345 // but can do no more than that.
346 if (is_string($structuredOutput)) {
347 $output->writeln($structuredOutput);
353 * If a status code was set, then return it; otherwise,
356 protected function interpretStatusCode($status)
358 if (isset($status)) {