From 0d0fa05f2567a41e5b43a2b420658887d7da6b50 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Thu, 1 Dec 2022 18:32:26 +0100 Subject: [PATCH] [VarDumper] Add support of named arguments to `dd()` and `dump()` to display the argument name --- CHANGELOG.md | 1 + Caster/ScalarStub.php | 27 +++++++++++++++++++++ Caster/StubCaster.php | 8 ++++++ Cloner/AbstractCloner.php | 1 + Cloner/Data.php | 12 ++++++++- Cloner/Stub.php | 1 + Dumper/ContextualizedDumper.php | 2 +- Resources/functions/dump.php | 33 ++++++++++++++++++------- Tests/Caster/StubCasterTest.php | 16 +++++++++++- Tests/Dumper/FunctionsTest.php | 43 +++++++++++++++++++++++++++++++-- VarDumper.php | 18 +++++++++++--- 11 files changed, 144 insertions(+), 18 deletions(-) create mode 100644 Caster/ScalarStub.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 97204fc6..c5abeee3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Add caster for `WeakMap` + * Add support of named arguments to `dd()` and `dump()` to display the argument name 6.2 --- diff --git a/Caster/ScalarStub.php b/Caster/ScalarStub.php new file mode 100644 index 00000000..3bb1935b --- /dev/null +++ b/Caster/ScalarStub.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents any arbitrary value. + * + * @author Alexandre Daubois + */ +class ScalarStub extends Stub +{ + public function __construct(mixed $value) + { + $this->value = $value; + } +} diff --git a/Caster/StubCaster.php b/Caster/StubCaster.php index 32ead7c2..9318ad1f 100644 --- a/Caster/StubCaster.php +++ b/Caster/StubCaster.php @@ -81,4 +81,12 @@ public static function castEnum(EnumStub $c, array $a, Stub $stub, bool $isNeste return $a; } + + public static function castScalar(ScalarStub $scalarStub, array $a, Stub $stub) + { + $stub->type = Stub::TYPE_SCALAR; + $stub->attr['value'] = $scalarStub->value; + + return $a; + } } diff --git a/Cloner/AbstractCloner.php b/Cloner/AbstractCloner.php index b7220258..bcd30137 100644 --- a/Cloner/AbstractCloner.php +++ b/Cloner/AbstractCloner.php @@ -28,6 +28,7 @@ abstract class AbstractCloner implements ClonerInterface 'Symfony\Component\VarDumper\Caster\CutArrayStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castCutArray'], 'Symfony\Component\VarDumper\Caster\ConstStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castStub'], 'Symfony\Component\VarDumper\Caster\EnumStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castEnum'], + 'Symfony\Component\VarDumper\Caster\ScalarStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castScalar'], 'Fiber' => ['Symfony\Component\VarDumper\Caster\FiberCaster', 'castFiber'], diff --git a/Cloner/Data.php b/Cloner/Data.php index 6ecb883e..d87d5690 100644 --- a/Cloner/Data.php +++ b/Cloner/Data.php @@ -211,6 +211,11 @@ public function withContext(array $context): static return $data; } + public function getContext(): array + { + return $this->context; + } + /** * Seeks to a specific key in nested data structures. */ @@ -262,11 +267,12 @@ public function dump(DumperInterface $dumper) { $refs = [0]; $cursor = new Cursor(); + $label = $this->context['label'] ?? ''; if ($cursor->attr = $this->context[SourceContextProvider::class] ?? []) { $cursor->attr['if_links'] = true; $cursor->hashType = -1; - $dumper->dumpScalar($cursor, 'default', '^'); + $dumper->dumpScalar($cursor, 'default', $label.'^'); $cursor->attr = ['if_links' => true]; $dumper->dumpScalar($cursor, 'default', ' '); $cursor->hashType = 0; @@ -362,6 +368,10 @@ private function dumpItem(DumperInterface $dumper, Cursor $cursor, array &$refs, $dumper->leaveHash($cursor, $item->type, $item->class, $withChildren, $cut); break; + case Stub::TYPE_SCALAR: + $dumper->dumpScalar($cursor, 'default', $item->attr['value']); + break; + default: throw new \RuntimeException(sprintf('Unexpected Stub type: "%s".', $item->type)); } diff --git a/Cloner/Stub.php b/Cloner/Stub.php index 1c5b8871..0c2a4b9d 100644 --- a/Cloner/Stub.php +++ b/Cloner/Stub.php @@ -23,6 +23,7 @@ class Stub public const TYPE_ARRAY = 3; public const TYPE_OBJECT = 4; public const TYPE_RESOURCE = 5; + public const TYPE_SCALAR = 6; public const STRING_BINARY = 1; public const STRING_UTF8 = 2; diff --git a/Dumper/ContextualizedDumper.php b/Dumper/ContextualizedDumper.php index 1ba803d8..c10cd444 100644 --- a/Dumper/ContextualizedDumper.php +++ b/Dumper/ContextualizedDumper.php @@ -33,7 +33,7 @@ public function __construct(DataDumperInterface $wrappedDumper, array $contextPr public function dump(Data $data) { - $context = []; + $context = $data->getContext(); foreach ($this->contextProviders as $contextProvider) { $context[$contextProvider::class] = $contextProvider->getContext(); } diff --git a/Resources/functions/dump.php b/Resources/functions/dump.php index 6221a4d1..4e7652c4 100644 --- a/Resources/functions/dump.php +++ b/Resources/functions/dump.php @@ -9,25 +9,36 @@ * file that was distributed with this source code. */ +use Symfony\Component\VarDumper\Caster\ScalarStub; use Symfony\Component\VarDumper\VarDumper; if (!function_exists('dump')) { /** * @author Nicolas Grekas + * @author Alexandre Daubois */ - function dump(mixed $var, mixed ...$moreVars): mixed + function dump(mixed ...$vars): mixed { - VarDumper::dump($var); + if (!$vars) { + VarDumper::dump(new ScalarStub('🐛')); - foreach ($moreVars as $v) { - VarDumper::dump($v); + return null; } - if (1 < func_num_args()) { - return func_get_args(); + if (isset($vars[0]) && 1 === count($vars)) { + VarDumper::dump($vars[0]); + $k = 0; + } else { + foreach ($vars as $k => $v) { + VarDumper::dump($v, is_int($k) ? 1 + $k : $k); + } } - return $var; + if (1 < count($vars)) { + return $vars; + } + + return $vars[$k]; } } @@ -41,8 +52,12 @@ function dd(...$vars): void header('HTTP/1.1 500 Internal Server Error'); } - foreach ($vars as $v) { - VarDumper::dump($v); + if (isset($vars[0]) && 1 === count($vars)) { + VarDumper::dump($vars[0]); + } else { + foreach ($vars as $k => $v) { + VarDumper::dump($v, is_int($k) ? 1 + $k : $k); + } } exit(1); diff --git a/Tests/Caster/StubCasterTest.php b/Tests/Caster/StubCasterTest.php index 6ca2dad3..8b3e12b5 100644 --- a/Tests/Caster/StubCasterTest.php +++ b/Tests/Caster/StubCasterTest.php @@ -15,6 +15,7 @@ use Symfony\Component\VarDumper\Caster\ArgsStub; use Symfony\Component\VarDumper\Caster\ClassStub; use Symfony\Component\VarDumper\Caster\LinkStub; +use Symfony\Component\VarDumper\Caster\ScalarStub; use Symfony\Component\VarDumper\Cloner\VarCloner; use Symfony\Component\VarDumper\Dumper\HtmlDumper; use Symfony\Component\VarDumper\Test\VarDumperTestTrait; @@ -87,6 +88,19 @@ public function testArgsStubWithClosure() $this->assertDumpMatchesFormat($expectedDump, $args); } + public function testEmptyStub() + { + $args = [new ScalarStub('🐛')]; + + $expectedDump = <<<'EODUMP' +array:1 [ + 0 => 🐛 +] +EODUMP; + + $this->assertDumpMatchesFormat($expectedDump, $args); + } + public function testLinkStub() { $var = [new LinkStub(__CLASS__, 0, __FILE__)]; @@ -203,7 +217,7 @@ public function testClassStubWithAnonymousClass() $expectedDump = <<<'EODUMP' array:1 [ - 0 => "Exception@anonymous" + 0 => "Exception@anonymous" ] EODUMP; diff --git a/Tests/Dumper/FunctionsTest.php b/Tests/Dumper/FunctionsTest.php index 7444d4bf..d158d7eb 100644 --- a/Tests/Dumper/FunctionsTest.php +++ b/Tests/Dumper/FunctionsTest.php @@ -18,6 +18,17 @@ class FunctionsTest extends TestCase { + public function testDumpWithoutArg() + { + $this->setupVarDumper(); + + ob_start(); + $return = dump(); + ob_end_clean(); + + $this->assertNull($return); + } + public function testDumpReturnsFirstArg() { $this->setupVarDumper(); @@ -28,7 +39,20 @@ public function testDumpReturnsFirstArg() $return = dump($var1); ob_end_clean(); - $this->assertEquals($var1, $return); + $this->assertSame($var1, $return); + } + + public function testDumpReturnsFirstNamedArgWithoutSectionName() + { + $this->setupVarDumper(); + + $var1 = 'a'; + + ob_start(); + $return = dump(first: $var1); + ob_end_clean(); + + $this->assertSame($var1, $return); } public function testDumpReturnsAllArgsInArray() @@ -43,7 +67,22 @@ public function testDumpReturnsAllArgsInArray() $return = dump($var1, $var2, $var3); ob_end_clean(); - $this->assertEquals([$var1, $var2, $var3], $return); + $this->assertSame([$var1, $var2, $var3], $return); + } + + public function testDumpReturnsAllNamedArgsInArray() + { + $this->setupVarDumper(); + + $var1 = 'a'; + $var2 = 'b'; + $var3 = 'c'; + + ob_start(); + $return = dump($var1, second: $var2, third: $var3); + ob_end_clean(); + + $this->assertSame([$var1, 'second' => $var2, 'third' => $var3], $return); } protected function setupVarDumper() diff --git a/VarDumper.php b/VarDumper.php index 840bfd64..7c160166 100644 --- a/VarDumper.php +++ b/VarDumper.php @@ -37,13 +37,17 @@ class VarDumper */ private static $handler; - public static function dump(mixed $var) + /** + * @param string|null $label + */ + public static function dump(mixed $var/* , string $label = null */) { + $label = 2 <= \func_num_args() ? func_get_arg(1) : null; if (null === self::$handler) { self::register(); } - return (self::$handler)($var); + return (self::$handler)($var, $label); } public static function setHandler(callable $callable = null): ?callable @@ -90,8 +94,14 @@ private static function register(): void $dumper = new ContextualizedDumper($dumper, [new SourceContextProvider()]); } - self::$handler = function ($var) use ($cloner, $dumper) { - $dumper->dump($cloner->cloneVar($var)); + self::$handler = function ($var, string $label = null) use ($cloner, $dumper) { + $var = $cloner->cloneVar($var); + + if (null !== $label) { + $var = $var->withContext(['label' => $label]); + } + + $dumper->dump($var); }; }