Skip to content

Commit a596b1c

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

File tree

11 files changed

+466
-24
lines changed

11 files changed

+466
-24
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('var_dump.cloner'),
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('var_dump.cloner'),
122+
]),
114123
],
115124
])
116125

src/Symfony/Bundle/DebugBundle/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
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",

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 options when using `dd()` and `dump()`
910

1011
6.2
1112
---

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

Lines changed: 17 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,14 @@ private function getStub(mixed $item)
429436

430437
return $stub;
431438
}
439+
440+
private function dumpDebugBacktrace(DumperInterface $dumper, Cursor $cursor): void
441+
{
442+
$backtrace = $cursor->attr['backtrace'];
443+
444+
$dumper->dumpScalar($cursor, 'default', '');
445+
$dumper->dumpScalar($cursor, 'default', '**DEBUG BACKTRACE**');
446+
447+
$backtrace->dump($dumper);
448+
}
432449
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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\Dumper\VarDumperOptions;
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 readonly ClonerInterface $cloner
30+
) {
31+
}
32+
33+
public function getContext(): ?array
34+
{
35+
if (false === $this->limit) {
36+
return [];
37+
}
38+
39+
$context = [];
40+
$traces = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS);
41+
42+
for ($i = self::BACKTRACE_CONTEXT_PROVIDER_DEPTH; $i < \count($traces); ++$i) {
43+
if (VarDumperOptions::class === ($traces[$i + 1]['class'] ?? null)) {
44+
continue;
45+
}
46+
47+
$context[] = $traces[$i];
48+
49+
if ($this->limit === \count($context)) {
50+
break;
51+
}
52+
}
53+
54+
$stub = new TraceStub($context);
55+
56+
return ['backtrace' => $this->cloner->cloneVar($stub->value)];
57+
}
58+
}
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
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+
public const AVAILABLE_OPTIONS = [
33+
self::FORMAT,
34+
self::TRACE,
35+
self::MAX_ITEMS,
36+
self::MIN_DEPTH,
37+
self::MAX_STRING,
38+
self::MAX_DEPTH,
39+
self::MAX_ITEMS_PER_DEPTH,
40+
self::THEME,
41+
self::FLAGS,
42+
self::CHARSET,
43+
];
44+
45+
private array $options;
46+
47+
public function __construct(array $options = [])
48+
{
49+
$this->options = array_filter(
50+
$options,
51+
static fn (mixed $key): bool => \in_array($key, self::AVAILABLE_OPTIONS),
52+
ARRAY_FILTER_USE_KEY
53+
);
54+
}
55+
56+
/**
57+
* @template T
58+
* @param T ...$vars
59+
* @return T
60+
*/
61+
public function dump(mixed ...$vars): mixed
62+
{
63+
if (!$vars) {
64+
$vars = [new ScalarStub('🐛')];
65+
}
66+
67+
return dump(...$vars + $this->options);
68+
}
69+
70+
public function dd(mixed ...$vars): never
71+
{
72+
dd(...$vars + $this->options);
73+
}
74+
75+
public function format(?string $format): static
76+
{
77+
$this->options[self::FORMAT] = $format;
78+
79+
return $this;
80+
}
81+
82+
public function trace(bool|int $trace = true): static
83+
{
84+
$this->options[self::TRACE] = $trace;
85+
86+
return $this;
87+
}
88+
89+
public function maxItems(int $maxItems): static
90+
{
91+
$this->options[self::MAX_ITEMS] = $maxItems;
92+
93+
return $this;
94+
}
95+
96+
public function minDepth(int $minDepth): static
97+
{
98+
$this->options[self::MIN_DEPTH] = $minDepth;
99+
100+
return $this;
101+
}
102+
103+
public function maxString(int $maxString): static
104+
{
105+
$this->options[self::MAX_STRING] = $maxString;
106+
107+
return $this;
108+
}
109+
110+
public function maxDepth(int $maxDepth): static
111+
{
112+
$this->options[self::MAX_DEPTH] = $maxDepth;
113+
114+
return $this;
115+
}
116+
117+
public function maxItemsPerDepth(int $maxItemsPerDepth): static
118+
{
119+
$this->options[self::MAX_ITEMS_PER_DEPTH] = $maxItemsPerDepth;
120+
121+
return $this;
122+
}
123+
124+
public function theme(?string $theme): static
125+
{
126+
$this->options[self::THEME] = $theme ?? 'dark';
127+
128+
return $this;
129+
}
130+
131+
/**
132+
* @param AbstractDumper::DUMP_* $flags
133+
*/
134+
public function flags(int $flags): static
135+
{
136+
$this->options[self::FLAGS] = $flags;
137+
138+
return $this;
139+
}
140+
141+
/**
142+
* Display arrays with short form (omitting elements count and `array` prefix).
143+
*/
144+
public function showLightArray(): static
145+
{
146+
$this->options[self::FLAGS] = ($this->options[self::FLAGS] ?? 0) | AbstractDumper::DUMP_LIGHT_ARRAY;
147+
148+
return $this;
149+
}
150+
151+
/**
152+
* Display string lengths, just before its value.
153+
*/
154+
public function showStringLength(): static
155+
{
156+
$this->options[self::FLAGS] = ($this->options[self::FLAGS] ?? 0) | AbstractDumper::DUMP_STRING_LENGTH;
157+
158+
return $this;
159+
}
160+
161+
/**
162+
* Display a comma at the end of the line of an array element.
163+
*/
164+
public function showCommaSeparator(): static
165+
{
166+
$this->options[self::FLAGS] = ($this->options[self::FLAGS] ?? 0) | AbstractDumper::DUMP_COMMA_SEPARATOR;
167+
168+
return $this;
169+
}
170+
171+
/**
172+
* Display a trailing comma after the last element of an array.
173+
*/
174+
public function showTrailingComma(): static
175+
{
176+
$this->options[self::FLAGS] = ($this->options[self::FLAGS] ?? 0) | AbstractDumper::DUMP_TRAILING_COMMA;
177+
178+
return $this;
179+
}
180+
181+
public function charset(string $charset): static
182+
{
183+
$this->options[self::CHARSET] = $charset;
184+
185+
return $this;
186+
}
187+
188+
public function get(string $option): mixed
189+
{
190+
return $this->options[$option] ?? null;
191+
}
192+
193+
public function toArray(): array
194+
{
195+
return $this->options;
196+
}
197+
}

0 commit comments

Comments
 (0)