From c4f80916902a661674f368977cc22f9c915a138a Mon Sep 17 00:00:00 2001 From: HypeMC Date: Sat, 11 Oct 2025 19:29:13 +0200 Subject: [PATCH] [HttpKernel][DebugBundle] Collect dumps when console profiling is enabled --- src/Symfony/Bundle/DebugBundle/CHANGELOG.md | 5 ++ .../DebugBundle/Resources/config/services.php | 6 +++ src/Symfony/Component/HttpKernel/CHANGELOG.md | 1 + .../HttpKernel/EventListener/DumpListener.php | 16 +++++- .../Tests/EventListener/DumpListenerTest.php | 54 +++++++++++++++++++ 5 files changed, 80 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Bundle/DebugBundle/CHANGELOG.md b/src/Symfony/Bundle/DebugBundle/CHANGELOG.md index 685dd1d0794f4..bcba1e2ede170 100644 --- a/src/Symfony/Bundle/DebugBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/DebugBundle/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.4 +--- + + * Wire the `$profilerDumper` argument in `DumpListener` + 4.1.0 ----- diff --git a/src/Symfony/Bundle/DebugBundle/Resources/config/services.php b/src/Symfony/Bundle/DebugBundle/Resources/config/services.php index ea2d057310f88..a32d1d6eef8c5 100644 --- a/src/Symfony/Bundle/DebugBundle/Resources/config/services.php +++ b/src/Symfony/Bundle/DebugBundle/Resources/config/services.php @@ -59,11 +59,17 @@ 'priority' => 240, ]) + ->set('.lazy.data_collector.dump', DumpDataCollector::class) + ->factory('current') + ->args([[service('data_collector.dump')]]) + ->lazy() + ->set('debug.dump_listener', DumpListener::class) ->args([ service('var_dumper.cloner'), service('var_dumper.cli_dumper'), null, + service('.lazy.data_collector.dump'), ]) ->tag('kernel.event_subscriber') diff --git a/src/Symfony/Component/HttpKernel/CHANGELOG.md b/src/Symfony/Component/HttpKernel/CHANGELOG.md index 70ada1a7e0de6..b27b72b0f4364 100644 --- a/src/Symfony/Component/HttpKernel/CHANGELOG.md +++ b/src/Symfony/Component/HttpKernel/CHANGELOG.md @@ -10,6 +10,7 @@ CHANGELOG * Add `#[IsSignatureValid]` attribute to validate URI signatures * Make `Profile` final and `Profiler::__sleep()` internal * Collect the application runner class + * Allow configuring `DumpListener` to use a different dumper when CLI profiling is enabled 7.3 --- diff --git a/src/Symfony/Component/HttpKernel/EventListener/DumpListener.php b/src/Symfony/Component/HttpKernel/EventListener/DumpListener.php index 836e54dcc3120..a310cdeb56a79 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/DumpListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/DumpListener.php @@ -12,6 +12,7 @@ namespace Symfony\Component\HttpKernel\EventListener; use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Event\ConsoleCommandEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\VarDumper\Cloner\ClonerInterface; use Symfony\Component\VarDumper\Dumper\DataDumperInterface; @@ -25,17 +26,28 @@ */ class DumpListener implements EventSubscriberInterface { + /** + * @param ?DataDumperInterface $profilerDumper The dumper to use when CLI profiling is enabled. + * If null, the default $dumper will be used instead. + */ public function __construct( private ClonerInterface $cloner, private DataDumperInterface $dumper, private ?Connection $connection = null, + private ?DataDumperInterface $profilerDumper = null, ) { } - public function configure(): void + /** + * @param ?ConsoleCommandEvent $event + */ + public function configure(/* ?ConsoleCommandEvent $event = null */): void { + $event = 1 <= \func_num_args() ? func_get_arg(0) : null; + $input = $event?->getInput(); + $cloner = $this->cloner; - $dumper = $this->dumper; + $dumper = !$this->profilerDumper || !$input?->hasOption('profile') || !$input?->getOption('profile') ? $this->dumper : $this->profilerDumper; $connection = $this->connection; VarDumper::setHandler(static function ($var, ?string $label = null) use ($cloner, $dumper, $connection) { diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/DumpListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/DumpListenerTest.php index b73c799c59a44..3496711e6de7b 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/DumpListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/DumpListenerTest.php @@ -11,8 +11,12 @@ namespace Symfony\Component\HttpKernel\Tests\EventListener; +use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase; use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Event\ConsoleCommandEvent; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\BufferedOutput; use Symfony\Component\HttpKernel\EventListener\DumpListener; use Symfony\Component\VarDumper\Cloner\ClonerInterface; use Symfony\Component\VarDumper\Cloner\Data; @@ -62,6 +66,44 @@ public function testConfigure() throw $exception; } } + + #[TestWith([false, false, '+foo-+bar-', []])] + #[TestWith([true, false, '+foo-+bar-', []])] + #[TestWith([true, true, '', ['foo-', 'bar-']])] + public function testConfigureWithProfilerDumper(bool $hasOption, bool $option, string $expectedOutput, array $expectedData) + { + $prevDumper = VarDumper::setHandler('var_dump'); + VarDumper::setHandler($prevDumper); + + $cloner = new MockCloner(); + $dumper = new MockDumper(); + $profilerDumper = new MockProfilerDumper(); + + $input = $this->createMock(InputInterface::class); + $input->method('hasOption')->willReturn($hasOption); + $input->method('getOption')->willReturn($option); + + ob_start(); + $exception = null; + $listener = new DumpListener($cloner, $dumper, null, $profilerDumper); + + try { + $listener->configure(new ConsoleCommandEvent(null, $input, new BufferedOutput())); + + VarDumper::dump('foo'); + VarDumper::dump('bar'); + + $this->assertSame($expectedOutput, ob_get_clean()); + $this->assertSame($expectedData, $profilerDumper->data); + } catch (\Exception $exception) { + } + + VarDumper::setHandler($prevDumper); + + if (null !== $exception) { + throw $exception; + } + } } class MockCloner implements ClonerInterface @@ -81,3 +123,15 @@ public function dump(Data $data): ?string return null; } } + +class MockProfilerDumper implements DataDumperInterface +{ + public array $data = []; + + public function dump(Data $data): ?string + { + $this->data[] = $data->getValue(); + + return null; + } +}