diff --git a/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md b/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md index 539d814d2a438..50787452ea277 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Add `ajax_replace` option for replacing toolbar on AJAX requests + * Show debug bar when using a streamed response 7.2 --- diff --git a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php index de7bb7b001ca0..d1eeeee1aa616 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php +++ b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php @@ -17,6 +17,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Session\Flash\AutoExpireFlashBag; +use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpKernel\DataCollector\DumpDataCollector; use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; @@ -110,7 +111,14 @@ public function onKernelResponse(ResponseEvent $event): void $session->getFlashBag()->setAll($session->getFlashBag()->peekAll()); } - $response->setContent($this->twig->render('@WebProfiler/Profiler/toolbar_redirect.html.twig', ['location' => $response->headers->get('Location'), 'host' => $request->getSchemeAndHttpHost()])); + if ($response instanceof StreamedResponse) { + $twig = $this->twig; + $response->setCallback(function () use ($twig, $request, $response): void { + echo $twig->render('@WebProfiler/Profiler/toolbar_redirect.html.twig', ['location' => $response->headers->get('Location'), 'host' => $request->getSchemeAndHttpHost()]); + }); + } else { + $response->setContent($this->twig->render('@WebProfiler/Profiler/toolbar_redirect.html.twig', ['location' => $response->headers->get('Location'), 'host' => $request->getSchemeAndHttpHost()])); + } $response->setStatusCode(200); $response->headers->remove('Location'); } @@ -133,26 +141,51 @@ public function onKernelResponse(ResponseEvent $event): void */ protected function injectToolbar(Response $response, Request $request, array $nonces): void { - $content = $response->getContent(); - $pos = strripos($content, '
'); - - if (false !== $pos) { - $toolbar = "\n".str_replace("\n", '', $this->twig->render( - '@WebProfiler/Profiler/toolbar_js.html.twig', - [ - 'full_stack' => class_exists(FullStack::class), - 'excluded_ajax_paths' => $this->excludedAjaxPaths, - 'token' => $response->headers->get('X-Debug-Token'), - 'request' => $request, - 'csp_script_nonce' => $nonces['csp_script_nonce'] ?? null, - 'csp_style_nonce' => $nonces['csp_style_nonce'] ?? null, - ] - ))."\n"; - $content = substr($content, 0, $pos).$toolbar.substr($content, $pos); - $response->setContent($content); + if ($response instanceof StreamedResponse) { + $callback = $response->getCallback(); + $toolbarHTMLContent = "\n".str_replace("\n", '', $this->getToolbarHTML($request, $response->headers->get('X-Debug-Token'), $nonces))."\n"; + $injectedCallback = static function () use ($toolbarHTMLContent, $callback): void { + ob_start(function (string $buffer, int $phase) use ($toolbarHTMLContent): string { + $pos = strripos($buffer, ''); + if (false !== $pos) { + $buffer = substr($buffer, 0, $pos).$toolbarHTMLContent.substr($buffer, $pos); + } + + return $buffer; + }, 8); // length of '' + + ($callback)(); + ob_end_flush(); + }; + $response->setCallback($injectedCallback); + } else { + $content = $response->getContent(); + $pos = strripos($content, ''); + + if (false !== $pos) { + $toolbar = "\n".str_replace("\n", '', $this->getToolbarHTML($request, $response->headers->get('X-Debug-Token'), $nonces))."\n"; + + $content = substr($content, 0, $pos).$toolbar.substr($content, $pos); + $response->setContent($content); + } } } + private function getToolbarHTML(Request $request, ?string $debugToken, array $nonces): string + { + return $this->twig->render( + '@WebProfiler/Profiler/toolbar_js.html.twig', + [ + 'full_stack' => class_exists(FullStack::class), + 'excluded_ajax_paths' => $this->excludedAjaxPaths, + 'token' => $debugToken, + 'request' => $request, + 'csp_script_nonce' => $nonces['csp_script_nonce'] ?? null, + 'csp_style_nonce' => $nonces['csp_style_nonce'] ?? null, + ] + ); + } + public static function getSubscribedEvents(): array { return [ diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/WebDebugToolbarListenerTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/WebDebugToolbarListenerTest.php index ff9bd096fb13f..bd4ca434596fd 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/WebDebugToolbarListenerTest.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/WebDebugToolbarListenerTest.php @@ -28,7 +28,7 @@ class WebDebugToolbarListenerTest extends TestCase /** * @dataProvider getInjectToolbarTests */ - public function testInjectToolbar($content, $expected) + public function testInjectToolbar(string $content, string $expected) { $listener = new WebDebugToolbarListener($this->getTwigMock()); $m = new \ReflectionMethod($listener, 'injectToolbar'); @@ -60,7 +60,7 @@ public static function getInjectToolbarTests() /** * @dataProvider provideRedirects */ - public function testHtmlRedirectionIsIntercepted($statusCode) + public function testHtmlRedirectionIsIntercepted(int $statusCode) { $response = new Response('Some content', $statusCode); $response->headers->set('X-Debug-Token', 'xxxxxxxx'); @@ -75,7 +75,7 @@ public function testHtmlRedirectionIsIntercepted($statusCode) public function testNonHtmlRedirectionIsNotIntercepted() { - $response = new Response('Some content', '301'); + $response = new Response('Some content', 301); $response->headers->set('X-Debug-Token', 'xxxxxxxx'); $event = new ResponseEvent($this->createMock(Kernel::class), new Request([], [], ['_format' => 'json']), HttpKernelInterface::MAIN_REQUEST, $response); @@ -136,7 +136,7 @@ public function testToolbarIsNotInjectedOnContentDispositionAttachment() * * @dataProvider provideRedirects */ - public function testToolbarIsNotInjectedOnRedirection($statusCode) + public function testToolbarIsNotInjectedOnRedirection(int $statusCode) { $response = new Response('