Skip to content

IBX-11747: Fixed exception loop when rendering HTTP 405 responses - #1996

Open
Sztig wants to merge 2 commits into
4.6from
IBX-11747-admin-405-exception-loop-fix
Open

IBX-11747: Fixed exception loop when rendering HTTP 405 responses#1996
Sztig wants to merge 2 commits into
4.6from
IBX-11747-admin-405-exception-loop-fix

Conversation

@Sztig

@Sztig Sztig commented Aug 6, 2026

Copy link
Copy Markdown
Contributor
🎫 Issue IBX-11747

Description:

I'm preventing the loping by only processing the error from the main request

        if (!$event->isMainRequest()) {
            return;
        }

I'm also handling the 405 code as a separate case with its own template

            case 405:
                $content = $this->twig->render('@ibexadesign/ui/error_page/405.html.twig');
                break;

This listener had no test coverage previously so I have added that as well, covered both the fix and the expected behavior outside this specific case.

For QA:

Documentation:

@sonarqubecloud

sonarqubecloud Bot commented Aug 6, 2026

Copy link
Copy Markdown

@alongosz alongosz changed the title IBX-11747: Admin 405 responses trigger exception loop in prod mode IBX-11747: Fixed exception loop when rendering HTTP 405 responses Sep 8, 2026
Comment on lines +94 to +96
if (!$event->isMainRequest()) {
return;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Sztig One thing that is unclear for me - does this mean that the other responses carried the same issue? Because while you've changed this for 405, it's gonna apply to all now.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is not just 405 error but I figured I should fix it here, I should elaborate what actually is happening here. The first exception is an actual relevant exception - 405, and since this is not a covered case, default template would also try to render the menu for the user when rendering the exception view - this also fails as a subrequest.

Its being thrown at symfony/http-kernel/Fragment/InlineFragmentRenderer, $this->dispatcher->dispatch($event, KernelEvents::EXCEPTION); dispatches the event, it lands in AdminExceptionListener repeating the process.

Symfony comments in the code mentions that this dispatch happens only to trigger the logging, the response that comes back should be ignored ergo the decision to stop the subrequest logging on our side in AdminExceptionListener - https://github.com/symfony/symfony/blob/5.4/src/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php#L82C1-L83C55

Please let me know what you think as maybe this is not the optimal place to fix it

Comment on lines +36 to +49
/** @var \Twig\Environment|\PHPUnit\Framework\MockObject\MockObject */
private $twig;

/** @var \Ibexa\Contracts\AdminUi\Notification\NotificationHandlerInterface|\PHPUnit\Framework\MockObject\Stub */
private $notificationHandler;

/** @var \Symfony\WebpackEncoreBundle\Asset\TagRenderer|\PHPUnit\Framework\MockObject\MockObject */
private $encoreTagRenderer;

/** @var \Symfony\WebpackEncoreBundle\Asset\EntrypointLookupCollectionInterface|\PHPUnit\Framework\MockObject\MockObject */
private $entrypointLookupCollection;

/** @var \Ibexa\AdminUi\EventListener\AdminExceptionListener */
private $listener;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use in PHPDoc intersection instead of union and strict-type the "main" object type.

/**
* @return iterable<string, array{0: string, 1: int, 2: string}>
*/
public function provideNoOpConditions(): iterable

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public function provideNoOpConditions(): iterable
public static function provideNoOpConditions(): iterable

/**
* @return iterable<string, array{0: \Throwable, 1: int, 2: string, 3: array<string, string>}>
*/
public function provideHttpExceptionsWithDedicatedErrorPages(): iterable

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public function provideHttpExceptionsWithDedicatedErrorPages(): iterable
public static function provideHttpExceptionsWithDedicatedErrorPages(): iterable

use Twig\Environment;
use Twig\Error\RuntimeError;

class AdminExceptionListenerTest extends TestCase

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
class AdminExceptionListenerTest extends TestCase
final class AdminExceptionListenerTest extends TestCase

int $requestType,
string $siteaccessName
): void {
$this->twig->expects($this->never())->method('render');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use https://github.com/ibexa/internal-ai/pull/3 for PHPUnit guidelines. This will help us avoid recurring review comments:

Suggested change
$this->twig->expects($this->never())->method('render');
$this->twig->expects(self::never())->method('render');

$event = $this->createExceptionEvent(new NotFoundHttpException(), $requestType, $siteaccessName);
$this->createListener($environment)->onKernelException($event);

$this->assertNull($event->getResponse());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$this->assertNull($event->getResponse());
self::assertNull($event->getResponse());

array $expectedHeaders
): void {
$this->twig
->expects($this->once())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

Comment on lines +110 to +112
$this->assertInstanceOf(Response::class, $response);
$this->assertSame($expectedStatusCode, $response->getStatusCode());
$this->assertSame('rendered_error_page_content', $response->getContent());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

$this->assertSame('rendered_error_page_content', $response->getContent());

foreach ($expectedHeaders as $name => $value) {
$this->assertSame($value, $response->headers->get($name));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

$entrypointLookup->expects($this->once())->method('reset');

$this->entrypointLookupCollection
->expects($this->once())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

$this->encoreTagRenderer->expects($this->once())->method('reset');

$this->twig
->expects($this->once())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

Comment on lines +173 to +174
$this->assertInstanceOf(Response::class, $response);
$this->assertSame(Response::HTTP_INTERNAL_SERVER_ERROR, $response->getStatusCode());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants