diff --git a/DataCollector/LoggerDataCollector.php b/DataCollector/LoggerDataCollector.php index 1460ec13dd..bbc9321320 100644 --- a/DataCollector/LoggerDataCollector.php +++ b/DataCollector/LoggerDataCollector.php @@ -144,7 +144,7 @@ public function getFilters() $allChannels = []; foreach ($this->getProcessedLogs() as $log) { - if ('' === trim($log['channel'])) { + if ('' === trim($log['channel'] ?? '')) { continue; } diff --git a/EventListener/AbstractSessionListener.php b/EventListener/AbstractSessionListener.php index 4573acab8e..05be006e17 100644 --- a/EventListener/AbstractSessionListener.php +++ b/EventListener/AbstractSessionListener.php @@ -153,6 +153,11 @@ public function onKernelResponse(ResponseEvent $event) $isSessionEmpty = ($session instanceof Session ? $session->isEmpty() : empty($session->all())) && empty($_SESSION); // checking $_SESSION to keep compatibility with native sessions if ($requestSessionCookieId && $isSessionEmpty) { + // PHP internally sets the session cookie value to "deleted" when setcookie() is called with empty string $value argument + // which happens in \Symfony\Component\HttpFoundation\Session\Storage\Handler\AbstractSessionHandler::destroy + // when the session gets invalidated (for example on logout) so we must handle this case here too + // otherwise we would send two Set-Cookie headers back with the response + SessionUtils::popSessionCookie($sessionName, 'deleted'); $response->headers->clearCookie( $sessionName, $sessionCookiePath, diff --git a/HttpKernel.php b/HttpKernel.php index a5177f18fb..f84a39e5a2 100644 --- a/HttpKernel.php +++ b/HttpKernel.php @@ -70,6 +70,7 @@ public function handle(Request $request, int $type = HttpKernelInterface::MAIN_R { $request->headers->set('X-Php-Ob-Level', (string) ob_get_level()); + $this->requestStack->push($request); try { return $this->handleRaw($request, $type); } catch (\Exception $e) { @@ -83,6 +84,8 @@ public function handle(Request $request, int $type = HttpKernelInterface::MAIN_R } return $this->handleThrowable($e, $request, $type); + } finally { + $this->requestStack->pop(); } } @@ -121,8 +124,6 @@ public function terminateWithException(\Throwable $exception, Request $request = */ private function handleRaw(Request $request, int $type = self::MAIN_REQUEST): Response { - $this->requestStack->push($request); - // request $event = new RequestEvent($this, $request, $type); $this->dispatcher->dispatch($event, KernelEvents::REQUEST); @@ -199,7 +200,6 @@ private function filterResponse(Response $response, Request $request, int $type) private function finishRequest(Request $request, int $type) { $this->dispatcher->dispatch(new FinishRequestEvent($this, $request, $type), KernelEvents::FINISH_REQUEST); - $this->requestStack->pop(); } /** diff --git a/Kernel.php b/Kernel.php index 29c7aac081..ec49d1a018 100644 --- a/Kernel.php +++ b/Kernel.php @@ -78,11 +78,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl */ private static array $freshCache = []; - public const VERSION = '6.1.3'; - public const VERSION_ID = 60103; + public const VERSION = '6.1.4'; + public const VERSION_ID = 60104; public const MAJOR_VERSION = 6; public const MINOR_VERSION = 1; - public const RELEASE_VERSION = 3; + public const RELEASE_VERSION = 4; public const EXTRA_VERSION = ''; public const END_OF_MAINTENANCE = '01/2023'; diff --git a/Tests/HttpKernelTest.php b/Tests/HttpKernelTest.php index 91636ba86c..182b524e98 100644 --- a/Tests/HttpKernelTest.php +++ b/Tests/HttpKernelTest.php @@ -40,6 +40,45 @@ public function testHandleWhenControllerThrowsAnExceptionAndCatchIsTrue() $kernel->handle(new Request(), HttpKernelInterface::MAIN_REQUEST, true); } + public function testRequestStackIsNotBrokenWhenControllerThrowsAnExceptionAndCatchIsTrue() + { + $requestStack = new RequestStack(); + $kernel = $this->getHttpKernel(new EventDispatcher(), function () { throw new \RuntimeException(); }, $requestStack); + + try { + $kernel->handle(new Request(), HttpKernelInterface::MASTER_REQUEST, true); + } catch (\Throwable $exception) { + } + + self::assertNull($requestStack->getCurrentRequest()); + } + + public function testRequestStackIsNotBrokenWhenControllerThrowsAnExceptionAndCatchIsFalse() + { + $requestStack = new RequestStack(); + $kernel = $this->getHttpKernel(new EventDispatcher(), function () { throw new \RuntimeException(); }, $requestStack); + + try { + $kernel->handle(new Request(), HttpKernelInterface::MASTER_REQUEST, false); + } catch (\Throwable $exception) { + } + + self::assertNull($requestStack->getCurrentRequest()); + } + + public function testRequestStackIsNotBrokenWhenControllerThrowsAnThrowable() + { + $requestStack = new RequestStack(); + $kernel = $this->getHttpKernel(new EventDispatcher(), function () { throw new \Error(); }, $requestStack); + + try { + $kernel->handle(new Request(), HttpKernelInterface::MASTER_REQUEST, true); + } catch (\Throwable $exception) { + } + + self::assertNull($requestStack->getCurrentRequest()); + } + public function testHandleWhenControllerThrowsAnExceptionAndCatchIsFalseAndNoListenerIsRegistered() { $this->expectException(\RuntimeException::class);