[VarDumper] add link to source next to class names
This commit is contained in:
parent
ccd4bbeb31
commit
5fcd6b1d4e
@ -43,6 +43,11 @@ class DebugExtension extends Extension
|
|||||||
->addMethodCall('setMinDepth', [$config['min_depth']])
|
->addMethodCall('setMinDepth', [$config['min_depth']])
|
||||||
->addMethodCall('setMaxString', [$config['max_string_length']]);
|
->addMethodCall('setMaxString', [$config['max_string_length']]);
|
||||||
|
|
||||||
|
if (method_exists(ReflectionClass::class, 'unsetClosureFileInfo')) {
|
||||||
|
$container->getDefinition('var_dumper.cloner')
|
||||||
|
->addMethodCall('addCasters', ReflectionClass::UNSET_CLOSURE_FILE_INFO);
|
||||||
|
}
|
||||||
|
|
||||||
if (method_exists(HtmlDumper::class, 'setTheme') && 'dark' !== $config['theme']) {
|
if (method_exists(HtmlDumper::class, 'setTheme') && 'dark' !== $config['theme']) {
|
||||||
$container->getDefinition('var_dumper.html_dumper')
|
$container->getDefinition('var_dumper.html_dumper')
|
||||||
->addMethodCall('setTheme', [$config['theme']]);
|
->addMethodCall('setTheme', [$config['theme']]);
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
namespace Symfony\Component\HttpKernel\DataCollector;
|
namespace Symfony\Component\HttpKernel\DataCollector;
|
||||||
|
|
||||||
use Symfony\Component\VarDumper\Caster\CutStub;
|
use Symfony\Component\VarDumper\Caster\CutStub;
|
||||||
|
use Symfony\Component\VarDumper\Caster\ReflectionCaster;
|
||||||
use Symfony\Component\VarDumper\Cloner\ClonerInterface;
|
use Symfony\Component\VarDumper\Cloner\ClonerInterface;
|
||||||
use Symfony\Component\VarDumper\Cloner\Data;
|
use Symfony\Component\VarDumper\Cloner\Data;
|
||||||
use Symfony\Component\VarDumper\Cloner\Stub;
|
use Symfony\Component\VarDumper\Cloner\Stub;
|
||||||
@ -79,7 +80,7 @@ abstract class DataCollector implements DataCollectorInterface, \Serializable
|
|||||||
*/
|
*/
|
||||||
protected function getCasters()
|
protected function getCasters()
|
||||||
{
|
{
|
||||||
return [
|
$casters = [
|
||||||
'*' => function ($v, array $a, Stub $s, $isNested) {
|
'*' => function ($v, array $a, Stub $s, $isNested) {
|
||||||
if (!$v instanceof Stub) {
|
if (!$v instanceof Stub) {
|
||||||
foreach ($a as $k => $v) {
|
foreach ($a as $k => $v) {
|
||||||
@ -92,5 +93,11 @@ abstract class DataCollector implements DataCollectorInterface, \Serializable
|
|||||||
return $a;
|
return $a;
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (method_exists(ReflectionCaster::class, 'unsetClosureFileInfo')) {
|
||||||
|
$casters += ReflectionCaster::UNSET_CLOSURE_FILE_INFO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $casters;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,21 +87,17 @@ class FileLinkFormatter
|
|||||||
|
|
||||||
private function getFileLinkFormat()
|
private function getFileLinkFormat()
|
||||||
{
|
{
|
||||||
if ($this->fileLinkFormat) {
|
|
||||||
return $this->fileLinkFormat;
|
|
||||||
}
|
|
||||||
if ($this->requestStack && $this->baseDir && $this->urlFormat) {
|
if ($this->requestStack && $this->baseDir && $this->urlFormat) {
|
||||||
$request = $this->requestStack->getMasterRequest();
|
$request = $this->requestStack->getMasterRequest();
|
||||||
if ($request instanceof Request) {
|
|
||||||
if ($this->urlFormat instanceof \Closure && !$this->urlFormat = ($this->urlFormat)()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
if ($request instanceof Request && (!$this->urlFormat instanceof \Closure || $this->urlFormat = ($this->urlFormat)())) {
|
||||||
|
$this->fileLinkFormat = [
|
||||||
$request->getSchemeAndHttpHost().$request->getBasePath().$this->urlFormat,
|
$request->getSchemeAndHttpHost().$request->getBasePath().$this->urlFormat,
|
||||||
$this->baseDir.\DIRECTORY_SEPARATOR, '',
|
$this->baseDir.\DIRECTORY_SEPARATOR, '',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $this->fileLinkFormat;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,18 +34,6 @@ class FileLinkFormatterTest extends TestCase
|
|||||||
$this->assertSame("debug://open?url=file://$file&line=3", $sut->format($file, 3));
|
$this->assertSame("debug://open?url=file://$file&line=3", $sut->format($file, 3));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testWhenFileLinkFormatAndRequest()
|
|
||||||
{
|
|
||||||
$file = __DIR__.\DIRECTORY_SEPARATOR.'file.php';
|
|
||||||
$requestStack = new RequestStack();
|
|
||||||
$request = new Request();
|
|
||||||
$requestStack->push($request);
|
|
||||||
|
|
||||||
$sut = new FileLinkFormatter('debug://open?url=file://%f&line=%l', $requestStack, __DIR__, '/_profiler/open?file=%f&line=%l#line%l');
|
|
||||||
|
|
||||||
$this->assertSame("debug://open?url=file://$file&line=3", $sut->format($file, 3));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testWhenNoFileLinkFormatAndRequest()
|
public function testWhenNoFileLinkFormatAndRequest()
|
||||||
{
|
{
|
||||||
$file = __DIR__.\DIRECTORY_SEPARATOR.'file.php';
|
$file = __DIR__.\DIRECTORY_SEPARATOR.'file.php';
|
||||||
|
@ -20,6 +20,8 @@ use Symfony\Component\VarDumper\Cloner\Stub;
|
|||||||
*/
|
*/
|
||||||
class ReflectionCaster
|
class ReflectionCaster
|
||||||
{
|
{
|
||||||
|
const UNSET_CLOSURE_FILE_INFO = ['Closure' => __CLASS__.'::unsetClosureFileInfo'];
|
||||||
|
|
||||||
private static $extraMap = [
|
private static $extraMap = [
|
||||||
'docComment' => 'getDocComment',
|
'docComment' => 'getDocComment',
|
||||||
'extension' => 'getExtensionName',
|
'extension' => 'getExtensionName',
|
||||||
@ -46,15 +48,20 @@ class ReflectionCaster
|
|||||||
|
|
||||||
$stub->class .= self::getSignature($a);
|
$stub->class .= self::getSignature($a);
|
||||||
|
|
||||||
|
if ($f = $c->getFileName()) {
|
||||||
|
$stub->attr['file'] = $f;
|
||||||
|
$stub->attr['line'] = $c->getStartLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($a[$prefix.'parameters']);
|
||||||
|
|
||||||
if ($filter & Caster::EXCLUDE_VERBOSE) {
|
if ($filter & Caster::EXCLUDE_VERBOSE) {
|
||||||
$stub->cut += ($c->getFileName() ? 2 : 0) + \count($a);
|
$stub->cut += ($c->getFileName() ? 2 : 0) + \count($a);
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
unset($a[$prefix.'parameters']);
|
if ($f) {
|
||||||
|
|
||||||
if ($f = $c->getFileName()) {
|
|
||||||
$a[$prefix.'file'] = new LinkStub($f, $c->getStartLine());
|
$a[$prefix.'file'] = new LinkStub($f, $c->getStartLine());
|
||||||
$a[$prefix.'line'] = $c->getStartLine().' to '.$c->getEndLine();
|
$a[$prefix.'line'] = $c->getStartLine().' to '.$c->getEndLine();
|
||||||
}
|
}
|
||||||
@ -62,6 +69,13 @@ class ReflectionCaster
|
|||||||
return $a;
|
return $a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function unsetClosureFileInfo(\Closure $c, array $a)
|
||||||
|
{
|
||||||
|
unset($a[Caster::PREFIX_VIRTUAL.'file'], $a[Caster::PREFIX_VIRTUAL.'line']);
|
||||||
|
|
||||||
|
return $a;
|
||||||
|
}
|
||||||
|
|
||||||
public static function castGenerator(\Generator $c, array $a, Stub $stub, $isNested)
|
public static function castGenerator(\Generator $c, array $a, Stub $stub, $isNested)
|
||||||
{
|
{
|
||||||
if (!class_exists('ReflectionGenerator', false)) {
|
if (!class_exists('ReflectionGenerator', false)) {
|
||||||
|
@ -279,7 +279,7 @@ abstract class AbstractCloner implements ClonerInterface
|
|||||||
$stub->class = get_parent_class($class).'@anonymous';
|
$stub->class = get_parent_class($class).'@anonymous';
|
||||||
}
|
}
|
||||||
if (isset($this->classInfo[$class])) {
|
if (isset($this->classInfo[$class])) {
|
||||||
list($i, $parents, $hasDebugInfo) = $this->classInfo[$class];
|
list($i, $parents, $hasDebugInfo, $fileInfo) = $this->classInfo[$class];
|
||||||
} else {
|
} else {
|
||||||
$i = 2;
|
$i = 2;
|
||||||
$parents = [$class];
|
$parents = [$class];
|
||||||
@ -295,9 +295,16 @@ abstract class AbstractCloner implements ClonerInterface
|
|||||||
}
|
}
|
||||||
$parents[] = '*';
|
$parents[] = '*';
|
||||||
|
|
||||||
$this->classInfo[$class] = [$i, $parents, $hasDebugInfo];
|
$r = new \ReflectionClass($class);
|
||||||
|
$fileInfo = $r->isInternal() || $r->isSubclassOf(Stub::class) ? [] : [
|
||||||
|
'file' => $r->getFileName(),
|
||||||
|
'line' => $r->getStartLine(),
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->classInfo[$class] = [$i, $parents, $hasDebugInfo, $fileInfo];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$stub->attr += $fileInfo;
|
||||||
$a = Caster::castObject($obj, $class, $hasDebugInfo);
|
$a = Caster::castObject($obj, $class, $hasDebugInfo);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -274,6 +274,7 @@ class CliDumper extends AbstractDumper
|
|||||||
public function enterHash(Cursor $cursor, $type, $class, $hasChild)
|
public function enterHash(Cursor $cursor, $type, $class, $hasChild)
|
||||||
{
|
{
|
||||||
$this->dumpKey($cursor);
|
$this->dumpKey($cursor);
|
||||||
|
$attr = $cursor->attr;
|
||||||
|
|
||||||
if ($this->collapseNextHash) {
|
if ($this->collapseNextHash) {
|
||||||
$cursor->skipChildren = true;
|
$cursor->skipChildren = true;
|
||||||
@ -282,11 +283,11 @@ class CliDumper extends AbstractDumper
|
|||||||
|
|
||||||
$class = $this->utf8Encode($class);
|
$class = $this->utf8Encode($class);
|
||||||
if (Cursor::HASH_OBJECT === $type) {
|
if (Cursor::HASH_OBJECT === $type) {
|
||||||
$prefix = $class && 'stdClass' !== $class ? $this->style('note', $class).' {' : '{';
|
$prefix = $class && 'stdClass' !== $class ? $this->style('note', $class, $attr).' {' : '{';
|
||||||
} elseif (Cursor::HASH_RESOURCE === $type) {
|
} elseif (Cursor::HASH_RESOURCE === $type) {
|
||||||
$prefix = $this->style('note', $class.' resource').($hasChild ? ' {' : ' ');
|
$prefix = $this->style('note', $class.' resource', $attr).($hasChild ? ' {' : ' ');
|
||||||
} else {
|
} else {
|
||||||
$prefix = $class && !(self::DUMP_LIGHT_ARRAY & $this->flags) ? $this->style('note', 'array:'.$class).' [' : '[';
|
$prefix = $class && !(self::DUMP_LIGHT_ARRAY & $this->flags) ? $this->style('note', 'array:'.$class, $attr).' [' : '[';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($cursor->softRefCount || 0 < $cursor->softRefHandle) {
|
if ($cursor->softRefCount || 0 < $cursor->softRefHandle) {
|
||||||
@ -454,11 +455,9 @@ class CliDumper extends AbstractDumper
|
|||||||
goto href;
|
goto href;
|
||||||
}
|
}
|
||||||
|
|
||||||
$style = $this->styles[$style];
|
|
||||||
|
|
||||||
$map = static::$controlCharsMap;
|
$map = static::$controlCharsMap;
|
||||||
$startCchr = $this->colors ? "\033[m\033[{$this->styles['default']}m" : '';
|
$startCchr = $this->colors ? "\033[m\033[{$this->styles['default']}m" : '';
|
||||||
$endCchr = $this->colors ? "\033[m\033[{$style}m" : '';
|
$endCchr = $this->colors ? "\033[m\033[{$this->styles[$style]}m" : '';
|
||||||
$value = preg_replace_callback(static::$controlCharsRx, function ($c) use ($map, $startCchr, $endCchr) {
|
$value = preg_replace_callback(static::$controlCharsRx, function ($c) use ($map, $startCchr, $endCchr) {
|
||||||
$s = $startCchr;
|
$s = $startCchr;
|
||||||
$c = $c[$i = 0];
|
$c = $c[$i = 0];
|
||||||
@ -473,7 +472,7 @@ class CliDumper extends AbstractDumper
|
|||||||
if ($cchrCount && "\033" === $value[0]) {
|
if ($cchrCount && "\033" === $value[0]) {
|
||||||
$value = substr($value, \strlen($startCchr));
|
$value = substr($value, \strlen($startCchr));
|
||||||
} else {
|
} else {
|
||||||
$value = "\033[{$style}m".$value;
|
$value = "\033[{$this->styles[$style]}m".$value;
|
||||||
}
|
}
|
||||||
if ($cchrCount && $endCchr === substr($value, -\strlen($endCchr))) {
|
if ($cchrCount && $endCchr === substr($value, -\strlen($endCchr))) {
|
||||||
$value = substr($value, 0, -\strlen($endCchr));
|
$value = substr($value, 0, -\strlen($endCchr));
|
||||||
@ -485,7 +484,11 @@ class CliDumper extends AbstractDumper
|
|||||||
href:
|
href:
|
||||||
if ($this->colors && $this->handlesHrefGracefully) {
|
if ($this->colors && $this->handlesHrefGracefully) {
|
||||||
if (isset($attr['file']) && $href = $this->getSourceLink($attr['file'], isset($attr['line']) ? $attr['line'] : 0)) {
|
if (isset($attr['file']) && $href = $this->getSourceLink($attr['file'], isset($attr['line']) ? $attr['line'] : 0)) {
|
||||||
$attr['href'] = $href;
|
if ('note' === $style) {
|
||||||
|
$value .= "\033]8;;{$href}\033\\^\033]8;;\033\\";
|
||||||
|
} else {
|
||||||
|
$attr['href'] = $href;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (isset($attr['href'])) {
|
if (isset($attr['href'])) {
|
||||||
$value = "\033]8;;{$attr['href']}\033\\{$value}\033]8;;\033\\";
|
$value = "\033]8;;{$attr['href']}\033\\{$value}\033]8;;\033\\";
|
||||||
@ -632,7 +635,7 @@ class CliDumper extends AbstractDumper
|
|||||||
private function getSourceLink($file, $line)
|
private function getSourceLink($file, $line)
|
||||||
{
|
{
|
||||||
if ($fmt = $this->displayOptions['fileLinkFormat']) {
|
if ($fmt = $this->displayOptions['fileLinkFormat']) {
|
||||||
return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : $fmt->format($file, $line);
|
return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : ($fmt->format($file, $line) ?: 'file://'.$file);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
namespace Symfony\Component\VarDumper\Dumper\ContextProvider;
|
namespace Symfony\Component\VarDumper\Dumper\ContextProvider;
|
||||||
|
|
||||||
use Symfony\Component\HttpFoundation\RequestStack;
|
use Symfony\Component\HttpFoundation\RequestStack;
|
||||||
|
use Symfony\Component\VarDumper\Caster\ReflectionCaster;
|
||||||
use Symfony\Component\VarDumper\Cloner\VarCloner;
|
use Symfony\Component\VarDumper\Cloner\VarCloner;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,6 +30,7 @@ final class RequestContextProvider implements ContextProviderInterface
|
|||||||
$this->requestStack = $requestStack;
|
$this->requestStack = $requestStack;
|
||||||
$this->cloner = new VarCloner();
|
$this->cloner = new VarCloner();
|
||||||
$this->cloner->setMaxItems(0);
|
$this->cloner->setMaxItems(0);
|
||||||
|
$this->cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getContext(): ?array
|
public function getContext(): ?array
|
||||||
|
@ -314,13 +314,17 @@ return function (root, x) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function a(e, f) {
|
function a(e, f) {
|
||||||
addEventListener(root, e, function (e) {
|
addEventListener(root, e, function (e, n) {
|
||||||
if ('A' == e.target.tagName) {
|
if ('A' == e.target.tagName) {
|
||||||
f(e.target, e);
|
f(e.target, e);
|
||||||
} else if ('A' == e.target.parentNode.tagName) {
|
} else if ('A' == e.target.parentNode.tagName) {
|
||||||
f(e.target.parentNode, e);
|
f(e.target.parentNode, e);
|
||||||
} else if (e.target.nextElementSibling && 'A' == e.target.nextElementSibling.tagName) {
|
} else if ((n = e.target.nextElementSibling) && 'A' == n.tagName) {
|
||||||
f(e.target.nextElementSibling, e, true);
|
if (!/\bsf-dump-toggle\b/.test(n.className)) {
|
||||||
|
n = n.nextElementSibling;
|
||||||
|
}
|
||||||
|
|
||||||
|
f(n, e, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -852,7 +856,13 @@ EOHTML
|
|||||||
} elseif ('str' === $style && 1 < $attr['length']) {
|
} elseif ('str' === $style && 1 < $attr['length']) {
|
||||||
$style .= sprintf(' title="%d%s characters"', $attr['length'], $attr['binary'] ? ' binary or non-UTF-8' : '');
|
$style .= sprintf(' title="%d%s characters"', $attr['length'], $attr['binary'] ? ' binary or non-UTF-8' : '');
|
||||||
} elseif ('note' === $style && false !== $c = strrpos($v, '\\')) {
|
} elseif ('note' === $style && false !== $c = strrpos($v, '\\')) {
|
||||||
return sprintf('<abbr title="%s" class=sf-dump-%s>%s</abbr>', $v, $style, substr($v, $c + 1));
|
if (isset($attr['file']) && $link = $this->getSourceLink($attr['file'], isset($attr['line']) ? $attr['line'] : 0)) {
|
||||||
|
$link = sprintf('<a href="%s" rel="noopener noreferrer">^</a>', esc($this->utf8Encode($link)));
|
||||||
|
} else {
|
||||||
|
$link = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprintf('<abbr title="%s" class=sf-dump-%s>%s</abbr>%s', $v, $style, substr($v, $c + 1), $link);
|
||||||
} elseif ('protected' === $style) {
|
} elseif ('protected' === $style) {
|
||||||
$style .= ' title="Protected property"';
|
$style .= ' title="Protected property"';
|
||||||
} elseif ('meta' === $style && isset($attr['title'])) {
|
} elseif ('meta' === $style && isset($attr['title'])) {
|
||||||
|
@ -114,7 +114,7 @@ EOTXT
|
|||||||
{
|
{
|
||||||
$var = function &($a = 5) {};
|
$var = function &($a = 5) {};
|
||||||
|
|
||||||
$this->assertDumpEquals('Closure&($a = 5) { …6}', $var, Caster::EXCLUDE_VERBOSE);
|
$this->assertDumpEquals('Closure&($a = 5) { …5}', $var, Caster::EXCLUDE_VERBOSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testReflectionParameter()
|
public function testReflectionParameter()
|
||||||
|
@ -411,6 +411,8 @@ Symfony\Component\VarDumper\Cloner\Data Object
|
|||||||
[position] => 1
|
[position] => 1
|
||||||
[attr] => Array
|
[attr] => Array
|
||||||
(
|
(
|
||||||
|
[file] => %a%eVarClonerTest.php
|
||||||
|
[line] => 20
|
||||||
)
|
)
|
||||||
|
|
||||||
)
|
)
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\VarDumper;
|
namespace Symfony\Component\VarDumper;
|
||||||
|
|
||||||
|
use Symfony\Component\VarDumper\Caster\ReflectionCaster;
|
||||||
use Symfony\Component\VarDumper\Cloner\VarCloner;
|
use Symfony\Component\VarDumper\Cloner\VarCloner;
|
||||||
use Symfony\Component\VarDumper\Dumper\CliDumper;
|
use Symfony\Component\VarDumper\Dumper\CliDumper;
|
||||||
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
|
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
|
||||||
@ -29,6 +30,7 @@ class VarDumper
|
|||||||
{
|
{
|
||||||
if (null === self::$handler) {
|
if (null === self::$handler) {
|
||||||
$cloner = new VarCloner();
|
$cloner = new VarCloner();
|
||||||
|
$cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO);
|
||||||
|
|
||||||
if (isset($_SERVER['VAR_DUMPER_FORMAT'])) {
|
if (isset($_SERVER['VAR_DUMPER_FORMAT'])) {
|
||||||
$dumper = 'html' === $_SERVER['VAR_DUMPER_FORMAT'] ? new HtmlDumper() : new CliDumper();
|
$dumper = 'html' === $_SERVER['VAR_DUMPER_FORMAT'] ? new HtmlDumper() : new CliDumper();
|
||||||
|
Reference in New Issue
Block a user