4 * This file is part of the Symfony package.
6 * (c) Fabien Potencier <fabien@symfony.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Symfony\Component\VarDumper\Dumper;
14 use Symfony\Component\VarDumper\Cloner\Cursor;
17 * CliDumper dumps variables for command line output.
19 * @author Nicolas Grekas <p@tchwork.com>
21 class CliDumper extends AbstractDumper
23 public static $defaultColors;
24 public static $defaultOutput = 'php://stdout';
27 protected $maxStringWidth = 0;
28 protected $styles = array(
29 // See http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
30 'default' => '38;5;208',
32 'const' => '1;38;5;208',
33 'str' => '1;38;5;113',
44 protected static $controlCharsRx = '/[\x00-\x1F\x7F]+/';
45 protected static $controlCharsMap = array(
57 public function __construct($output = null, $charset = null)
59 parent::__construct($output, $charset);
61 if ('\\' === DIRECTORY_SEPARATOR && 'ON' !== @getenv('ConEmuANSI') && 'xterm' !== @getenv('TERM')) {
62 // Use only the base 16 xterm colors when using ANSICON or standard Windows 10 CLI
63 $this->setStyles(array(
78 * Enables/disables colored output.
82 public function setColors($colors)
84 $this->colors = (bool) $colors;
88 * Sets the maximum number of characters per line for dumped strings.
90 * @param int $maxStringWidth
92 public function setMaxStringWidth($maxStringWidth)
94 $this->maxStringWidth = (int) $maxStringWidth;
100 * @param array $styles A map of style names to style definitions
102 public function setStyles(array $styles)
104 $this->styles = $styles + $this->styles;
110 public function dumpScalar(Cursor $cursor, $type, $value)
112 $this->dumpKey($cursor);
126 case INF === $value: $value = 'INF'; break;
127 case -INF === $value: $value = '-INF'; break;
128 case is_nan($value): $value = 'NAN'; break;
130 $value = (string) $value;
131 if (false === strpos($value, $this->decimalPoint)) {
132 $value .= $this->decimalPoint.'0';
143 $value = $value ? 'true' : 'false';
147 $attr['value'] = isset($value[0]) && !preg_match('//u', $value) ? $this->utf8Encode($value) : $value;
148 $value = isset($type[0]) && !preg_match('//u', $type) ? $this->utf8Encode($type) : $type;
152 $this->line .= $this->style($style, $value, $attr);
154 $this->dumpLine($cursor->depth, true);
160 public function dumpString(Cursor $cursor, $str, $bin, $cut)
162 $this->dumpKey($cursor);
165 $str = $this->utf8Encode($str);
169 $this->dumpLine($cursor->depth, true);
172 'length' => 0 <= $cut ? mb_strlen($str, 'UTF-8') + $cut : 0,
175 $str = explode("\n", $str);
176 if (isset($str[1]) && !isset($str[2]) && !isset($str[1][0])) {
180 $m = count($str) - 1;
188 $this->line .= '"""';
189 $this->dumpLine($cursor->depth);
194 foreach ($str as $str) {
198 if (0 < $this->maxStringWidth && $this->maxStringWidth < $len = mb_strlen($str, 'UTF-8')) {
199 $str = mb_substr($str, 0, $this->maxStringWidth, 'UTF-8');
200 $lineCut = $len - $this->maxStringWidth;
202 if ($m && 0 < $cursor->depth) {
203 $this->line .= $this->indentPad;
206 $this->line .= $this->style('str', $str, $attr);
211 $this->dumpLine($cursor->depth);
212 if (0 < $cursor->depth) {
213 $this->line .= $this->indentPad;
216 $this->line .= '"""';
228 $this->line .= '…'.$lineCut;
232 $this->dumpLine($cursor->depth, $i > $m);
240 public function enterHash(Cursor $cursor, $type, $class, $hasChild)
242 $this->dumpKey($cursor);
244 if (!preg_match('//u', $class)) {
245 $class = $this->utf8Encode($class);
247 if (Cursor::HASH_OBJECT === $type) {
248 $prefix = $class && 'stdClass' !== $class ? $this->style('note', $class).' {' : '{';
249 } elseif (Cursor::HASH_RESOURCE === $type) {
250 $prefix = $this->style('note', $class.' resource').($hasChild ? ' {' : ' ');
252 $prefix = $class ? $this->style('note', 'array:'.$class).' [' : '[';
255 if ($cursor->softRefCount || 0 < $cursor->softRefHandle) {
256 $prefix .= $this->style('ref', (Cursor::HASH_RESOURCE === $type ? '@' : '#').(0 < $cursor->softRefHandle ? $cursor->softRefHandle : $cursor->softRefTo), array('count' => $cursor->softRefCount));
257 } elseif ($cursor->hardRefTo && !$cursor->refIndex && $class) {
258 $prefix .= $this->style('ref', '&'.$cursor->hardRefTo, array('count' => $cursor->hardRefCount));
259 } elseif (!$hasChild && Cursor::HASH_RESOURCE === $type) {
260 $prefix = substr($prefix, 0, -1);
263 $this->line .= $prefix;
266 $this->dumpLine($cursor->depth);
273 public function leaveHash(Cursor $cursor, $type, $class, $hasChild, $cut)
275 $this->dumpEllipsis($cursor, $hasChild, $cut);
276 $this->line .= Cursor::HASH_OBJECT === $type ? '}' : (Cursor::HASH_RESOURCE !== $type ? ']' : ($hasChild ? '}' : ''));
277 $this->dumpLine($cursor->depth, true);
281 * Dumps an ellipsis for cut children.
283 * @param Cursor $cursor The Cursor position in the dump
284 * @param bool $hasChild When the dump of the hash has child item
285 * @param int $cut The number of items the hash has been cut by
287 protected function dumpEllipsis(Cursor $cursor, $hasChild, $cut)
295 $this->dumpLine($cursor->depth + 1);
301 * Dumps a key in a hash structure.
303 * @param Cursor $cursor The Cursor position in the dump
305 protected function dumpKey(Cursor $cursor)
307 if (null !== $key = $cursor->hashKey) {
308 if ($cursor->hashKeyIsBinary) {
309 $key = $this->utf8Encode($key);
311 $attr = array('binary' => $cursor->hashKeyIsBinary);
312 $bin = $cursor->hashKeyIsBinary ? 'b' : '';
314 switch ($cursor->hashType) {
316 case Cursor::HASH_INDEXED:
318 case Cursor::HASH_ASSOC:
320 $this->line .= $this->style($style, $key).' => ';
322 $this->line .= $bin.'"'.$this->style($style, $key).'" => ';
326 case Cursor::HASH_RESOURCE:
329 case Cursor::HASH_OBJECT:
330 if (!isset($key[0]) || "\0" !== $key[0]) {
331 $this->line .= '+'.$bin.$this->style('public', $key).': ';
332 } elseif (0 < strpos($key, "\0", 1)) {
333 $key = explode("\0", substr($key, 1), 2);
336 case '+': // User inserted keys
337 $attr['dynamic'] = true;
338 $this->line .= '+'.$bin.'"'.$this->style('public', $key[1], $attr).'": ';
344 $style = 'protected';
348 $attr['class'] = $key[0];
354 $this->line .= $bin.$this->style($style, $key[1], $attr).': ';
356 // This case should not happen
357 $this->line .= '-'.$bin.'"'.$this->style('private', $key, array('class' => '')).'": ';
362 if ($cursor->hardRefTo) {
363 $this->line .= $this->style('ref', '&'.($cursor->hardRefCount ? $cursor->hardRefTo : ''), array('count' => $cursor->hardRefCount)).' ';
369 * Decorates a value with some style.
371 * @param string $style The type of style being applied
372 * @param string $value The value being styled
373 * @param array $attr Optional context information
375 * @return string The value with style decoration
377 protected function style($style, $value, $attr = array())
379 if (null === $this->colors) {
380 $this->colors = $this->supportsColors();
383 $style = $this->styles[$style];
385 $map = static::$controlCharsMap;
386 $startCchr = $this->colors ? "\033[m\033[{$this->styles['default']}m" : '';
387 $endCchr = $this->colors ? "\033[m\033[{$style}m" : '';
388 $value = preg_replace_callback(static::$controlCharsRx, function ($c) use ($map, $startCchr, $endCchr) {
392 $s .= isset($map[$c[$i]]) ? $map[$c[$i]] : sprintf('\x%02X', ord($c[$i]));
393 } while (isset($c[++$i]));
396 }, $value, -1, $cchrCount);
399 if ($cchrCount && "\033" === $value[0]) {
400 $value = substr($value, strlen($startCchr));
402 $value = "\033[{$style}m".$value;
404 if ($cchrCount && $endCchr === substr($value, -strlen($endCchr))) {
405 $value = substr($value, 0, -strlen($endCchr));
407 $value .= "\033[{$this->styles['default']}m";
415 * @return bool Tells if the current output stream supports ANSI colors or not
417 protected function supportsColors()
419 if ($this->outputStream !== static::$defaultOutput) {
420 return @(is_resource($this->outputStream) && function_exists('posix_isatty') && posix_isatty($this->outputStream));
422 if (null !== static::$defaultColors) {
423 return static::$defaultColors;
425 if (isset($_SERVER['argv'][1])) {
426 $colors = $_SERVER['argv'];
429 if (isset($colors[$i][5])) {
430 switch ($colors[$i]) {
434 case '--color=force':
435 case '--color=always':
436 return static::$defaultColors = true;
441 case '--color=never':
442 return static::$defaultColors = false;
448 if ('\\' === DIRECTORY_SEPARATOR) {
449 static::$defaultColors = @(
450 '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR.'.'.PHP_WINDOWS_VERSION_MINOR.'.'.PHP_WINDOWS_VERSION_BUILD
451 || false !== getenv('ANSICON')
452 || 'ON' === getenv('ConEmuANSI')
453 || 'xterm' === getenv('TERM')
455 } elseif (function_exists('posix_isatty')) {
456 $h = stream_get_meta_data($this->outputStream) + array('wrapper_type' => null);
457 $h = 'Output' === $h['stream_type'] && 'PHP' === $h['wrapper_type'] ? fopen('php://stdout', 'wb') : $this->outputStream;
458 static::$defaultColors = @posix_isatty($h);
460 static::$defaultColors = false;
463 return static::$defaultColors;
469 protected function dumpLine($depth, $endOfValue = false)
472 $this->line = sprintf("\033[%sm%s\033[m", $this->styles['default'], $this->line);
474 parent::dumpLine($depth);