Skip to content

Commit

Permalink
feature #19797 [VarDumper] Handle attributes in Data clones for more …
Browse files Browse the repository at this point in the history
…semantic dumps (nicolas-grekas)

This PR was merged into the 3.2-dev branch.

Discussion
----------

[VarDumper] Handle attributes in Data clones for more semantic dumps

| Q             | A
| ------------- | ---
| Branch?       | master
| New feature?  | yes
| Tests pass?   | yes
| License       | MIT

Casters can now add attributes to the stub they create and to virtual properties so that e.g. the HtmlDumper knows more about the structure it is dumping. This allow for fine tuned HTML representations.

The ExceptionCaster uses this feature to make traces more useful, by telling the HtmlDumper that a which keys/values are files, lines or code excerpt (and which language). Thus, code excerpts can now be opened directly in the IDE.

Commits
-------

2937ffa [VarDumper] Handle attributes in Data clones for more semantic dumps
  • Loading branch information
fabpot committed Aug 31, 2016
2 parents ec9cbab + 2937ffa commit 2cd6c63
Show file tree
Hide file tree
Showing 12 changed files with 177 additions and 86 deletions.
Expand Up @@ -912,6 +912,11 @@ table.logs .sf-call-stack abbr {
#collector-content .sf-dump .trace li.selected {
background: rgba(255, 255, 153, 0.5);
}
#collector-content .sf-dump-expanded code { color: #222; }
#collector-content .sf-dump-expanded code .sf-dump-const {
background: rgba(255, 255, 153, 0.5);
font-weight: normal;
}

