Skip to content

Commit 1a3ab94

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

File tree

14 files changed

+693
-20
lines changed

14 files changed

+693
-20
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 (true === $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: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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 ?int $limit = null,
27+
private readonly ?FileLinkFormatter $fileLinkFormatter = null
28+
) {
29+
}
30+
31+
public function getContext(): ?array
32+
{
33+
$context = [];
34+
$trace = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS);
35+
36+
for ($i = self::BACKTRACE_CONTEXT_PROVIDER_DEPTH; $i < \count($trace); ++$i) {
37+
$file = $trace[$i]['file'];
38+
$line = $trace[$i]['line'];
39+
40+
$name = str_replace('\\', '/', $file);
41+
$name = substr($name, strrpos($name, '/') + 1);
42+
43+
if ($this->fileLinkFormatter) {
44+
$fileLink = $this->fileLinkFormatter->format($file, $line);
45+
}
46+
47+
$context[] = ['name' => $name, 'file' => $file, 'line' => $line, 'file_link' => $fileLink ?? null];
48+
49+
if ($this->limit === \count($context)) {
50+
break;
51+
}
52+
}
53+
54+
return $context;
55+
}
56+
}
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: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
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+
class VarDumperOptions
18+
{
19+
public const FORMAT = '_format';
20+
public const TRACE = '_trace';
21+
public const TRACE_LIMIT = '_trace_limit';
22+
public const MAX_ITEMS = '_max_items';
23+
public const MIN_DEPTH = '_min_depth';
24+
public const MAX_STRING = '_max_string';
25+
public const MAX_DEPTH = '_max_depth';
26+
public const MAX_ITEMS_PER_DEPTH = '_max_items_per_depth';
27+
public const THEME = '_theme';
28+
public const FLAGS = '_flags';
29+
public const CHARSET = '_charset';
30+
31+
private array $options = [
32+
self::FORMAT => null,
33+
self::TRACE => false,
34+
self::TRACE_LIMIT => 0,
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, $options);
48+
}
49+
50+
public function format(?string $format): static
51+
{
52+
$this->options[self::FORMAT] = $format;
53+
54+
return $this;
55+
}
56+
57+
public function trace(): static
58+
{
59+
$this->options[self::TRACE] = true;
60+
61+
return $this;
62+
}
63+
64+
public function traceLimit(int $limit): static
65+
{
66+
$this->options[self::TRACE_LIMIT] = max(0, $limit);
67+
68+
return $this;
69+
}
70+
71+
public function maxItems(int $maxItems): static
72+
{
73+
$this->options[self::MAX_ITEMS] = $maxItems;
74+
75+
return $this;
76+
}
77+
78+
public function minDepth(int $minDepth): static
79+
{
80+
$this->options[self::MIN_DEPTH] = $minDepth;
81+
82+
return $this;
83+
}
84+
85+
public function maxString(int $maxString): static
86+
{
87+
$this->options[self::MAX_STRING] = $maxString;
88+
89+
return $this;
90+
}
91+
92+
public function maxDepth(int $maxDepth): static
93+
{
94+
$this->options[self::MAX_DEPTH] = $maxDepth;
95+
96+
return $this;
97+
}
98+
99+
public function maxItemsPerDepth(int $maxItemsPerDepth): static
100+
{
101+
$this->options[self::MAX_ITEMS_PER_DEPTH] = $maxItemsPerDepth;
102+
103+
return $this;
104+
}
105+
106+
public function theme(?HtmlDumperTheme $theme): static
107+
{
108+
$this->options[self::THEME] = $theme ?? HtmlDumperTheme::Dark;
109+
110+
return $this;
111+
}
112+
113+
/**
114+
* Set flags manually. Valid flags are {@see AbstractDumper::DUMP_*} constants.
115+
*/
116+
public function flags(int $flags): static
117+
{
118+
$this->options[self::FLAGS] = $flags;
119+
120+
return $this;
121+
}
122+
123+
/**
124+
* Display arrays with short form (omitting elements count and `array` prefix).
125+
*/
126+
public function lightArray(): static
127+
{
128+
$this->options[self::FLAGS] |= AbstractDumper::DUMP_LIGHT_ARRAY;
129+
130+
return $this;
131+
}
132+
133+
/**
134+
* Display string lengths, just before its value.
135+
*/
136+
public function stringLength(): static
137+
{
138+
$this->options[self::FLAGS] |= AbstractDumper::DUMP_STRING_LENGTH;
139+
140+
return $this;
141+
}
142+
143+
/**
144+
* Display a comma at the end of the line of an array element.
145+
*/
146+
public function commaSeparator(): static
147+
{
148+
$this->options[self::FLAGS] |= AbstractDumper::DUMP_COMMA_SEPARATOR;
149+
150+
return $this;
151+
}
152+
153+
/**
154+
* Display a trailing comma after the last element of an array.
155+
*/
156+
public function trailingComma(): static
157+
{
158+
$this->options[self::FLAGS] |= AbstractDumper::DUMP_TRAILING_COMMA;
159+
160+
return $this;
161+
}
162+
163+
public function charset(string $charset): static
164+
{
165+
$this->options[self::CHARSET] = $charset;
166+
167+
return $this;
168+
}
169+
170+
public function get(string $option): mixed
171+
{
172+
return $this->options[$option] ?? null;
173+
}
174+
175+
public function getOptions(): array
176+
{
177+
return $this->options;
178+
}
179+
180+
public function set(string $option, mixed $value): static
181+
{
182+
$this->options[$option] = $value;
183+
184+
return $this;
185+
}
186+
}

0 commit comments

Comments
 (0)