Magento 2 Documentation  2.3
Documentation for Magento 2 CMS v2.3 (December 2018)
MagentoStyle.php
Go to the documentation of this file.
1 <?php
8 
10 use Symfony\Component\Console\Exception\RuntimeException;
11 use Symfony\Component\Console\Formatter\OutputFormatter;
12 use Symfony\Component\Console\Helper\Helper;
13 use Symfony\Component\Console\Helper\ProgressBar;
14 use Symfony\Component\Console\Helper\SymfonyQuestionHelper;
15 use Symfony\Component\Console\Helper\Table;
16 use Symfony\Component\Console\Input\InputInterface;
17 use Symfony\Component\Console\Output\BufferedOutput;
18 use Symfony\Component\Console\Output\OutputInterface;
19 use Symfony\Component\Console\Question\ChoiceQuestion;
20 use Symfony\Component\Console\Question\ConfirmationQuestion;
21 use Symfony\Component\Console\Question\Question;
22 use Symfony\Component\Console\Style\OutputStyle;
23 use Symfony\Component\Console\Terminal;
24 
29 class MagentoStyle extends OutputStyle implements MagentoStyleInterface
30 {
34  const MAX_LINE_LENGTH = 120;
35 
41  private $input;
42 
48  private $questionHelper;
49 
55  private $progressBar;
56 
62  private $lineLength;
63 
69  private $bufferedOutput;
70 
77  public function __construct(InputInterface $input, OutputInterface $output)
78  {
79  $this->input = $input;
80  $this->bufferedOutput = new BufferedOutput($output->getVerbosity(), false, clone $output->getFormatter());
81  // Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not.
82  $currentLength = $this->getTerminalWidth() - (int)(DIRECTORY_SEPARATOR === '\\');
83  $this->lineLength = min($currentLength, self::MAX_LINE_LENGTH);
84  parent::__construct($output);
85  }
86 
97  public function block(
98  $messages,
99  string $type = null,
100  string $style = null,
101  string $prefix = ' ',
102  bool $padding = false
103  ) {
104  $messages = is_array($messages) ? array_values($messages) : [$messages];
105  $this->autoPrependBlock();
106  $this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding));
107  $this->newLine();
108  }
109 
113  public function title($message)
114  {
115  $this->autoPrependBlock();
116  $bar = str_repeat('=', Helper::strlenWithoutDecoration($this->getFormatter(), $message));
117  $this->writeln([
118  sprintf(' <options=bold>%s</>', OutputFormatter::escapeTrailingBackslash($message)),
119  sprintf(' <options=bold>%s</>', $bar),
120  ]);
121  $this->newLine();
122  }
123 
127  public function section($message)
128  {
129  $this->autoPrependBlock();
130  $bar = str_repeat('-', Helper::strlenWithoutDecoration($this->getFormatter(), $message));
131  $this->writeln([
132  sprintf(' <fg=white>%s</>', OutputFormatter::escapeTrailingBackslash($message)),
133  sprintf(' <fg=white>%s</>', $bar),
134  ]);
135  $this->newLine();
136  }
137 
141  public function listing(array $elements)
142  {
143  $this->autoPrependText();
144  $elements = array_map(function ($element) {
145  return sprintf(' * %s', $element);
146  }, $elements);
147 
148  $this->writeln($elements);
149  $this->newLine();
150  }
151 
155  public function text($message)
156  {
157  $this->autoPrependText();
158  $messages = is_array($message) ? array_values($message) : [$message];
159  foreach ($messages as $singleMessage) {
160  $this->writeln(sprintf(' %s', $singleMessage));
161  }
162  }
163 
171  public function comment($message, $padding = false)
172  {
173  $this->block($message, null, 'comment', ' ', $padding);
174  }
175 
179  public function success($message, $padding = true)
180  {
181  $this->block($message, 'SUCCESS', 'fg=black;bg=green', ' ', $padding);
182  }
183 
187  public function error($message, $padding = true)
188  {
189  $this->block($message, 'ERROR', 'fg=white;bg=red', ' ', $padding);
190  }
191 
195  public function warning($message, $padding = true)
196  {
197  $this->block($message, 'WARNING', 'fg=black;bg=yellow', ' ', $padding);
198  }
199 
203  public function note($message, $padding = false)
204  {
205  $this->block($message, 'NOTE', 'fg=yellow', ' ', $padding);
206  }
207 
211  public function caution($message, $padding = true)
212  {
213  $this->block($message, 'CAUTION', 'fg=black;bg=yellow', ' ! ', $padding);
214  }
215 
219  public function table(array $headers, array $rows)
220  {
221  $style = clone Table::getStyleDefinition('symfony-style-guide');
222  $style->setCellHeaderFormat('<info>%s</info>');
223 
224  $table = new Table($this);
225  $table->setHeaders($headers);
226  $table->setRows($rows);
227  $table->setStyle($style);
228 
229  $table->render();
230  $this->newLine();
231  }
232 
237  public function ask($question, $default = null, $validator = null, $maxAttempts = null)
238  {
239  $question = new Question($question, $default);
240  $question->setValidator($validator);
241  $question->setMaxAttempts($maxAttempts);
242 
243  return $this->askQuestion($question);
244  }
245 
250  public function askHidden($question, $validator = null)
251  {
252  $question = new Question($question);
253 
254  $question->setHidden(true);
255  $question->setValidator($validator);
256 
257  return $this->askQuestion($question);
258  }
259 
263  public function confirm($question, $default = true)
264  {
265  return $this->askQuestion(new ConfirmationQuestion($question, $default));
266  }
267 
271  public function choice($question, array $choices, $default = null)
272  {
273  if (null !== $default) {
274  $values = array_flip($choices);
275  $default = $values[$default];
276  }
277 
278  return $this->askQuestion(new ChoiceQuestion($question, $choices, $default));
279  }
280 
284  public function progressStart($max = 0)
285  {
286  $this->progressBar = $this->createProgressBar($max);
287  $this->progressBar->start();
288  }
289 
295  public function progressAdvance($step = 1)
296  {
297  $this->getProgressBar()->advance($step);
298  }
299 
304  public function progressFinish()
305  {
306  $this->getProgressBar()->finish();
307  $this->newLine(2);
308  $this->progressBar = null;
309  }
310 
314  public function createProgressBar($max = 0)
315  {
316  $progressBar = parent::createProgressBar($max);
317  $progressBar->setEmptyBarCharacter(' ');
318  $progressBar->setProgressCharacter('>');
319  $progressBar->setBarCharacter('=');
320 
321  return $progressBar;
322  }
323 
331  public function askQuestion(Question $question)
332  {
333  if ($this->input->isInteractive()) {
334  $this->autoPrependBlock();
335  }
336 
337  if (!$this->questionHelper) {
338  $this->questionHelper = new SymfonyQuestionHelper();
339  }
340 
341  $answer = $this->questionHelper->ask($this->input, $this, $question);
342 
343  if ($this->input->isInteractive()) {
344  $this->newLine();
345  $this->bufferedOutput->write(PHP_EOL);
346  }
347 
348  return $answer;
349  }
350 
363  public function askForMissingArgument(
364  string $argument,
365  string $question,
366  string $default = null,
367  callable $validator = null,
368  int $maxAttempts = null,
369  bool $comment = null,
370  string $commentFormat = 'Argument [%s] set to: %s'
371  ) {
372  try {
373  if ($this->input->getArgument($argument) === null) {
374  $this->input->setArgument($argument, $this->ask($question, $default, $validator, $maxAttempts));
375  }
376  $argumentValue = $this->input->getArgument($argument);
377  $validated = (is_callable($validator) ? $validator($argumentValue) : $argumentValue);
378  if ((bool)($comment ?? $this->isDebug())) {
379  $this->comment(sprintf($commentFormat, $argument, $validated));
380  }
381  } catch (InputValidationException $e) {
382  $this->error('Validation Error: ' . $e->getMessage());
383  $this->askForMissingArgument(
384  $argument,
385  $question,
386  $default,
387  $validator,
388  $maxAttempts,
389  $comment,
390  $commentFormat
391  );
392  }
393  }
394 
407  public function askForMissingOption(
408  string $option,
409  string $question,
410  string $default = null,
411  callable $validator = null,
412  int $maxAttempts = null,
413  bool $comment = null,
414  string $commentFormat = 'Option [%s] set to: %s'
415  ) {
416  try {
417  if (null === $this->input->getOption($option)) {
418  $this->input->setOption($option, $this->ask($question, $default, $validator, $maxAttempts));
419  }
420  $optionValue = $this->input->getOption($option);
421  $validated = (is_callable($validator) ? $validator($optionValue) : $optionValue);
422  if ((bool)($comment ?? $this->isDebug())) {
423  $this->comment(sprintf($commentFormat, $option, $validated));
424  }
425  } catch (InputValidationException $e) {
426  $this->error('Validation Error: ' . $e->getMessage());
427  $this->askForMissingOption(
428  $option,
429  $question,
430  $default,
431  $validator,
432  $maxAttempts,
433  $comment,
434  $commentFormat
435  );
436  }
437  }
438 
442  public function writeln($messages, $type = self::OUTPUT_NORMAL)
443  {
444  parent::writeln($messages, $type);
445  $this->bufferedOutput->writeln($this->reduceBuffer($messages), $type);
446  }
447 
451  public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL)
452  {
453  parent::write($messages, $newline, $type);
454  $this->bufferedOutput->write($this->reduceBuffer($messages), $newline, $type);
455  }
456 
460  public function newLine($count = 1)
461  {
462  parent::newLine($count);
463  $this->bufferedOutput->write(str_repeat(PHP_EOL, $count));
464  }
465 
472  private function getProgressBar()
473  {
474  if (!$this->progressBar) {
475  throw new RuntimeException('The ProgressBar is not started.');
476  }
477 
478  return $this->progressBar;
479  }
480 
484  private function getTerminalWidth()
485  {
486  $terminal = new Terminal();
487  $width = $terminal->getWidth();
488 
489  return $width ?: self::MAX_LINE_LENGTH;
490  }
491 
497  private function autoPrependBlock()
498  {
499  $chars = substr($this->bufferedOutput->fetch(), -2);
500  if (!isset($chars[0])) {
501  $this->newLine(); //empty history, so we should start with a new line.
502  }
503  //Prepend new line for each non LF chars (This means no blank line was output before)
504  $this->newLine(2 - substr_count($chars, PHP_EOL));
505  }
506 
512  private function autoPrependText()
513  {
514  $fetched = $this->bufferedOutput->fetch();
515  //Prepend new line if last char isn't EOL:
516  if (PHP_EOL !== substr($fetched, -1)) {
517  $this->newLine();
518  }
519  }
520 
525  private function reduceBuffer($messages)
526  {
527  // We need to know if the two last chars are PHP_EOL
528  // Preserve the last 4 chars inserted (PHP_EOL on windows is two chars) in the history buffer
529  return array_map(function ($value) {
530  return substr($value, -4);
531  }, array_merge([$this->bufferedOutput->fetch()], (array)$messages));
532  }
533 
544  private function createBlock(
545  array $messages,
546  string $type = null,
547  string $style = null,
548  string $prefix = ' ',
549  bool $padding = false
550  ) {
551  $indentLength = 0;
552  $prefixLength = Helper::strlenWithoutDecoration($this->getFormatter(), $prefix);
553  if (null !== $type) {
554  $type = sprintf('[%s] ', $type);
555  $indentLength = strlen($type);
556  $lineIndentation = str_repeat(' ', $indentLength);
557  }
558  $lines = $this->getBlockLines($messages, $prefixLength, $indentLength);
559  $firstLineIndex = 0;
560  if ($padding && $this->isDecorated()) {
561  $firstLineIndex = 1;
562  array_unshift($lines, '');
563  $lines[] = '';
564  }
565  foreach ($lines as $i => &$line) {
566  if (null !== $type) {
567  $line = $firstLineIndex === $i ? $type . $line : $lineIndentation . $line;
568  }
569  $line = $prefix . $line;
570  $multiplier = $this->lineLength - Helper::strlenWithoutDecoration($this->getFormatter(), $line);
571  $line .= str_repeat(' ', $multiplier);
572  if ($style) {
573  $line = sprintf('<%s>%s</>', $style, $line);
574  }
575  }
576 
577  return $lines;
578  }
579 
588  private function getBlockLines(
589  array $messages,
590  int $prefixLength,
591  int $indentLength
592  ) {
593  $lines = [[]];
594  foreach ($messages as $key => $message) {
595  $message = OutputFormatter::escape($message);
596  $wordwrap = wordwrap($message, $this->lineLength - $prefixLength - $indentLength, PHP_EOL, true);
597  $lines[] = explode(PHP_EOL, $wordwrap);
598  if (count($messages) > 1 && $key < count($messages) - 1) {
599  $lines[][] = '';
600  }
601  }
602  $lines = array_merge(...$lines);
603 
604  return $lines;
605  }
606 }
__construct(InputInterface $input, OutputInterface $output)
askForMissingOption(string $option, string $question, string $default=null, callable $validator=null, int $maxAttempts=null, bool $comment=null, string $commentFormat='Option [%s] set to:%s')
askForMissingArgument(string $argument, string $question, string $default=null, callable $validator=null, int $maxAttempts=null, bool $comment=null, string $commentFormat='Argument [%s] set to:%s')
write($messages, $newline=false, $type=self::OUTPUT_NORMAL)
$count
Definition: recent.phtml:13
$values
Definition: options.phtml:88
writeln($messages, $type=self::OUTPUT_NORMAL)
$message
ask($question, $default=null, $validator=null, $maxAttempts=null)
$type
Definition: item.phtml:13
$prefix
Definition: name.phtml:25
$value
Definition: gender.phtml:16
askHidden($question, $validator=null)
block( $messages, string $type=null, string $style=null, string $prefix=' ', bool $padding=false)
choice($question, array $choices, $default=null)
$table
Definition: trigger.php:14
table(array $headers, array $rows)
$i
Definition: gallery.phtml:31
$element
Definition: element.phtml:12