diff --git a/Controller/ArgumentResolver/DateTimeValueResolver.php b/Controller/ArgumentResolver/DateTimeValueResolver.php index e9a7494a4c..f014b5bcb8 100644 --- a/Controller/ArgumentResolver/DateTimeValueResolver.php +++ b/Controller/ArgumentResolver/DateTimeValueResolver.php @@ -39,9 +39,10 @@ public function supports(Request $request, ArgumentMetadata $argument): bool public function resolve(Request $request, ArgumentMetadata $argument): iterable { $value = $request->attributes->get($argument->getName()); + $class = \DateTimeInterface::class === $argument->getType() ? \DateTimeImmutable::class : $argument->getType(); if ($value instanceof \DateTimeInterface) { - yield $value; + yield $value instanceof $class ? $value : $class::createFromInterface($value); return; } @@ -52,7 +53,6 @@ public function resolve(Request $request, ArgumentMetadata $argument): iterable return; } - $class = \DateTimeInterface::class === $argument->getType() ? \DateTimeImmutable::class : $argument->getType(); $format = null; if ($attributes = $argument->getAttributes(MapDateTime::class, ArgumentMetadata::IS_INSTANCEOF)) { diff --git a/ControllerMetadata/ArgumentMetadataFactory.php b/ControllerMetadata/ArgumentMetadataFactory.php index 921fba03c2..890589d652 100644 --- a/ControllerMetadata/ArgumentMetadataFactory.php +++ b/ControllerMetadata/ArgumentMetadataFactory.php @@ -33,7 +33,7 @@ public function createArgumentMetadata(string|object|array $controller): array $class = $reflection->class; } else { $reflection = new \ReflectionFunction($controller); - if ($class = str_contains($reflection->name, '{closure}') ? null : $reflection->getClosureScopeClass()) { + if ($class = str_contains($reflection->name, '{closure}') ? null : (\PHP_VERSION_ID >= 80111 ? $reflection->getClosureCalledClass() : $reflection->getClosureScopeClass())) { $class = $class->name; } } diff --git a/DataCollector/RequestDataCollector.php b/DataCollector/RequestDataCollector.php index ca1764cb46..8f45610b2c 100644 --- a/DataCollector/RequestDataCollector.php +++ b/DataCollector/RequestDataCollector.php @@ -477,7 +477,7 @@ private function parseController(array|object|string|null $controller): array|st } $controller['method'] = $r->name; - if ($class = $r->getClosureScopeClass()) { + if ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { $controller['class'] = $class->name; } else { return $r->name; diff --git a/Debug/FileLinkFormatter.php b/Debug/FileLinkFormatter.php index 0944652741..4eaeeb1513 100644 --- a/Debug/FileLinkFormatter.php +++ b/Debug/FileLinkFormatter.php @@ -35,7 +35,8 @@ class FileLinkFormatter */ public function __construct(string|array $fileLinkFormat = null, RequestStack $requestStack = null, string $baseDir = null, string|\Closure $urlFormat = null) { - $fileLinkFormat ??= $_SERVER['SYMFONY_IDE'] ?? ''; + $fileLinkFormat ??= $_ENV['SYMFONY_IDE'] ?? $_SERVER['SYMFONY_IDE'] ?? ''; + if (!\is_array($fileLinkFormat) && $fileLinkFormat = (ErrorRendererInterface::IDE_LINK_FORMATS[$fileLinkFormat] ?? $fileLinkFormat) ?: \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format') ?: false) { $i = strpos($f = $fileLinkFormat, '&', max(strrpos($f, '%f'), strrpos($f, '%l'))) ?: \strlen($f); $fileLinkFormat = [substr($f, 0, $i)] + preg_split('/&([^>]++)>/', substr($f, $i), -1, \PREG_SPLIT_DELIM_CAPTURE); diff --git a/EventListener/AbstractSessionListener.php b/EventListener/AbstractSessionListener.php index 05be006e17..e57bb17e6f 100644 --- a/EventListener/AbstractSessionListener.php +++ b/EventListener/AbstractSessionListener.php @@ -195,10 +195,11 @@ public function onKernelResponse(ResponseEvent $event) } if ($autoCacheControl) { + $maxAge = $response->headers->hasCacheControlDirective('public') ? 0 : (int) $response->getMaxAge(); $response - ->setExpires(new \DateTime()) + ->setExpires(new \DateTimeImmutable('+'.$maxAge.' seconds')) ->setPrivate() - ->setMaxAge(0) + ->setMaxAge($maxAge) ->headers->addCacheControlDirective('must-revalidate'); } diff --git a/Kernel.php b/Kernel.php index f279be7534..22efec0525 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.8'; - public const VERSION_ID = 60108; + public const VERSION = '6.1.9'; + public const VERSION_ID = 60109; public const MAJOR_VERSION = 6; public const MINOR_VERSION = 1; - public const RELEASE_VERSION = 8; + public const RELEASE_VERSION = 9; public const EXTRA_VERSION = ''; public const END_OF_MAINTENANCE = '01/2023'; diff --git a/Tests/Controller/ArgumentResolver/DateTimeValueResolverTest.php b/Tests/Controller/ArgumentResolver/DateTimeValueResolverTest.php index b438c527a3..ac10dfc1a5 100644 --- a/Tests/Controller/ArgumentResolver/DateTimeValueResolverTest.php +++ b/Tests/Controller/ArgumentResolver/DateTimeValueResolverTest.php @@ -20,7 +20,7 @@ class DateTimeValueResolverTest extends TestCase { - private $defaultTimezone; + private readonly string $defaultTimezone; protected function setUp(): void { @@ -32,13 +32,20 @@ protected function tearDown(): void date_default_timezone_set($this->defaultTimezone); } - public function getTimeZones() + public static function getTimeZones() { yield ['UTC']; yield ['Etc/GMT+9']; yield ['Etc/GMT-14']; } + public static function getClasses() + { + yield [\DateTimeInterface::class]; + yield [\DateTime::class]; + yield [FooDateTime::class]; + } + public function testSupports() { $resolver = new DateTimeValueResolver(); @@ -133,11 +140,16 @@ public function testNow(string $timezone) $this->assertSame($timezone, $results[0]->getTimezone()->getName(), 'Default timezone'); } - public function testPreviouslyConvertedAttribute() + /** + * @param class-string<\DateTimeInterface> $class + * + * @dataProvider getClasses + */ + public function testPreviouslyConvertedAttribute(string $class) { $resolver = new DateTimeValueResolver(); - $argument = new ArgumentMetadata('dummy', \DateTime::class, false, false, null, true); + $argument = new ArgumentMetadata('dummy', $class, false, false, null, true); $request = self::requestWithAttributes(['dummy' => $datetime = new \DateTime()]); /** @var \Generator $results */ @@ -145,7 +157,8 @@ public function testPreviouslyConvertedAttribute() $results = iterator_to_array($results); $this->assertCount(1, $results); - $this->assertSame($datetime, $results[0]); + $this->assertEquals($datetime, $results[0], 'The value is the same, but the class can be modified.'); + $this->assertInstanceOf($class, $results[0]); } public function testCustomClass() @@ -209,7 +222,7 @@ public function testWithFormat(string $timezone) $this->assertEquals('2016-09-08 12:34:56', $results[0]->format('Y-m-d H:i:s')); } - public function provideInvalidDates() + public static function provideInvalidDates() { return [ 'invalid date' => [ diff --git a/Tests/Debug/FileLinkFormatterTest.php b/Tests/Debug/FileLinkFormatterTest.php index d24958c6c5..57605c1ae4 100644 --- a/Tests/Debug/FileLinkFormatterTest.php +++ b/Tests/Debug/FileLinkFormatterTest.php @@ -27,9 +27,17 @@ public function testWhenNoFileLinkFormatAndNoRequest() public function testAfterUnserialize() { + $ide = $_ENV['SYMFONY_IDE'] ?? $_SERVER['SYMFONY_IDE'] ?? null; + $_ENV['SYMFONY_IDE'] = $_SERVER['SYMFONY_IDE'] = null; $sut = unserialize(serialize(new FileLinkFormatter())); $this->assertFalse($sut->format('/kernel/root/src/my/very/best/file.php', 3)); + + if (null === $ide) { + unset($_ENV['SYMFONY_IDE'], $_SERVER['SYMFONY_IDE']); + } else { + $_ENV['SYMFONY_IDE'] = $_SERVER['SYMFONY_IDE'] = $ide; + } } public function testWhenFileLinkFormatAndNoRequest() diff --git a/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php b/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php index 302f9e35dd..d6be9d3bd0 100644 --- a/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php +++ b/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php @@ -467,7 +467,7 @@ public function testAutowireAttribute() $container = new ContainerBuilder(); $resolver = $container->register('argument_resolver.service', 'stdClass')->addArgument([]); - $container->register('some.id', \stdClass::class); + $container->register('some.id', \stdClass::class)->setPublic(true); $container->setParameter('some.parameter', 'foo'); $container->register('foo', WithAutowireAttribute::class) diff --git a/Tests/EventListener/SessionListenerTest.php b/Tests/EventListener/SessionListenerTest.php index 02db1946c7..bc48f4b6cb 100644 --- a/Tests/EventListener/SessionListenerTest.php +++ b/Tests/EventListener/SessionListenerTest.php @@ -38,6 +38,7 @@ class SessionListenerTest extends TestCase { /** * @dataProvider provideSessionOptions + * * @runInSeparateProcess */ public function testSessionCookieOptions(array $phpSessionOptions, array $sessionOptions, array $expectedSessionOptions) @@ -554,6 +555,69 @@ public function testUninitializedSessionWithoutInitializedSession() $this->assertSame('60', $response->headers->getCacheControlDirective('s-maxage')); } + public function testResponseHeadersMaxAgeAndExpiresNotBeOverridenIfSessionStarted() + { + $session = $this->createMock(Session::class); + $session->expects($this->once())->method('getUsageIndex')->willReturn(1); + $session->expects($this->once())->method('getName')->willReturn('foo'); + $sessionFactory = $this->createMock(SessionFactory::class); + $sessionFactory->expects($this->once())->method('createSession')->willReturn($session); + + $container = new Container(); + $container->set('session_factory', $sessionFactory); + + $listener = new SessionListener($container); + $kernel = $this->createMock(HttpKernelInterface::class); + + $request = new Request(); + $listener->onKernelRequest(new RequestEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST)); + + $request->getSession(); + + $response = new Response(); + $response->setPrivate(); + $expiresHeader = gmdate('D, d M Y H:i:s', time() + 600).' GMT'; + $response->setMaxAge(600); + $listener->onKernelResponse(new ResponseEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, $response)); + + $this->assertTrue($response->headers->has('expires')); + $this->assertSame($expiresHeader, $response->headers->get('expires')); + $this->assertFalse($response->headers->has('max-age')); + $this->assertSame('600', $response->headers->getCacheControlDirective('max-age')); + $this->assertFalse($response->headers->hasCacheControlDirective('public')); + $this->assertTrue($response->headers->hasCacheControlDirective('private')); + $this->assertTrue($response->headers->hasCacheControlDirective('must-revalidate')); + $this->assertFalse($response->headers->has(AbstractSessionListener::NO_AUTO_CACHE_CONTROL_HEADER)); + } + + public function testResponseHeadersMaxAgeAndExpiresDefaultValuesIfSessionStarted() + { + $session = $this->createMock(Session::class); + $session->expects($this->once())->method('getUsageIndex')->willReturn(1); + + $container = new Container(); + + $listener = new SessionListener($container); + $kernel = $this->createMock(HttpKernelInterface::class); + + $request = new Request(); + $request->setSession($session); + $listener->onKernelRequest(new RequestEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST)); + + $response = new Response(); + $expiresHeader = gmdate('D, d M Y H:i:s', time()).' GMT'; + $listener->onKernelResponse(new ResponseEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, $response)); + + $this->assertTrue($response->headers->has('expires')); + $this->assertSame($expiresHeader, $response->headers->get('expires')); + $this->assertFalse($response->headers->has('max-age')); + $this->assertSame('0', $response->headers->getCacheControlDirective('max-age')); + $this->assertFalse($response->headers->hasCacheControlDirective('public')); + $this->assertTrue($response->headers->hasCacheControlDirective('private')); + $this->assertTrue($response->headers->hasCacheControlDirective('must-revalidate')); + $this->assertFalse($response->headers->has(AbstractSessionListener::NO_AUTO_CACHE_CONTROL_HEADER)); + } + public function testSurrogateMainRequestIsPublic() { $session = $this->createMock(Session::class);