6e0e53d78d9290ab44a94ca9b7f6786a6d2a00c6
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Mvc / Cli / ConsoleOutput.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Mvc\Cli;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use Symfony\Component\Console\Formatter\OutputFormatterStyle;
18 use Symfony\Component\Console\Helper\FormatterHelper;
19 use Symfony\Component\Console\Helper\HelperSet;
20 use Symfony\Component\Console\Helper\ProgressBar;
21 use Symfony\Component\Console\Helper\QuestionHelper;
22 use Symfony\Component\Console\Helper\Table;
23 use Symfony\Component\Console\Input\ArgvInput;
24 use Symfony\Component\Console\Output\ConsoleOutput as SymfonyConsoleOutput;
25 use Symfony\Component\Console\Question\ChoiceQuestion;
26 use Symfony\Component\Console\Question\ConfirmationQuestion;
27 use Symfony\Component\Console\Question\Question;
28 use TYPO3\CMS\Core\Utility\GeneralUtility;
29
30 /**
31 * A wrapper for Symfony ConsoleOutput and related helpers
32 */
33 class ConsoleOutput
34 {
35 /**
36 * @var ArgvInput
37 */
38 protected $input;
39
40 /**
41 * @var SymfonyConsoleOutput
42 */
43 protected $output;
44
45 /**
46 * @var QuestionHelper
47 */
48 protected $questionHelper;
49
50 /**
51 * @var ProgressBar
52 */
53 protected $progressBar;
54
55 /**
56 * @var Table
57 */
58 protected $table;
59
60 /**
61 * Creates and initializes the Symfony I/O instances
62 */
63 public function __construct()
64 {
65 $this->output = new SymfonyConsoleOutput();
66 $this->output->getFormatter()->setStyle('b', new OutputFormatterStyle(null, null, ['bold']));
67 $this->output->getFormatter()->setStyle('i', new OutputFormatterStyle('black', 'white'));
68 $this->output->getFormatter()->setStyle('u', new OutputFormatterStyle(null, null, ['underscore']));
69 $this->output->getFormatter()->setStyle('em', new OutputFormatterStyle(null, null, ['reverse']));
70 $this->output->getFormatter()->setStyle('strike', new OutputFormatterStyle(null, null, ['conceal']));
71 }
72
73 /**
74 * Returns the desired maximum line length for console output.
75 *
76 * @return int
77 */
78 public function getMaximumLineLength()
79 {
80 return 79;
81 }
82
83 /**
84 * Outputs specified text to the console window
85 * You can specify arguments that will be passed to the text via sprintf
86 * @see http://www.php.net/sprintf
87 *
88 * @param string $text Text to output
89 * @param array $arguments Optional arguments to use for sprintf
90 */
91 public function output($text, array $arguments = [])
92 {
93 if ($arguments !== []) {
94 $text = vsprintf($text, $arguments);
95 }
96 $this->output->write($text);
97 }
98
99 /**
100 * Outputs specified text to the console window and appends a line break
101 *
102 * @param string $text Text to output
103 * @param array $arguments Optional arguments to use for sprintf
104 * @see output()
105 * @see outputLines()
106 */
107 public function outputLine($text = '', array $arguments = [])
108 {
109 $this->output($text . PHP_EOL, $arguments);
110 }
111
112 /**
113 * Formats the given text to fit into the maximum line length and outputs it to the
114 * console window
115 *
116 * @param string $text Text to output
117 * @param array $arguments Optional arguments to use for sprintf
118 * @param int $leftPadding The number of spaces to use for indentation
119 * @see outputLine()
120 */
121 public function outputFormatted($text = '', array $arguments = [], $leftPadding = 0)
122 {
123 $lines = explode(PHP_EOL, $text);
124 foreach ($lines as $line) {
125 $formattedText = str_repeat(' ', $leftPadding) . wordwrap($line, $this->getMaximumLineLength() - $leftPadding, PHP_EOL . str_repeat(' ', $leftPadding), true);
126 $this->outputLine($formattedText, $arguments);
127 }
128 }
129
130 /**
131 * Renders a table like output of the given $rows
132 *
133 * @param array $rows
134 * @param array $headers
135 */
136 public function outputTable($rows, $headers = null)
137 {
138 $table = $this->getTable();
139 if ($headers !== null) {
140 $table->setHeaders($headers);
141 }
142 $table->setRows($rows);
143 $table->render();
144 }
145
146 /**
147 * Asks the user to select a value
148 *
149 * @param string|array $question The question to ask. If an array each array item is turned into one line of a multi-line question
150 * @param array $choices List of choices to pick from
151 * @param bool $default The default answer if the user enters nothing
152 * @param bool $multiSelect If TRUE the result will be an array with the selected options. Multiple options can be given separated by commas
153 * @param null|bool|int $attempts Max number of times to ask before giving up (null by default, which means infinite)
154 * @return int|string|array The selected value or values (the key of the choices array)
155 * @throws \InvalidArgumentException
156 */
157 public function select($question, $choices, $default = null, $multiSelect = false, $attempts = null)
158 {
159 // boolean option is @deprecated in TYPO3 v8, will be removed in TYPO3 v9
160 if ($attempts === false) {
161 GeneralUtility::deprecationLog('CLI output select() asks for infinite attempts by setting the value to "false", but should be null by default. Use "null" instead in your CommandController.');
162 $attempts = null;
163 }
164 $question = (new ChoiceQuestion($question, $choices, $default))
165 ->setMultiselect($multiSelect)
166 ->setMaxAttempts($attempts)
167 ->setErrorMessage('Value "%s" is invalid');
168
169 return $this->getQuestionHelper()->ask($this->getInput(), $this->output, $question);
170 }
171
172 /**
173 * Asks a question to the user
174 *
175 * @param string|array $question The question to ask. If an array each array item is turned into one line of a multi-line question
176 * @param string $default The default answer if none is given by the user
177 * @param array $autocomplete List of values to autocomplete. This only works if "stty" is installed
178 * @return string The user answer
179 * @throws \RuntimeException If there is no data to read in the input stream
180 */
181 public function ask($question, $default = null, array $autocomplete = null)
182 {
183 $question = (new Question($question, $default))
184 ->setAutocompleterValues($autocomplete);
185
186 return $this->getQuestionHelper()->ask($this->getInput(), $this->output, $question);
187 }
188
189 /**
190 * Asks a confirmation to the user.
191 *
192 * The question will be asked until the user answers by nothing, yes, or no.
193 *
194 * @param string|array $question The question to ask. If an array each array item is turned into one line of a multi-line question
195 * @param bool $default The default answer if the user enters nothing
196 * @return bool true if the user has confirmed, false otherwise
197 */
198 public function askConfirmation($question, $default = true)
199 {
200 $question = new ConfirmationQuestion($question, $default);
201
202 return $this->getQuestionHelper()->ask($this->getInput(), $this->output, $question);
203 }
204
205 /**
206 * Asks a question to the user, the response is hidden
207 *
208 * @param string|array $question The question. If an array each array item is turned into one line of a multi-line question
209 * @param bool $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not
210 * @return string The answer
211 * @throws \RuntimeException In case the fallback is deactivated and the response can not be hidden
212 */
213 public function askHiddenResponse($question, $fallback = true)
214 {
215 $question = (new Question($question))
216 ->setHidden(true)
217 ->setHiddenFallback($fallback);
218
219 return $this->getQuestionHelper()->ask($this->getInput(), $this->output, $question);
220 }
221
222 /**
223 * Asks for a value and validates the response
224 *
225 * The validator receives the data to validate. It must return the
226 * validated data when the data is valid and throw an exception
227 * otherwise.
228 *
229 * @param string|array $question The question to ask. If an array each array item is turned into one line of a multi-line question
230 * @param callable $validator A PHP callback that gets a value and is expected to return the (transformed) value or throw an exception if it wasn't valid
231 * @param int|null|bool $attempts Max number of times to ask before giving up (null by default, which means infinite)
232 * @param string $default The default answer if none is given by the user
233 * @param array $autocomplete List of values to autocomplete. This only works if "stty" is installed
234 * @return mixed
235 * @throws \Exception When any of the validators return an error
236 */
237 public function askAndValidate($question, $validator, $attempts = null, $default = null, array $autocomplete = null)
238 {
239 // boolean option is @deprecated in TYPO3 v8, will be removed in TYPO3 v9
240 if ($attempts === false) {
241 GeneralUtility::deprecationLog('CLI output askAndValidate() sets infinite attempts by setting the value to "false", but should be null by default. Use "null" instead in your CommandController.');
242 $attempts = null;
243 }
244 $question = (new Question($question, $default))
245 ->setValidator($validator)
246 ->setMaxAttempts($attempts)
247 ->setAutocompleterValues($autocomplete);
248
249 return $this->getQuestionHelper()->ask($this->getInput(), $this->output, $question);
250 }
251
252 /**
253 * Asks for a value, hide and validates the response
254 *
255 * The validator receives the data to validate. It must return the
256 * validated data when the data is valid and throw an exception
257 * otherwise.
258 *
259 * @param string|array $question The question to ask. If an array each array item is turned into one line of a multi-line question
260 * @param callable $validator A PHP callback that gets a value and is expected to return the (transformed) value or throw an exception if it wasn't valid
261 * @param int|bool $attempts Max number of times to ask before giving up (false by default, which means infinite)
262 * @param bool $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not
263 * @return string The response
264 * @throws \Exception When any of the validators return an error
265 * @throws \RuntimeException In case the fallback is deactivated and the response can not be hidden
266 */
267 public function askHiddenResponseAndValidate($question, $validator, $attempts = false, $fallback = true)
268 {
269 $question = (new Question($question))
270 ->setValidator($validator)
271 ->setMaxAttempts($attempts)
272 ->setHidden(true)
273 ->setHiddenFallback($fallback);
274
275 return $this->getQuestionHelper()->ask($this->getInput(), $this->output, $question);
276 }
277
278 /**
279 * Starts the progress output
280 *
281 * @param int $max Maximum steps. If NULL an indeterminate progress bar is rendered
282 */
283 public function progressStart($max = null)
284 {
285 $this->getProgressBar()->start($max);
286 }
287
288 /**
289 * Advances the progress output X steps
290 *
291 * @param int $step Number of steps to advance
292 * @throws \LogicException
293 */
294 public function progressAdvance($step = 1)
295 {
296 $this->getProgressBar()->advance($step);
297 }
298
299 /**
300 * Sets the current progress
301 *
302 * @param int $current The current progress
303 * @throws \LogicException
304 */
305 public function progressSet($current)
306 {
307 $this->getProgressBar()->setProgress($current);
308 }
309
310 /**
311 * Finishes the progress output
312 */
313 public function progressFinish()
314 {
315 $this->getProgressBar()->finish();
316 }
317
318 /**
319 * @return ArgvInput
320 * @throws \RuntimeException
321 */
322 protected function getInput()
323 {
324 if ($this->input === null) {
325 if (!isset($_SERVER['argv'])) {
326 throw new \RuntimeException('Cannot initialize ArgvInput object without CLI context.', 1456914444);
327 }
328 $this->input = new ArgvInput();
329 }
330
331 return $this->input;
332 }
333
334 /**
335 * Returns or initializes the symfony/console QuestionHelper
336 *
337 * @return QuestionHelper
338 */
339 protected function getQuestionHelper()
340 {
341 if ($this->questionHelper === null) {
342 $this->questionHelper = new QuestionHelper();
343 $helperSet = new HelperSet([new FormatterHelper()]);
344 $this->questionHelper->setHelperSet($helperSet);
345 }
346 return $this->questionHelper;
347 }
348
349 /**
350 * Returns or initializes the symfony/console ProgressBar
351 *
352 * @return ProgressBar
353 */
354 protected function getProgressBar()
355 {
356 if ($this->progressBar === null) {
357 $this->progressBar = new ProgressBar($this->output);
358 }
359 return $this->progressBar;
360 }
361
362 /**
363 * Returns or initializes the symfony/console Table
364 *
365 * @return Table
366 */
367 protected function getTable()
368 {
369 if ($this->table === null) {
370 $this->table = new Table($this->output);
371 }
372 return $this->table;
373 }
374 }