Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace Symfony\Component\VarDumper\Dumper; Chris@0: Chris@0: use Symfony\Component\VarDumper\Cloner\Cursor; Chris@0: use Symfony\Component\VarDumper\Cloner\Data; Chris@0: Chris@0: /** Chris@0: * HtmlDumper dumps variables as HTML. Chris@0: * Chris@0: * @author Nicolas Grekas
Chris@0: */ Chris@0: class HtmlDumper extends CliDumper Chris@0: { Chris@0: public static $defaultOutput = 'php://output'; Chris@0: Chris@0: protected $dumpHeader; Chris@0: protected $dumpPrefix = '
'; Chris@0: protected $dumpSuffix = ''; Chris@0: protected $dumpId = 'sf-dump'; Chris@0: protected $colors = true; Chris@0: protected $headerIsDumped = false; Chris@0: protected $lastDepth = -1; Chris@17: protected $styles = [ Chris@0: 'default' => 'background-color:#18171B; color:#FF8400; line-height:1.2em; font:12px Menlo, Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: break-all', Chris@0: 'num' => 'font-weight:bold; color:#1299DA', Chris@0: 'const' => 'font-weight:bold', Chris@0: 'str' => 'font-weight:bold; color:#56DB3A', Chris@0: 'note' => 'color:#1299DA', Chris@0: 'ref' => 'color:#A0A0A0', Chris@0: 'public' => 'color:#FFFFFF', Chris@0: 'protected' => 'color:#FFFFFF', Chris@0: 'private' => 'color:#FFFFFF', Chris@0: 'meta' => 'color:#B729D9', Chris@0: 'key' => 'color:#56DB3A', Chris@0: 'index' => 'color:#1299DA', Chris@0: 'ellipsis' => 'color:#FF8400', Chris@17: ]; Chris@0: Chris@17: private $displayOptions = [ Chris@0: 'maxDepth' => 1, Chris@0: 'maxStringLength' => 160, Chris@0: 'fileLinkFormat' => null, Chris@17: ]; Chris@17: private $extraDisplayOptions = []; Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function __construct($output = null, $charset = null, $flags = 0) Chris@0: { Chris@0: AbstractDumper::__construct($output, $charset, $flags); Chris@0: $this->dumpId = 'sf-dump-'.mt_rand(); Chris@0: $this->displayOptions['fileLinkFormat'] = ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setStyles(array $styles) Chris@0: { Chris@0: $this->headerIsDumped = false; Chris@0: $this->styles = $styles + $this->styles; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Configures display options. Chris@0: * Chris@0: * @param array $displayOptions A map of display options to customize the behavior Chris@0: */ Chris@0: public function setDisplayOptions(array $displayOptions) Chris@0: { Chris@0: $this->headerIsDumped = false; Chris@0: $this->displayOptions = $displayOptions + $this->displayOptions; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets an HTML header that will be dumped once in the output stream. Chris@0: * Chris@0: * @param string $header An HTML string Chris@0: */ Chris@0: public function setDumpHeader($header) Chris@0: { Chris@0: $this->dumpHeader = $header; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets an HTML prefix and suffix that will encapse every single dump. Chris@0: * Chris@0: * @param string $prefix The prepended HTML string Chris@0: * @param string $suffix The appended HTML string Chris@0: */ Chris@0: public function setDumpBoundaries($prefix, $suffix) Chris@0: { Chris@0: $this->dumpPrefix = $prefix; Chris@0: $this->dumpSuffix = $suffix; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@17: public function dump(Data $data, $output = null, array $extraDisplayOptions = []) Chris@0: { Chris@0: $this->extraDisplayOptions = $extraDisplayOptions; Chris@0: $result = parent::dump($data, $output); Chris@0: $this->dumpId = 'sf-dump-'.mt_rand(); Chris@0: Chris@0: return $result; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Dumps the HTML header. Chris@0: */ Chris@0: protected function getDumpHeader() Chris@0: { Chris@0: $this->headerIsDumped = null !== $this->outputStream ? $this->outputStream : $this->lineDumper; Chris@0: Chris@0: if (null !== $this->dumpHeader) { Chris@0: return $this->dumpHeader; Chris@0: } Chris@0: Chris@0: $line = str_replace('{$options}', json_encode($this->displayOptions, JSON_FORCE_OBJECT), <<<'EOHTML' Chris@0: '.$this->dumpHeader; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function enterHash(Cursor $cursor, $type, $class, $hasChild) Chris@0: { Chris@0: parent::enterHash($cursor, $type, $class, false); Chris@0: Chris@12: if ($cursor->skipChildren) { Chris@12: $cursor->skipChildren = false; Chris@12: $eol = ' class=sf-dump-compact>'; Chris@12: } elseif ($this->expandNextHash) { Chris@12: $this->expandNextHash = false; Chris@12: $eol = ' class=sf-dump-expanded>'; Chris@12: } else { Chris@12: $eol = '>'; Chris@12: } Chris@12: Chris@0: if ($hasChild) { Chris@12: $this->line .= 'refIndex) { Chris@0: $r = Cursor::HASH_OBJECT !== $type ? 1 - (Cursor::HASH_RESOURCE !== $type) : 2; Chris@0: $r .= $r && 0 < $cursor->softRefHandle ? $cursor->softRefHandle : $cursor->refIndex; Chris@0: Chris@12: $this->line .= sprintf(' id=%s-ref%s', $this->dumpId, $r); Chris@0: } Chris@12: $this->line .= $eol; Chris@0: $this->dumpLine($cursor->depth); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function leaveHash(Cursor $cursor, $type, $class, $hasChild, $cut) Chris@0: { Chris@0: $this->dumpEllipsis($cursor, $hasChild, $cut); Chris@0: if ($hasChild) { Chris@0: $this->line .= ''; Chris@0: } Chris@0: parent::leaveHash($cursor, $type, $class, $hasChild, 0); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@17: protected function style($style, $value, $attr = []) Chris@0: { Chris@0: if ('' === $value) { Chris@0: return ''; Chris@0: } Chris@0: Chris@0: $v = esc($value); Chris@0: Chris@0: if ('ref' === $style) { Chris@0: if (empty($attr['count'])) { Chris@0: return sprintf('%s', $v); Chris@0: } Chris@0: $r = ('#' !== $v[0] ? 1 - ('@' !== $v[0]) : 2).substr($value, 1); Chris@0: Chris@0: return sprintf('%s', $this->dumpId, $r, 1 + $attr['count'], $v); Chris@0: } Chris@0: Chris@0: if ('const' === $style && isset($attr['value'])) { Chris@0: $style .= sprintf(' title="%s"', esc(is_scalar($attr['value']) ? $attr['value'] : json_encode($attr['value']))); Chris@0: } elseif ('public' === $style) { Chris@0: $style .= sprintf(' title="%s"', empty($attr['dynamic']) ? 'Public property' : 'Runtime added dynamic property'); Chris@0: } elseif ('str' === $style && 1 < $attr['length']) { Chris@0: $style .= sprintf(' title="%d%s characters"', $attr['length'], $attr['binary'] ? ' binary or non-UTF-8' : ''); Chris@0: } elseif ('note' === $style && false !== $c = strrpos($v, '\\')) { Chris@0: return sprintf('%s', $v, $style, substr($v, $c + 1)); Chris@0: } elseif ('protected' === $style) { Chris@0: $style .= ' title="Protected property"'; Chris@0: } elseif ('meta' === $style && isset($attr['title'])) { Chris@0: $style .= sprintf(' title="%s"', esc($this->utf8Encode($attr['title']))); Chris@0: } elseif ('private' === $style) { Chris@0: $style .= sprintf(' title="Private property defined in class: `%s`"', esc($this->utf8Encode($attr['class']))); Chris@0: } Chris@0: $map = static::$controlCharsMap; Chris@0: Chris@0: if (isset($attr['ellipsis'])) { Chris@0: $class = 'sf-dump-ellipsis'; Chris@0: if (isset($attr['ellipsis-type'])) { Chris@0: $class = sprintf('"%s sf-dump-ellipsis-%s"', $class, $attr['ellipsis-type']); Chris@0: } Chris@0: $label = esc(substr($value, -$attr['ellipsis'])); Chris@0: $style = str_replace(' title="', " title=\"$v\n", $style); Chris@17: $v = sprintf('%s', $class, substr($v, 0, -\strlen($label))); Chris@0: Chris@0: if (!empty($attr['ellipsis-tail'])) { Chris@17: $tail = \strlen(esc(substr($value, -$attr['ellipsis'], $attr['ellipsis-tail']))); Chris@0: $v .= sprintf('%s%s', substr($label, 0, $tail), substr($label, $tail)); Chris@0: } else { Chris@0: $v .= $label; Chris@0: } Chris@0: } Chris@0: Chris@0: $v = "".preg_replace_callback(static::$controlCharsRx, function ($c) use ($map) { Chris@0: $s = ''; Chris@0: $c = $c[$i = 0]; Chris@0: do { Chris@17: $s .= isset($map[$c[$i]]) ? $map[$c[$i]] : sprintf('\x%02X', \ord($c[$i])); Chris@0: } while (isset($c[++$i])); Chris@0: Chris@0: return $s.''; Chris@0: }, $v).''; Chris@0: Chris@0: if (isset($attr['file']) && $href = $this->getSourceLink($attr['file'], isset($attr['line']) ? $attr['line'] : 0)) { Chris@0: $attr['href'] = $href; Chris@0: } Chris@0: if (isset($attr['href'])) { Chris@12: $target = isset($attr['file']) ? '' : ' target="_blank"'; Chris@12: $v = sprintf('%s', esc($this->utf8Encode($attr['href'])), $target, $v); Chris@0: } Chris@0: if (isset($attr['lang'])) { Chris@0: $v = sprintf('
%s
', esc($attr['lang']), $v);
Chris@0: }
Chris@0:
Chris@0: return $v;
Chris@0: }
Chris@0:
Chris@0: /**
Chris@0: * {@inheritdoc}
Chris@0: */
Chris@0: protected function dumpLine($depth, $endOfValue = false)
Chris@0: {
Chris@0: if (-1 === $this->lastDepth) {
Chris@0: $this->line = sprintf($this->dumpPrefix, $this->dumpId, $this->indentPad).$this->line;
Chris@0: }
Chris@0: if ($this->headerIsDumped !== (null !== $this->outputStream ? $this->outputStream : $this->lineDumper)) {
Chris@0: $this->line = $this->getDumpHeader().$this->line;
Chris@0: }
Chris@0:
Chris@0: if (-1 === $depth) {
Chris@17: $args = ['"'.$this->dumpId.'"'];
Chris@0: if ($this->extraDisplayOptions) {
Chris@0: $args[] = json_encode($this->extraDisplayOptions, JSON_FORCE_OBJECT);
Chris@0: }
Chris@0: // Replace is for BC
Chris@0: $this->line .= sprintf(str_replace('"%s"', '%s', $this->dumpSuffix), implode(', ', $args));
Chris@0: }
Chris@0: $this->lastDepth = $depth;
Chris@0:
Chris@0: $this->line = mb_convert_encoding($this->line, 'HTML-ENTITIES', 'UTF-8');
Chris@0:
Chris@0: if (-1 === $depth) {
Chris@0: AbstractDumper::dumpLine(0);
Chris@0: }
Chris@0: AbstractDumper::dumpLine($depth);
Chris@0: }
Chris@0:
Chris@0: private function getSourceLink($file, $line)
Chris@0: {
Chris@0: $options = $this->extraDisplayOptions + $this->displayOptions;
Chris@0:
Chris@0: if ($fmt = $options['fileLinkFormat']) {
Chris@17: return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : $fmt->format($file, $line);
Chris@0: }
Chris@0:
Chris@0: return false;
Chris@0: }
Chris@0: }
Chris@0:
Chris@0: function esc($str)
Chris@0: {
Chris@0: return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
Chris@0: }