{# Search Results page
========================================================================= #}
Expand Down
50 changes: 32 additions & 18 deletions src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php
Expand Up @@ -89,7 +89,6 @@ public static function castTraceStub(TraceStub $trace, array $a, Stub $stub, $is
$stub->handle = 0;
$frames = $trace->value;
$prefix = Caster::PREFIX_VIRTUAL;
$format = "\0~Stack level %s.\0%s";

$a = array();
$j = count($frames);
Expand All @@ -99,14 +98,14 @@ public static function castTraceStub(TraceStub $trace, array $a, Stub $stub, $is
if (!isset($trace->value[$i])) {
return array();
}
$lastCall = isset($frames[$i]['function']) ? ' ==> '.(isset($frames[$i]['class']) ? $frames[0]['class'].$frames[$i]['type'] : '').$frames[$i]['function'].'()' : '';
$lastCall = isset($frames[$i]['function']) ? (isset($frames[$i]['class']) ? $frames[0]['class'].$frames[$i]['type'] : '').$frames[$i]['function'].'()' : '';
$frames[] = array('function' => '');

for ($j += $trace->numberingOffset - $i++; isset($frames[$i]); ++$i, --$j) {
$f = $frames[$i];
$call = isset($f['function']) ? (isset($f['class']) ? $f['class'].$f['type'] : '').$f['function'].'()' : '???';

$label = $call.$lastCall;
$label = substr_replace($prefix, "title=Stack level $j.", 2, 0).$lastCall;
$frame = new FrameStub(
array(
'object' => isset($f['object']) ? $f['object'] : null,
Expand All @@ -120,14 +119,15 @@ public static function castTraceStub(TraceStub $trace, array $a, Stub $stub, $is
$f = self::castFrameStub($frame, array(), $frame, true);
if (isset($f[$prefix.'src'])) {
foreach ($f[$prefix.'src']->value as $label => $frame) {
$label = substr_replace($label, "title=Stack level $j.&", 2, 0);
}
if (isset($f[$prefix.'args']) && $frame instanceof EnumStub) {
$frame->value['args'] = $f[$prefix.'args'];
}
}
$a[sprintf($format, $j, $label)] = $frame;
$a[$label] = $frame;

$lastCall = ' ==> '.$call;
$lastCall = $call;
}
if (null !== $trace->sliceLength) {
$a = array_slice($a, 0, $trace->sliceLength, true);
Expand All @@ -149,28 +149,38 @@ public static function castFrameStub(FrameStub $frame, array $a, Stub $stub, $is
$f['file'] = substr($f['file'], 0, -strlen($match[0]));
$f['line'] = (int) $match[1];
}
$src = array();
$caller = isset($f['function']) ? sprintf('in %s() on line %d', (isset($f['class']) ? $f['class'].$f['type'] : '').$f['function'], $f['line']) : null;
$src = $f['line'];
$srcKey = $f['file'];
$ellipsis = explode(DIRECTORY_SEPARATOR, $srcKey);
$ellipsis = 3 < count($ellipsis) ? 2 + strlen(implode(array_slice($ellipsis, -2))) : 0;

if (file_exists($f['file']) && 0 <= self::$srcContext) {
if (!empty($f['class']) && is_subclass_of($f['class'], 'Twig_Template') && method_exists($f['class'], 'getDebugInfo')) {
$template = isset($f['object']) ? $f['object'] : new $f['class'](new \Twig_Environment(new \Twig_Loader_Filesystem()));

try {
$ellipsis = 0;
$templateName = $template->getTemplateName();
$templateSrc = explode("\n", method_exists($template, 'getSource') ? $template->getSource() : $template->getEnvironment()->getLoader()->getSource($templateName));
$templateInfo = $template->getDebugInfo();
if (isset($templateInfo[$f['line']])) {
$src[$templateName.':'.$templateInfo[$f['line']]] = self::extractSource($templateSrc, $templateInfo[$f['line']], self::$srcContext);
$src = self::extractSource($templateSrc, $templateInfo[$f['line']], self::$srcContext, $caller, 'twig');
$srcKey = $templateName.':'.$templateInfo[$f['line']];
}
} catch (\Twig_Error_Loader $e) {
}
}
if (!$src) {
$src[$f['file'].':'.$f['line']] = self::extractSource(explode("\n", file_get_contents($f['file'])), $f['line'], self::$srcContext);
if ($srcKey == $f['file']) {
$src = self::extractSource(explode("\n", file_get_contents($f['file'])), $f['line'], self::$srcContext, $caller, 'php', $f['file']);
$srcKey .= ':'.$f['line'];
if ($ellipsis) {
$ellipsis += 1 + strlen($f['line']);
}
}
} else {
$src[$f['file']] = $f['line'];
}
$a[$prefix.'src'] = new EnumStub($src);
$srcAttr = $ellipsis ? 'ellipsis='.$ellipsis : '';
$a[$prefix.'src'] = new EnumStub(array("\0~$srcAttr\0$srcKey" => $src));
}

unset($a[$prefix.'args'], $a[$prefix.'line'], $a[$prefix.'file']);
Expand Down Expand Up @@ -214,7 +224,7 @@ private static function filterExceptionArray($xClass, array $a, $xPrefix, $filte
return $a;
}

private static function extractSource(array $srcArray, $line, $srcContext)
private static function extractSource(array $srcArray, $line, $srcContext, $title, $lang, $file = null)
{
$src = array();

Expand All @@ -239,20 +249,24 @@ private static function extractSource(array $srcArray, $line, $srcContext)
} while (0 > $i && null !== $pad);

--$ltrim;

$pad = strlen($line + $srcContext);
$srcArray = array();

foreach ($src as $i => $c) {
if ($ltrim) {
$c = isset($c[$ltrim]) && "\r" !== $c[$ltrim] ? substr($c, $ltrim) : ltrim($c, " \t");
}
$c = substr($c, 0, -1);
$c = new ConstStub($c, $c);
if ($i !== $srcContext) {
$c->class = 'default';
$c = new ConstStub('default', $c);
} else {
$c = new ConstStub($c, $title);
if (null !== $file) {
$c->attr['file'] = $file;
$c->attr['line'] = $line;
}
}
$srcArray[sprintf("% {$pad}d", $i + $line - $srcContext)] = $c;
$c->attr['lang'] = $lang;
$srcArray[sprintf("\0~%d\0", $i + $line - $srcContext)] = $c;
}

return new EnumStub($srcArray);
Expand Down
2 changes: 2 additions & 0 deletions src/Symfony/Component/VarDumper/Caster/StubCaster.php
Expand Up @@ -28,6 +28,7 @@ public static function castStub(Stub $c, array $a, Stub $stub, $isNested)
$stub->value = $c->value;
$stub->handle = $c->handle;
$stub->cut = $c->cut;
$stub->attr = $c->attr;

return array();
}
Expand Down Expand Up @@ -56,6 +57,7 @@ public static function castEnum(EnumStub $c, array $a, Stub $stub, $isNested)
$stub->handle = 0;
$stub->value = null;
$stub->cut = $c->cut;
$stub->attr = $c->attr;

$a = array();

Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/VarDumper/Cloner/Cursor.php
Expand Up @@ -38,4 +38,5 @@ class Cursor
public $hashLength = 0;
public $hashCut = 0;
public $stop = false;
public $attr = array();
}
3 changes: 3 additions & 0 deletions src/Symfony/Component/VarDumper/Cloner/Data.php
Expand Up @@ -155,6 +155,7 @@ private function dumpItem($dumper, $cursor, &$refs, $item)
$firstSeen = true;

if (!$item instanceof Stub) {
$cursor->attr = array();
$type = gettype($item);
} elseif (Stub::TYPE_REF === $item->type) {
if ($item->handle) {
Expand All @@ -167,6 +168,7 @@ private function dumpItem($dumper, $cursor, &$refs, $item)
$cursor->hardRefHandle = $this->useRefHandles & $item->handle;
$cursor->hardRefCount = $item->refCount;
}
$cursor->attr = $item->attr;
$type = $item->class ?: gettype($item->value);
$item = $item->value;
}
Expand All @@ -181,6 +183,7 @@ private function dumpItem($dumper, $cursor, &$refs, $item)
}
$cursor->softRefHandle = $this->useRefHandles & $item->handle;
$cursor->softRefCount = $item->refCount;
$cursor->attr = $item->attr;
$cut = $item->cut;

if ($item->position && $firstSeen) {
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/VarDumper/Cloner/Stub.php
Expand Up @@ -37,4 +37,5 @@ class Stub
public $handle = 0;
public $refCount = 0;
public $position = 0;
public $attr = array();
}
10 changes: 6 additions & 4 deletions src/Symfony/Component/VarDumper/Dumper/CliDumper.php
Expand Up @@ -112,7 +112,7 @@ public function dumpScalar(Cursor $cursor, $type, $value)
$this->dumpKey($cursor);

$style = 'const';
$attr = array();
$attr = $cursor->attr;

switch ($type) {
case 'default':
Expand Down Expand Up @@ -148,7 +148,7 @@ public function dumpScalar(Cursor $cursor, $type, $value)
break;

default:
$attr['value'] = isset($value[0]) && !preg_match('//u', $value) ? $this->utf8Encode($value) : $value;
$attr += array('value' => isset($value[0]) && !preg_match('//u', $value) ? $this->utf8Encode($value) : $value);
$value = isset($type[0]) && !preg_match('//u', $type) ? $this->utf8Encode($type) : $type;
break;
}
Expand All @@ -164,6 +164,7 @@ public function dumpScalar(Cursor $cursor, $type, $value)
public function dumpString(Cursor $cursor, $str, $bin, $cut)
{
$this->dumpKey($cursor);
$attr = $cursor->attr;

if ($bin) {
$str = $this->utf8Encode($str);
Expand All @@ -172,7 +173,7 @@ public function dumpString(Cursor $cursor, $str, $bin, $cut)
$this->line .= '""';
$this->dumpLine($cursor->depth, true);
} else {
$attr = array(
$attr += array(
'length' => 0 <= $cut ? mb_strlen($str, 'UTF-8') + $cut : 0,
'binary' => $bin,
);
Expand Down Expand Up @@ -350,7 +351,8 @@ protected function dumpKey(Cursor $cursor)
case '~':
$style = 'meta';
if (isset($key[0][1])) {
$attr['title'] = substr($key[0], 1);
parse_str(substr($key[0], 1), $attr);
$attr += array('binary' => $cursor->hashKeyIsBinary);
}
break;
case '*':
Expand Down
63 changes: 50 additions & 13 deletions src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php
Expand Up @@ -43,11 +43,13 @@ class HtmlDumper extends CliDumper
'meta' => 'color:#B729D9',
'key' => 'color:#56DB3A',
'index' => 'color:#1299DA',
'expanded code.hljs' => 'display:inline; padding:0; background:none',
);

private $displayOptions = array(
'maxDepth' => 1,
'maxStringLength' => 160,
'fileLinkFormat' => null,
);
private $extraDisplayOptions = array();

Expand Down Expand Up @@ -184,6 +186,19 @@ function toggle(a, recursive) {
return function (root, x) {
root = doc.getElementById(root);
var indentRx = new RegExp('^('+(root.getAttribute('data-indent-pad') || ' ').replace(rxEsc, '\\$1')+')+', 'm'),
options = {$options},
elt = root.getElementsByTagName('A'),
len = elt.length,
i = 0, s, h,
t = [];
while (i < len) t.push(elt[i++]);
for (i in x) {
options[i] = x[i];
}
function a(e, f) {
addEventListener(root, e, function (e) {
if ('A' == e.target.tagName) {
Expand All @@ -201,6 +216,20 @@ function isCtrlKey(e) {
refStyle.innerHTML = '';
}
});
if (options.fileLinkFormat) {
addEventListener(root, 'click', function (e) {
e = e.target;
while (root != e && 'CODE' != e.tagName) {
e = e.parentNode;
}
if ('CODE' == e.tagName) {
var f = e.getAttribute('data-file'), l = e.getAttribute('data-line');
if (f && l) {
location.href = options.fileLinkFormat.replace('%f', f).replace('%l', l);
}
}
});
}
a('mouseover', function (a) {
if (a = idRx.exec(a.className)) {
try {
Expand Down Expand Up @@ -246,19 +275,6 @@ function isCtrlKey(e) {
}
});
var indentRx = new RegExp('^('+(root.getAttribute('data-indent-pad') || ' ').replace(rxEsc, '\\$1')+')+', 'm'),
options = {$options},
elt = root.getElementsByTagName('A'),
len = elt.length,
i = 0, s, h,
t = [];
while (i < len) t.push(elt[i++]);
for (i in x) {
options[i] = x[i];
}
elt = root.getElementsByTagName('SAMP');
len = elt.length;
i = 0;
Expand Down Expand Up @@ -369,6 +385,15 @@ function isCtrlKey(e) {
border: 0;
outline: none;
}
pre.sf-dump .sf-dump-ellipsis {
display: inline-block;
overflow: visible;
text-overflow: ellipsis;
width: 50px;
white-space: nowrap;
overflow: hidden;
vertical-align: top;
}
.sf-dump-str-collapse .sf-dump-str-collapse {
display: none;
}
Expand Down Expand Up @@ -452,6 +477,11 @@ protected function style($style, $value, $attr = array())
} elseif ('private' === $style) {
$style .= sprintf(' title="Private property defined in class:&#10;`%s`"', esc($attr['class']));
}
if (isset($attr['ellipsis'])) {
$label = esc(substr($value, -$attr['ellipsis']));

return sprintf('<span class=sf-dump-%s><abbr title="%s" class=sf-dump-ellipsis>%2$s</abbr>%s</span>', $style, substr($v, 0, -strlen($label)), $label);
}

$map = static::$controlCharsMap;
$style = "<span class=sf-dump-{$style}>";
Expand All @@ -475,6 +505,13 @@ protected function style($style, $value, $attr = array())
} else {
$v .= '</span>';
}
if (isset($attr['lang'])) {
if (isset($attr['file'], $attr['line'])) {
$v = sprintf('<code class="%s" data-file="%s" data-line="%d">%s</code>', esc($attr['lang']), esc($attr['file']), $attr['line'], $v);
} else {
$v = sprintf('<code class="%s">%s</code>', esc($attr['lang']), $v);
}
}

return $v;
}
Expand Down

0 comments on commit 2cd6c63

Please sign in to comment.