Skip to content

Commit b700997

Browse files
[VarDumper] Add an options builder to configure VarDumper's behaviour at runtime
1 parent 99b25a1 commit b700997

File tree

9 files changed

+307
-38
lines changed

9 files changed

+307
-38
lines changed

src/Symfony/Bundle/DebugBundle/Resources/config/services.php

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Symfony\Component\VarDumper\Command\Descriptor\HtmlDescriptor;
2323
use Symfony\Component\VarDumper\Command\ServerDumpCommand;
2424
use Symfony\Component\VarDumper\Dumper\CliDumper;
25+
use Symfony\Component\VarDumper\Dumper\ContextProvider\BacktraceContextProvider;
2526
use Symfony\Component\VarDumper\Dumper\ContextProvider\CliContextProvider;
2627
use Symfony\Component\VarDumper\Dumper\ContextProvider\RequestContextProvider;
2728
use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider;
@@ -35,6 +36,21 @@
3536
->set('env(VAR_DUMPER_SERVER)', '127.0.0.1:9912')
3637
;
3738

39+
$contextProviders = [
40+
'source' => inline_service(SourceContextProvider::class)->args([
41+
param('kernel.charset'),
42+
param('kernel.project_dir'),
43+
service('debug.file_link_formatter')->nullOnInvalid(),
44+
]),
45+
];
46+
47+
if (class_exists(BacktraceContextProvider::class)) {
48+
$contextProviders['backtrace'] = inline_service(BacktraceContextProvider::class)->args([
49+
0,
50+
service('var_dump.cloner')->nullOnInvalid(),
51+
]);
52+
}
53+
3854
$container->services()
3955

4056
->set('twig.extension.dump', DumpExtension::class)
@@ -81,13 +97,7 @@
8197
->decorate('var_dumper.cli_dumper')
8298
->args([
8399
service('var_dumper.contextualized_cli_dumper.inner'),
84-
[
85-
'source' => inline_service(SourceContextProvider::class)->args([
86-
param('kernel.charset'),
87-
param('kernel.project_dir'),
88-
service('debug.file_link_formatter')->nullOnInvalid(),
89-
]),
90-
],
100+
$contextProviders,
91101
])
92102

