From 685d43acdfd479332dc0efe98f55e9c2da8200e8 Mon Sep 17 00:00:00 2001 From: KevinMartinsDev Date: Sun, 19 Apr 2026 16:49:45 +0200 Subject: [PATCH] fix(symfony): ensure ErrorListener is fully stateless to prevent state contamination in Worker mode --- src/Symfony/EventListener/ErrorListener.php | 18 +++++++++++++-- .../Tests/EventListener/ErrorListenerTest.php | 23 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/Symfony/EventListener/ErrorListener.php b/src/Symfony/EventListener/ErrorListener.php index c0eab7879dc..9b5849bc133 100644 --- a/src/Symfony/EventListener/ErrorListener.php +++ b/src/Symfony/EventListener/ErrorListener.php @@ -80,9 +80,17 @@ protected function duplicateRequest(\Throwable $exception, Request $request): Re // Let the error handler take this we don't handle HTML nor non-api platform requests if (false === ($apiOperation?->getExtraProperties()['_api_error_handler'] ?? true) || 'html' === $format) { - $this->controller = 'error_controller'; + if (null === $this->controller) { + $dup = $request->duplicate(null, null, ['_controller' => 'error_controller', 'exception' => $exception]); + $dup->setMethod('GET'); - return parent::duplicateRequest($exception, $request); + return $dup; + } + + $dup = parent::duplicateRequest($exception, $request); + $dup->attributes->set('_controller', 'error_controller'); + + return $dup; } if ($this->debug) { @@ -90,6 +98,12 @@ protected function duplicateRequest(\Throwable $exception, Request $request): Re } $dup = parent::duplicateRequest($exception, $request); + + if ($dup === $request) { + $dup = $request->duplicate(null, null, ['exception' => $exception]); + $dup->setMethod('GET'); + } + $operation = $this->initializeExceptionOperation($request, $exception, $format, $apiOperation); if (null === $operation->getProvider()) { diff --git a/src/Symfony/Tests/EventListener/ErrorListenerTest.php b/src/Symfony/Tests/EventListener/ErrorListenerTest.php index 09b8a990248..f5677d7553c 100644 --- a/src/Symfony/Tests/EventListener/ErrorListenerTest.php +++ b/src/Symfony/Tests/EventListener/ErrorListenerTest.php @@ -142,4 +142,27 @@ public function testDuplicateExceptionWithErrorResource(): void $errorListener = new ErrorListener('action', null, true, [], $resourceMetadataCollectionFactory, ['jsonld' => ['application/ld+json']], [], $identifiersExtractor, $resourceClassResolver); $errorListener->onKernelException($exceptionEvent); } + + public function testStateIsNeverModified(): void + { + $resourceClassResolver = $this->createMock(ResourceClassResolverInterface::class); + $kernel = $this->createStub(KernelInterface::class); + $kernel->method('handle')->willReturn(new Response()); + + $request = Request::create('/'); + $request->headers->set('Accept', 'text/html'); + $exception = new \Exception(); + $exceptionEvent = new ExceptionEvent($kernel, $request, HttpKernelInterface::SUB_REQUEST, $exception); + + $initialController = 'initial_controller'; + $errorListener = new ErrorListener($initialController, null, false, [], null, ['jsonproblem' => ['application/problem+json']], [], null, $resourceClassResolver); + + $errorListener->onKernelException($exceptionEvent); + + $refl = new \ReflectionClass($errorListener); + $controllerProp = $refl->getProperty('controller'); + $controllerProp->setAccessible(true); + + $this->assertEquals($initialController, $controllerProp->getValue($errorListener), 'The controller property must never be modified.'); + } }