Skip to content

Commit 893f586

Browse files
committed
Adding dump call location caret.
1 parent 63f4d13 commit 893f586

File tree

7 files changed

+229
-7
lines changed

7 files changed

+229
-7
lines changed

src/Symfony/Component/VarDumper/Caster/Caster.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,6 @@ public static function castPhpIncompleteClass(\__PHP_Incomplete_Class $c, array
158158
unset($a['__PHP_Incomplete_Class_Name']);
159159
}
160160

161-
162161
return $a;
163162
}
164163
}

src/Symfony/Component/VarDumper/Cloner/Data.php

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class Data implements \ArrayAccess, \Countable, \IteratorAggregate
2424
private $maxDepth = 20;
2525
private $maxItemsPerDepth = -1;
2626
private $useRefHandles = -1;
27+
private $context = [];
2728

2829
/**
2930
* @param array $data An array as returned by ClonerInterface::cloneVar()
@@ -278,10 +279,10 @@ private function dumpItem($dumper, $cursor, &$refs, $item)
278279
$cursor->refIndex = 0;
279280
$cursor->softRefTo = $cursor->softRefHandle = $cursor->softRefCount = 0;
280281
$cursor->hardRefTo = $cursor->hardRefHandle = $cursor->hardRefCount = 0;
282+
$cursor->attr = $this->context;
281283
$firstSeen = true;
282284

283285
if (!$item instanceof Stub) {
284-
$cursor->attr = [];
285286
$type = \gettype($item);
286287
if ($item && 'array' === $type) {
287288
$item = $this->getStub($item);
@@ -297,7 +298,7 @@ private function dumpItem($dumper, $cursor, &$refs, $item)
297298
$cursor->hardRefHandle = $this->useRefHandles & $item->handle;
298299
$cursor->hardRefCount = $item->refCount;
299300
}
300-
$cursor->attr = $item->attr;
301+
$cursor->attr += $item->attr;
301302
$type = $item->class ?: \gettype($item->value);
302303
$item = $this->getStub($item->value);
303304
}
@@ -312,7 +313,7 @@ private function dumpItem($dumper, $cursor, &$refs, $item)
312313
}
313314
$cursor->softRefHandle = $this->useRefHandles & $item->handle;
314315
$cursor->softRefCount = $item->refCount;
315-
$cursor->attr = $item->attr;
316+
$cursor->attr += $item->attr;
316317
$cut = $item->cut;
317318

318319
if ($item->position && $firstSeen) {
@@ -420,4 +421,14 @@ private function getStub($item)
420421

421422
return $stub;
422423
}
424+
425+
public function getContext(): array
426+
{
427+
return $this->context;
428+
}
429+
430+
public function setContext(array $context): void
431+
{
432+
$this->context = $context;
433+
}
423434
}

src/Symfony/Component/VarDumper/Dumper/CliDumper.php

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ public function enterHash(Cursor $cursor, $type, $class, $hasChild)
298298
$prefix = substr($prefix, 0, -1);
299299
}
300300

301-
$this->line .= $prefix;
301+
$this->line .= $prefix.$this->dumpLocation($cursor);
302302

303303
if ($hasChild) {
304304
$this->dumpLine($cursor->depth);
@@ -640,4 +640,28 @@ private function getSourceLink($file, $line)
640640

641641
return false;
642642
}
643+
644+
/**
645+
* Return the substring with the dump() function file location to get displayed with a small caret after
646+
* an hash.
647+
*
648+
* @param Cursor $cursor
649+
*
650+
* @return string
651+
*/
652+
private function dumpLocation(Cursor $cursor): string
653+
{
654+
if (
655+
$this->handlesHrefGracefully
656+
&& 0 === $cursor->depth
657+
&& !empty($cursor->attr['source_context']['file'])
658+
) {
659+
$href = $this->getSourceLink($cursor->attr['source_context']['file'], $cursor->attr['source_context']['line'] ?? 0);
660+
$color = $this->colors ? "\033" : '';
661+
662+
return " {$color}]8;;{$href}{$color}\\[^]{$color}]8;;{$color}\\";
663+
}
664+
665+
return '';
666+
}
643667
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\VarDumper\Dumper;
13+
14+
use Symfony\Component\VarDumper\Cloner\Data;
15+
use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface;
16+
17+
/**
18+
* @author Kévin Thérage <therage.kevin@gmail.com>
19+
*/
20+
class ContextualizedDumper implements DataDumperInterface
21+
{
22+
private $wrappedDumper;
23+
private $contextProviders;
24+
25+
/**
26+
* @param DataDumperInterface $wrappedDumper
27+
* @param ContextProviderInterface[] $contextProviders
28+
*/
29+
public function __construct(DataDumperInterface $wrappedDumper, array $contextProviders)
30+
{
31+
$this->wrappedDumper = $wrappedDumper;
32+
$this->contextProviders = $contextProviders;
33+
}
34+
35+
public function dump(Data $data): void
36+
{
37+
$data->setContext($this->getContext());
38+
$this->wrappedDumper->dump($data);
39+
}
40+
41+
private function getContext(): array
42+
{
43+
$context = [];
44+
foreach ($this->contextProviders as $name => $contextProvider) {
45+
if ($contextProvider instanceof ContextProviderInterface) {
46+
$context[$name] = $contextProvider->getContext();
47+
}
48+
}
49+
50+
return $context;
51+
}
52+
}

