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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy