diff --git a/src/Symfony/Bundle/DebugBundle/DependencyInjection/DebugExtension.php b/src/Symfony/Bundle/DebugBundle/DependencyInjection/DebugExtension.php
index 9c098a0996..a183e82cf8 100644
--- a/src/Symfony/Bundle/DebugBundle/DependencyInjection/DebugExtension.php
+++ b/src/Symfony/Bundle/DebugBundle/DependencyInjection/DebugExtension.php
@@ -43,6 +43,11 @@ class DebugExtension extends Extension
->addMethodCall('setMinDepth', [$config['min_depth']])
->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']) {
$container->getDefinition('var_dumper.html_dumper')
->addMethodCall('setTheme', [$config['theme']]);
diff --git a/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php
index efe1705179..3b15868ff5 100644
--- a/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php
+++ b/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php
@@ -12,6 +12,7 @@
namespace Symfony\Component\HttpKernel\DataCollector;
use Symfony\Component\VarDumper\Caster\CutStub;
+use Symfony\Component\VarDumper\Caster\ReflectionCaster;
use Symfony\Component\VarDumper\Cloner\ClonerInterface;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Cloner\Stub;
@@ -79,7 +80,7 @@ abstract class DataCollector implements DataCollectorInterface, \Serializable
*/
protected function getCasters()
{
- return [
+ $casters = [
'*' => function ($v, array $a, Stub $s, $isNested) {
if (!$v instanceof Stub) {
foreach ($a as $k => $v) {
@@ -92,5 +93,11 @@ abstract class DataCollector implements DataCollectorInterface, \Serializable
return $a;
},
];
+
+ if (method_exists(ReflectionCaster::class, 'unsetClosureFileInfo')) {
+ $casters += ReflectionCaster::UNSET_CLOSURE_FILE_INFO;
+ }
+
+ return $casters;
}
}
diff --git a/src/Symfony/Component/HttpKernel/Debug/FileLinkFormatter.php b/src/Symfony/Component/HttpKernel/Debug/FileLinkFormatter.php
index d83f7920df..54defbf1d1 100644
--- a/src/Symfony/Component/HttpKernel/Debug/FileLinkFormatter.php
+++ b/src/Symfony/Component/HttpKernel/Debug/FileLinkFormatter.php
@@ -87,21 +87,17 @@ class FileLinkFormatter
private function getFileLinkFormat()
{
- if ($this->fileLinkFormat) {
- return $this->fileLinkFormat;
- }
if ($this->requestStack && $this->baseDir && $this->urlFormat) {
$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,
$this->baseDir.\DIRECTORY_SEPARATOR, '',
];
}
}
+
+ return $this->fileLinkFormat;
}
}
diff --git a/src/Symfony/Component/HttpKernel/Tests/Debug/FileLinkFormatterTest.php b/src/Symfony/Component/HttpKernel/Tests/Debug/FileLinkFormatterTest.php
index 5c93bd90e3..1f4d298bf3 100644
--- a/src/Symfony/Component/HttpKernel/Tests/Debug/FileLinkFormatterTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/Debug/FileLinkFormatterTest.php
@@ -34,18 +34,6 @@ class FileLinkFormatterTest extends TestCase
$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()
{
$file = __DIR__.\DIRECTORY_SEPARATOR.'file.php';
diff --git a/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php b/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php
index 5290fedfcf..c686962dbd 100644
--- a/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php
+++ b/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php
@@ -20,6 +20,8 @@ use Symfony\Component\VarDumper\Cloner\Stub;
*/
class ReflectionCaster
{
+ const UNSET_CLOSURE_FILE_INFO = ['Closure' => __CLASS__.'::unsetClosureFileInfo'];
+
private static $extraMap = [
'docComment' => 'getDocComment',
'extension' => 'getExtensionName',
@@ -46,15 +48,20 @@ class ReflectionCaster
$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) {
$stub->cut += ($c->getFileName() ? 2 : 0) + \count($a);
return [];
}
- unset($a[$prefix.'parameters']);
-
- if ($f = $c->getFileName()) {
+ if ($f) {
$a[$prefix.'file'] = new LinkStub($f, $c->getStartLine());
$a[$prefix.'line'] = $c->getStartLine().' to '.$c->getEndLine();
}
@@ -62,6 +69,13 @@ class ReflectionCaster
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)
{
if (!class_exists('ReflectionGenerator', false)) {
diff --git a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php
index 80b5d548fb..7891edf2a4 100644
--- a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php
+++ b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php
@@ -279,7 +279,7 @@ abstract class AbstractCloner implements ClonerInterface
$stub->class = get_parent_class($class).'@anonymous';
}
if (isset($this->classInfo[$class])) {
- list($i, $parents, $hasDebugInfo) = $this->classInfo[$class];
+ list($i, $parents, $hasDebugInfo, $fileInfo) = $this->classInfo[$class];
} else {
$i = 2;
$parents = [$class];
@@ -295,9 +295,16 @@ abstract class AbstractCloner implements ClonerInterface
}
$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);
try {
diff --git a/src/Symfony/Component/VarDumper/Dumper/CliDumper.php b/src/Symfony/Component/VarDumper/Dumper/CliDumper.php
index 3fb7ac619e..3ca3e33587 100644
--- a/src/Symfony/Component/VarDumper/Dumper/CliDumper.php
+++ b/src/Symfony/Component/VarDumper/Dumper/CliDumper.php
@@ -274,6 +274,7 @@ class CliDumper extends AbstractDumper
public function enterHash(Cursor $cursor, $type, $class, $hasChild)
{
$this->dumpKey($cursor);
+ $attr = $cursor->attr;
if ($this->collapseNextHash) {
$cursor->skipChildren = true;
@@ -282,11 +283,11 @@ class CliDumper extends AbstractDumper
$class = $this->utf8Encode($class);
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) {
- $prefix = $this->style('note', $class.' resource').($hasChild ? ' {' : ' ');
+ $prefix = $this->style('note', $class.' resource', $attr).($hasChild ? ' {' : ' ');
} 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) {
@@ -454,11 +455,9 @@ class CliDumper extends AbstractDumper
goto href;
}
- $style = $this->styles[$style];
-
$map = static::$controlCharsMap;
$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) {
$s = $startCchr;
$c = $c[$i = 0];
@@ -473,7 +472,7 @@ class CliDumper extends AbstractDumper
if ($cchrCount && "\033" === $value[0]) {
$value = substr($value, \strlen($startCchr));
} else {
- $value = "\033[{$style}m".$value;
+ $value = "\033[{$this->styles[$style]}m".$value;
}
if ($cchrCount && $endCchr === substr($value, -\strlen($endCchr))) {
$value = substr($value, 0, -\strlen($endCchr));
@@ -485,7 +484,11 @@ class CliDumper extends AbstractDumper
href:
if ($this->colors && $this->handlesHrefGracefully) {
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'])) {
$value = "\033]8;;{$attr['href']}\033\\{$value}\033]8;;\033\\";
@@ -632,7 +635,7 @@ class CliDumper extends AbstractDumper
private function getSourceLink($file, $line)
{
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;
diff --git a/src/Symfony/Component/VarDumper/Dumper/ContextProvider/RequestContextProvider.php b/src/Symfony/Component/VarDumper/Dumper/ContextProvider/RequestContextProvider.php
index bb3cc863fe..3684a47535 100644
--- a/src/Symfony/Component/VarDumper/Dumper/ContextProvider/RequestContextProvider.php
+++ b/src/Symfony/Component/VarDumper/Dumper/ContextProvider/RequestContextProvider.php
@@ -12,6 +12,7 @@
namespace Symfony\Component\VarDumper\Dumper\ContextProvider;
use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\VarDumper\Caster\ReflectionCaster;
use Symfony\Component\VarDumper\Cloner\VarCloner;
/**
@@ -29,6 +30,7 @@ final class RequestContextProvider implements ContextProviderInterface
$this->requestStack = $requestStack;
$this->cloner = new VarCloner();
$this->cloner->setMaxItems(0);
+ $this->cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO);
}
public function getContext(): ?array
diff --git a/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php
index 37651816e6..0acf3b502b 100644
--- a/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php
+++ b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php
@@ -314,13 +314,17 @@ return function (root, x) {
}
function a(e, f) {
- addEventListener(root, e, function (e) {
+ addEventListener(root, e, function (e, n) {
if ('A' == e.target.tagName) {
f(e.target, e);
} else if ('A' == e.target.parentNode.tagName) {
f(e.target.parentNode, e);
- } else if (e.target.nextElementSibling && 'A' == e.target.nextElementSibling.tagName) {
- f(e.target.nextElementSibling, e, true);
+ } else if ((n = e.target.nextElementSibling) && 'A' == n.tagName) {
+ 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']) {
$style .= sprintf(' title="%d%s characters"', $attr['length'], $attr['binary'] ? ' binary or non-UTF-8' : '');
} elseif ('note' === $style && false !== $c = strrpos($v, '\\')) {
- return sprintf('%s', $v, $style, substr($v, $c + 1));
+ if (isset($attr['file']) && $link = $this->getSourceLink($attr['file'], isset($attr['line']) ? $attr['line'] : 0)) {
+ $link = sprintf('^', esc($this->utf8Encode($link)));
+ } else {
+ $link = '';
+ }
+
+ return sprintf('%s%s', $v, $style, substr($v, $c + 1), $link);
} elseif ('protected' === $style) {
$style .= ' title="Protected property"';
} elseif ('meta' === $style && isset($attr['title'])) {
diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php
index f53f06ab5f..ebe9ab4b94 100644
--- a/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php
+++ b/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php
@@ -114,7 +114,7 @@ EOTXT
{
$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()
diff --git a/src/Symfony/Component/VarDumper/Tests/Cloner/VarClonerTest.php b/src/Symfony/Component/VarDumper/Tests/Cloner/VarClonerTest.php
index 3b180af498..d3141c6eaf 100644
--- a/src/Symfony/Component/VarDumper/Tests/Cloner/VarClonerTest.php
+++ b/src/Symfony/Component/VarDumper/Tests/Cloner/VarClonerTest.php
@@ -411,6 +411,8 @@ Symfony\Component\VarDumper\Cloner\Data Object
[position] => 1
[attr] => Array
(
+ [file] => %a%eVarClonerTest.php
+ [line] => 20
)
)
diff --git a/src/Symfony/Component/VarDumper/VarDumper.php b/src/Symfony/Component/VarDumper/VarDumper.php
index 4271e63965..009f662f3b 100644
--- a/src/Symfony/Component/VarDumper/VarDumper.php
+++ b/src/Symfony/Component/VarDumper/VarDumper.php
@@ -11,6 +11,7 @@
namespace Symfony\Component\VarDumper;
+use Symfony\Component\VarDumper\Caster\ReflectionCaster;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
@@ -29,6 +30,7 @@ class VarDumper
{
if (null === self::$handler) {
$cloner = new VarCloner();
+ $cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO);
if (isset($_SERVER['VAR_DUMPER_FORMAT'])) {
$dumper = 'html' === $_SERVER['VAR_DUMPER_FORMAT'] ? new HtmlDumper() : new CliDumper();