TYPO3  7.6
ProgressBar.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the Symfony package.
5  *
6  * (c) Fabien Potencier <fabien@symfony.com>
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11 
12 namespace Symfony\Component\Console\Helper;
13 
15 
23 {
24  // options
25  private $barWidth = 28;
26  private $barChar;
27  private $emptyBarChar = '-';
28  private $progressChar = '>';
29  private $format = null;
30  private $redrawFreq = 1;
31 
35  private $output;
36  private $step = 0;
37  private $max;
38  private $startTime;
39  private $stepWidth;
40  private $percent = 0.0;
41  private $lastMessagesLength = 0;
43  private $messages;
44  private $overwrite = true;
45 
46  private static $formatters;
47  private static $formats;
48 
55  public function __construct(OutputInterface $output, $max = 0)
56  {
57  $this->output = $output;
58  $this->setMaxSteps($max);
59 
60  if (!$this->output->isDecorated()) {
61  // disable overwrite when output does not support ANSI codes.
62  $this->overwrite = false;
63 
64  if ($this->max > 10) {
65  // set a reasonable redraw frequency so output isn't flooded
66  $this->setRedrawFrequency($max / 10);
67  }
68  }
69 
70  $this->setFormat($this->determineBestFormat());
71 
72  $this->startTime = time();
73  }
74 
83  public static function setPlaceholderFormatterDefinition($name, $callable)
84  {
85  if (!self::$formatters) {
86  self::$formatters = self::initPlaceholderFormatters();
87  }
88 
89  self::$formatters[$name] = $callable;
90  }
91 
99  public static function getPlaceholderFormatterDefinition($name)
100  {
101  if (!self::$formatters) {
102  self::$formatters = self::initPlaceholderFormatters();
103  }
104 
105  return isset(self::$formatters[$name]) ? self::$formatters[$name] : null;
106  }
107 
116  public static function setFormatDefinition($name, $format)
117  {
118  if (!self::$formats) {
119  self::$formats = self::initFormats();
120  }
121 
122  self::$formats[$name] = $format;
123  }
124 
132  public static function getFormatDefinition($name)
133  {
134  if (!self::$formats) {
135  self::$formats = self::initFormats();
136  }
137 
138  return isset(self::$formats[$name]) ? self::$formats[$name] : null;
139  }
140 
141  public function setMessage($message, $name = 'message')
142  {
143  $this->messages[$name] = $message;
144  }
145 
146  public function getMessage($name = 'message')
147  {
148  return $this->messages[$name];
149  }
150 
156  public function getStartTime()
157  {
158  return $this->startTime;
159  }
160 
166  public function getMaxSteps()
167  {
168  return $this->max;
169  }
170 
178  public function getStep()
179  {
180  @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the getProgress() method instead.', E_USER_DEPRECATED);
181 
182  return $this->getProgress();
183  }
184 
190  public function getProgress()
191  {
192  return $this->step;
193  }
194 
202  public function getStepWidth()
203  {
204  return $this->stepWidth;
205  }
206 
212  public function getProgressPercent()
213  {
214  return $this->percent;
215  }
216 
222  public function setBarWidth($size)
223  {
224  $this->barWidth = (int) $size;
225  }
226 
232  public function getBarWidth()
233  {
234  return $this->barWidth;
235  }
236 
242  public function setBarCharacter($char)
243  {
244  $this->barChar = $char;
245  }
246 
252  public function getBarCharacter()
253  {
254  if (null === $this->barChar) {
255  return $this->max ? '=' : $this->emptyBarChar;
256  }
257 
258  return $this->barChar;
259  }
260 
266  public function setEmptyBarCharacter($char)
267  {
268  $this->emptyBarChar = $char;
269  }
270 
276  public function getEmptyBarCharacter()
277  {
278  return $this->emptyBarChar;
279  }
280 
286  public function setProgressCharacter($char)
287  {
288  $this->progressChar = $char;
289  }
290 
296  public function getProgressCharacter()
297  {
298  return $this->progressChar;
299  }
300 
306  public function setFormat($format)
307  {
308  // try to use the _nomax variant if available
309  if (!$this->max && null !== self::getFormatDefinition($format.'_nomax')) {
310  $this->format = self::getFormatDefinition($format.'_nomax');
311  } elseif (null !== self::getFormatDefinition($format)) {
312  $this->format = self::getFormatDefinition($format);
313  } else {
314  $this->format = $format;
315  }
316 
317  $this->formatLineCount = substr_count($this->format, "\n");
318  }
319 
325  public function setRedrawFrequency($freq)
326  {
327  $this->redrawFreq = (int) $freq;
328  }
329 
335  public function start($max = null)
336  {
337  $this->startTime = time();
338  $this->step = 0;
339  $this->percent = 0.0;
340 
341  if (null !== $max) {
342  $this->setMaxSteps($max);
343  }
344 
345  $this->display();
346  }
347 
355  public function advance($step = 1)
356  {
357  $this->setProgress($this->step + $step);
358  }
359 
369  public function setCurrent($step)
370  {
371  @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the setProgress() method instead.', E_USER_DEPRECATED);
372 
373  $this->setProgress($step);
374  }
375 
381  public function setOverwrite($overwrite)
382  {
383  $this->overwrite = (bool) $overwrite;
384  }
385 
393  public function setProgress($step)
394  {
395  $step = (int) $step;
396  if ($step < $this->step) {
397  throw new \LogicException('You can\'t regress the progress bar.');
398  }
399 
400  if ($this->max && $step > $this->max) {
401  $this->max = $step;
402  }
403 
404  $prevPeriod = (int) ($this->step / $this->redrawFreq);
405  $currPeriod = (int) ($step / $this->redrawFreq);
406  $this->step = $step;
407  $this->percent = $this->max ? (float) $this->step / $this->max : 0;
408  if ($prevPeriod !== $currPeriod || $this->max === $step) {
409  $this->display();
410  }
411  }
412 
416  public function finish()
417  {
418  if (!$this->max) {
419  $this->max = $this->step;
420  }
421 
422  if ($this->step === $this->max && !$this->overwrite) {
423  // prevent double 100% output
424  return;
425  }
426 
427  $this->setProgress($this->max);
428  }
429 
433  public function display()
434  {
435  if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
436  return;
437  }
438 
439  // these 3 variables can be removed in favor of using $this in the closure when support for PHP 5.3 will be dropped.
440  $self = $this;
443  $this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) use ($self, $output, $messages) {
444  if ($formatter = $self::getPlaceholderFormatterDefinition($matches[1])) {
445  $text = call_user_func($formatter, $self, $output);
446  } elseif (isset($messages[$matches[1]])) {
447  $text = $messages[$matches[1]];
448  } else {
449  return $matches[0];
450  }
451 
452  if (isset($matches[2])) {
453  $text = sprintf('%'.$matches[2], $text);
454  }
455 
456  return $text;
457  }, $this->format));
458  }
459 
467  public function clear()
468  {
469  if (!$this->overwrite) {
470  return;
471  }
472 
473  $this->overwrite(str_repeat("\n", $this->formatLineCount));
474  }
475 
481  private function setMaxSteps($max)
482  {
483  $this->max = max(0, (int) $max);
484  $this->stepWidth = $this->max ? Helper::strlen($this->max) : 4;
485  }
486 
492  private function overwrite($message)
493  {
494  $lines = explode("\n", $message);
495 
496  // append whitespace to match the line's length
497  if (null !== $this->lastMessagesLength) {
498  foreach ($lines as $i => $line) {
499  if ($this->lastMessagesLength > Helper::strlenWithoutDecoration($this->output->getFormatter(), $line)) {
500  $lines[$i] = str_pad($line, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT);
501  }
502  }
503  }
504 
505  if ($this->overwrite) {
506  // move back to the beginning of the progress bar before redrawing it
507  $this->output->write("\x0D");
508  } elseif ($this->step > 0) {
509  // move to new line
510  $this->output->writeln('');
511  }
512 
513  if ($this->formatLineCount) {
514  $this->output->write(sprintf("\033[%dA", $this->formatLineCount));
515  }
516  $this->output->write(implode("\n", $lines));
517 
518  $this->lastMessagesLength = 0;
519  foreach ($lines as $line) {
520  $len = Helper::strlenWithoutDecoration($this->output->getFormatter(), $line);
521  if ($len > $this->lastMessagesLength) {
522  $this->lastMessagesLength = $len;
523  }
524  }
525  }
526 
527  private function determineBestFormat()
528  {
529  switch ($this->output->getVerbosity()) {
530  // OutputInterface::VERBOSITY_QUIET: display is disabled anyway
532  return $this->max ? 'verbose' : 'verbose_nomax';
534  return $this->max ? 'very_verbose' : 'very_verbose_nomax';
536  return $this->max ? 'debug' : 'debug_nomax';
537  default:
538  return $this->max ? 'normal' : 'normal_nomax';
539  }
540  }
541 
542  private static function initPlaceholderFormatters()
543  {
544  return array(
545  'bar' => function (ProgressBar $bar, OutputInterface $output) {
546  $completeBars = floor($bar->getMaxSteps() > 0 ? $bar->getProgressPercent() * $bar->getBarWidth() : $bar->getProgress() % $bar->getBarWidth());
547  $display = str_repeat($bar->getBarCharacter(), $completeBars);
548  if ($completeBars < $bar->getBarWidth()) {
549  $emptyBars = $bar->getBarWidth() - $completeBars - Helper::strlenWithoutDecoration($output->getFormatter(), $bar->getProgressCharacter());
550  $display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars);
551  }
552 
553  return $display;
554  },
555  'elapsed' => function (ProgressBar $bar) {
556  return Helper::formatTime(time() - $bar->getStartTime());
557  },
558  'remaining' => function (ProgressBar $bar) {
559  if (!$bar->getMaxSteps()) {
560  throw new \LogicException('Unable to display the remaining time if the maximum number of steps is not set.');
561  }
562 
563  if (!$bar->getProgress()) {
564  $remaining = 0;
565  } else {
566  $remaining = round((time() - $bar->getStartTime()) / $bar->getProgress() * ($bar->getMaxSteps() - $bar->getProgress()));
567  }
568 
569  return Helper::formatTime($remaining);
570  },
571  'estimated' => function (ProgressBar $bar) {
572  if (!$bar->getMaxSteps()) {
573  throw new \LogicException('Unable to display the estimated time if the maximum number of steps is not set.');
574  }
575 
576  if (!$bar->getProgress()) {
577  $estimated = 0;
578  } else {
579  $estimated = round((time() - $bar->getStartTime()) / $bar->getProgress() * $bar->getMaxSteps());
580  }
581 
582  return Helper::formatTime($estimated);
583  },
584  'memory' => function (ProgressBar $bar) {
585  return Helper::formatMemory(memory_get_usage(true));
586  },
587  'current' => function (ProgressBar $bar) {
588  return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', STR_PAD_LEFT);
589  },
590  'max' => function (ProgressBar $bar) {
591  return $bar->getMaxSteps();
592  },
593  'percent' => function (ProgressBar $bar) {
594  return floor($bar->getProgressPercent() * 100);
595  },
596  );
597  }
598 
599  private static function initFormats()
600  {
601  return array(
602  'normal' => ' %current%/%max% [%bar%] %percent:3s%%',
603  'normal_nomax' => ' %current% [%bar%]',
604 
605  'verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%',
606  'verbose_nomax' => ' %current% [%bar%] %elapsed:6s%',
607 
608  'very_verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%',
609  'very_verbose_nomax' => ' %current% [%bar%] %elapsed:6s%',
610 
611  'debug' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%',
612  'debug_nomax' => ' %current% [%bar%] %elapsed:6s% %memory:6s%',
613  );
614  }
615 }