/
githubmirror
/
symfony
Обзор
Документация
Войти
/
githubmirror
/
symfony
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
8.2
src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php
643 строки
24 KB
Nicolas Grekas
[HttpKernel] Fix TypeError in ResponseEvent when argument resolution throws
01 июн 2026, 15:43
01 июн 2026, 15:43
e924d26
Код
Авторство
О чём код?
<?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\HttpKernel\Tests; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface; use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; use Symfony\Component\HttpKernel\Event\ExceptionEvent; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\ControllerDoesNotReturnResponseException; use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\HttpKernel; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\HttpKernel\Tests\Fixtures\Attribute\Bar; class HttpKernelTest extends TestCase { /** * Catch exceptions: true * Throwable type: RuntimeException * Listener: false. */ public function testHandleWhenControllerThrowsAnExceptionAndCatchIsTrue() { $this->expectException(\RuntimeException::class); $kernel = $this->getHttpKernel(new EventDispatcher(), static fn () => throw new \RuntimeException()); $kernel->handle(new Request(), HttpKernelInterface::MAIN_REQUEST, true); } public function testRequestStackIsNotBrokenWhenControllerThrowsAnExceptionAndCatchIsTrue() { $requestStack = new RequestStack(); $kernel = $this->getHttpKernel(new EventDispatcher(), static fn () => throw new \RuntimeException(), $requestStack); try { $kernel->handle(new Request(), HttpKernelInterface::MAIN_REQUEST, true); } catch (\Throwable $exception) { } self::assertNull($requestStack->getCurrentRequest()); } public function testRequestStackIsNotBrokenWhenControllerThrowsAnExceptionAndCatchIsFalse() { $requestStack = new RequestStack(); $kernel = $this->getHttpKernel(new EventDispatcher(), static fn () => throw new \RuntimeException(), $requestStack); try { $kernel->handle(new Request(), HttpKernelInterface::MAIN_REQUEST, false); } catch (\Throwable $exception) { } self::assertNull($requestStack->getCurrentRequest()); } public function testRequestStackIsNotBrokenWhenControllerThrowsAnThrowable() { $requestStack = new RequestStack(); $kernel = $this->getHttpKernel(new EventDispatcher(), static fn () => throw new \Error(), $requestStack); try { $kernel->handle(new Request(), HttpKernelInterface::MAIN_REQUEST, true); } catch (\Throwable $exception) { } self::assertNull($requestStack->getCurrentRequest()); } /** * Catch exceptions: false * Throwable type: RuntimeException * Listener: false. */ public function testHandleWhenControllerThrowsAnExceptionAndCatchIsFalseAndNoListenerIsRegistered() { $this->expectException(\RuntimeException::class); $kernel = $this->getHttpKernel(new EventDispatcher(), static fn () => throw new \RuntimeException()); $kernel->handle(new Request(), HttpKernelInterface::MAIN_REQUEST, false); } /** * Catch exceptions: true * Throwable type: RuntimeException * Listener: true. */ public function testHandleWhenControllerThrowsAnExceptionAndCatchIsTrueWithAHandlingListener() { $dispatcher = new EventDispatcher(); $dispatcher->addListener(KernelEvents::EXCEPTION, static function ($event) { $event->setResponse(new Response($event->getThrowable()->getMessage())); }); $kernel = $this->getHttpKernel($dispatcher, static fn () => throw new \RuntimeException('foo')); $response = $kernel->handle(new Request(), HttpKernelInterface::MAIN_REQUEST, true); $this->assertEquals('500', $response->getStatusCode()); $this->assertEquals('foo', $response->getContent()); } /** * Catch exceptions: true * Throwable type: TypeError * Listener: true. */ public function testHandleWhenControllerThrowsAThrowableAndCatchIsTrueWithAHandlingListener() { $dispatcher = new EventDispatcher(); $dispatcher->addListener(KernelEvents::EXCEPTION, static function ($event) { $event->setResponse(new Response($event->getThrowable()->getMessage())); }); $kernel = $this->getHttpKernel($dispatcher, static fn () => throw new \TypeError('foo'), handleAllThrowables: true); $response = $kernel->handle(new Request(), HttpKernelInterface::MAIN_REQUEST, true); $this->assertEquals('500', $response->getStatusCode()); $this->assertEquals('foo', $response->getContent()); } /** * Catch exceptions: false * Throwable type: TypeError * Listener: true. */ public function testHandleWhenControllerThrowsAThrowableAndCatchIsFalseWithAHandlingListener() { $dispatcher = new EventDispatcher(); $dispatcher->addListener(KernelEvents::EXCEPTION, static function ($event) { $event->setResponse(new Response($event->getThrowable()->getMessage())); }); $kernel = $this->getHttpKernel($dispatcher, static fn () => throw new \TypeError('foo'), handleAllThrowables: true); $this->expectException(\TypeError::class); $kernel->handle(new Request(), HttpKernelInterface::MAIN_REQUEST, false); } /** * Catch exceptions: true * Throwable type: TypeError * Listener: true. */ public function testHandleWhenControllerThrowsAThrowableAndCatchIsTrueNotHandlingThrowables() { $dispatcher = new EventDispatcher(); $dispatcher->addListener(KernelEvents::EXCEPTION, static function ($event) { $event->setResponse(new Response($event->getThrowable()->getMessage())); }); $controllerResolver = $this->createStub(ControllerResolverInterface::class); $controllerResolver ->method('getController') ->willReturn(static fn () => throw new \TypeError('foo')); $argumentResolver = $this->createStub(ArgumentResolverInterface::class); $argumentResolver ->method('getArguments') ->willReturn([]); $kernel = new HttpKernel($dispatcher, $controllerResolver, null, $argumentResolver); $this->expectException(\TypeError::class); $kernel->handle(new Request(), HttpKernelInterface::MAIN_REQUEST, true); } /** * Catch exceptions: true * Throwable type: RuntimeException * Listener: true. */ public function testHandleWhenControllerThrowsAnExceptionAndCatchIsTrueWithANonHandlingListener() { $exception = new \RuntimeException(); $dispatcher = new EventDispatcher(); $dispatcher->addListener(KernelEvents::EXCEPTION, static function ($event) { // should set a response, but does not }); $kernel = $this->getHttpKernel($dispatcher, static fn () => throw $exception); try { $kernel->handle(new Request(), HttpKernelInterface::MAIN_REQUEST, true); $this->fail('LogicException expected'); } catch (\RuntimeException $e) { $this->assertSame($exception, $e); } } public function testHandleExceptionWithARedirectionResponse() { $dispatcher = new EventDispatcher(); $dispatcher->addListener(KernelEvents::EXCEPTION, static function ($event) { $event->setResponse(new RedirectResponse('/login', 301)); }); $kernel = $this->getHttpKernel($dispatcher, static fn () => throw new AccessDeniedHttpException()); $response = $kernel->handle(new Request()); $this->assertEquals('301', $response->getStatusCode()); $this->assertEquals('/login', $response->headers->get('Location')); } public function testHandleHttpException() { $dispatcher = new EventDispatcher(); $dispatcher->addListener(KernelEvents::EXCEPTION, static function ($event) { $event->setResponse(new Response($event->getThrowable()->getMessage())); }); $kernel = $this->getHttpKernel($dispatcher, static fn () => throw new MethodNotAllowedHttpException(['POST'])); $response = $kernel->handle(new Request()); $this->assertEquals('405', $response->getStatusCode()); $this->assertEquals('POST', $response->headers->get('Allow')); } public function getStatusCodes() { return [ [200, 404], [404, 200], [301, 200], [500, 200], ]; } #[DataProvider('getSpecificStatusCodes')] public function testHandleWhenAnExceptionIsHandledWithASpecificStatusCode($expectedStatusCode) { $dispatcher = new EventDispatcher(); $dispatcher->addListener(KernelEvents::EXCEPTION, static function (ExceptionEvent $event) use ($expectedStatusCode) { $event->allowCustomResponseCode(); $event->setResponse(new Response('', $expectedStatusCode)); }); $kernel = $this->getHttpKernel($dispatcher, static fn () => throw new \RuntimeException()); $response = $kernel->handle(new Request()); $this->assertEquals($expectedStatusCode, $response->getStatusCode()); } public static function getSpecificStatusCodes() { return [ [200], [302], [403], ]; } public function testHandleWhenAListenerReturnsAResponse() { $dispatcher = new EventDispatcher(); $dispatcher->addListener(KernelEvents::REQUEST, static function ($event) { $event->setResponse(new Response('hello')); }); $kernel = $this->getHttpKernel($dispatcher); $this->assertEquals('hello', $kernel->handle(new Request())->getContent()); } public function testHandleWhenNoControllerIsFound() { $this->expectException(NotFoundHttpException::class); $dispatcher = new EventDispatcher(); $kernel = $this->getHttpKernel($dispatcher, false); $kernel->handle(new Request()); } public function testHandleWhenTheControllerIsAClosure() { $response = new Response('foo'); $dispatcher = new EventDispatcher(); $kernel = $this->getHttpKernel($dispatcher, static fn () => $response); $this->assertSame($response, $kernel->handle(new Request())); } public function testHandleWhenTheControllerIsAnObjectWithInvoke() { $dispatcher = new EventDispatcher(); $kernel = $this->getHttpKernel($dispatcher, new TestController()); $this->assertResponseEquals(new Response('foo'), $kernel->handle(new Request())); } public function testHandleWhenTheControllerIsAFunction() { $dispatcher = new EventDispatcher(); $kernel = $this->getHttpKernel($dispatcher, 'Symfony\Component\HttpKernel\Tests\controller_func'); $this->assertResponseEquals(new Response('foo'), $kernel->handle(new Request())); } public function testHandleWhenTheControllerIsAnArray() { $dispatcher = new EventDispatcher(); $kernel = $this->getHttpKernel($dispatcher, [new TestController(), 'controller']); $this->assertResponseEquals(new Response('foo'), $kernel->handle(new Request())); } public function testHandleWhenTheControllerIsAStaticArray() { $dispatcher = new EventDispatcher(); $kernel = $this->getHttpKernel($dispatcher, ['Symfony\Component\HttpKernel\Tests\TestController', 'staticcontroller']); $this->assertResponseEquals(new Response('foo'), $kernel->handle(new Request())); } public function testHandleWhenTheControllerDoesNotReturnAResponse() { $dispatcher = new EventDispatcher(); $kernel = $this->getHttpKernel($dispatcher, static fn () => null); try { $kernel->handle(new Request()); $this->fail('The kernel should throw an exception.'); } catch (ControllerDoesNotReturnResponseException $e) { $first = $e->getTrace()[0]; // `file` index the array starting at 0, and __FILE__ starts at 1 $line = file($first['file'])[$first['line'] - 2]; $this->assertStringContainsString('// call controller', $line); } } public function testHandleWhenTheControllerDoesNotReturnAResponseButAViewIsRegistered() { $dispatcher = new EventDispatcher(); $dispatcher->addListener(KernelEvents::VIEW, static function ($event) { $event->setResponse(new Response($event->getControllerResult())); }); $kernel = $this->getHttpKernel($dispatcher, static fn () => 'foo'); $this->assertEquals('foo', $kernel->handle(new Request())->getContent()); } public function testHandleWithAResponseListener() { $dispatcher = new EventDispatcher(); $dispatcher->addListener(KernelEvents::RESPONSE, static function ($event) { $event->setResponse(new Response('foo')); }); $kernel = $this->getHttpKernel($dispatcher); $this->assertEquals('foo', $kernel->handle(new Request())->getContent()); } public function testHandleAllowChangingControllerArguments() { $dispatcher = new EventDispatcher(); $dispatcher->addListener(KernelEvents::CONTROLLER_ARGUMENTS, static function ($event) { $event->setArguments(['foo']); }); $kernel = $this->getHttpKernel($dispatcher, static fn ($content) => new Response($content)); $this->assertResponseEquals(new Response('foo'), $kernel->handle(new Request())); } public function testHandleAllowChangingControllerAndArguments() { $dispatcher = new EventDispatcher(); $dispatcher->addListener(KernelEvents::CONTROLLER_ARGUMENTS, static function ($event) { $oldController = $event->getController(); $oldArguments = $event->getArguments(); $newController = static function ($id) use ($oldController, $oldArguments) { $response = $oldController(...$oldArguments); $response->headers->set('X-Id', $id); return $response; }; $event->setController($newController); $event->setArguments(['bar']); }); $kernel = $this->getHttpKernel($dispatcher, static fn ($content) => new Response($content), null, ['foo']); $this->assertResponseEquals(new Response('foo', 200, ['X-Id' => 'bar']), $kernel->handle(new Request())); } public function testTerminate() { $dispatcher = new EventDispatcher(); $kernel = $this->getHttpKernel($dispatcher); $dispatcher->addListener(KernelEvents::TERMINATE, static function ($event) use (&$called, &$capturedKernel, &$capturedRequest, &$capturedResponse) { $called = true; $capturedKernel = $event->getKernel(); $capturedRequest = $event->getRequest(); $capturedResponse = $event->getResponse(); }); $kernel->terminate($request = Request::create('/'), $response = new Response()); $this->assertTrue($called); $this->assertEquals($kernel, $capturedKernel); $this->assertEquals($request, $capturedRequest); $this->assertEquals($response, $capturedResponse); } public function testTerminateWithException() { $dispatcher = new EventDispatcher(); $requestStack = new RequestStack(); $kernel = $this->getHttpKernel($dispatcher, null, $requestStack); $dispatcher->addListener(KernelEvents::EXCEPTION, static function (ExceptionEvent $event) use (&$capturedRequest, $requestStack) { $capturedRequest = $requestStack->getCurrentRequest(); $event->setResponse(new Response()); }); $kernel->terminateWithException(new \Exception('boo'), $request = Request::create('/')); $this->assertSame($request, $capturedRequest); $this->assertNull($requestStack->getCurrentRequest()); } public function testVerifyRequestStackPushPopDuringHandle() { $request = new Request(); $stack = $this->getMockBuilder(RequestStack::class)->onlyMethods(['push', 'pop'])->getMock(); $stack->expects($this->once())->method('push')->with($this->equalTo($request)); $stack->expects($this->once())->method('pop'); $dispatcher = new EventDispatcher(); $kernel = $this->getHttpKernel($dispatcher, null, $stack); $kernel->handle($request, HttpKernelInterface::MAIN_REQUEST); } public function testVerifyRequestStackPushPopWithStreamedResponse() { $request = new Request(); $stack = new RequestStack(); $dispatcher = new EventDispatcher(); $kernel = $this->getHttpKernel($dispatcher, static fn () => new StreamedResponse(static function () use ($stack) { echo $stack->getMainRequest()::class; }), $stack); $response = $kernel->handle($request, HttpKernelInterface::MAIN_REQUEST); self::assertNull($stack->getMainRequest()); ob_start(); $response->send(); self::assertSame(Request::class, ob_get_clean()); self::assertNull($stack->getMainRequest()); } public function testInconsistentClientIpsOnMainRequests() { $this->expectException(BadRequestHttpException::class); $request = new Request(); $request->setTrustedProxies(['1.1.1.1'], Request::HEADER_X_FORWARDED_FOR | Request::HEADER_FORWARDED); $request->server->set('REMOTE_ADDR', '1.1.1.1'); $request->headers->set('FORWARDED', 'for=2.2.2.2'); $request->headers->set('X_FORWARDED_FOR', '3.3.3.3'); $dispatcher = new EventDispatcher(); $dispatcher->addListener(KernelEvents::REQUEST, static function ($event) { $event->getRequest()->getClientIp(); }); $kernel = $this->getHttpKernel($dispatcher); $kernel->handle($request, $kernel::MAIN_REQUEST, false); Request::setTrustedProxies([], -1); } public function testResponseEventCanAccessControllerAttributes() { $dispatcher = new EventDispatcher(); $capturedAttributes = null; $dispatcher->addListener(KernelEvents::CONTROLLER, static function ($event) { $event->setController($event->getController(), [new Bar('test')]); }); $dispatcher->addListener(KernelEvents::RESPONSE, static function ($event) use (&$capturedAttributes) { $capturedAttributes = $event->controllerMetadata->getAttributes('*'); }); $kernel = $this->getHttpKernel($dispatcher); $kernel->handle(new Request(), HttpKernelInterface::MAIN_REQUEST, false); $this->assertEquals([new Bar('test')], $capturedAttributes); } public function testViewEventProvidesControllerArgumentsViaMetadata() { $dispatcher = new EventDispatcher(); $capturedArguments = $capturedNamedArguments = null; $dispatcher->addListener(KernelEvents::VIEW, static function ($event) use (&$capturedArguments, &$capturedNamedArguments) { $capturedArguments = $event->controllerMetadata->getArguments(); $capturedNamedArguments = $event->controllerMetadata->getNamedArguments(); $event->setResponse(new Response('ok')); }); $kernel = $this->getHttpKernel($dispatcher, static fn ($value) => $value, arguments: ['resolved']); $kernel->handle(new Request(), HttpKernelInterface::MAIN_REQUEST, false); $this->assertSame(['resolved'], $capturedArguments); $this->assertSame(['value' => 'resolved'], $capturedNamedArguments); } public function testExceptionEventProvidesControllerMetadata() { $dispatcher = new EventDispatcher(); $capturedController = $capturedArguments = null; $controller = static fn (string $value) => throw new \RuntimeException('boom'); $dispatcher->addListener(KernelEvents::EXCEPTION, static function ($event) use (&$capturedController, &$capturedArguments) { $capturedController = $event->controllerMetadata->getController(); $capturedArguments = $event->controllerMetadata->getArguments(); $event->setResponse(new Response('handled')); }); $kernel = $this->getHttpKernel($dispatcher, $controller, arguments: ['meta']); $response = $kernel->handle(new Request(), HttpKernelInterface::MAIN_REQUEST, true); $this->assertSame('handled', $response->getContent()); $this->assertSame($controller, $capturedController); $this->assertSame(['meta'], $capturedArguments); } public function testResponseEventWhenArgumentResolutionThrows() { $dispatcher = new EventDispatcher(); $dispatcher->addListener(KernelEvents::EXCEPTION, static function ($event) { $event->setResponse(new Response('handled', 400)); }); $controllerResolver = $this->createStub(ControllerResolverInterface::class); $controllerResolver ->method('getController') ->willReturn(static fn () => new Response('never')); $argumentResolver = $this->createStub(ArgumentResolverInterface::class); $argumentResolver ->method('getArguments') ->willThrowException(new BadRequestHttpException('boom')); $kernel = new HttpKernel($dispatcher, $controllerResolver, null, $argumentResolver); $response = $kernel->handle(new Request(), HttpKernelInterface::MAIN_REQUEST, true); $this->assertSame('handled', $response->getContent()); $this->assertSame(400, $response->getStatusCode()); } public function testFinishRequestEventKeepsControllerMetadata() { $dispatcher = new EventDispatcher(); $capturedArguments = null; $dispatcher->addListener(KernelEvents::FINISH_REQUEST, static function ($event) use (&$capturedArguments) { $capturedArguments = $event->controllerMetadata->getArguments(); }); $kernel = $this->getHttpKernel($dispatcher, static fn ($value) => new Response($value), arguments: ['done']); $response = $kernel->handle(new Request(), HttpKernelInterface::MAIN_REQUEST, false); $this->assertSame('done', $response->getContent()); $this->assertSame(['done'], $capturedArguments); } private function getHttpKernel(EventDispatcherInterface $eventDispatcher, $controller = null, ?RequestStack $requestStack = null, array $arguments = [], bool $handleAllThrowables = false) { $controller ??= static fn () => new Response('Hello'); $controllerResolver = $this->createStub(ControllerResolverInterface::class); $controllerResolver ->method('getController') ->willReturn($controller); $argumentResolver = $this->createStub(ArgumentResolverInterface::class); $argumentResolver ->method('getArguments') ->willReturn($arguments); return new HttpKernel($eventDispatcher, $controllerResolver, $requestStack, $argumentResolver, $handleAllThrowables); } private function assertResponseEquals(Response $expected, Response $actual) { $expected->setDate($actual->getDate()); $this->assertEquals($expected, $actual); } } class TestController { public function __invoke() { return new Response('foo'); } public function controller() { return new Response('foo'); } public static function staticController() { return new Response('foo'); } } function controller_func() { return new Response('foo'); }