Skip to content

Commit 1b933a2

Browse files
[VarDumper] Add an options builder to configure VarDumper's behaviour at runtime
1 parent d8d93c6 commit 1b933a2

File tree

14 files changed

+703
-23
lines changed

14 files changed

+703
-23
lines changed

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

Lines changed: 9 additions & 0 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;
@@ -87,6 +88,10 @@
8788
param('kernel.project_dir'),
8889
service('debug.file_link_formatter')->nullOnInvalid(),
8990
]),
91+
'backtrace' => inline_service(BacktraceContextProvider::class)->args([
92+
null,
93+
service('debug.file_link_formatter')->nullOnInvalid(),
94+
]),
9095
],
9196
])
9297

@@ -111,6 +116,10 @@
111116
]),
112117
'request' => inline_service(RequestContextProvider::class)->args([service('request_stack')]),
113118
'cli' => inline_service(CliContextProvider::class),
119+
'backtrace' => inline_service(BacktraceContextProvider::class)->args([
120+
null,
121+
service('debug.file_link_formatter')->nullOnInvalid(),
122+
]),
114123
],
115124
])
116125

src/Symfony/Bundle/DebugBundle/composer.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,16 @@
2121
"symfony/dependency-injection": "^5.4|^6.0",
2222
"symfony/http-kernel": "^5.4|^6.0",
2323
"symfony/twig-bridge": "^5.4|^6.0",
24-
"symfony/var-dumper": "^5.4|^6.0"
24+
"symfony/var-dumper": "^6.3"
2525
},
2626
"require-dev": {
2727
"symfony/config": "^5.4|^6.0",
2828
"symfony/web-profiler-bundle": "^5.4|^6.0"
2929
},
3030
"conflict": {
3131
"symfony/config": "<5.4",
32-
"symfony/dependency-injection": "<5.4"
32+
"symfony/dependency-injection": "<5.4",
33+
"symfony/var-dumper": "<6.3"
3334
},
3435
"suggest": {
3536
"symfony/config": "For service container configuration",

src/Symfony/Component/VarDumper/CHANGELOG.md

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

77
* Add caster for `WeakMap`
88
* Add support of named arguments to `dd()` and `dump()` to display the argument name
9+
* Add support of dumper's behavior configuration with the special `_options` named argument for `dump` and `dd`
910

1011
6.2
1112
---

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
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;
17+
use Symfony\Component\VarDumper\Dumper\VarDumperOptions;
1618

1719
/**
1820
* @author Nicolas Grekas <p@tchwork.com>
@@ -268,6 +270,7 @@ public function dump(DumperInterface $dumper)
268270
$refs = [0];
269271
$cursor = new Cursor();
270272
$label = $this->context['label'] ?? '';
273+
$options = $this->context['options'] ?? new VarDumperOptions();
271274

272275
if ($cursor->attr = $this->context[SourceContextProvider::class] ?? []) {
273276
$cursor->attr['if_links'] = true;
@@ -279,6 +282,10 @@ public function dump(DumperInterface $dumper)
279282
}
280283

281284
$this->dumpItem($dumper, $cursor, $refs, $this->data[$this->position][$this->key]);
285+
286+
if (false !== $options->get(VarDumperOptions::TRACE) && $cursor->attr = $this->context[BacktraceContextProvider::class] ?? []) {
287+
$this->dumpDebugBacktrace($dumper, $cursor);
288+
}
282289
}
283290

284291
/**
@@ -429,4 +436,26 @@ private function getStub(mixed $item)
429436

430437
return $stub;
431438
}
439+
440+
private function dumpDebugBacktrace(DumperInterface $dumper, Cursor $cursor): void
441+
{
442+
$traces = $cursor->attr;
443+
$dumper->dumpScalar($cursor, 'default', '');
444+
$dumper->enterHash($cursor, Cursor::HASH_OBJECT, '**DEBUG BACKTRACE**', true);
445+
446+
foreach ($traces as $trace) {
447+
$traceCursor = new Cursor();
448+
$traceCursor->attr = $trace;
449+
450+
$cursor->hashType = -1;
451+
452+
$traceCursor->attr['if_links'] = true;
453+
$dumper->dumpScalar($cursor, 'default', ' ');
454+
$dumper->dumpScalar($traceCursor, 'default', sprintf('%s:%s', $trace['file'], $trace['line']));
455+
456+
$cursor->hashType = 0;
457+
}
458+
459+
$dumper->leaveHash($cursor, Cursor::HASH_OBJECT, '**DEBUG BACKTRACE**', true, 0);
460+
}
432461
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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\HttpKernel\Debug\FileLinkFormatter;
15+
16+
/**
17+
* Provides the debug stacktrace of the VarDumper call.
18+
*
19+
* @author Alexandre Daubois <alex.daubois@gmail.com>
20+
*/
21+
final class BacktraceContextProvider implements ContextProviderInterface
22+
{
23+
private const BACKTRACE_CONTEXT_PROVIDER_DEPTH = 5;
24+
25+
public function __construct(
26+
private readonly bool|int $limit,
27+
private readonly ?FileLinkFormatter $fileLinkFormatter = null
28+
) {
29+
}
30+
31+
public function getContext(): ?array
32+
{
33+
if (false === $this->limit) {
34+
return [];
35+
}
36+
37+
$context = [];
38+
$trace = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS);
39+
40+
for ($i = self::BACKTRACE_CONTEXT_PROVIDER_DEPTH; $i < \count($trace); ++$i) {
41+
$file = $trace[$i]['file'];
42+
$line = $trace[$i]['line'];
43+
44+
$name = str_replace('\\', '/', $file);
45+
$name = substr($name, strrpos($name, '/') + 1);
46+
47+
if ($this->fileLinkFormatter) {
48+
$fileLink = $this->fileLinkFormatter->format($file, $line);
49+
}
50+
51+
$context[] = ['name' => $name, 'file' => $file, 'line' => $line, 'file_link' => $fileLink ?? null];
52+
53+
if ($this->limit === \count($context)) {
54+
break;
55+
}
56+
}
57+
58+
return $context;
59+
}
60+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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+
/**
15+
* @author Alexandre Daubois <alex.daubois@gmail.com>
16+
*/
17+
enum HtmlDumperTheme: string
18+
{
19+
case Dark = 'dark';
20+
case Light = 'light';
21+
}
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
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\Caster\ScalarStub;
15+
16+
/**
17+
* @author Alexandre Daubois <alex.daubois@gmail.com>
18+
*/
19+
class VarDumperOptions
20+
{
21+
public const FORMAT = '_format';
22+
public const TRACE = '_trace';
23+
public const MAX_ITEMS = '_max_items';
24+
public const MIN_DEPTH = '_min_depth';
25+
public const MAX_STRING = '_max_string';
26+
public const MAX_DEPTH = '_max_depth';
27+
public const MAX_ITEMS_PER_DEPTH = '_max_items_per_depth';
28+
public const THEME = '_theme';
29+
public const FLAGS = '_flags';
30+
public const CHARSET = '_charset';
31+
32+
private array $options = [
33+
self::FORMAT => null,
34+
self::TRACE => false,
35+
self::MAX_ITEMS => null,
36+
self::MIN_DEPTH => null,
37+
self::MAX_STRING => null,
38+
self::MAX_DEPTH => null,
39+
self::MAX_ITEMS_PER_DEPTH => null,
40+
self::THEME => HtmlDumperTheme::Dark,
41+
self::FLAGS => 0,
42+
self::CHARSET => null,
43+
];
44+
45+
public function __construct(array $options = [])
46+
{
47+
$this->options = array_replace($this->options, \array_intersect_key($options, $this->options));
48+
}
49+
50+
public function dump(mixed ...$vars): mixed
51+
{
52+
if (!$vars) {
53+
$vars = [new ScalarStub('🐛')];
54+
}
55+
56+
return dump(...($vars + $this->options));
57+
}
58+
59+
public function dd(mixed ...$vars): never
60+
{
61+
dd(...($vars + $this->options));
62+
}
63+
64+
public function format(?string $format): static
65+
{
66+
$this->options[self::FORMAT] = $format;
67+
68+
return $this;
69+
}
70+
71+
public function trace(bool|int $trace = true): static
72+
{
73+
$this->options[self::TRACE] = $trace;
74+
75+
return $this;
76+
}
77+
78+
public function maxItems(int $maxItems): static
79+
{
80+
$this->options[self::MAX_ITEMS] = $maxItems;
81+
82+
return $this;
83+
}
84+
85+
public function minDepth(int $minDepth): static
86+
{
87+
$this->options[self::MIN_DEPTH] = $minDepth;
88+
89+
return $this;
90+
}
91+
92+
public function maxString(int $maxString): static
93+
{
94+
$this->options[self::MAX_STRING] = $maxString;
95+
96+
return $this;
97+
}
98+
99+
public function maxDepth(int $maxDepth): static
100+
{
101+
$this->options[self::MAX_DEPTH] = $maxDepth;
102+
103+
return $this;
104+
}
105+
106+
public function maxItemsPerDepth(int $maxItemsPerDepth): static
107+
{
108+
$this->options[self::MAX_ITEMS_PER_DEPTH] = $maxItemsPerDepth;
109+
110+
return $this;
111+
}
112+
113+
public function theme(?HtmlDumperTheme $theme): static
114+
{
115+
$this->options[self::THEME] = $theme ?? HtmlDumperTheme::Dark;
116+
117+
return $this;
118+
}
119+
120+
/**
121+
* Set flags manually. Valid flags are {@see AbstractDumper::DUMP_*} constants.
122+
*/
123+
public function flags(int $flags): static
124+
{
125+
$this->options[self::FLAGS] = $flags;
126+
127+
return $this;
128+
}
129+
130+
/**
131+
* Display arrays with short form (omitting elements count and `array` prefix).
132+
*/
133+
public function lightArray(): static
134+
{
135+
$this->options[self::FLAGS] |= AbstractDumper::DUMP_LIGHT_ARRAY;
136+
137+
return $this;
138+
}
139+
140+
/**
141+
* Display string lengths, just before its value.
142+
*/
143+
public function stringLength(): static
144+
{
145+
$this->options[self::FLAGS] |= AbstractDumper::DUMP_STRING_LENGTH;
146+
147+
return $this;
148+
}
149+
150+
/**
151+
* Display a comma at the end of the line of an array element.
152+
*/
153+
public function commaSeparator(): static
154+
{
155+
$this->options[self::FLAGS] |= AbstractDumper::DUMP_COMMA_SEPARATOR;
156+
157+
return $this;
158+
}
159+
160+
/**
161+
* Display a trailing comma after the last element of an array.
162+
*/
163+
public function trailingComma(): static
164+
{
165+
$this->options[self::FLAGS] |= AbstractDumper::DUMP_TRAILING_COMMA;
166+
167+
return $this;
168+
}
169+
170+
public function charset(string $charset): static
171+
{
172+
$this->options[self::CHARSET] = $charset;
173+
174+
return $this;
175+
}
176+
177+
public function get(string $option): mixed
178+
{
179+
return $this->options[$option] ?? null;
180+
}
181+
182+
public function getOptions(): array
183+
{
184+
return $this->options;
185+
}
186+
187+
public function set(string $option, mixed $value): static
188+
{
189+
$this->options[$option] = $value;
190+
191+
return $this;
192+
}
193+
}

0 commit comments

Comments
 (0)