93103
->set('var_dumper.html_dumper', HtmlDumper::class)
@@ -104,11 +114,7 @@
104114
->args([
105115
'', // server host
106116
[
107-
'source' => inline_service(SourceContextProvider::class)->args([
108-
param('kernel.charset'),
109-
param('kernel.project_dir'),
110-
service('debug.file_link_formatter')->nullOnInvalid(),
111-
]),
117+
...$contextProviders,
112118
'request' => inline_service(RequestContextProvider::class)->args([service('request_stack')]),
113119
'cli' => inline_service(CliContextProvider::class),
114120
],

src/Symfony/Component/VarDumper/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ CHANGELOG
55
---
66

77
* Add support for `FORCE_COLOR` environment variable
8+
* Add support of options when using `dd()` and `dump()`
89

910
7.1
1011
---

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\VarDumper\Cloner;
1313

1414
use Symfony\Component\VarDumper\Caster\Caster;
15+
use Symfony\Component\VarDumper\Dumper\ContextProvider\BacktraceContextProvider;
1516
use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider;
1617

1718
/**
@@ -270,12 +271,17 @@ public function dump(DumperInterface $dumper): void
270271
$cursor->hashType = -1;
271272
$cursor->attr = $this->context[SourceContextProvider::class] ?? [];
272273
$label = $this->context['label'] ?? '';
274+
$options = $this->context['options'] ?? [];
273275

274276
if ($cursor->attr || '' !== $label) {
275277
$dumper->dumpScalar($cursor, 'label', $label);
276278
}
277279
$cursor->hashType = 0;
278280
$this->dumpItem($dumper, $cursor, $refs, $this->data[$this->position][$this->key]);
281+
282+
if (false !== ($options['_trace'] ?? false) && $cursor->attr = $this->context[BacktraceContextProvider::class] ?? []) {
283+
$this->dumpDebugBacktrace($dumper, $cursor);
284+
}
279285
}
280286

281287
/**
@@ -426,4 +432,14 @@ private function getStub(mixed $item): mixed
426432

427433
return $stub;
428434
}
435+
436+
private function dumpDebugBacktrace(DumperInterface $dumper, Cursor $cursor): void
437+
{
438+
$backtrace = $cursor->attr['backtrace'];
439+
440+
$dumper->dumpScalar($cursor, 'default', '');
441+
$dumper->dumpScalar($cursor, 'default', '**DEBUG BACKTRACE**');
442+
443+
$backtrace->dump($dumper);
444+
}
429445
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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\ContextProvider;
13+
14+
use Symfony\Component\VarDumper\Caster\TraceStub;
15+
use Symfony\Component\VarDumper\Cloner\ClonerInterface;
16+
use Symfony\Component\VarDumper\Cloner\VarCloner;
17+
18+
/**
19+
* Provides the debug stacktrace of the VarDumper call.
20+
*
21+
* @author Alexandre Daubois <alex.daubois@gmail.com>
22+
*/
23+
final class BacktraceContextProvider implements ContextProviderInterface
24+
{
25+
private const BACKTRACE_CONTEXT_PROVIDER_DEPTH = 4;
26+
27+
public function __construct(
28+
private readonly bool|int $limit,
29+
private ?ClonerInterface $cloner,
30+
) {
31+
$this->cloner ??= new VarCloner();
32+
}
33+
34+
public function getContext(): ?array
35+
{
36+
if (false === $this->limit) {
37+
return [];
38+
}
39+
40+
$context = [];
41+
$traces = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS);
42+
43+
for ($i = self::BACKTRACE_CONTEXT_PROVIDER_DEPTH; $i < \count($traces); ++$i) {
44+
$context[] = $traces[$i];
45+
46+
if ($this->limit === \count($context)) {
47+
break;
48+
}
49+
}
50+
51+
$stub = new TraceStub($context);
52+
53+
return ['backtrace' => $this->cloner->cloneVar($stub->value)];
54+
}
55+
}

src/Symfony/Component/VarDumper/Resources/functions/dump.php

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,33 @@
1919
*/
2020
function dump(mixed ...$vars): mixed
2121
{
22-
if (!$vars) {
23-
VarDumper::dump(new ScalarStub('🐛'));
22+
$options = array_filter(
23+
$vars,
24+
static fn (mixed $key): bool => \in_array($key, VarDumper::AVAILABLE_OPTIONS, true),
25+
\ARRAY_FILTER_USE_KEY
26+
);
2427

25-
return null;
26-
}
28+
$trace = $options['_trace'] ?? null;
29+
unset($options['_trace']);
2730

2831
if (array_key_exists(0, $vars) && 1 === count($vars)) {
29-
VarDumper::dump($vars[0]);
32+
VarDumper::dump($vars[0], null, $options);
3033
$k = 0;
3134
} else {
35+
$vars = array_filter($vars, static fn (int|string $key) => !str_starts_with($key, '_'), \ARRAY_FILTER_USE_KEY);
36+
37+
if (!$vars) {
38+
VarDumper::dump(new ScalarStub('🐛'), null, $options);
39+
40+
return null;
41+
}
42+
3243
foreach ($vars as $k => $v) {
33-
VarDumper::dump($v, is_int($k) ? 1 + $k : $k);
44+
if (array_key_last($vars) === $k) {
45+
$options['_trace'] = $trace;
46+
}
47+
48+
VarDumper::dump($v, is_int($k) ? 1 + $k : $k, $options);
3449
}
3550
}
3651

@@ -55,11 +70,21 @@ function dd(mixed ...$vars): never
5570
exit(1);
5671
}
5772

73+
$options = array_filter(
74+
$vars,
75+
static fn (mixed $key): bool => \in_array($key, VarDumper::AVAILABLE_OPTIONS, true),
76+
\ARRAY_FILTER_USE_KEY
77+
);
78+
5879
if (array_key_exists(0, $vars) && 1 === count($vars)) {
59-
VarDumper::dump($vars[0]);
80+
VarDumper::dump($vars[0], null, $options);
6081
} else {
6182
foreach ($vars as $k => $v) {
62-
VarDumper::dump($v, is_int($k) ? 1 + $k : $k);
83+
if (str_starts_with($k, '_')) {
84+
continue;
85+
}
86+
87+
VarDumper::dump($v, is_int($k) ? 1 + $k : $k, $options);
6388
}
6489
}
6590

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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\ContextProvider;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\VarDumper\Cloner\VarCloner;
16+
use Symfony\Component\VarDumper\Dumper\ContextProvider\BacktraceContextProvider;
17+
18+
class BacktraceContextProviderTest extends TestCase
19+
{
20+
public function testFalseBacktraceLimit()
21+
{
22+
$provider = new BacktraceContextProvider(false, new VarCloner());
23+
$this->assertArrayNotHasKey('backtrace', $provider->getContext());
24+
}
25+
26+
public function testPositiveBacktraceLimit()
27+
{
28+
$provider = new BacktraceContextProvider(2, new VarCloner());
29+
$this->assertCount(2, $provider->getContext()['backtrace']);
30+
}
31+
}

src/Symfony/Component/VarDumper/Tests/Dumper/ContextualizedDumperTest.php

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@
1212
namespace Symfony\Component\VarDumper\Tests\Dumper;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter;
1516
use Symfony\Component\VarDumper\Cloner\VarCloner;
1617
use Symfony\Component\VarDumper\Dumper\CliDumper;
18+
use Symfony\Component\VarDumper\Dumper\ContextProvider\BacktraceContextProvider;
1719
use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider;
1820
use Symfony\Component\VarDumper\Dumper\ContextualizedDumper;
1921

2022
/**
2123
* @author Kévin Thérage <therage.kevin@gmail.com>
24+
* @author Alexandre Daubois <alex.daubois@gmail.com>
2225
*/
2326
class ContextualizedDumperTest extends TestCase
2427
{
@@ -28,8 +31,8 @@ public function testContextualizedCliDumper()
2831
$wrappedDumper->setColors(true);
2932

3033
$var = 'example';
31-
$href = \sprintf('file://%s#L%s', __FILE__, 37);
32-
$dumper = new ContextualizedDumper($wrappedDumper, [new SourceContextProvider()]);
34+
$href = \sprintf('file://%s#L%s', __FILE__, 40);
35+
$dumper = new ContextualizedDumper($wrappedDumper, [new SourceContextProvider(fileLinkFormatter: new FileLinkFormatter())]);
3336
$cloner = new VarCloner();
3437
$data = $cloner->cloneVar($var);
3538

@@ -40,4 +43,22 @@ public function testContextualizedCliDumper()
4043
$this->assertStringContainsString("\e]8;;{$href}\e\\^\e]", $out);
4144
$this->assertStringContainsString("m{$var}\e[", $out);
4245
}
46+
47+
public function testEnablingBacktraceDisplaysIt()
48+
{
49+
$wrappedDumper = new CliDumper('php://output');
50+
$cloner = new VarCloner();
51+
52+
$dumper = new ContextualizedDumper($wrappedDumper, [new SourceContextProvider(), new BacktraceContextProvider(0, $cloner)]);
53+
54+
ob_start();
55+
$dumper->dump(
56+
$cloner->cloneVar(123)->withContext([
57+
'options' => ['_trace' => true],
58+
])
59+
);
60+
$result = ob_get_clean();
61+
62+
$this->assertStringContainsString('**DEBUG BACKTRACE**', $result);
63+
}
4364
}

src/Symfony/Component/VarDumper/Tests/Dumper/FunctionsTest.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,20 @@ public function testDumpReturnsAllNamedArgsInArray()
8585
$this->assertSame([$var1, 'second' => $var2, 'third' => $var3], $return);
8686
}
8787

88+
public function testDumpReturnsAllNamedArgsExceptSpecialOptionOnes()
89+
{
90+
$this->setupVarDumper();
91+
92+
$var1 = 'a';
93+
$var2 = 'b';
94+
95+
ob_start();
96+
$return = dump($var1, named: $var2, _trace: true, _flags: 0);
97+
ob_end_clean();
98+
99+
$this->assertSame([$var1, 'named' => $var2], $return);
100+
}
101+
88102
protected function setupVarDumper()
89103
{
90104
$cloner = new VarCloner();

0 commit comments

Comments
 (0)