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 public function __construct(HookManager $hookManager)
45 $this->hookManager = $hookManager;
49 * Return the hook manager
52 public function hookManager()
54 return $this->hookManager;
57 public function addPrepareFormatter(PrepareFormatter $preparer)
59 $this->prepareOptionsList[] = $preparer;
62 public function setFormatterManager(FormatterManager $formatterManager)
64 $this->formatterManager = $formatterManager;
68 public function setDisplayErrorFunction(callable $fn)
70 $this->displayErrorFunction = $fn;
75 * Return the formatter manager
76 * @return FormatterManager
78 public function formatterManager()
80 return $this->formatterManager;
83 public function initializeHook(
84 InputInterface $input,
86 AnnotationData $annotationData
88 $initializeDispatcher = new InitializeHookDispatcher($this->hookManager(), $names);
89 return $initializeDispatcher->initialize($input, $annotationData);
92 public function optionsHook(
93 AnnotatedCommand $command,
95 AnnotationData $annotationData
97 $optionsDispatcher = new OptionsHookDispatcher($this->hookManager(), $names);
98 $optionsDispatcher->getOptions($command, $annotationData);
101 public function interact(
102 InputInterface $input,
103 OutputInterface $output,
105 AnnotationData $annotationData
107 $interactDispatcher = new InteractHookDispatcher($this->hookManager(), $names);
108 return $interactDispatcher->interact($input, $output, $annotationData);
111 public function process(
112 OutputInterface $output,
115 CommandData $commandData
119 $result = $this->validateRunAndAlter(
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);
131 public function validateRunAndAlter(
134 CommandData $commandData
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)) {
144 $replaceDispatcher = new ReplaceCommandHookDispatcher($this->hookManager(), $names);
146 $replaceDispatcher->setLogger($this->logger);
148 if ($replaceDispatcher->hasReplaceCommandHook()) {
149 $commandCallback = $replaceDispatcher->getReplacementCommand($commandData);
152 // Run the command, alter the results, and then handle output and status
153 $result = $this->runCommandCallback($commandCallback, $commandData);
154 return $this->processResults($names, $result, $commandData);
157 public function processResults($names, $result, CommandData $commandData)
159 $processDispatcher = new ProcessResultHookDispatcher($this->hookManager(), $names);
160 return $processDispatcher->process($result, $commandData);
164 * Handle the result output and status code calculation.
166 public function handleResults(OutputInterface $output, $names, $result, CommandData $commandData)
168 $statusCodeDispatcher = new StatusDeterminerHookDispatcher($this->hookManager(), $names);
169 $status = $statusCodeDispatcher->determineStatusCode($result);
170 // If the result is an integer and no separate status code was provided, then use the result as the status and do no output.
171 if (is_integer($result) && !isset($status)) {
174 $status = $this->interpretStatusCode($status);
176 // Get the structured output, the output stream and the formatter
177 $extractDispatcher = new ExtracterHookDispatcher($this->hookManager(), $names);
178 $structuredOutput = $extractDispatcher->extractOutput($result);
179 $output = $this->chooseOutputStream($output, $status);
181 return $this->writeErrorMessage($output, $status, $structuredOutput, $result);
183 if ($this->dataCanBeFormatted($structuredOutput) && isset($this->formatterManager)) {
184 return $this->writeUsingFormatter($output, $structuredOutput, $commandData);
186 return $this->writeCommandOutput($output, $structuredOutput);
189 protected function dataCanBeFormatted($structuredOutput)
191 if (!isset($this->formatterManager)) {
195 is_object($structuredOutput) ||
196 is_array($structuredOutput);
200 * Run the main command callback
202 protected function runCommandCallback($commandCallback, CommandData $commandData)
206 $args = $commandData->getArgsAndOptions();
207 $result = call_user_func_array($commandCallback, $args);
208 } catch (\Exception $e) {
209 $result = new CommandError($e->getMessage(), $e->getCode());
215 * Determine the formatter that should be used to render
218 * If the user specified a format via the --format option,
219 * then always return that. Otherwise, return the default
220 * format, unless --pipe was specified, in which case
221 * return the default pipe format, format-pipe.
223 * n.b. --pipe is a handy option introduced in Drush 2
224 * (or perhaps even Drush 1) that indicates that the command
225 * should select the output format that is most appropriate
226 * for use in scripts (e.g. to pipe to another command).
230 protected function getFormat(FormatterOptions $options)
232 // In Symfony Console, there is no way for us to differentiate
233 // between the user specifying '--format=table', and the user
234 // not specifying --format when the default value is 'table'.
235 // Therefore, we must make --field always override --format; it
236 // cannot become the default value for --format.
237 if ($options->get('field')) {
241 if ($options->get('pipe')) {
242 return $options->get('pipe-format', [], 'tsv');
244 return $options->getFormat($defaults);
248 * Determine whether we should use stdout or stderr.
250 protected function chooseOutputStream(OutputInterface $output, $status)
252 // If the status code indicates an error, then print the
253 // result to stderr rather than stdout
254 if ($status && ($output instanceof ConsoleOutputInterface)) {
255 return $output->getErrorOutput();
261 * Call the formatter to output the provided data.
263 protected function writeUsingFormatter(OutputInterface $output, $structuredOutput, CommandData $commandData)
265 $formatterOptions = $this->createFormatterOptions($commandData);
266 $format = $this->getFormat($formatterOptions);
267 $this->formatterManager->write(
277 * Create a FormatterOptions object for use in writing the formatted output.
278 * @param CommandData $commandData
279 * @return FormatterOptions
281 protected function createFormatterOptions($commandData)
283 $options = $commandData->input()->getOptions();
284 $formatterOptions = new FormatterOptions($commandData->annotationData()->getArrayCopy(), $options);
285 foreach ($this->prepareOptionsList as $preparer) {
286 $preparer->prepare($commandData, $formatterOptions);
288 return $formatterOptions;
293 * @param OutputInterface $output
295 * @param string $structuredOutput
296 * @param mixed $originalResult
299 protected function writeErrorMessage($output, $status, $structuredOutput, $originalResult)
301 if (isset($this->displayErrorFunction)) {
302 call_user_func($this->displayErrorFunction, $output, $structuredOutput, $status, $originalResult);
304 $this->writeCommandOutput($output, $structuredOutput);
310 * If the result object is a string, then print it.
312 protected function writeCommandOutput(
313 OutputInterface $output,
316 // If there is no formatter, we will print strings,
317 // but can do no more than that.
318 if (is_string($structuredOutput)) {
319 $output->writeln($structuredOutput);
325 * If a status code was set, then return it; otherwise,
328 protected function interpretStatusCode($status)
330 if (isset($status)) {