Add custom 404 and 500 error pages via ExceptionListener

- Create ExceptionListener that renders custom error templates in prod
- Skip in dev environment to keep Symfony debug pages
- 404: neo-brutalist page with large "404" text and return link
- 500: generic server error page with status code display
- Add 4 unit tests (dev skip, 404 render, 500 render, other HTTP codes)
- Register kernel.environment parameter for ExceptionListener

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Serreau Jovann
2026-03-20 14:37:25 +01:00
parent bc93a1c9d5
commit 0fb51726bc
5 changed files with 185 additions and 0 deletions

View File

@@ -0,0 +1,86 @@
<?php
namespace App\Tests\EventListener;
use App\EventListener\ExceptionListener;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Twig\Environment;
class ExceptionListenerTest extends TestCase
{
public function testDoesNothingInDevEnvironment(): void
{
$twig = $this->createMock(Environment::class);
$twig->expects(self::never())->method('render');
$listener = new ExceptionListener($twig, 'dev');
$event = $this->createEvent(new \RuntimeException('test'));
$listener($event);
self::assertNull($event->getResponse());
}
public function testRenders404Template(): void
{
$twig = $this->createMock(Environment::class);
$twig->expects(self::once())
->method('render')
->with('error/404.html.twig', ['status_code' => 404])
->willReturn('<h1>404</h1>');
$listener = new ExceptionListener($twig, 'prod');
$event = $this->createEvent(new NotFoundHttpException('Not found'));
$listener($event);
self::assertNotNull($event->getResponse());
self::assertSame(404, $event->getResponse()->getStatusCode());
self::assertSame('<h1>404</h1>', $event->getResponse()->getContent());
}
public function testRenders500TemplateForGenericException(): void
{
$twig = $this->createMock(Environment::class);
$twig->expects(self::once())
->method('render')
->with('error/500.html.twig', ['status_code' => 500])
->willReturn('<h1>500</h1>');
$listener = new ExceptionListener($twig, 'prod');
$event = $this->createEvent(new \RuntimeException('Server error'));
$listener($event);
self::assertNotNull($event->getResponse());
self::assertSame(500, $event->getResponse()->getStatusCode());
}
public function testRenders500TemplateForOtherHttpCodes(): void
{
$twig = $this->createMock(Environment::class);
$twig->expects(self::once())
->method('render')
->with('error/500.html.twig', ['status_code' => 403])
->willReturn('<h1>403</h1>');
$listener = new ExceptionListener($twig, 'prod');
$event = $this->createEvent(new \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException('Forbidden'));
$listener($event);
self::assertNotNull($event->getResponse());
self::assertSame(403, $event->getResponse()->getStatusCode());
}
private function createEvent(\Throwable $exception): ExceptionEvent
{
$kernel = $this->createMock(HttpKernelInterface::class);
return new ExceptionEvent($kernel, new Request(), HttpKernelInterface::MAIN_REQUEST, $exception);
}
}