src/Symfony/Component/VarDumper/Tests/Cloner/VarClonerTest.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ public function testMaxIntBoundary()
5252
[maxDepth:Symfony\Component\VarDumper\Cloner\Data:private] => 20
5353
[maxItemsPerDepth:Symfony\Component\VarDumper\Cloner\Data:private] => -1
5454
[useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1
55+
[context:Symfony\Component\VarDumper\Cloner\Data:private] => Array
56+
(
57+
)
58+
5559
)
5660
5761
EOTXT;
@@ -140,6 +144,10 @@ public function testClone()
140144
[maxDepth:Symfony\Component\VarDumper\Cloner\Data:private] => 20
141145
[maxItemsPerDepth:Symfony\Component\VarDumper\Cloner\Data:private] => -1
142146
[useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1
147+
[context:Symfony\Component\VarDumper\Cloner\Data:private] => Array
148+
(
149+
)
150+
143151
)
144152
145153
EOTXT;
@@ -308,6 +316,10 @@ public function testLimits()
308316
[maxDepth:Symfony\Component\VarDumper\Cloner\Data:private] => 20
309317
[maxItemsPerDepth:Symfony\Component\VarDumper\Cloner\Data:private] => -1
310318
[useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1
319+
[context:Symfony\Component\VarDumper\Cloner\Data:private] => Array
320+
(
321+
)
322+
311323
)
312324
313325
EOTXT;
@@ -326,7 +338,7 @@ public function testJsonCast()
326338
$clone = $cloner->cloneVar($data);
327339

328340
$expected = <<<'EOTXT'
329-
object(Symfony\Component\VarDumper\Cloner\Data)#%i (6) {
341+
object(Symfony\Component\VarDumper\Cloner\Data)#%d (7) {
330342
["data":"Symfony\Component\VarDumper\Cloner\Data":private]=>
331343
array(2) {
332344
[0]=>
@@ -371,6 +383,9 @@ public function testJsonCast()
371383
int(-1)
372384
["useRefHandles":"Symfony\Component\VarDumper\Cloner\Data":private]=>
373385
int(-1)
386+
["context":"Symfony\Component\VarDumper\Cloner\Data":private]=>
387+
array(0) {
388+
}
374389
}
375390

376391
EOTXT;
@@ -431,6 +446,10 @@ public function testCaster()
431446
[maxDepth:Symfony\Component\VarDumper\Cloner\Data:private] => 20
432447
[maxItemsPerDepth:Symfony\Component\VarDumper\Cloner\Data:private] => -1
433448
[useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1
449+
[context:Symfony\Component\VarDumper\Cloner\Data:private] => Array
450+
(
451+
)
452+
434453
)
435454
436455
EOTXT;
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\VarDumper\Tests\Dumper;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\VarDumper\Cloner\VarCloner;
16+
use Symfony\Component\VarDumper\Dumper\CliDumper;
17+
use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface;
18+
use Symfony\Component\VarDumper\Dumper\ContextualizedDumper;
19+
20+
/**
21+
* @author Kévin Thérage <therage.kevin@gmail.com>
22+
*/
23+
class ContextualizedDumperTest extends TestCase
24+
{
25+
public function testContextualizedCliDumper(): void
26+
{
27+
require __DIR__.'/../Fixtures/dumb-var.php';
28+
$wrappedDumper = new CliDumper('php://output');
29+
$wrappedDumper->setColors(false);
30+
31+
$dumper = new ContextualizedDumper($wrappedDumper, [
32+
'source_context' => new class() implements ContextProviderInterface {
33+
public function getContext(): ?array
34+
{
35+
return [
36+
'file' => '/home/example.php',
37+
'line' => 42,
38+
];
39+
}
40+
},
41+
]);
42+
$cloner = new VarCloner();
43+
$cloner->addCasters([
44+
':stream' => function ($res, $a) {
45+
unset($a['uri'], $a['wrapper_data']);
46+
47+
return $a;
48+
},
49+
]);
50+
$data = $cloner->cloneVar($var);
51+
52+
ob_start();
53+
$dumper->dump($data);
54+
$out = ob_get_clean();
55+
$out = preg_replace('/[ \t]+$/m', '', $out);
56+
$intMax = PHP_INT_MAX;
57+
$res = (int) $var['res'];
58+
59+
$this->assertStringMatchesFormat(
60+
<<<EOTXT
61+
array:24 [ ]8;;file:///home/example.php\[^]]8;;\
62+
"number" => 1
63+
0 => &1 null
64+
"const" => 1.1
65+
1 => true
66+
2 => false
67+
3 => NAN
68+
4 => INF
69+
5 => -INF
70+
6 => {$intMax}
71+
"str" => "déjà\\n"
72+
7 => b"""
73+
é\\x00test\\t\\n
74+
ing
75+
"""
76+
"[]" => []
77+
"res" => stream resource {@{$res}
78+
%A wrapper_type: "plainfile"
79+
stream_type: "STDIO"
80+
mode: "r"
81+
unread_bytes: 0
82+
seekable: true
83+
%A options: []
84+
}
85+
"obj" => Symfony\Component\VarDumper\Tests\Fixture\DumbFoo {#%d
86+
+foo: "foo"
87+
+"bar": "bar"
88+
}
89+
"closure" => Closure(\$a, PDO &\$b = null) {#%d
90+
class: "Symfony\Component\VarDumper\Tests\Dumper\ContextualizedDumperTest"
91+
this: Symfony\Component\VarDumper\Tests\Dumper\ContextualizedDumperTest {#%d …}
92+
file: "%s%eTests%eFixtures%edumb-var.php"
93+
line: "{$var['line']} to {$var['line']}"
94+
}
95+
"line" => {$var['line']}
96+
"nobj" => array:1 [
97+
0 => &3 {#%d}
98+
]
99+
"recurs" => &4 array:1 [
100+
0 => &4 array:1 [&4]
101+
]
102+
8 => &1 null
103+
"sobj" => Symfony\Component\VarDumper\Tests\Fixture\DumbFoo {#%d}
104+
"snobj" => &3 {#%d}
105+
"snobj2" => {#%d}
106+
"file" => "{$var['file']}"
107+
b"bin-key-é" => ""
108+
]
109+
110+
EOTXT
111+
,
112+
$out
113+
);
114+
}
115+
}

src/Symfony/Component/VarDumper/VarDumper.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
use Symfony\Component\VarDumper\Caster\ReflectionCaster;
1515
use Symfony\Component\VarDumper\Cloner\VarCloner;
1616
use Symfony\Component\VarDumper\Dumper\CliDumper;
17+
use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider;
18+
use Symfony\Component\VarDumper\Dumper\ContextualizedDumper;
1719
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
1820

1921
// Load the global dump() function
@@ -35,7 +37,7 @@ public static function dump($var)
3537
if (isset($_SERVER['VAR_DUMPER_FORMAT'])) {
3638
$dumper = 'html' === $_SERVER['VAR_DUMPER_FORMAT'] ? new HtmlDumper() : new CliDumper();
3739
} else {
38-
$dumper = \in_array(\PHP_SAPI, ['cli', 'phpdbg']) ? new CliDumper() : new HtmlDumper();
40+
$dumper = \in_array(\PHP_SAPI, ['cli', 'phpdbg']) ? new ContextualizedDumper(new CliDumper(), ['source_context' => new SourceContextProvider()]) : new HtmlDumper();
3941
}
4042

4143
self::$handler = function ($var) use ($cloner, $dumper) {

0 commit comments

Comments
 (0)