From 92d01724686241218c20a696ae362d8a02d4a5f9 Mon Sep 17 00:00:00 2001
From: Shyim
Date: Mon, 14 Oct 2024 15:41:45 +0200
Subject: [PATCH 01/44] [HttpKernel] Let Monolog create the log folder
---
Kernel.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Kernel.php b/Kernel.php
index 5f32158f68..223baa3afd 100644
--- a/Kernel.php
+++ b/Kernel.php
@@ -632,7 +632,7 @@ protected function getKernelParameters()
*/
protected function buildContainer()
{
- foreach (['cache' => $this->getCacheDir(), 'build' => $this->warmupDir ?: $this->getBuildDir(), 'logs' => $this->getLogDir()] as $name => $dir) {
+ foreach (['cache' => $this->getCacheDir(), 'build' => $this->warmupDir ?: $this->getBuildDir()] as $name => $dir) {
if (!is_dir($dir)) {
if (false === @mkdir($dir, 0777, true) && !is_dir($dir)) {
throw new \RuntimeException(sprintf('Unable to create the "%s" directory (%s).', $name, $dir));
From 4b68f96409285fc1fc370ed09d25dad449d78f03 Mon Sep 17 00:00:00 2001
From: Fabien Potencier
Date: Wed, 20 Nov 2024 09:03:09 +0100
Subject: [PATCH 02/44] Bump version to 7.3
---
Kernel.php | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/Kernel.php b/Kernel.php
index 7e8b002079..ec5e3b0df3 100644
--- a/Kernel.php
+++ b/Kernel.php
@@ -73,15 +73,15 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
*/
private static array $freshCache = [];
- public const VERSION = '7.2.0-DEV';
- public const VERSION_ID = 70200;
+ public const VERSION = '7.3.0-DEV';
+ public const VERSION_ID = 70300;
public const MAJOR_VERSION = 7;
- public const MINOR_VERSION = 2;
+ public const MINOR_VERSION = 3;
public const RELEASE_VERSION = 0;
public const EXTRA_VERSION = 'DEV';
- public const END_OF_MAINTENANCE = '07/2025';
- public const END_OF_LIFE = '07/2025';
+ public const END_OF_MAINTENANCE = '05/2025';
+ public const END_OF_LIFE = '01/2026';
public function __construct(
protected string $environment,
From a1ecd5c472717cd4fb638b6c5f0ebbceb23096ce Mon Sep 17 00:00:00 2001
From: Felix Eymonot
Date: Tue, 10 Dec 2024 12:40:59 +0100
Subject: [PATCH 03/44] [HttpKernel] [MapQueryString] added key argument to
MapQueryString attribute
---
Attribute/MapQueryString.php | 1 +
CHANGELOG.md | 5 +++++
.../RequestPayloadValueResolver.php | 2 +-
.../RequestPayloadValueResolverTest.php | 21 +++++++++++++++++++
4 files changed, 28 insertions(+), 1 deletion(-)
diff --git a/Attribute/MapQueryString.php b/Attribute/MapQueryString.php
index dfff4ddcc9..07418df85c 100644
--- a/Attribute/MapQueryString.php
+++ b/Attribute/MapQueryString.php
@@ -37,6 +37,7 @@ public function __construct(
public readonly string|GroupSequence|array|null $validationGroups = null,
string $resolver = RequestPayloadValueResolver::class,
public readonly int $validationFailedStatusCode = Response::HTTP_NOT_FOUND,
+ public readonly ?string $key = null,
) {
parent::__construct($resolver);
}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1fc103b48d..501ddbe6b7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,11 @@
CHANGELOG
=========
+7.3
+---
+
+ * Add `$key` argument to `#[MapQueryString]` that allows using a specific key for argument resolving
+
7.2
---
diff --git a/Controller/ArgumentResolver/RequestPayloadValueResolver.php b/Controller/ArgumentResolver/RequestPayloadValueResolver.php
index 1f0ff7cc0f..2da4b43905 100644
--- a/Controller/ArgumentResolver/RequestPayloadValueResolver.php
+++ b/Controller/ArgumentResolver/RequestPayloadValueResolver.php
@@ -186,7 +186,7 @@ public static function getSubscribedEvents(): array
private function mapQueryString(Request $request, ArgumentMetadata $argument, MapQueryString $attribute): ?object
{
- if (!($data = $request->query->all()) && ($argument->isNullable() || $argument->hasDefaultValue())) {
+ if (!($data = $request->query->all($attribute->key)) && ($argument->isNullable() || $argument->hasDefaultValue())) {
return null;
}
diff --git a/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php b/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php
index 8b26767f9e..2ed2e77042 100644
--- a/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php
+++ b/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php
@@ -874,6 +874,27 @@ public function testBoolArgumentInJsonBody()
$this->assertTrue($event->getArguments()[0]->value);
}
+
+ public function testConfigKeyForQueryString()
+ {
+ $serializer = new Serializer([new ObjectNormalizer()]);
+ $validator = $this->createMock(ValidatorInterface::class);
+ $resolver = new RequestPayloadValueResolver($serializer, $validator);
+
+ $argument = new ArgumentMetadata('filtered', QueryPayload::class, false, false, null, false, [
+ MapQueryString::class => new MapQueryString(key: 'value'),
+ ]);
+ $request = Request::create('/', Request::METHOD_GET, ['value' => ['page' => 1.0]]);
+
+ $kernel = $this->createMock(HttpKernelInterface::class);
+ $arguments = $resolver->resolve($request, $argument);
+ $event = new ControllerArgumentsEvent($kernel, function () {}, $arguments, $request, HttpKernelInterface::MAIN_REQUEST);
+
+ $resolver->onKernelControllerArguments($event);
+
+ $this->assertInstanceOf(QueryPayload::class, $event->getArguments()[0]);
+ $this->assertSame(1.0, $event->getArguments()[0]->page);
+ }
}
class RequestPayload
From 406c453966dc1420d8b19ff45007bac8a51d401c Mon Sep 17 00:00:00 2001
From: Dariusz Ruminski
Date: Wed, 11 Dec 2024 14:08:35 +0100
Subject: [PATCH 04/44] chore: PHP CS Fixer fixes
---
DataCollector/ConfigDataCollector.php | 6 +++---
HttpCache/ResponseCacheStrategy.php | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/DataCollector/ConfigDataCollector.php b/DataCollector/ConfigDataCollector.php
index 8713dcf1e5..cc8ff3ada9 100644
--- a/DataCollector/ConfigDataCollector.php
+++ b/DataCollector/ConfigDataCollector.php
@@ -57,11 +57,11 @@ public function collect(Request $request, Response $response, ?\Throwable $excep
'php_intl_locale' => class_exists(\Locale::class, false) && \Locale::getDefault() ? \Locale::getDefault() : 'n/a',
'php_timezone' => date_default_timezone_get(),
'xdebug_enabled' => \extension_loaded('xdebug'),
- 'xdebug_status' => \extension_loaded('xdebug') ? ($xdebugMode && 'off' !== $xdebugMode ? 'Enabled (' . $xdebugMode . ')' : 'Not enabled') : 'Not installed',
+ 'xdebug_status' => \extension_loaded('xdebug') ? ($xdebugMode && 'off' !== $xdebugMode ? 'Enabled ('.$xdebugMode.')' : 'Not enabled') : 'Not installed',
'apcu_enabled' => \extension_loaded('apcu') && filter_var(\ini_get('apc.enabled'), \FILTER_VALIDATE_BOOL),
- 'apcu_status' => \extension_loaded('apcu') ? (filter_var(\ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? 'Enabled' : 'Not enabled') : 'Not installed',
+ 'apcu_status' => \extension_loaded('apcu') ? (filter_var(\ini_get('apc.enabled'), \FILTER_VALIDATE_BOOLEAN) ? 'Enabled' : 'Not enabled') : 'Not installed',
'zend_opcache_enabled' => \extension_loaded('Zend OPcache') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOL),
- 'zend_opcache_status' => \extension_loaded('Zend OPcache') ? (filter_var(\ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN) ? 'Enabled' : 'Not enabled') : 'Not installed',
+ 'zend_opcache_status' => \extension_loaded('Zend OPcache') ? (filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN) ? 'Enabled' : 'Not enabled') : 'Not installed',
'bundles' => [],
'sapi_name' => \PHP_SAPI,
];
diff --git a/HttpCache/ResponseCacheStrategy.php b/HttpCache/ResponseCacheStrategy.php
index 9176ba5881..4aba46728d 100644
--- a/HttpCache/ResponseCacheStrategy.php
+++ b/HttpCache/ResponseCacheStrategy.php
@@ -222,7 +222,7 @@ private function storeRelativeAgeDirective(string $directive, ?int $value, ?int
}
if (false !== $this->ageDirectives[$directive]) {
- $value = min($value ?? PHP_INT_MAX, $expires ?? PHP_INT_MAX);
+ $value = min($value ?? \PHP_INT_MAX, $expires ?? \PHP_INT_MAX);
$value -= $age;
$this->ageDirectives[$directive] = null !== $this->ageDirectives[$directive] ? min($this->ageDirectives[$directive], $value) : $value;
}
From 49bce5da5a3135fca24b33fda1f8276bdcd64f1b Mon Sep 17 00:00:00 2001
From: valtzu
Date: Wed, 27 Nov 2024 22:28:12 +0200
Subject: [PATCH 05/44] Generate url-safe signatures
---
Tests/Fragment/EsiFragmentRendererTest.php | 4 ++--
Tests/Fragment/HIncludeFragmentRendererTest.php | 2 +-
Tests/Fragment/SsiFragmentRendererTest.php | 4 ++--
composer.json | 2 +-
4 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/Tests/Fragment/EsiFragmentRendererTest.php b/Tests/Fragment/EsiFragmentRendererTest.php
index fa9885d275..6a08d7eae6 100644
--- a/Tests/Fragment/EsiFragmentRendererTest.php
+++ b/Tests/Fragment/EsiFragmentRendererTest.php
@@ -61,7 +61,7 @@ public function testRenderControllerReference()
$altReference = new ControllerReference('alt_controller', [], []);
$this->assertEquals(
- '',
+ '',
$strategy->render($reference, $request, ['alt' => $altReference])->getContent()
);
}
@@ -79,7 +79,7 @@ public function testRenderControllerReferenceWithAbsoluteUri()
$altReference = new ControllerReference('alt_controller', [], []);
$this->assertSame(
- '',
+ '',
$strategy->render($reference, $request, ['alt' => $altReference, 'absolute_uri' => true])->getContent()
);
}
diff --git a/Tests/Fragment/HIncludeFragmentRendererTest.php b/Tests/Fragment/HIncludeFragmentRendererTest.php
index f74887ade3..82b80a86ff 100644
--- a/Tests/Fragment/HIncludeFragmentRendererTest.php
+++ b/Tests/Fragment/HIncludeFragmentRendererTest.php
@@ -32,7 +32,7 @@ public function testRenderWithControllerAndSigner()
{
$strategy = new HIncludeFragmentRenderer(null, new UriSigner('foo'));
- $this->assertEquals('', $strategy->render(new ControllerReference('main_controller', [], []), Request::create('/'))->getContent());
+ $this->assertEquals('', $strategy->render(new ControllerReference('main_controller', [], []), Request::create('/'))->getContent());
}
public function testRenderWithUri()
diff --git a/Tests/Fragment/SsiFragmentRendererTest.php b/Tests/Fragment/SsiFragmentRendererTest.php
index 4af00f9f75..0d3f1dc2d4 100644
--- a/Tests/Fragment/SsiFragmentRendererTest.php
+++ b/Tests/Fragment/SsiFragmentRendererTest.php
@@ -52,7 +52,7 @@ public function testRenderControllerReference()
$altReference = new ControllerReference('alt_controller', [], []);
$this->assertEquals(
- '',
+ '',
$strategy->render($reference, $request, ['alt' => $altReference])->getContent()
);
}
@@ -70,7 +70,7 @@ public function testRenderControllerReferenceWithAbsoluteUri()
$altReference = new ControllerReference('alt_controller', [], []);
$this->assertSame(
- '',
+ '',
$strategy->render($reference, $request, ['alt' => $altReference, 'absolute_uri' => true])->getContent()
);
}
diff --git a/composer.json b/composer.json
index 89421417f5..e9cb077587 100644
--- a/composer.json
+++ b/composer.json
@@ -20,7 +20,7 @@
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/error-handler": "^6.4|^7.0",
"symfony/event-dispatcher": "^6.4|^7.0",
- "symfony/http-foundation": "^6.4|^7.0",
+ "symfony/http-foundation": "^7.3",
"symfony/polyfill-ctype": "^1.8",
"psr/log": "^1|^2|^3"
},
From f4a8c06e9a8b6e8a994ce4ecec9f7e334be933cd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Simon=20Andr=C3=A9?=
Date: Thu, 26 Dec 2024 01:19:19 +0100
Subject: [PATCH 06/44] [Cache][HttpKernel] Add a `noStore` argument to the `#`
attribute
---
Attribute/Cache.php | 12 +++++
EventListener/CacheAttributeListener.php | 9 ++++
.../CacheAttributeListenerTest.php | 47 +++++++++++++++++++
3 files changed, 68 insertions(+)
diff --git a/Attribute/Cache.php b/Attribute/Cache.php
index 19d13e9228..fa2401a78c 100644
--- a/Attribute/Cache.php
+++ b/Attribute/Cache.php
@@ -102,6 +102,18 @@ public function __construct(
* It can be expressed in seconds or with a relative time format (1 day, 2 weeks, ...).
*/
public int|string|null $staleIfError = null,
+
+ /**
+ * Add the "no-store" Cache-Control directive when set to true.
+ *
+ * This directive indicates that no part of the response can be cached
+ * in any cache (not in a shared cache, nor in a private cache).
+ *
+ * Supersedes the "$public" and "$smaxage" values.
+ *
+ * @see https://datatracker.ietf.org/doc/html/rfc7234#section-5.2.2.3
+ */
+ public ?bool $noStore = null,
) {
}
}
diff --git a/EventListener/CacheAttributeListener.php b/EventListener/CacheAttributeListener.php
index f428ea9462..e913edf9e5 100644
--- a/EventListener/CacheAttributeListener.php
+++ b/EventListener/CacheAttributeListener.php
@@ -163,6 +163,15 @@ public function onKernelResponse(ResponseEvent $event): void
if (false === $cache->public) {
$response->setPrivate();
}
+
+ if (true === $cache->noStore) {
+ $response->setPrivate();
+ $response->headers->addCacheControlDirective('no-store');
+ }
+
+ if (false === $cache->noStore) {
+ $response->headers->removeCacheControlDirective('no-store');
+ }
}
}
diff --git a/Tests/EventListener/CacheAttributeListenerTest.php b/Tests/EventListener/CacheAttributeListenerTest.php
index b888579b80..b185ea8994 100644
--- a/Tests/EventListener/CacheAttributeListenerTest.php
+++ b/Tests/EventListener/CacheAttributeListenerTest.php
@@ -91,6 +91,50 @@ public function testResponseIsPrivateIfConfigurationIsPublicFalse()
$this->assertTrue($this->response->headers->hasCacheControlDirective('private'));
}
+ public function testResponseIsPublicIfConfigurationIsPublicTrueNoStoreFalse()
+ {
+ $request = $this->createRequest(new Cache(public: true, noStore: false));
+
+ $this->listener->onKernelResponse($this->createEventMock($request, $this->response));
+
+ $this->assertTrue($this->response->headers->hasCacheControlDirective('public'));
+ $this->assertFalse($this->response->headers->hasCacheControlDirective('private'));
+ $this->assertFalse($this->response->headers->hasCacheControlDirective('no-store'));
+ }
+
+ public function testResponseIsPrivateIfConfigurationIsPublicTrueNoStoreTrue()
+ {
+ $request = $this->createRequest(new Cache(public: true, noStore: true));
+
+ $this->listener->onKernelResponse($this->createEventMock($request, $this->response));
+
+ $this->assertFalse($this->response->headers->hasCacheControlDirective('public'));
+ $this->assertTrue($this->response->headers->hasCacheControlDirective('private'));
+ $this->assertTrue($this->response->headers->hasCacheControlDirective('no-store'));
+ }
+
+ public function testResponseIsPrivateNoStoreIfConfigurationIsNoStoreTrue()
+ {
+ $request = $this->createRequest(new Cache(noStore: true));
+
+ $this->listener->onKernelResponse($this->createEventMock($request, $this->response));
+
+ $this->assertFalse($this->response->headers->hasCacheControlDirective('public'));
+ $this->assertTrue($this->response->headers->hasCacheControlDirective('private'));
+ $this->assertTrue($this->response->headers->hasCacheControlDirective('no-store'));
+ }
+
+ public function testResponseIsPrivateIfSharedMaxAgeSetAndNoStoreIsTrue()
+ {
+ $request = $this->createRequest(new Cache(smaxage: 1, noStore: true));
+
+ $this->listener->onKernelResponse($this->createEventMock($request, $this->response));
+
+ $this->assertFalse($this->response->headers->hasCacheControlDirective('public'));
+ $this->assertTrue($this->response->headers->hasCacheControlDirective('private'));
+ $this->assertTrue($this->response->headers->hasCacheControlDirective('no-store'));
+ }
+
public function testResponseVary()
{
$vary = ['foobar'];
@@ -132,6 +176,7 @@ public function testAttributeConfigurationsAreSetOnResponse()
$this->assertFalse($this->response->headers->hasCacheControlDirective('max-stale'));
$this->assertFalse($this->response->headers->hasCacheControlDirective('stale-while-revalidate'));
$this->assertFalse($this->response->headers->hasCacheControlDirective('stale-if-error'));
+ $this->assertFalse($this->response->headers->hasCacheControlDirective('no-store'));
$this->request->attributes->set('_cache', [new Cache(
expires: 'tomorrow',
@@ -140,6 +185,7 @@ public function testAttributeConfigurationsAreSetOnResponse()
maxStale: '5',
staleWhileRevalidate: '6',
staleIfError: '7',
+ noStore: true,
)]);
$this->listener->onKernelResponse($this->event);
@@ -149,6 +195,7 @@ public function testAttributeConfigurationsAreSetOnResponse()
$this->assertSame('5', $this->response->headers->getCacheControlDirective('max-stale'));
$this->assertSame('6', $this->response->headers->getCacheControlDirective('stale-while-revalidate'));
$this->assertSame('7', $this->response->headers->getCacheControlDirective('stale-if-error'));
+ $this->assertTrue($this->response->headers->hasCacheControlDirective('no-store'));
$this->assertInstanceOf(\DateTimeInterface::class, $this->response->getExpires());
}
From 9c1165cecf40349035aa5b8492caa4ccf5233973 Mon Sep 17 00:00:00 2001
From: Jan Rosier
Date: Mon, 6 Jan 2025 15:35:18 +0100
Subject: [PATCH 07/44] Use spl_object_id() instead of spl_object_hash()
---
DataCollector/LoggerDataCollector.php | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/DataCollector/LoggerDataCollector.php b/DataCollector/LoggerDataCollector.php
index 428d676240..29024f6e74 100644
--- a/DataCollector/LoggerDataCollector.php
+++ b/DataCollector/LoggerDataCollector.php
@@ -233,10 +233,10 @@ private function sanitizeLogs(array $logs): array
$exception = $log['context']['exception'];
if ($exception instanceof SilencedErrorContext) {
- if (isset($silencedLogs[$h = spl_object_hash($exception)])) {
+ if (isset($silencedLogs[$id = spl_object_id($exception)])) {
continue;
}
- $silencedLogs[$h] = true;
+ $silencedLogs[$id] = true;
if (!isset($sanitizedLogs[$message])) {
$sanitizedLogs[$message] = $log + [
@@ -312,10 +312,10 @@ private function computeErrorsCount(array $containerDeprecationLogs): array
if ($this->isSilencedOrDeprecationErrorLog($log)) {
$exception = $log['context']['exception'];
if ($exception instanceof SilencedErrorContext) {
- if (isset($silencedLogs[$h = spl_object_hash($exception)])) {
+ if (isset($silencedLogs[$id = spl_object_id($exception)])) {
continue;
}
- $silencedLogs[$h] = true;
+ $silencedLogs[$id] = true;
$count['scream_count'] += $exception->count;
} else {
++$count['deprecation_count'];
From eb0e7ae3cffc41d41fc9705c35366e8aac763d95 Mon Sep 17 00:00:00 2001
From: Dariusz Ruminski
Date: Fri, 10 Jan 2025 15:17:09 +0100
Subject: [PATCH 08/44] chore: PHP CS Fixer fixes
---
.../ArgumentResolver/RequestPayloadValueResolverTest.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php b/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php
index 649a7dc87e..77cf7d9c58 100644
--- a/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php
+++ b/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php
@@ -420,7 +420,7 @@ public function testQueryStringParameterTypeMismatch()
try {
$resolver->onKernelControllerArguments($event);
- $this->fail(sprintf('Expected "%s" to be thrown.', HttpException::class));
+ $this->fail(\sprintf('Expected "%s" to be thrown.', HttpException::class));
} catch (HttpException $e) {
$validationFailedException = $e->getPrevious();
$this->assertInstanceOf(ValidationFailedException::class, $validationFailedException);
@@ -514,7 +514,7 @@ public function testRequestInputTypeMismatch()
try {
$resolver->onKernelControllerArguments($event);
- $this->fail(sprintf('Expected "%s" to be thrown.', HttpException::class));
+ $this->fail(\sprintf('Expected "%s" to be thrown.', HttpException::class));
} catch (HttpException $e) {
$validationFailedException = $e->getPrevious();
$this->assertInstanceOf(ValidationFailedException::class, $validationFailedException);
From dc54eb349f6b261db71cb6d5bddf6ca4241deef9 Mon Sep 17 00:00:00 2001
From: Nicolas Grekas
Date: Wed, 15 Jan 2025 18:41:27 +0100
Subject: [PATCH 09/44] [HttpKernel] Improve MapQueryParameter handling of
options
---
Attribute/MapQueryParameter.php | 6 ++---
.../QueryParameterValueResolver.php | 2 +-
.../QueryParameterValueResolverTest.php | 22 +++++++++----------
3 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/Attribute/MapQueryParameter.php b/Attribute/MapQueryParameter.php
index ec9bf57726..486813a820 100644
--- a/Attribute/MapQueryParameter.php
+++ b/Attribute/MapQueryParameter.php
@@ -28,9 +28,9 @@ final class MapQueryParameter extends ValueResolver
* @see https://php.net/manual/filter.constants for filter, flags and options
*
* @param string|null $name The name of the query parameter; if null, the name of the argument in the controller will be used
- * @param (FILTER_VALIDATE_*)|(FILTER_SANITIZE_*)|null $filter The filter to pass to "filter_var()"
- * @param int-mask-of<(FILTER_FLAG_*)|FILTER_NULL_ON_FAILURE> $flags The flags to pass to "filter_var()"
- * @param array $options The options to pass to "filter_var()"
+ * @param (FILTER_VALIDATE_*)|(FILTER_SANITIZE_*)|null $filter The filter to pass to "filter_var()", deduced from the type-hint if null
+ * @param int-mask-of<(FILTER_FLAG_*)|FILTER_NULL_ON_FAILURE> $flags
+ * @param array{min_range?: int|float, max_range?: int|float, regexp?: string, ...} $options
* @param class-string|string $resolver The name of the resolver to use
*/
public function __construct(
diff --git a/Controller/ArgumentResolver/QueryParameterValueResolver.php b/Controller/ArgumentResolver/QueryParameterValueResolver.php
index 8ceaba6da4..1566119dfd 100644
--- a/Controller/ArgumentResolver/QueryParameterValueResolver.php
+++ b/Controller/ArgumentResolver/QueryParameterValueResolver.php
@@ -76,7 +76,7 @@ public function resolve(Request $request, ArgumentMetadata $argument): array
$enumType = null;
$filter = match ($type) {
'array' => \FILTER_DEFAULT,
- 'string' => \FILTER_DEFAULT,
+ 'string' => isset($attribute->options['regexp']) ? \FILTER_VALIDATE_REGEXP : \FILTER_DEFAULT,
'int' => \FILTER_VALIDATE_INT,
'float' => \FILTER_VALIDATE_FLOAT,
'bool' => \FILTER_VALIDATE_BOOL,
diff --git a/Tests/Controller/ArgumentResolver/QueryParameterValueResolverTest.php b/Tests/Controller/ArgumentResolver/QueryParameterValueResolverTest.php
index 43161d1a10..194cfd2e52 100644
--- a/Tests/Controller/ArgumentResolver/QueryParameterValueResolverTest.php
+++ b/Tests/Controller/ArgumentResolver/QueryParameterValueResolverTest.php
@@ -108,50 +108,50 @@ public static function validDataProvider(): iterable
yield 'parameter found and string with regexp filter that matches' => [
Request::create('/', 'GET', ['firstName' => 'John']),
- new ArgumentMetadata('firstName', 'string', false, false, false, attributes: [new MapQueryParameter(filter: \FILTER_VALIDATE_REGEXP, flags: \FILTER_NULL_ON_FAILURE, options: ['regexp' => '/John/'])]),
+ new ArgumentMetadata('firstName', 'string', false, false, false, attributes: [new MapQueryParameter(options: ['regexp' => '/John/'])]),
['John'],
];
yield 'parameter found and string with regexp filter that falls back to null on failure' => [
Request::create('/', 'GET', ['firstName' => 'Fabien']),
- new ArgumentMetadata('firstName', 'string', false, false, false, attributes: [new MapQueryParameter(filter: \FILTER_VALIDATE_REGEXP, flags: \FILTER_NULL_ON_FAILURE, options: ['regexp' => '/John/'])]),
+ new ArgumentMetadata('firstName', 'string', false, false, false, attributes: [new MapQueryParameter(flags: \FILTER_NULL_ON_FAILURE, options: ['regexp' => '/John/'])]),
[null],
];
yield 'parameter found and string variadic with regexp filter that matches' => [
Request::create('/', 'GET', ['firstName' => ['John', 'John']]),
- new ArgumentMetadata('firstName', 'string', true, false, false, attributes: [new MapQueryParameter(filter: \FILTER_VALIDATE_REGEXP, flags: \FILTER_NULL_ON_FAILURE, options: ['regexp' => '/John/'])]),
+ new ArgumentMetadata('firstName', 'string', true, false, false, attributes: [new MapQueryParameter(options: ['regexp' => '/John/'])]),
['John', 'John'],
];
yield 'parameter found and string variadic with regexp filter that falls back to null on failure' => [
Request::create('/', 'GET', ['firstName' => ['John', 'Fabien']]),
- new ArgumentMetadata('firstName', 'string', true, false, false, attributes: [new MapQueryParameter(filter: \FILTER_VALIDATE_REGEXP, flags: \FILTER_NULL_ON_FAILURE, options: ['regexp' => '/John/'])]),
+ new ArgumentMetadata('firstName', 'string', true, false, false, attributes: [new MapQueryParameter(flags: \FILTER_NULL_ON_FAILURE, options: ['regexp' => '/John/'])]),
['John'],
];
yield 'parameter found and integer' => [
- Request::create('/', 'GET', ['age' => 123]),
+ Request::create('/', 'GET', ['age' => '123']),
new ArgumentMetadata('age', 'int', false, false, false, attributes: [new MapQueryParameter()]),
[123],
];
yield 'parameter found and integer variadic' => [
- Request::create('/', 'GET', ['age' => [123, 222]]),
+ Request::create('/', 'GET', ['age' => ['123', '222']]),
new ArgumentMetadata('age', 'int', true, false, false, attributes: [new MapQueryParameter()]),
[123, 222],
];
yield 'parameter found and float' => [
- Request::create('/', 'GET', ['price' => 10.99]),
+ Request::create('/', 'GET', ['price' => '10.99']),
new ArgumentMetadata('price', 'float', false, false, false, attributes: [new MapQueryParameter()]),
[10.99],
];
yield 'parameter found and float variadic' => [
- Request::create('/', 'GET', ['price' => [10.99, 5.99]]),
+ Request::create('/', 'GET', ['price' => ['10.99e2', '5.99']]),
new ArgumentMetadata('price', 'float', true, false, false, attributes: [new MapQueryParameter()]),
- [10.99, 5.99],
+ [1099.0, 5.99],
];
yield 'parameter found and boolean yes' => [
@@ -209,7 +209,7 @@ public static function validDataProvider(): iterable
];
yield 'parameter found and backing type variadic and at least one backing value not int nor string that fallbacks to null on failure' => [
- Request::create('/', 'GET', ['suits' => [1, 'D']]),
+ Request::create('/', 'GET', ['suits' => ['1', 'D']]),
new ArgumentMetadata('suits', Suit::class, false, false, false, attributes: [new MapQueryParameter(flags: \FILTER_NULL_ON_FAILURE)]),
[null],
];
@@ -265,7 +265,7 @@ public static function invalidArgumentTypeProvider(): iterable
public static function invalidOrMissingArgumentProvider(): iterable
{
yield 'parameter found and array variadic with parameter not array failure' => [
- Request::create('/', 'GET', ['ids' => [['1', '2'], 1]]),
+ Request::create('/', 'GET', ['ids' => [['1', '2'], '1']]),
new ArgumentMetadata('ids', 'array', true, false, false, attributes: [new MapQueryParameter()]),
new NotFoundHttpException('Invalid query parameter "ids".'),
];
From cb1eeade523fd4daae0b053678bca1361b4465e3 Mon Sep 17 00:00:00 2001
From: seb-jean
Date: Wed, 30 Oct 2024 14:27:35 +0100
Subject: [PATCH 10/44] [HttpKernel] Support `Uid` in `#[MapQueryParameter]`
---
CHANGELOG.md | 1 +
.../QueryParameterValueResolver.php | 14 +++++++++++++-
.../QueryParameterValueResolverTest.php | 13 ++++++++++---
3 files changed, 24 insertions(+), 4 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 501ddbe6b7..cc9da8a2a9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@ CHANGELOG
---
* Add `$key` argument to `#[MapQueryString]` that allows using a specific key for argument resolving
+ * Support `Uid` in `#[MapQueryParameter]`
7.2
---
diff --git a/Controller/ArgumentResolver/QueryParameterValueResolver.php b/Controller/ArgumentResolver/QueryParameterValueResolver.php
index 1566119dfd..5fe3d75313 100644
--- a/Controller/ArgumentResolver/QueryParameterValueResolver.php
+++ b/Controller/ArgumentResolver/QueryParameterValueResolver.php
@@ -16,6 +16,7 @@
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
use Symfony\Component\HttpKernel\Exception\HttpException;
+use Symfony\Component\Uid\AbstractUid;
/**
* Resolve arguments of type: array, string, int, float, bool, \BackedEnum from query parameters.
@@ -73,6 +74,12 @@ public function resolve(Request $request, ArgumentMetadata $argument): array
$options['flags'] |= \FILTER_REQUIRE_SCALAR;
}
+ $uidType = null;
+ if (is_subclass_of($type, AbstractUid::class)) {
+ $uidType = $type;
+ $type = 'uid';
+ }
+
$enumType = null;
$filter = match ($type) {
'array' => \FILTER_DEFAULT,
@@ -80,10 +87,11 @@ public function resolve(Request $request, ArgumentMetadata $argument): array
'int' => \FILTER_VALIDATE_INT,
'float' => \FILTER_VALIDATE_FLOAT,
'bool' => \FILTER_VALIDATE_BOOL,
+ 'uid' => \FILTER_DEFAULT,
default => match ($enumType = is_subclass_of($type, \BackedEnum::class) ? (new \ReflectionEnum($type))->getBackingType()->getName() : null) {
'int' => \FILTER_VALIDATE_INT,
'string' => \FILTER_DEFAULT,
- default => throw new \LogicException(\sprintf('#[MapQueryParameter] cannot be used on controller argument "%s$%s" of type "%s"; one of array, string, int, float, bool or \BackedEnum should be used.', $argument->isVariadic() ? '...' : '', $argument->getName(), $type ?? 'mixed')),
+ default => throw new \LogicException(\sprintf('#[MapQueryParameter] cannot be used on controller argument "%s$%s" of type "%s"; one of array, string, int, float, bool, uid or \BackedEnum should be used.', $argument->isVariadic() ? '...' : '', $argument->getName(), $type ?? 'mixed')),
},
};
@@ -105,6 +113,10 @@ public function resolve(Request $request, ArgumentMetadata $argument): array
$value = \is_array($value) ? array_map($enumFrom, $value) : $enumFrom($value);
}
+ if (null !== $uidType) {
+ $value = \is_array($value) ? array_map([$uidType, 'fromString'], $value) : $uidType::fromString($value);
+ }
+
if (null === $value && !($attribute->flags & \FILTER_NULL_ON_FAILURE)) {
throw HttpException::fromStatusCode($validationFailedCode, \sprintf('Invalid query parameter "%s".', $name));
}
diff --git a/Tests/Controller/ArgumentResolver/QueryParameterValueResolverTest.php b/Tests/Controller/ArgumentResolver/QueryParameterValueResolverTest.php
index 194cfd2e52..2b887db821 100644
--- a/Tests/Controller/ArgumentResolver/QueryParameterValueResolverTest.php
+++ b/Tests/Controller/ArgumentResolver/QueryParameterValueResolverTest.php
@@ -22,6 +22,7 @@
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Tests\Fixtures\Suit;
+use Symfony\Component\Uid\Ulid;
class QueryParameterValueResolverTest extends TestCase
{
@@ -44,7 +45,7 @@ public function testSkipWhenNoAttribute()
*/
public function testResolvingSuccessfully(Request $request, ArgumentMetadata $metadata, array $expected)
{
- $this->assertSame($expected, $this->resolver->resolve($request, $metadata));
+ $this->assertEquals($expected, $this->resolver->resolve($request, $metadata));
}
/**
@@ -231,6 +232,12 @@ public static function validDataProvider(): iterable
new ArgumentMetadata('firstName', 'string', false, true, false, attributes: [new MapQueryParameter()]),
[],
];
+
+ yield 'parameter found and ULID' => [
+ Request::create('/', 'GET', ['groupId' => '01E439TP9XJZ9RPFH3T1PYBCR8']),
+ new ArgumentMetadata('groupId', Ulid::class, false, true, false, attributes: [new MapQueryParameter()]),
+ [Ulid::fromString('01E439TP9XJZ9RPFH3T1PYBCR8')],
+ ];
}
/**
@@ -245,13 +252,13 @@ public static function invalidArgumentTypeProvider(): iterable
yield 'unsupported type' => [
Request::create('/', 'GET', ['standardClass' => 'test']),
new ArgumentMetadata('standardClass', \stdClass::class, false, false, false, attributes: [new MapQueryParameter()]),
- '#[MapQueryParameter] cannot be used on controller argument "$standardClass" of type "stdClass"; one of array, string, int, float, bool or \BackedEnum should be used.',
+ '#[MapQueryParameter] cannot be used on controller argument "$standardClass" of type "stdClass"; one of array, string, int, float, bool, uid or \BackedEnum should be used.',
];
yield 'unsupported type variadic' => [
Request::create('/', 'GET', ['standardClass' => 'test']),
new ArgumentMetadata('standardClass', \stdClass::class, true, false, false, attributes: [new MapQueryParameter()]),
- '#[MapQueryParameter] cannot be used on controller argument "...$standardClass" of type "stdClass"; one of array, string, int, float, bool or \BackedEnum should be used.',
+ '#[MapQueryParameter] cannot be used on controller argument "...$standardClass" of type "stdClass"; one of array, string, int, float, bool, uid or \BackedEnum should be used.',
];
}
From 6feb9cf0d7ef1b7e01dadfd6c9ee5925eff57b7d Mon Sep 17 00:00:00 2001
From: Christian Flothmann
Date: Sun, 2 Mar 2025 16:03:52 +0100
Subject: [PATCH 11/44] replace assertEmpty() with stricter assertions
---
.../TraceableValueResolverTest.php | 2 +-
.../UploadedFileValueResolverTest.php | 2 +-
Tests/DataCollector/DumpDataCollectorTest.php | 6 ++---
.../AddAnnotatedClassesToCachePassTest.php | 22 +++++++++----------
...sterControllerArgumentLocatorsPassTest.php | 6 ++---
Tests/EventListener/SessionListenerTest.php | 8 +++----
Tests/Fragment/InlineFragmentRendererTest.php | 2 +-
Tests/HttpCache/HttpCacheTest.php | 12 +++++-----
Tests/HttpCache/StoreTest.php | 10 ++++-----
Tests/Profiler/FileProfilerStorageTest.php | 2 +-
10 files changed, 36 insertions(+), 36 deletions(-)
diff --git a/Tests/Controller/ArgumentResolver/TraceableValueResolverTest.php b/Tests/Controller/ArgumentResolver/TraceableValueResolverTest.php
index 5ede33ccb3..cf4e837f73 100644
--- a/Tests/Controller/ArgumentResolver/TraceableValueResolverTest.php
+++ b/Tests/Controller/ArgumentResolver/TraceableValueResolverTest.php
@@ -32,7 +32,7 @@ public function testTimingsInResolve()
foreach ($iterable as $index => $resolved) {
$event = $stopwatch->getEvent(ResolverStub::class.'::resolve');
$this->assertTrue($event->isStarted());
- $this->assertEmpty($event->getPeriods());
+ $this->assertSame([], $event->getPeriods());
switch ($index) {
case 0:
$this->assertEquals('first', $resolved);
diff --git a/Tests/Controller/ArgumentResolver/UploadedFileValueResolverTest.php b/Tests/Controller/ArgumentResolver/UploadedFileValueResolverTest.php
index 5eb0d32483..11ab6f36a1 100644
--- a/Tests/Controller/ArgumentResolver/UploadedFileValueResolverTest.php
+++ b/Tests/Controller/ArgumentResolver/UploadedFileValueResolverTest.php
@@ -85,7 +85,7 @@ static function () {},
$resolver->onKernelControllerArguments($event);
$data = $event->getArguments()[0];
- $this->assertEmpty($data);
+ $this->assertSame([], $data);
}
/**
diff --git a/Tests/DataCollector/DumpDataCollectorTest.php b/Tests/DataCollector/DumpDataCollectorTest.php
index e55af09fe5..11e1bc2e6c 100644
--- a/Tests/DataCollector/DumpDataCollectorTest.php
+++ b/Tests/DataCollector/DumpDataCollectorTest.php
@@ -79,7 +79,7 @@ public function testDumpWithServerConnection()
// Collect doesn't re-trigger dump
ob_start();
$collector->collect(new Request(), new Response());
- $this->assertEmpty(ob_get_clean());
+ $this->assertSame('', ob_get_clean());
$this->assertStringMatchesFormat('%a;a:%d:{i:0;a:6:{s:4:"data";%c:39:"Symfony\Component\VarDumper\Cloner\Data":%a', serialize($collector));
}
@@ -157,7 +157,7 @@ public function testFlushNothingWhenDataDumperIsProvided()
ob_start();
$collector->__destruct();
- $this->assertEmpty(ob_get_clean());
+ $this->assertSame('', ob_get_clean());
}
public function testNullContentTypeWithNoDebugEnv()
@@ -175,6 +175,6 @@ public function testNullContentTypeWithNoDebugEnv()
ob_start();
$collector->__destruct();
- $this->assertEmpty(ob_get_clean());
+ $this->assertSame('', ob_get_clean());
}
}
diff --git a/Tests/DependencyInjection/AddAnnotatedClassesToCachePassTest.php b/Tests/DependencyInjection/AddAnnotatedClassesToCachePassTest.php
index 387a5108ec..e57c349609 100644
--- a/Tests/DependencyInjection/AddAnnotatedClassesToCachePassTest.php
+++ b/Tests/DependencyInjection/AddAnnotatedClassesToCachePassTest.php
@@ -36,33 +36,33 @@ public function testExpandClasses()
$this->assertSame('Foo\\Bar', $expand(['Foo\\'], ['\\Foo\\Bar'])[0]);
$this->assertSame('Foo\\Bar\\Acme', $expand(['Foo\\'], ['\\Foo\\Bar\\Acme'])[0]);
- $this->assertEmpty($expand(['Foo\\'], ['\\Foo']));
+ $this->assertSame([], $expand(['Foo\\'], ['\\Foo']));
$this->assertSame('Acme\\Foo\\Bar', $expand(['**\\Foo\\'], ['\\Acme\\Foo\\Bar'])[0]);
- $this->assertEmpty($expand(['**\\Foo\\'], ['\\Foo\\Bar']));
- $this->assertEmpty($expand(['**\\Foo\\'], ['\\Acme\\Foo']));
- $this->assertEmpty($expand(['**\\Foo\\'], ['\\Foo']));
+ $this->assertSame([], $expand(['**\\Foo\\'], ['\\Foo\\Bar']));
+ $this->assertSame([], $expand(['**\\Foo\\'], ['\\Acme\\Foo']));
+ $this->assertSame([], $expand(['**\\Foo\\'], ['\\Foo']));
$this->assertSame('Acme\\Foo', $expand(['**\\Foo'], ['\\Acme\\Foo'])[0]);
- $this->assertEmpty($expand(['**\\Foo'], ['\\Acme\\Foo\\AcmeBundle']));
- $this->assertEmpty($expand(['**\\Foo'], ['\\Acme\\FooBar\\AcmeBundle']));
+ $this->assertSame([], $expand(['**\\Foo'], ['\\Acme\\Foo\\AcmeBundle']));
+ $this->assertSame([], $expand(['**\\Foo'], ['\\Acme\\FooBar\\AcmeBundle']));
$this->assertSame('Foo\\Acme\\Bar', $expand(['Foo\\*\\Bar'], ['\\Foo\\Acme\\Bar'])[0]);
- $this->assertEmpty($expand(['Foo\\*\\Bar'], ['\\Foo\\Acme\\Bundle\\Bar']));
+ $this->assertSame([], $expand(['Foo\\*\\Bar'], ['\\Foo\\Acme\\Bundle\\Bar']));
$this->assertSame('Foo\\Acme\\Bar', $expand(['Foo\\**\\Bar'], ['\\Foo\\Acme\\Bar'])[0]);
$this->assertSame('Foo\\Acme\\Bundle\\Bar', $expand(['Foo\\**\\Bar'], ['\\Foo\\Acme\\Bundle\\Bar'])[0]);
$this->assertSame('Acme\\Bar', $expand(['*\\Bar'], ['\\Acme\\Bar'])[0]);
- $this->assertEmpty($expand(['*\\Bar'], ['\\Bar']));
- $this->assertEmpty($expand(['*\\Bar'], ['\\Foo\\Acme\\Bar']));
+ $this->assertSame([], $expand(['*\\Bar'], ['\\Bar']));
+ $this->assertSame([], $expand(['*\\Bar'], ['\\Foo\\Acme\\Bar']));
$this->assertSame('Foo\\Acme\\Bar', $expand(['**\\Bar'], ['\\Foo\\Acme\\Bar'])[0]);
$this->assertSame('Foo\\Acme\\Bundle\\Bar', $expand(['**\\Bar'], ['\\Foo\\Acme\\Bundle\\Bar'])[0]);
- $this->assertEmpty($expand(['**\\Bar'], ['\\Bar']));
+ $this->assertSame([], $expand(['**\\Bar'], ['\\Bar']));
$this->assertSame('Foo\\Bar', $expand(['Foo\\*'], ['\\Foo\\Bar'])[0]);
- $this->assertEmpty($expand(['Foo\\*'], ['\\Foo\\Acme\\Bar']));
+ $this->assertSame([], $expand(['Foo\\*'], ['\\Foo\\Acme\\Bar']));
$this->assertSame('Foo\\Bar', $expand(['Foo\\**'], ['\\Foo\\Bar'])[0]);
$this->assertSame('Foo\\Acme\\Bar', $expand(['Foo\\**'], ['\\Foo\\Acme\\Bar'])[0]);
diff --git a/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php b/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php
index 02b5df6512..03aef073d0 100644
--- a/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php
+++ b/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php
@@ -277,7 +277,7 @@ public function testArgumentWithNoTypeHintIsOk()
$pass->process($container);
$locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0);
- $this->assertEmpty(array_keys($locator));
+ $this->assertSame([], array_keys($locator));
}
public function testControllersAreMadePublic()
@@ -440,7 +440,7 @@ public function testEnumArgumentIsIgnored()
$pass->process($container);
$locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0);
- $this->assertEmpty(array_keys($locator), 'enum typed argument is ignored');
+ $this->assertSame([], array_keys($locator), 'enum typed argument is ignored');
}
public function testBindWithTarget()
@@ -480,7 +480,7 @@ public function testResponseArgumentIsIgnored()
(new RegisterControllerArgumentLocatorsPass())->process($container);
$locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0);
- $this->assertEmpty(array_keys($locator), 'Response typed argument is ignored');
+ $this->assertSame([], array_keys($locator), 'Response typed argument is ignored');
}
public function testAutowireAttribute()
diff --git a/Tests/EventListener/SessionListenerTest.php b/Tests/EventListener/SessionListenerTest.php
index 2aa5d622e2..deba6661f0 100644
--- a/Tests/EventListener/SessionListenerTest.php
+++ b/Tests/EventListener/SessionListenerTest.php
@@ -896,8 +896,8 @@ public function testReset()
(new SessionListener($container, true))->reset();
- $this->assertEmpty($_SESSION);
- $this->assertEmpty(session_id());
+ $this->assertSame([], $_SESSION);
+ $this->assertSame('', session_id());
$this->assertSame(\PHP_SESSION_NONE, session_status());
}
@@ -917,8 +917,8 @@ public function testResetUnclosedSession()
(new SessionListener($container, true))->reset();
- $this->assertEmpty($_SESSION);
- $this->assertEmpty(session_id());
+ $this->assertSame([], $_SESSION);
+ $this->assertSame('', session_id());
$this->assertSame(\PHP_SESSION_NONE, session_status());
}
diff --git a/Tests/Fragment/InlineFragmentRendererTest.php b/Tests/Fragment/InlineFragmentRendererTest.php
index 2d492c5359..8266458fd6 100644
--- a/Tests/Fragment/InlineFragmentRendererTest.php
+++ b/Tests/Fragment/InlineFragmentRendererTest.php
@@ -97,7 +97,7 @@ public function testRenderExceptionIgnoreErrors()
$strategy = new InlineFragmentRenderer($kernel, $dispatcher);
- $this->assertEmpty($strategy->render('/', $request, ['ignore_errors' => true])->getContent());
+ $this->assertSame('', $strategy->render('/', $request, ['ignore_errors' => true])->getContent());
}
public function testRenderExceptionIgnoreErrorsWithAlt()
diff --git a/Tests/HttpCache/HttpCacheTest.php b/Tests/HttpCache/HttpCacheTest.php
index 0a9e548990..e82e8fd81b 100644
--- a/Tests/HttpCache/HttpCacheTest.php
+++ b/Tests/HttpCache/HttpCacheTest.php
@@ -195,7 +195,7 @@ public function testRespondsWith304WhenIfModifiedSinceMatchesLastModified()
$this->assertHttpKernelIsCalled();
$this->assertEquals(304, $this->response->getStatusCode());
$this->assertEquals('', $this->response->headers->get('Content-Type'));
- $this->assertEmpty($this->response->getContent());
+ $this->assertSame('', $this->response->getContent());
$this->assertTraceContains('miss');
$this->assertTraceContains('store');
}
@@ -209,7 +209,7 @@ public function testRespondsWith304WhenIfNoneMatchMatchesETag()
$this->assertEquals(304, $this->response->getStatusCode());
$this->assertEquals('', $this->response->headers->get('Content-Type'));
$this->assertTrue($this->response->headers->has('ETag'));
- $this->assertEmpty($this->response->getContent());
+ $this->assertSame('', $this->response->getContent());
$this->assertTraceContains('miss');
$this->assertTraceContains('store');
}
@@ -1281,7 +1281,7 @@ public function testEsiCacheSendsTheLowestTtlForHeadRequests()
$this->request('HEAD', '/', [], [], true);
- $this->assertEmpty($this->response->getContent());
+ $this->assertSame('', $this->response->getContent());
$this->assertEquals(100, $this->response->getTtl());
}
@@ -1510,7 +1510,7 @@ public function testEsiCacheForceValidationForHeadRequests()
// The response has been assembled from expiration and validation based resources
// This can neither be cached nor revalidated, so it should be private/no cache
- $this->assertEmpty($this->response->getContent());
+ $this->assertSame('', $this->response->getContent());
$this->assertNull($this->response->getTtl());
$this->assertTrue($this->response->headers->hasCacheControlDirective('private'));
$this->assertTrue($this->response->headers->hasCacheControlDirective('no-cache'));
@@ -1568,7 +1568,7 @@ public function testEsiRecalculateContentLengthHeaderForHeadRequest()
// in decimal number of OCTETs, sent to the recipient or, in the case of the HEAD
// method, the size of the entity-body that would have been sent had the request
// been a GET."
- $this->assertEmpty($this->response->getContent());
+ $this->assertSame('', $this->response->getContent());
$this->assertEquals(12, $this->response->headers->get('Content-Length'));
}
@@ -1692,7 +1692,7 @@ public function testEsiCacheRemoveValidationHeadersIfEmbeddedResponsesAndHeadReq
$this->setNextResponses($responses);
$this->request('HEAD', '/', [], [], true);
- $this->assertEmpty($this->response->getContent());
+ $this->assertSame('', $this->response->getContent());
$this->assertNull($this->response->getETag());
$this->assertNull($this->response->getLastModified());
}
diff --git a/Tests/HttpCache/StoreTest.php b/Tests/HttpCache/StoreTest.php
index 8c41ac5986..a24aa95c87 100644
--- a/Tests/HttpCache/StoreTest.php
+++ b/Tests/HttpCache/StoreTest.php
@@ -40,7 +40,7 @@ protected function tearDown(): void
public function testReadsAnEmptyArrayWithReadWhenNothingCachedAtKey()
{
- $this->assertEmpty($this->getStoreMetadata('/nothing'));
+ $this->assertSame([], $this->getStoreMetadata('/nothing'));
}
public function testUnlockFileThatDoesExist()
@@ -65,7 +65,7 @@ public function testRemovesEntriesForKeyWithPurge()
$this->assertNotEmpty($metadata);
$this->assertTrue($this->store->purge('/foo'));
- $this->assertEmpty($this->getStoreMetadata($request));
+ $this->assertSame([], $this->getStoreMetadata($request));
// cached content should be kept after purging
$path = $this->store->getPath($metadata[0][1]['x-content-digest'][0]);
@@ -291,7 +291,7 @@ public function testPurgeHttps()
$this->assertNotEmpty($this->getStoreMetadata($request));
$this->assertTrue($this->store->purge('https://example.com/foo'));
- $this->assertEmpty($this->getStoreMetadata($request));
+ $this->assertSame([], $this->getStoreMetadata($request));
}
public function testPurgeHttpAndHttps()
@@ -306,8 +306,8 @@ public function testPurgeHttpAndHttps()
$this->assertNotEmpty($this->getStoreMetadata($requestHttps));
$this->assertTrue($this->store->purge('http://example.com/foo'));
- $this->assertEmpty($this->getStoreMetadata($requestHttp));
- $this->assertEmpty($this->getStoreMetadata($requestHttps));
+ $this->assertSame([], $this->getStoreMetadata($requestHttp));
+ $this->assertSame([], $this->getStoreMetadata($requestHttps));
}
public function testDoesNotStorePrivateHeaders()
diff --git a/Tests/Profiler/FileProfilerStorageTest.php b/Tests/Profiler/FileProfilerStorageTest.php
index d191ff074e..eb8f99c806 100644
--- a/Tests/Profiler/FileProfilerStorageTest.php
+++ b/Tests/Profiler/FileProfilerStorageTest.php
@@ -292,7 +292,7 @@ public function testPurge()
$this->storage->purge();
- $this->assertEmpty($this->storage->read('token'), '->purge() removes all data stored by profiler');
+ $this->assertNull($this->storage->read('token'), '->purge() removes all data stored by profiler');
$this->assertCount(0, $this->storage->find('127.0.0.1', '', 10, 'GET'), '->purge() removes all items from index');
}
From 1b5ec906db2d2feebc3c7846a2ce549854314a60 Mon Sep 17 00:00:00 2001
From: Nicolas Grekas
Date: Sat, 15 Mar 2025 14:10:48 +0100
Subject: [PATCH 12/44] Various cleanups
---
DependencyInjection/ServicesResetter.php | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/DependencyInjection/ServicesResetter.php b/DependencyInjection/ServicesResetter.php
index 7636e52a18..c2a19d992f 100644
--- a/DependencyInjection/ServicesResetter.php
+++ b/DependencyInjection/ServicesResetter.php
@@ -46,6 +46,10 @@ public function reset(): void
continue;
}
+ if (\PHP_VERSION_ID >= 80400 && (new \ReflectionClass($service))->isUninitializedLazyObject($service)) {
+ continue;
+ }
+
foreach ((array) $this->resetMethods[$id] as $resetMethod) {
if ('?' === $resetMethod[0] && !method_exists($service, $resetMethod = substr($resetMethod, 1))) {
continue;
From 2ec3b84cd88992214f379059b03625ac1efbc90c Mon Sep 17 00:00:00 2001
From: Pierre Ambroise
Date: Wed, 19 Mar 2025 09:22:52 +0100
Subject: [PATCH 13/44] Open doc in new page in default page
---
Resources/welcome.html.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Resources/welcome.html.php b/Resources/welcome.html.php
index 810c71f988..549c4ff192 100644
--- a/Resources/welcome.html.php
+++ b/Resources/welcome.html.php
@@ -265,7 +265,7 @@
Next Step
- Create your first page
+ Create your first page
to replace this placeholder page.
From 84b25e32c3872d70c92fcb848b35800df2c8052d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?=
Date: Fri, 21 Mar 2025 13:14:10 +0100
Subject: [PATCH 14/44] [FrameworkBundle] Add alias `ServicesResetter` for
`services_resetter` service
---
CHANGELOG.md | 1 +
DependencyInjection/ServicesResetter.php | 3 +--
.../ServicesResetterInterface.php | 21 +++++++++++++++++++
3 files changed, 23 insertions(+), 2 deletions(-)
create mode 100644 DependencyInjection/ServicesResetterInterface.php
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cc9da8a2a9..1d533c29f5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@ CHANGELOG
* Add `$key` argument to `#[MapQueryString]` that allows using a specific key for argument resolving
* Support `Uid` in `#[MapQueryParameter]`
+ * Add `ServicesResetterInterface`, implemented by `ServicesResetter`
7.2
---
diff --git a/DependencyInjection/ServicesResetter.php b/DependencyInjection/ServicesResetter.php
index c2a19d992f..57e394fcc5 100644
--- a/DependencyInjection/ServicesResetter.php
+++ b/DependencyInjection/ServicesResetter.php
@@ -13,7 +13,6 @@
use ProxyManager\Proxy\LazyLoadingInterface;
use Symfony\Component\VarExporter\LazyObjectInterface;
-use Symfony\Contracts\Service\ResetInterface;
/**
* Resets provided services.
@@ -23,7 +22,7 @@
*
* @final since Symfony 7.2
*/
-class ServicesResetter implements ResetInterface
+class ServicesResetter implements ServicesResetterInterface
{
/**
* @param \Traversable $resettableServices
diff --git a/DependencyInjection/ServicesResetterInterface.php b/DependencyInjection/ServicesResetterInterface.php
new file mode 100644
index 0000000000..88fba821db
--- /dev/null
+++ b/DependencyInjection/ServicesResetterInterface.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpKernel\DependencyInjection;
+
+use Symfony\Contracts\Service\ResetInterface;
+
+/**
+ * Resets provided services.
+ */
+interface ServicesResetterInterface extends ResetInterface
+{
+}
From d71f6c83a7d35a72397ab7fbdfdc5aad22635de7 Mon Sep 17 00:00:00 2001
From: Nicolas Grekas
Date: Fri, 28 Feb 2025 22:27:11 +0100
Subject: [PATCH 15/44] [VarExporter] Leverage native lazy objects
---
Tests/Fixtures/LazyResettableService.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Tests/Fixtures/LazyResettableService.php b/Tests/Fixtures/LazyResettableService.php
index 543cf0d953..1b66415c4b 100644
--- a/Tests/Fixtures/LazyResettableService.php
+++ b/Tests/Fixtures/LazyResettableService.php
@@ -11,7 +11,7 @@
namespace Symfony\Component\HttpKernel\Tests\Fixtures;
-class LazyResettableService
+class LazyResettableService extends \stdClass
{
public static $counter = 0;
From aa58e4994cea483bd1c2166817ae9b11c77236a1 Mon Sep 17 00:00:00 2001
From: eltharin
Date: Mon, 3 Mar 2025 21:12:56 +0100
Subject: [PATCH 16/44] [Routing] Add alias in `{foo:bar}` syntax in route
parameter
---
EventListener/RouterListener.php | 9 ++++-
Tests/EventListener/RouterListenerTest.php | 44 ++++++++++++++++++++++
2 files changed, 52 insertions(+), 1 deletion(-)
diff --git a/EventListener/RouterListener.php b/EventListener/RouterListener.php
index fb4bd60bc3..dd6f5bb214 100644
--- a/EventListener/RouterListener.php
+++ b/EventListener/RouterListener.php
@@ -117,7 +117,14 @@ public function onKernelRequest(RequestEvent $event): void
$attributes = [];
foreach ($parameters as $parameter => $value) {
- $attribute = $mapping[$parameter] ?? $parameter;
+ if (!isset($mapping[$parameter])) {
+ $attribute = $parameter;
+ } elseif (\is_array($mapping[$parameter])) {
+ [$attribute, $parameter] = $mapping[$parameter];
+ $mappedAttributes[$attribute] = '';
+ } else {
+ $attribute = $mapping[$parameter];
+ }
if (!isset($mappedAttributes[$attribute])) {
$attributes[$attribute] = $value;
diff --git a/Tests/EventListener/RouterListenerTest.php b/Tests/EventListener/RouterListenerTest.php
index d13093db0c..ca7bb1b1f6 100644
--- a/Tests/EventListener/RouterListenerTest.php
+++ b/Tests/EventListener/RouterListenerTest.php
@@ -323,5 +323,49 @@ public static function provideRouteMapping(): iterable
],
],
];
+
+ yield [
+ [
+ 'conference' => ['slug' => 'vienna-2024'],
+ ],
+ [
+ 'slug' => 'vienna-2024',
+ '_route_mapping' => [
+ 'slug' => [
+ 'conference',
+ 'slug',
+ ],
+ ],
+ ],
+ ];
+
+ yield [
+ [
+ 'article' => [
+ 'id' => 'abc123',
+ 'date' => '2024-04-24',
+ 'slug' => 'symfony-rocks',
+ ],
+ ],
+ [
+ 'id' => 'abc123',
+ 'date' => '2024-04-24',
+ 'slug' => 'symfony-rocks',
+ '_route_mapping' => [
+ 'id' => [
+ 'article',
+ 'id'
+ ],
+ 'date' => [
+ 'article',
+ 'date',
+ ],
+ 'slug' => [
+ 'article',
+ 'slug',
+ ],
+ ],
+ ],
+ ];
}
}
From 5e575fbdc09d69e68bb8fd8dd4bde2eab5737c86 Mon Sep 17 00:00:00 2001
From: Arkalo2 <24898676+Arkalo2@users.noreply.github.com>
Date: Sat, 22 Mar 2025 19:57:41 +0100
Subject: [PATCH 17/44] [FrameworkBundle][HttpKernel] Allow configuring the
logging channel per type of exceptions
---
CHANGELOG.md | 3 +-
EventListener/ErrorListener.php | 38 +++++++++++----
Tests/EventListener/ErrorListenerTest.php | 57 +++++++++++++++++++++++
3 files changed, 89 insertions(+), 9 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1d533c29f5..6bf1a60ebc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,7 +7,8 @@ CHANGELOG
* Add `$key` argument to `#[MapQueryString]` that allows using a specific key for argument resolving
* Support `Uid` in `#[MapQueryParameter]`
* Add `ServicesResetterInterface`, implemented by `ServicesResetter`
-
+ * Allow configuring the logging channel per type of exceptions in ErrorListener
+
7.2
---
diff --git a/EventListener/ErrorListener.php b/EventListener/ErrorListener.php
index c677958cde..18e8bff441 100644
--- a/EventListener/ErrorListener.php
+++ b/EventListener/ErrorListener.php
@@ -34,13 +34,14 @@
class ErrorListener implements EventSubscriberInterface
{
/**
- * @param array|null}> $exceptionsMapping
+ * @param array|null, log_channel: string|null}> $exceptionsMapping
*/
public function __construct(
protected string|object|array|null $controller,
protected ?LoggerInterface $logger = null,
protected bool $debug = false,
protected array $exceptionsMapping = [],
+ protected array $loggers = [],
) {
}
@@ -48,6 +49,7 @@ public function logKernelException(ExceptionEvent $event): void
{
$throwable = $event->getThrowable();
$logLevel = $this->resolveLogLevel($throwable);
+ $logChannel = $this->resolveLogChannel($throwable);
foreach ($this->exceptionsMapping as $class => $config) {
if (!$throwable instanceof $class || !$config['status_code']) {
@@ -69,7 +71,7 @@ public function logKernelException(ExceptionEvent $event): void
$e = FlattenException::createFromThrowable($throwable);
- $this->logException($throwable, \sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', $e->getClass(), $e->getMessage(), basename($e->getFile()), $e->getLine()), $logLevel);
+ $this->logException($throwable, \sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', $e->getClass(), $e->getMessage(), basename($e->getFile()), $e->getLine()), $logLevel, $logChannel);
}
public function onKernelException(ExceptionEvent $event): void
@@ -159,16 +161,20 @@ public static function getSubscribedEvents(): array
/**
* Logs an exception.
+ *
+ * @param ?string $logChannel
*/
- protected function logException(\Throwable $exception, string $message, ?string $logLevel = null): void
+ protected function logException(\Throwable $exception, string $message, ?string $logLevel = null, /* ?string $logChannel = null */): void
{
- if (null === $this->logger) {
+ $logChannel = (3 < \func_num_args() ? \func_get_arg(3) : null) ?? $this->resolveLogChannel($exception);
+
+ $logLevel ??= $this->resolveLogLevel($exception);
+
+ if(!$logger = $this->getLogger($logChannel)) {
return;
}
- $logLevel ??= $this->resolveLogLevel($exception);
-
- $this->logger->log($logLevel, $message, ['exception' => $exception]);
+ $logger->log($logLevel, $message, ['exception' => $exception]);
}
/**
@@ -193,6 +199,17 @@ private function resolveLogLevel(\Throwable $throwable): string
return LogLevel::ERROR;
}
+ private function resolveLogChannel(\Throwable $throwable): ?string
+ {
+ foreach ($this->exceptionsMapping as $class => $config) {
+ if ($throwable instanceof $class && isset($config['log_channel'])) {
+ return $config['log_channel'];
+ }
+ }
+
+ return null;
+ }
+
/**
* Clones the request for the exception.
*/
@@ -201,7 +218,7 @@ protected function duplicateRequest(\Throwable $exception, Request $request): Re
$attributes = [
'_controller' => $this->controller,
'exception' => $exception,
- 'logger' => DebugLoggerConfigurator::getDebugLogger($this->logger),
+ 'logger' => DebugLoggerConfigurator::getDebugLogger($this->getLogger($exception)),
];
$request = $request->duplicate(null, null, $attributes);
$request->setMethod('GET');
@@ -249,4 +266,9 @@ private function getInheritedAttribute(string $class, string $attribute): ?objec
return $attributeReflector?->newInstance();
}
+
+ private function getLogger(?string $logChannel): ?LoggerInterface
+ {
+ return $logChannel ? $this->loggers[$logChannel] ?? $this->logger : $this->logger;
+ }
}
diff --git a/Tests/EventListener/ErrorListenerTest.php b/Tests/EventListener/ErrorListenerTest.php
index 2e1f7d58b7..7fdda59635 100644
--- a/Tests/EventListener/ErrorListenerTest.php
+++ b/Tests/EventListener/ErrorListenerTest.php
@@ -143,6 +143,63 @@ public function testHandleWithLogLevelAttribute()
$this->assertCount(1, $logger->getLogsForLevel('warning'));
}
+ public function testHandleWithLogChannel()
+ {
+ $request = new Request();
+ $event = new ExceptionEvent(new TestKernel(), $request, HttpKernelInterface::MAIN_REQUEST, new \RuntimeException('bar'));
+
+ $defaultLogger = new TestLogger();
+ $channelLoger = new TestLogger();
+
+ $l = new ErrorListener('not used', $defaultLogger, false, [
+ \RuntimeException::class => [
+ 'log_level' => 'warning',
+ 'status_code' => 401,
+ 'log_channel' => 'channel',
+ ],
+ \Exception::class => [
+ 'log_level' => 'error',
+ 'status_code' => 402,
+ ],
+ ], ['channel' => $channelLoger]);
+
+ $l->logKernelException($event);
+ $l->onKernelException($event);
+
+ $this->assertCount(0, $defaultLogger->getLogsForLevel('error'));
+ $this->assertCount(0, $defaultLogger->getLogsForLevel('warning'));
+ $this->assertCount(0, $channelLoger->getLogsForLevel('error'));
+ $this->assertCount(1, $channelLoger->getLogsForLevel('warning'));
+ }
+
+ public function testHandleWithLoggerChannelNotUsed()
+ {
+ $request = new Request();
+ $event = new ExceptionEvent(new TestKernel(), $request, HttpKernelInterface::MAIN_REQUEST, new \RuntimeException('bar'));
+ $defaultLogger = new TestLogger();
+ $channelLoger = new TestLogger();
+ $l = new ErrorListener('not used', $defaultLogger, false, [
+ \RuntimeException::class => [
+ 'log_level' => 'warning',
+ 'status_code' => 401,
+ ],
+ \ErrorException::class => [
+ 'log_level' => 'error',
+ 'status_code' => 402,
+ 'log_channel' => 'channel',
+ ],
+ ], ['channel' => $channelLoger]);
+ $l->logKernelException($event);
+ $l->onKernelException($event);
+
+ $this->assertSame(0, $defaultLogger->countErrors());
+ $this->assertCount(0, $defaultLogger->getLogsForLevel('critical'));
+ $this->assertCount(1, $defaultLogger->getLogsForLevel('warning'));
+ $this->assertCount(0, $channelLoger->getLogsForLevel('warning'));
+ $this->assertCount(0, $channelLoger->getLogsForLevel('error'));
+ $this->assertCount(0, $channelLoger->getLogsForLevel('critical'));
+ }
+
public function testHandleClassImplementingInterfaceWithLogLevelAttribute()
{
$request = new Request();
From 5617b2e23c0196df04476290921ab4b70c106bce Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?=
Date: Fri, 28 Mar 2025 18:29:28 +0100
Subject: [PATCH 18/44] Remove always-true condition
Introduced in symfony/symfony#45657
symfony/dependency-injection 6.4+ is required, so the class always exists
---
.../RegisterControllerArgumentLocatorsPass.php | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/DependencyInjection/RegisterControllerArgumentLocatorsPass.php b/DependencyInjection/RegisterControllerArgumentLocatorsPass.php
index d473a2e6b0..a5fa06f17b 100644
--- a/DependencyInjection/RegisterControllerArgumentLocatorsPass.php
+++ b/DependencyInjection/RegisterControllerArgumentLocatorsPass.php
@@ -51,8 +51,6 @@ public function process(ContainerBuilder $container): void
}
}
- $emptyAutowireAttributes = class_exists(Autowire::class) ? null : [];
-
foreach ($container->findTaggedServiceIds('controller.service_arguments', true) as $id => $tags) {
$def = $container->getDefinition($id);
$def->setPublic(true);
@@ -129,7 +127,7 @@ public function process(ContainerBuilder $container): void
/** @var \ReflectionParameter $p */
$type = preg_replace('/(^|[(|&])\\\\/', '\1', $target = ltrim(ProxyHelper::exportType($p) ?? '', '?'));
$invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
- $autowireAttributes = $autowire ? $emptyAutowireAttributes : [];
+ $autowireAttributes = null;
$parsedName = $p->name;
$k = null;
@@ -155,7 +153,7 @@ public function process(ContainerBuilder $container): void
$args[$p->name] = $bindingValue;
continue;
- } elseif (!$autowire || (!($autowireAttributes ??= $p->getAttributes(Autowire::class, \ReflectionAttribute::IS_INSTANCEOF)) && (!$type || '\\' !== $target[0]))) {
+ } elseif (!$autowire || (!($autowireAttributes = $p->getAttributes(Autowire::class, \ReflectionAttribute::IS_INSTANCEOF)) && (!$type || '\\' !== $target[0]))) {
continue;
} elseif (is_subclass_of($type, \UnitEnum::class)) {
// do not attempt to register enum typed arguments if not already present in bindings
From 610f13eca95059fb0af6eb164b4cf7ca6e980778 Mon Sep 17 00:00:00 2001
From: Nicolas Grekas
Date: Fri, 18 Apr 2025 14:51:48 +0200
Subject: [PATCH 19/44] Don't enable tracing unless the profiler is enabled
---
Debug/TraceableEventDispatcher.php | 6 ++++++
Profiler/ProfilerStateChecker.php | 33 ++++++++++++++++++++++++++++++
composer.json | 2 +-
3 files changed, 40 insertions(+), 1 deletion(-)
create mode 100644 Profiler/ProfilerStateChecker.php
diff --git a/Debug/TraceableEventDispatcher.php b/Debug/TraceableEventDispatcher.php
index beca6bfb14..915862eddb 100644
--- a/Debug/TraceableEventDispatcher.php
+++ b/Debug/TraceableEventDispatcher.php
@@ -25,6 +25,9 @@ class TraceableEventDispatcher extends BaseTraceableEventDispatcher
{
protected function beforeDispatch(string $eventName, object $event): void
{
+ if ($this->disabled?->__invoke()) {
+ return;
+ }
switch ($eventName) {
case KernelEvents::REQUEST:
$event->getRequest()->attributes->set('_stopwatch_token', bin2hex(random_bytes(3)));
@@ -57,6 +60,9 @@ protected function beforeDispatch(string $eventName, object $event): void
protected function afterDispatch(string $eventName, object $event): void
{
+ if ($this->disabled?->__invoke()) {
+ return;
+ }
switch ($eventName) {
case KernelEvents::CONTROLLER_ARGUMENTS:
$this->stopwatch->start('controller', 'section');
diff --git a/Profiler/ProfilerStateChecker.php b/Profiler/ProfilerStateChecker.php
new file mode 100644
index 0000000000..56cb4e3cc5
--- /dev/null
+++ b/Profiler/ProfilerStateChecker.php
@@ -0,0 +1,33 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpKernel\Profiler;
+
+use Psr\Container\ContainerInterface;
+
+class ProfilerStateChecker
+{
+ public function __construct(
+ private ContainerInterface $container,
+ private bool $defaultEnabled,
+ ) {
+ }
+
+ public function isProfilerEnabled(): bool
+ {
+ return $this->container->get('profiler')?->isEnabled() ?? $this->defaultEnabled;
+ }
+
+ public function isProfilerDisabled(): bool
+ {
+ return !$this->isProfilerEnabled();
+ }
+}
diff --git a/composer.json b/composer.json
index e9cb077587..bb9f4ba617 100644
--- a/composer.json
+++ b/composer.json
@@ -19,7 +19,7 @@
"php": ">=8.2",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/error-handler": "^6.4|^7.0",
- "symfony/event-dispatcher": "^6.4|^7.0",
+ "symfony/event-dispatcher": "^7.3",
"symfony/http-foundation": "^7.3",
"symfony/polyfill-ctype": "^1.8",
"psr/log": "^1|^2|^3"
From 8ff29094f0fe007ae73534950cf4f694955ad224 Mon Sep 17 00:00:00 2001
From: Fabien Potencier
Date: Sat, 10 May 2025 14:09:33 +0200
Subject: [PATCH 20/44] Update VERSION for 7.3.0-BETA2
---
Kernel.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Kernel.php b/Kernel.php
index b5a41236d1..d09c86966d 100644
--- a/Kernel.php
+++ b/Kernel.php
@@ -73,12 +73,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
*/
private static array $freshCache = [];
- public const VERSION = '7.3.0-DEV';
+ public const VERSION = '7.3.0-BETA2';
public const VERSION_ID = 70300;
public const MAJOR_VERSION = 7;
public const MINOR_VERSION = 3;
public const RELEASE_VERSION = 0;
- public const EXTRA_VERSION = 'DEV';
+ public const EXTRA_VERSION = 'BETA2';
public const END_OF_MAINTENANCE = '05/2025';
public const END_OF_LIFE = '01/2026';
From 85ad11b3df4f1d292d074b81d50c9ac79e79ec7b Mon Sep 17 00:00:00 2001
From: Fabien Potencier
Date: Sat, 10 May 2025 14:15:19 +0200
Subject: [PATCH 21/44] Bump Symfony version to 7.3.0
---
Kernel.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Kernel.php b/Kernel.php
index d09c86966d..b5a41236d1 100644
--- a/Kernel.php
+++ b/Kernel.php
@@ -73,12 +73,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
*/
private static array $freshCache = [];
- public const VERSION = '7.3.0-BETA2';
+ public const VERSION = '7.3.0-DEV';
public const VERSION_ID = 70300;
public const MAJOR_VERSION = 7;
public const MINOR_VERSION = 3;
public const RELEASE_VERSION = 0;
- public const EXTRA_VERSION = 'BETA2';
+ public const EXTRA_VERSION = 'DEV';
public const END_OF_MAINTENANCE = '05/2025';
public const END_OF_LIFE = '01/2026';
From b4d1f001bddcb11662794f5d5247639f5a51ce33 Mon Sep 17 00:00:00 2001
From: Fabien Potencier
Date: Sun, 25 May 2025 22:29:38 +0200
Subject: [PATCH 22/44] Update VERSION for 7.3.0-RC1
---
Kernel.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Kernel.php b/Kernel.php
index b5a41236d1..566e721bf3 100644
--- a/Kernel.php
+++ b/Kernel.php
@@ -73,12 +73,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
*/
private static array $freshCache = [];
- public const VERSION = '7.3.0-DEV';
+ public const VERSION = '7.3.0-RC1';
public const VERSION_ID = 70300;
public const MAJOR_VERSION = 7;
public const MINOR_VERSION = 3;
public const RELEASE_VERSION = 0;
- public const EXTRA_VERSION = 'DEV';
+ public const EXTRA_VERSION = 'RC1';
public const END_OF_MAINTENANCE = '05/2025';
public const END_OF_LIFE = '01/2026';
From 410a7f16a68b6cb19c18d21effc380292526d7a4 Mon Sep 17 00:00:00 2001
From: Fabien Potencier
Date: Sun, 25 May 2025 23:07:09 +0200
Subject: [PATCH 23/44] Bump Symfony version to 7.3.0
---
Kernel.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Kernel.php b/Kernel.php
index 566e721bf3..b5a41236d1 100644
--- a/Kernel.php
+++ b/Kernel.php
@@ -73,12 +73,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
*/
private static array $freshCache = [];
- public const VERSION = '7.3.0-RC1';
+ public const VERSION = '7.3.0-DEV';
public const VERSION_ID = 70300;
public const MAJOR_VERSION = 7;
public const MINOR_VERSION = 3;
public const RELEASE_VERSION = 0;
- public const EXTRA_VERSION = 'RC1';
+ public const EXTRA_VERSION = 'DEV';
public const END_OF_MAINTENANCE = '05/2025';
public const END_OF_LIFE = '01/2026';
From ac7b8e163e8c83dce3abcc055a502d4486051a9f Mon Sep 17 00:00:00 2001
From: Fabien Potencier
Date: Thu, 29 May 2025 09:47:32 +0200
Subject: [PATCH 24/44] Update VERSION for 7.3.0
---
Kernel.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Kernel.php b/Kernel.php
index b5a41236d1..bfef40fac5 100644
--- a/Kernel.php
+++ b/Kernel.php
@@ -73,12 +73,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
*/
private static array $freshCache = [];
- public const VERSION = '7.3.0-DEV';
+ public const VERSION = '7.3.0';
public const VERSION_ID = 70300;
public const MAJOR_VERSION = 7;
public const MINOR_VERSION = 3;
public const RELEASE_VERSION = 0;
- public const EXTRA_VERSION = 'DEV';
+ public const EXTRA_VERSION = '';
public const END_OF_MAINTENANCE = '05/2025';
public const END_OF_LIFE = '01/2026';
From a73edfc38d6bdc779c8c60f57c5a3ed79706eb84 Mon Sep 17 00:00:00 2001
From: Fabien Potencier
Date: Thu, 29 May 2025 09:51:04 +0200
Subject: [PATCH 25/44] Bump Symfony version to 7.3.1
---
Kernel.php | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/Kernel.php b/Kernel.php
index bfef40fac5..dfee565d06 100644
--- a/Kernel.php
+++ b/Kernel.php
@@ -73,12 +73,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
*/
private static array $freshCache = [];
- public const VERSION = '7.3.0';
- public const VERSION_ID = 70300;
+ public const VERSION = '7.3.1-DEV';
+ public const VERSION_ID = 70301;
public const MAJOR_VERSION = 7;
public const MINOR_VERSION = 3;
- public const RELEASE_VERSION = 0;
- public const EXTRA_VERSION = '';
+ public const RELEASE_VERSION = 1;
+ public const EXTRA_VERSION = 'DEV';
public const END_OF_MAINTENANCE = '05/2025';
public const END_OF_LIFE = '01/2026';
From 003a7facfa510e8e68fc62e55615d3f3fc35ee1d Mon Sep 17 00:00:00 2001
From: Alexander Schranz
Date: Wed, 28 May 2025 09:33:08 +0200
Subject: [PATCH 26/44] [HttpKernel] Do not superseed private cache-control
when no-store is set
---
EventListener/CacheAttributeListener.php | 1 -
Tests/EventListener/CacheAttributeListenerTest.php | 14 +++++++-------
2 files changed, 7 insertions(+), 8 deletions(-)
diff --git a/EventListener/CacheAttributeListener.php b/EventListener/CacheAttributeListener.php
index e913edf9e5..436e031bbb 100644
--- a/EventListener/CacheAttributeListener.php
+++ b/EventListener/CacheAttributeListener.php
@@ -165,7 +165,6 @@ public function onKernelResponse(ResponseEvent $event): void
}
if (true === $cache->noStore) {
- $response->setPrivate();
$response->headers->addCacheControlDirective('no-store');
}
diff --git a/Tests/EventListener/CacheAttributeListenerTest.php b/Tests/EventListener/CacheAttributeListenerTest.php
index b185ea8994..d2c8ed0db6 100644
--- a/Tests/EventListener/CacheAttributeListenerTest.php
+++ b/Tests/EventListener/CacheAttributeListenerTest.php
@@ -102,18 +102,18 @@ public function testResponseIsPublicIfConfigurationIsPublicTrueNoStoreFalse()
$this->assertFalse($this->response->headers->hasCacheControlDirective('no-store'));
}
- public function testResponseIsPrivateIfConfigurationIsPublicTrueNoStoreTrue()
+ public function testResponseKeepPublicIfConfigurationIsPublicTrueNoStoreTrue()
{
$request = $this->createRequest(new Cache(public: true, noStore: true));
$this->listener->onKernelResponse($this->createEventMock($request, $this->response));
- $this->assertFalse($this->response->headers->hasCacheControlDirective('public'));
- $this->assertTrue($this->response->headers->hasCacheControlDirective('private'));
+ $this->assertTrue($this->response->headers->hasCacheControlDirective('public'));
+ $this->assertFalse($this->response->headers->hasCacheControlDirective('private'));
$this->assertTrue($this->response->headers->hasCacheControlDirective('no-store'));
}
- public function testResponseIsPrivateNoStoreIfConfigurationIsNoStoreTrue()
+ public function testResponseKeepPrivateNoStoreIfConfigurationIsNoStoreTrue()
{
$request = $this->createRequest(new Cache(noStore: true));
@@ -124,14 +124,14 @@ public function testResponseIsPrivateNoStoreIfConfigurationIsNoStoreTrue()
$this->assertTrue($this->response->headers->hasCacheControlDirective('no-store'));
}
- public function testResponseIsPrivateIfSharedMaxAgeSetAndNoStoreIsTrue()
+ public function testResponseIsPublicIfSharedMaxAgeSetAndNoStoreIsTrue()
{
$request = $this->createRequest(new Cache(smaxage: 1, noStore: true));
$this->listener->onKernelResponse($this->createEventMock($request, $this->response));
- $this->assertFalse($this->response->headers->hasCacheControlDirective('public'));
- $this->assertTrue($this->response->headers->hasCacheControlDirective('private'));
+ $this->assertTrue($this->response->headers->hasCacheControlDirective('public'));
+ $this->assertFalse($this->response->headers->hasCacheControlDirective('private'));
$this->assertTrue($this->response->headers->hasCacheControlDirective('no-store'));
}
From 059b29f20366d8ed04acd22271c9949052fd2dd1 Mon Sep 17 00:00:00 2001
From: Adam
Date: Sat, 31 May 2025 13:46:05 +0200
Subject: [PATCH 27/44] [HttpKernel] Fix Symfony 7.3 end of maintenance date
---
Kernel.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Kernel.php b/Kernel.php
index dfee565d06..10e2512cc0 100644
--- a/Kernel.php
+++ b/Kernel.php
@@ -80,7 +80,7 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
public const RELEASE_VERSION = 1;
public const EXTRA_VERSION = 'DEV';
- public const END_OF_MAINTENANCE = '05/2025';
+ public const END_OF_MAINTENANCE = '01/2026';
public const END_OF_LIFE = '01/2026';
public function __construct(
From 0b4ef6a6145d510d9a428e0a61021e4548feaf82 Mon Sep 17 00:00:00 2001
From: Christian Flothmann
Date: Sun, 1 Jun 2025 22:50:36 +0200
Subject: [PATCH 28/44] pass log level instead of exception to resolve the
logger
---
EventListener/ErrorListener.php | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/EventListener/ErrorListener.php b/EventListener/ErrorListener.php
index 18e8bff441..2599b27de0 100644
--- a/EventListener/ErrorListener.php
+++ b/EventListener/ErrorListener.php
@@ -161,15 +161,15 @@ public static function getSubscribedEvents(): array
/**
* Logs an exception.
- *
+ *
* @param ?string $logChannel
*/
protected function logException(\Throwable $exception, string $message, ?string $logLevel = null, /* ?string $logChannel = null */): void
{
$logChannel = (3 < \func_num_args() ? \func_get_arg(3) : null) ?? $this->resolveLogChannel($exception);
-
+
$logLevel ??= $this->resolveLogLevel($exception);
-
+
if(!$logger = $this->getLogger($logChannel)) {
return;
}
@@ -218,7 +218,7 @@ protected function duplicateRequest(\Throwable $exception, Request $request): Re
$attributes = [
'_controller' => $this->controller,
'exception' => $exception,
- 'logger' => DebugLoggerConfigurator::getDebugLogger($this->getLogger($exception)),
+ 'logger' => DebugLoggerConfigurator::getDebugLogger($this->getLogger($this->resolveLogChannel($exception))),
];
$request = $request->duplicate(null, null, $attributes);
$request->setMethod('GET');
From 1644879a66e4aa29c36fe33dfa6c54b450ce1831 Mon Sep 17 00:00:00 2001
From: Fabien Potencier
Date: Sat, 28 Jun 2025 10:24:55 +0200
Subject: [PATCH 29/44] Update VERSION for 7.3.1
---
Kernel.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Kernel.php b/Kernel.php
index 10e2512cc0..4829bfb7de 100644
--- a/Kernel.php
+++ b/Kernel.php
@@ -73,12 +73,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
*/
private static array $freshCache = [];
- public const VERSION = '7.3.1-DEV';
+ public const VERSION = '7.3.1';
public const VERSION_ID = 70301;
public const MAJOR_VERSION = 7;
public const MINOR_VERSION = 3;
public const RELEASE_VERSION = 1;
- public const EXTRA_VERSION = 'DEV';
+ public const EXTRA_VERSION = '';
public const END_OF_MAINTENANCE = '01/2026';
public const END_OF_LIFE = '01/2026';
From ab1f64e4bf0eeafb927e7ad6f9de4b0be3f3966e Mon Sep 17 00:00:00 2001
From: Fabien Potencier
Date: Sat, 28 Jun 2025 10:29:55 +0200
Subject: [PATCH 30/44] Bump Symfony version to 7.3.2
---
Kernel.php | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/Kernel.php b/Kernel.php
index 4829bfb7de..7bc59a9e68 100644
--- a/Kernel.php
+++ b/Kernel.php
@@ -73,12 +73,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
*/
private static array $freshCache = [];
- public const VERSION = '7.3.1';
- public const VERSION_ID = 70301;
+ public const VERSION = '7.3.2-DEV';
+ public const VERSION_ID = 70302;
public const MAJOR_VERSION = 7;
public const MINOR_VERSION = 3;
- public const RELEASE_VERSION = 1;
- public const EXTRA_VERSION = '';
+ public const RELEASE_VERSION = 2;
+ public const EXTRA_VERSION = 'DEV';
public const END_OF_MAINTENANCE = '01/2026';
public const END_OF_LIFE = '01/2026';
From b81dcdbe34b8e8f7b3fc7b2a47fa065d5bf30726 Mon Sep 17 00:00:00 2001
From: Fabien Potencier
Date: Thu, 31 Jul 2025 11:23:30 +0200
Subject: [PATCH 31/44] Update VERSION for 6.4.24
---
Kernel.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Kernel.php b/Kernel.php
index 9a9aa07ab0..281b82a96d 100644
--- a/Kernel.php
+++ b/Kernel.php
@@ -76,12 +76,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
*/
private static array $freshCache = [];
- public const VERSION = '6.4.24-DEV';
+ public const VERSION = '6.4.24';
public const VERSION_ID = 60424;
public const MAJOR_VERSION = 6;
public const MINOR_VERSION = 4;
public const RELEASE_VERSION = 24;
- public const EXTRA_VERSION = 'DEV';
+ public const EXTRA_VERSION = '';
public const END_OF_MAINTENANCE = '11/2026';
public const END_OF_LIFE = '11/2027';
From acf463ac20aba652a1e6e02d7b2f6d9476a18202 Mon Sep 17 00:00:00 2001
From: Fabien Potencier
Date: Thu, 31 Jul 2025 11:35:03 +0200
Subject: [PATCH 32/44] Bump Symfony version to 6.4.25
---
Kernel.php | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/Kernel.php b/Kernel.php
index 281b82a96d..e9de050f24 100644
--- a/Kernel.php
+++ b/Kernel.php
@@ -76,12 +76,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
*/
private static array $freshCache = [];
- public const VERSION = '6.4.24';
- public const VERSION_ID = 60424;
+ public const VERSION = '6.4.25-DEV';
+ public const VERSION_ID = 60425;
public const MAJOR_VERSION = 6;
public const MINOR_VERSION = 4;
- public const RELEASE_VERSION = 24;
- public const EXTRA_VERSION = '';
+ public const RELEASE_VERSION = 25;
+ public const EXTRA_VERSION = 'DEV';
public const END_OF_MAINTENANCE = '11/2026';
public const END_OF_LIFE = '11/2027';
From 6ecc895559ec0097e221ed2fd5eb44d5fede083c Mon Sep 17 00:00:00 2001
From: Fabien Potencier
Date: Thu, 31 Jul 2025 12:45:04 +0200
Subject: [PATCH 33/44] Update VERSION for 7.3.2
---
Kernel.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Kernel.php b/Kernel.php
index 7bc59a9e68..d8be61d6c6 100644
--- a/Kernel.php
+++ b/Kernel.php
@@ -73,12 +73,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
*/
private static array $freshCache = [];
- public const VERSION = '7.3.2-DEV';
+ public const VERSION = '7.3.2';
public const VERSION_ID = 70302;
public const MAJOR_VERSION = 7;
public const MINOR_VERSION = 3;
public const RELEASE_VERSION = 2;
- public const EXTRA_VERSION = 'DEV';
+ public const EXTRA_VERSION = '';
public const END_OF_MAINTENANCE = '01/2026';
public const END_OF_LIFE = '01/2026';
From a839550dd70d9c55e4e82cf59f1b8285aaafb13d Mon Sep 17 00:00:00 2001
From: Fabien Potencier
Date: Thu, 31 Jul 2025 12:54:53 +0200
Subject: [PATCH 34/44] Bump Symfony version to 7.3.3
---
Kernel.php | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/Kernel.php b/Kernel.php
index d8be61d6c6..42123cea19 100644
--- a/Kernel.php
+++ b/Kernel.php
@@ -73,12 +73,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
*/
private static array $freshCache = [];
- public const VERSION = '7.3.2';
- public const VERSION_ID = 70302;
+ public const VERSION = '7.3.3-DEV';
+ public const VERSION_ID = 70303;
public const MAJOR_VERSION = 7;
public const MINOR_VERSION = 3;
- public const RELEASE_VERSION = 2;
- public const EXTRA_VERSION = '';
+ public const RELEASE_VERSION = 3;
+ public const EXTRA_VERSION = 'DEV';
public const END_OF_MAINTENANCE = '01/2026';
public const END_OF_LIFE = '01/2026';
From 4ed8624311f09586823ef14e9001516a1c59f4b2 Mon Sep 17 00:00:00 2001
From: Nicolas Grekas
Date: Fri, 1 Aug 2025 17:44:34 +0200
Subject: [PATCH 35/44] Fix wrong boolean values
---
Tests/DataCollector/LoggerDataCollectorTest.php | 2 +-
.../RegisterControllerArgumentLocatorsPassTest.php | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Tests/DataCollector/LoggerDataCollectorTest.php b/Tests/DataCollector/LoggerDataCollectorTest.php
index 3e1654247b..8babfe4a7d 100644
--- a/Tests/DataCollector/LoggerDataCollectorTest.php
+++ b/Tests/DataCollector/LoggerDataCollectorTest.php
@@ -33,7 +33,7 @@ public function testCollectWithUnexpectedFormat()
$c = new LoggerDataCollector($logger, __DIR__.'/');
$c->lateCollect();
- $compilerLogs = $c->getCompilerLogs()->getValue('message');
+ $compilerLogs = $c->getCompilerLogs()->getValue(true);
$this->assertSame([
['message' => 'Removed service "Psr\Container\ContainerInterface"; reason: private alias.'],
diff --git a/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php b/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php
index f121322bb5..62d0cde969 100644
--- a/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php
+++ b/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php
@@ -505,7 +505,7 @@ public function testAutowireAttribute()
$this->assertInstanceOf(\stdClass::class, $locator->get('serviceAsValue'));
$this->assertInstanceOf(\stdClass::class, $locator->get('expressionAsValue'));
$this->assertSame('bar', $locator->get('rawValue'));
- $this->stringContains('Symfony_Component_HttpKernel_Tests_Fixtures_Suit_APP_SUIT', $locator->get('suit'));
+ $this->assertStringContainsString('Symfony_Component_HttpKernel_Tests_Fixtures_Suit_APP_SUIT', $locator->get('suit'));
$this->assertSame('@bar', $locator->get('escapedRawValue'));
$this->assertSame('foo', $locator->get('customAutowire'));
$this->assertInstanceOf(FooInterface::class, $autowireCallable = $locator->get('autowireCallable'));
From d77a209c7b2186d214eea5262138c01f9a11aef3 Mon Sep 17 00:00:00 2001
From: Nicolas Grekas
Date: Wed, 13 Aug 2025 10:17:00 +0200
Subject: [PATCH 36/44] Remove deprecated calls to deprecated methods of
SplObjectStorage
---
DataCollector/RouterDataCollector.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/DataCollector/RouterDataCollector.php b/DataCollector/RouterDataCollector.php
index 4d91fd6e14..75c21e4c59 100644
--- a/DataCollector/RouterDataCollector.php
+++ b/DataCollector/RouterDataCollector.php
@@ -40,7 +40,7 @@ public function collect(Request $request, Response $response, ?\Throwable $excep
$this->data['redirect'] = true;
$this->data['url'] = $response->getTargetUrl();
- if ($this->controllers->contains($request)) {
+ if ($this->controllers->offsetExists($request)) {
$this->data['route'] = $this->guessRoute($request, $this->controllers[$request]);
}
}
From e364bfd7f919c28c26427ccdb1061790195effc6 Mon Sep 17 00:00:00 2001
From: Muhammad Elhwawshy
Date: Sun, 10 Aug 2025 19:28:08 +0200
Subject: [PATCH 37/44] [HttpKernel] #[MapUploadedFile] throws http exception
on empty files array if argument not nullable nor has default value
---
.../RequestPayloadValueResolver.php | 4 +-
.../UploadedFileValueResolverTest.php | 59 +++++++++++++++++++
2 files changed, 61 insertions(+), 2 deletions(-)
diff --git a/Controller/ArgumentResolver/RequestPayloadValueResolver.php b/Controller/ArgumentResolver/RequestPayloadValueResolver.php
index 0ca965db4b..bac746c7ab 100644
--- a/Controller/ArgumentResolver/RequestPayloadValueResolver.php
+++ b/Controller/ArgumentResolver/RequestPayloadValueResolver.php
@@ -232,10 +232,10 @@ private function mapRequestPayload(Request $request, ArgumentMetadata $argument,
private function mapUploadedFile(Request $request, ArgumentMetadata $argument, MapUploadedFile $attribute): UploadedFile|array|null
{
- if (!($files = $request->files->get($attribute->name ?? $argument->getName(), [])) && ($argument->isNullable() || $argument->hasDefaultValue())) {
+ if (!($files = $request->files->get($attribute->name ?? $argument->getName())) && ($argument->isNullable() || $argument->hasDefaultValue())) {
return null;
}
- return $files;
+ return $files ?? ('array' === $argument->getType() ? [] : null);
}
}
diff --git a/Tests/Controller/ArgumentResolver/UploadedFileValueResolverTest.php b/Tests/Controller/ArgumentResolver/UploadedFileValueResolverTest.php
index 91e28c864e..c0005325a7 100644
--- a/Tests/Controller/ArgumentResolver/UploadedFileValueResolverTest.php
+++ b/Tests/Controller/ArgumentResolver/UploadedFileValueResolverTest.php
@@ -82,6 +82,35 @@ static function () {},
$request,
HttpKernelInterface::MAIN_REQUEST
);
+
+ $this->expectException(HttpException::class);
+
+ $resolver->onKernelControllerArguments($event);
+ }
+
+ /**
+ * @dataProvider provideContext
+ */
+ public function testEmptyArrayArgument(RequestPayloadValueResolver $resolver, Request $request)
+ {
+ $attribute = new MapUploadedFile();
+ $argument = new ArgumentMetadata(
+ 'qux',
+ 'array',
+ false,
+ false,
+ null,
+ false,
+ [$attribute::class => $attribute]
+ );
+ $event = new ControllerArgumentsEvent(
+ $this->createMock(HttpKernelInterface::class),
+ static function () {},
+ $resolver->resolve($request, $argument),
+ $request,
+ HttpKernelInterface::MAIN_REQUEST
+ );
+
$resolver->onKernelControllerArguments($event);
$data = $event->getArguments()[0];
@@ -337,6 +366,36 @@ static function () {},
$this->assertNull($data);
}
+ /**
+ * @dataProvider provideContext
+ */
+ public function testShouldAllowEmptyWhenNullableArray(RequestPayloadValueResolver $resolver, Request $request)
+ {
+ $attribute = new MapUploadedFile();
+ $argument = new ArgumentMetadata(
+ 'qux',
+ 'array',
+ false,
+ false,
+ null,
+ true,
+ [$attribute::class => $attribute]
+ );
+ /** @var HttpKernelInterface&MockObject $httpKernel */
+ $httpKernel = $this->createMock(HttpKernelInterface::class);
+ $event = new ControllerArgumentsEvent(
+ $httpKernel,
+ static function () {},
+ $resolver->resolve($request, $argument),
+ $request,
+ HttpKernelInterface::MAIN_REQUEST
+ );
+ $resolver->onKernelControllerArguments($event);
+ $data = $event->getArguments()[0];
+
+ $this->assertNull($data);
+ }
+
/**
* @dataProvider provideContext
*/
From a0ee3cea5cabf4ed960fd2ef57668ceeacdb6e15 Mon Sep 17 00:00:00 2001
From: Fabien Potencier
Date: Fri, 29 Aug 2025 09:55:45 +0200
Subject: [PATCH 38/44] Update VERSION for 6.4.25
---
Kernel.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Kernel.php b/Kernel.php
index e9de050f24..a04739505f 100644
--- a/Kernel.php
+++ b/Kernel.php
@@ -76,12 +76,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
*/
private static array $freshCache = [];
- public const VERSION = '6.4.25-DEV';
+ public const VERSION = '6.4.25';
public const VERSION_ID = 60425;
public const MAJOR_VERSION = 6;
public const MINOR_VERSION = 4;
public const RELEASE_VERSION = 25;
- public const EXTRA_VERSION = 'DEV';
+ public const EXTRA_VERSION = '';
public const END_OF_MAINTENANCE = '11/2026';
public const END_OF_LIFE = '11/2027';
From 20c93d7606e67ab9a923bb669d0100396bb97b8d Mon Sep 17 00:00:00 2001
From: Fabien Potencier
Date: Fri, 29 Aug 2025 10:19:12 +0200
Subject: [PATCH 39/44] Bump Symfony version to 6.4.26
---
Kernel.php | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/Kernel.php b/Kernel.php
index a04739505f..e274da593a 100644
--- a/Kernel.php
+++ b/Kernel.php
@@ -76,12 +76,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
*/
private static array $freshCache = [];
- public const VERSION = '6.4.25';
- public const VERSION_ID = 60425;
+ public const VERSION = '6.4.26-DEV';
+ public const VERSION_ID = 60426;
public const MAJOR_VERSION = 6;
public const MINOR_VERSION = 4;
- public const RELEASE_VERSION = 25;
- public const EXTRA_VERSION = '';
+ public const RELEASE_VERSION = 26;
+ public const EXTRA_VERSION = 'DEV';
public const END_OF_MAINTENANCE = '11/2026';
public const END_OF_LIFE = '11/2027';
From 72c304de37e1a1cec6d5d12b81187ebd4850a17b Mon Sep 17 00:00:00 2001
From: Fabien Potencier
Date: Fri, 29 Aug 2025 10:23:45 +0200
Subject: [PATCH 40/44] Update VERSION for 7.3.3
---
Kernel.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Kernel.php b/Kernel.php
index 42123cea19..722ba79b90 100644
--- a/Kernel.php
+++ b/Kernel.php
@@ -73,12 +73,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
*/
private static array $freshCache = [];
- public const VERSION = '7.3.3-DEV';
+ public const VERSION = '7.3.3';
public const VERSION_ID = 70303;
public const MAJOR_VERSION = 7;
public const MINOR_VERSION = 3;
public const RELEASE_VERSION = 3;
- public const EXTRA_VERSION = 'DEV';
+ public const EXTRA_VERSION = '';
public const END_OF_MAINTENANCE = '01/2026';
public const END_OF_LIFE = '01/2026';
From 6037b28272930ef158a0702257ff8b225cd177fd Mon Sep 17 00:00:00 2001
From: Fabien Potencier
Date: Fri, 29 Aug 2025 10:28:01 +0200
Subject: [PATCH 41/44] Bump Symfony version to 7.3.4
---
Kernel.php | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/Kernel.php b/Kernel.php
index 722ba79b90..2d2086db7f 100644
--- a/Kernel.php
+++ b/Kernel.php
@@ -73,12 +73,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
*/
private static array $freshCache = [];
- public const VERSION = '7.3.3';
- public const VERSION_ID = 70303;
+ public const VERSION = '7.3.4-DEV';
+ public const VERSION_ID = 70304;
public const MAJOR_VERSION = 7;
public const MINOR_VERSION = 3;
- public const RELEASE_VERSION = 3;
- public const EXTRA_VERSION = '';
+ public const RELEASE_VERSION = 4;
+ public const EXTRA_VERSION = 'DEV';
public const END_OF_MAINTENANCE = '01/2026';
public const END_OF_LIFE = '01/2026';
From 299523b87fc1d8f26c13558cc56a7dc981eab072 Mon Sep 17 00:00:00 2001
From: Nicolas Grekas
Date: Thu, 11 Sep 2025 10:16:56 +0200
Subject: [PATCH 42/44] Replace __sleep/wakeup() by __(un)serialize() for
throwing and internal usages
---
DataCollector/DumpDataCollector.php | 17 +++++------------
Tests/DataCollector/DumpDataCollectorTest.php | 2 +-
2 files changed, 6 insertions(+), 13 deletions(-)
diff --git a/DataCollector/DumpDataCollector.php b/DataCollector/DumpDataCollector.php
index 2300acf914..8c45b42792 100644
--- a/DataCollector/DumpDataCollector.php
+++ b/DataCollector/DumpDataCollector.php
@@ -143,10 +143,7 @@ public function reset(): void
$this->clonesIndex = 0;
}
- /**
- * @internal
- */
- public function __sleep(): array
+ public function __serialize(): array
{
if (!$this->dataCount) {
$this->data = [];
@@ -161,16 +158,12 @@ public function __sleep(): array
$this->dataCount = 0;
$this->isCollected = true;
- return parent::__sleep();
+ return ['data' => $this->data];
}
- /**
- * @internal
- */
- public function __wakeup(): void
+ public function __unserialize(array $data): void
{
- parent::__wakeup();
-
+ $this->data = array_pop($data) ?? [];
$charset = array_pop($this->data);
$fileLinkFormat = array_pop($this->data);
$this->dataCount = \count($this->data);
@@ -180,7 +173,7 @@ public function __wakeup(): void
}
}
- self::__construct($this->stopwatch, \is_string($fileLinkFormat) || $fileLinkFormat instanceof FileLinkFormatter ? $fileLinkFormat : null, \is_string($charset) ? $charset : null);
+ self::__construct($this->stopwatch ?? null, \is_string($fileLinkFormat) || $fileLinkFormat instanceof FileLinkFormatter ? $fileLinkFormat : null, \is_string($charset) ? $charset : null);
}
public function getDumpsCount(): int
diff --git a/Tests/DataCollector/DumpDataCollectorTest.php b/Tests/DataCollector/DumpDataCollectorTest.php
index e55af09fe5..5be6c40b43 100644
--- a/Tests/DataCollector/DumpDataCollectorTest.php
+++ b/Tests/DataCollector/DumpDataCollectorTest.php
@@ -60,7 +60,7 @@ public function testDump()
$this->assertSame(0, $collector->getDumpsCount());
$serialized = serialize($collector);
- $this->assertSame("O:60:\"Symfony\Component\HttpKernel\DataCollector\DumpDataCollector\":1:{s:7:\"\0*\0data\";a:2:{i:0;b:0;i:1;s:5:\"UTF-8\";}}", $serialized);
+ $this->assertSame("O:60:\"Symfony\Component\HttpKernel\DataCollector\DumpDataCollector\":1:{s:4:\"data\";a:2:{i:0;b:0;i:1;s:5:\"UTF-8\";}}", $serialized);
$this->assertInstanceOf(DumpDataCollector::class, unserialize($serialized));
}
From ef6b55331cd78891038e21990dda6767de876efd Mon Sep 17 00:00:00 2001
From: Philippe Pichet
Date: Mon, 25 Aug 2025 13:08:58 +0200
Subject: [PATCH 43/44] [HttpKernel] Handle an array vary header in the http
cache store for write
---
HttpCache/Store.php | 10 +--
Tests/HttpCache/StoreTest.php | 121 ++++++++++++++++++++++++++++++++++
2 files changed, 124 insertions(+), 7 deletions(-)
diff --git a/HttpCache/Store.php b/HttpCache/Store.php
index 473537d85b..4eba39337d 100644
--- a/HttpCache/Store.php
+++ b/HttpCache/Store.php
@@ -211,13 +211,9 @@ public function write(Request $request, Response $response): string
// read existing cache entries, remove non-varying, and add this one to the list
$entries = [];
- $vary = $response->headers->get('vary');
+ $vary = implode(', ', $response->headers->all('vary'));
foreach ($this->getMetadata($key) as $entry) {
- if (!isset($entry[1]['vary'][0])) {
- $entry[1]['vary'] = [''];
- }
-
- if ($entry[1]['vary'][0] != $vary || !$this->requestsMatch($vary ?? '', $entry[0], $storedEnv)) {
+ if (!$this->requestsMatch($vary ?? '', $entry[0], $storedEnv)) {
$entries[] = $entry;
}
}
@@ -285,7 +281,7 @@ public function invalidate(Request $request)
*/
private function requestsMatch(?string $vary, array $env1, array $env2): bool
{
- if (empty($vary)) {
+ if ('' === ($vary ?? '')) {
return true;
}
diff --git a/Tests/HttpCache/StoreTest.php b/Tests/HttpCache/StoreTest.php
index 1942e8d337..f1c944a936 100644
--- a/Tests/HttpCache/StoreTest.php
+++ b/Tests/HttpCache/StoreTest.php
@@ -349,6 +349,127 @@ public function testLoadsBodyEval()
$this->assertSame($content, $response->getContent());
}
+ /**
+ * Basic case when the second header has a different value.
+ * Both responses should be cached
+ */
+ public function testWriteWithMultipleVaryAndCachedAllResponse()
+ {
+ $req1 = Request::create('/foo', 'get', [], [], [], ['HTTP_FOO' => 'foo', 'HTTP_BAR' => 'bar']);
+ $content = str_repeat('a', 24).'b'.str_repeat('a', 24);
+ $res1 = new Response($content, 200, ['vary' => ['Foo', 'Bar'], 'X-Body-Eval' => 'SSI']);
+ $this->store->write($req1, $res1);
+
+ $responseLook = $this->store->lookup($req1);
+ $this->assertSame($content, $responseLook->getContent());
+
+ $req2 = Request::create('/foo', 'get', [], [], [], ['HTTP_FOO' => 'foo', 'HTTP_BAR' => 'foobar']);
+ $content2 = str_repeat('b', 24).'a'.str_repeat('b', 24);
+ $res2 = new Response($content2, 200, ['vary' => ['Foo', 'Bar'], 'X-Body-Eval' => 'SSI']);
+ $this->store->write($req2, $res2);
+
+ $responseLook = $this->store->lookup($req2);
+ $this->assertSame($content2, $responseLook->getContent());
+
+ $responseLook = $this->store->lookup($req1);
+ $this->assertSame($content, $responseLook->getContent());
+ }
+
+ /**
+ * Basic case when the second header has the same value on both requests.
+ * The last response should be cached
+ */
+ public function testWriteWithMultipleVaryAndCachedLastResponse()
+ {
+ $req1 = Request::create('/foo', 'get', [], [], [], ['HTTP_FOO' => 'foo', 'HTTP_BAR' => 'bar']);
+ $content = str_repeat('a', 24).'b'.str_repeat('a', 24);
+ $res1 = new Response($content, 200, ['vary' => ['Foo', 'Bar'], 'X-Body-Eval' => 'SSI']);
+ $this->store->write($req1, $res1);
+
+ $responseLook = $this->store->lookup($req1);
+ $this->assertSame($content, $responseLook->getContent());
+
+ $req2 = Request::create('/foo', 'get', [], [], [], ['HTTP_FOO' => 'foo', 'HTTP_BAR' => 'bar']);
+ $content2 = str_repeat('b', 24).'a'.str_repeat('b', 24);
+ $res2 = new Response($content2, 200, ['vary' => ['Foo', 'Bar'], 'X-Body-Eval' => 'SSI']);
+ $this->store->write($req2, $res2);
+
+ $responseLook = $this->store->lookup($req2);
+ $this->assertSame($content2, $responseLook->getContent());
+
+ $responseLook = $this->store->lookup($req1);
+ $this->assertSame($content2, $responseLook->getContent());
+ }
+
+ /**
+ * Case when a vary value has been removed.
+ * Both responses should be cached
+ */
+ public function testWriteWithChangingVary()
+ {
+ $req1 = Request::create('/foo', 'get', [], [], [], ['HTTP_FOO' => 'foo', 'HTTP_BAR' => 'bar']);
+ $content = str_repeat('a', 24).'b'.str_repeat('a', 24);
+ $res1 = new Response($content, 200, ['vary' => ['Foo', 'bar', 'foobar'], 'X-Body-Eval' => 'SSI']);
+ $this->store->write($req1, $res1);
+
+ $req2 = Request::create('/foo', 'get', [], [], [], ['HTTP_FOO' => 'foo', 'HTTP_FOOBAR' => 'bar']);
+ $content2 = str_repeat('b', 24).'a'.str_repeat('b', 24);
+ $res2 = new Response($content2, 200, ['vary' => ['Foo', 'foobar'], 'X-Body-Eval' => 'SSI']);
+ $this->store->write($req2, $res2);
+
+ $responseLook = $this->store->lookup($req2);
+ $this->assertSame($content2, $responseLook->getContent());
+
+ $responseLook = $this->store->lookup($req1);
+ $this->assertSame($content, $responseLook->getContent());
+ }
+
+ /**
+ * Case when a vary value has been removed and headers of the new vary list are the same.
+ * The last response should be cached
+ */
+ public function testWriteWithRemoveVaryAndAllHeadersOnTheList()
+ {
+ $req1 = Request::create('/foo', 'get', [], [], [], ['HTTP_FOO' => 'foo', 'HTTP_FOOBAR' => 'bar',]);
+ $content = str_repeat('a', 24).'b'.str_repeat('a', 24);
+ $res1 = new Response($content, 200, ['vary' => ['Foo', 'bar', 'foobar'], 'X-Body-Eval' => 'SSI']);
+ $this->store->write($req1, $res1);
+
+ $req2 = Request::create('/foo', 'get', [], [], [], ['HTTP_FOO' => 'foo', 'HTTP_FOOBAR' => 'bar']);
+ $content2 = str_repeat('b', 24).'a'.str_repeat('b', 24);
+ $res2 = new Response($content2, 200, ['vary' => ['Foo', 'foobar'], 'X-Body-Eval' => 'SSI']);
+ $this->store->write($req2, $res2);
+
+ $responseLook = $this->store->lookup($req2);
+ $this->assertSame($content2, $responseLook->getContent());
+
+ $responseLook = $this->store->lookup($req1);
+ $this->assertSame($content2, $responseLook->getContent());
+ }
+
+ /**
+ * Case when a vary value has been added and headers of the new vary list are the same.
+ * The last response should be cached
+ */
+ public function testWriteWithAddingVaryAndAllHeadersOnTheList()
+ {
+ $req1 = Request::create('/foo', 'get', [], [], [], ['HTTP_FOO' => 'foo', 'HTTP_FOOBAR' => 'bar']);
+ $content = str_repeat('a', 24).'b'.str_repeat('a', 24);
+ $res1 = new Response($content, 200, ['vary' => ['Foo', 'foobar'], 'X-Body-Eval' => 'SSI']);
+ $this->store->write($req1, $res1);
+
+ $req2 = Request::create('/foo', 'get', [], [], [], ['HTTP_FOO' => 'foo', 'HTTP_BAR' => 'foobar', 'HTTP_FOOBAR' => 'bar']);
+ $content2 = str_repeat('b', 24).'a'.str_repeat('b', 24);
+ $res2 = new Response($content2, 200, ['vary' => ['Foo', 'bar', 'foobar'], 'X-Body-Eval' => 'SSI']);
+ $this->store->write($req2, $res2);
+
+ $responseLook = $this->store->lookup($req2);
+ $this->assertSame($content2, $responseLook->getContent());
+
+ $responseLook = $this->store->lookup($req1);
+ $this->assertSame($content, $responseLook->getContent());
+ }
+
protected function storeSimpleEntry($path = null, $headers = [])
{
$path ??= '/test';
From e15b2e67ad75fb0c5a59135074e4cfbe3faf4762 Mon Sep 17 00:00:00 2001
From: Santiago San Martin
Date: Fri, 8 Aug 2025 21:26:21 -0300
Subject: [PATCH 44/44] [HttpKernel] Refine Vary header check to skip special
handling of 'Accept-Language' when it's the only entry and
'_vary_by_language' is `true` in `CacheAttributeListener`
---
EventListener/CacheAttributeListener.php | 4 +-
.../CacheAttributeListenerTest.php | 64 +++++++++++++++++++
2 files changed, 67 insertions(+), 1 deletion(-)
diff --git a/EventListener/CacheAttributeListener.php b/EventListener/CacheAttributeListener.php
index 723e758cd0..a633d12e12 100644
--- a/EventListener/CacheAttributeListener.php
+++ b/EventListener/CacheAttributeListener.php
@@ -123,7 +123,9 @@ public function onKernelResponse(ResponseEvent $event)
unset($this->lastModified[$request]);
unset($this->etags[$request]);
- $hasVary = $response->headers->has('Vary');
+ // Check if the response has a Vary header that should be considered, ignoring cases where
+ // it's only 'Accept-Language' and the request has the '_vary_by_language' attribute
+ $hasVary = ['Accept-Language'] === $response->getVary() ? !$request->attributes->get('_vary_by_language') : $response->hasVary();
foreach (array_reverse($attributes) as $cache) {
if (null !== $cache->smaxage && !$response->headers->hasCacheControlDirective('s-maxage')) {
diff --git a/Tests/EventListener/CacheAttributeListenerTest.php b/Tests/EventListener/CacheAttributeListenerTest.php
index 1542b35e74..30caba83ea 100644
--- a/Tests/EventListener/CacheAttributeListenerTest.php
+++ b/Tests/EventListener/CacheAttributeListenerTest.php
@@ -318,6 +318,70 @@ public function testAttribute()
$this->assertSame(CacheAttributeController::CLASS_SMAXAGE, $response->getMaxAge());
}
+ /**
+ * @dataProvider provideVaryHeaderScenarios
+ */
+ public function testHasRelevantVaryHeaderBehavior(array $responseVary, array $cacheVary, bool $varyByLanguage, array $expectedVary)
+ {
+ $request = $this->createRequest(new Cache(vary: $cacheVary));
+ $request->attributes->set('_vary_by_language', $varyByLanguage);
+
+ $response = new Response();
+ $response->setVary($responseVary);
+
+ $listener = new CacheAttributeListener();
+ $event = new ResponseEvent($this->getKernel(), $request, HttpKernelInterface::MAIN_REQUEST, $response);
+ $listener->onKernelResponse($event);
+
+ $this->assertSame($expectedVary, $response->getVary());
+ }
+
+ public static function provideVaryHeaderScenarios(): \Traversable
+ {
+ yield 'no vary headers at all' => [
+ 'responseVary' => [],
+ 'cacheVary' => ['X-Foo'],
+ 'varyByLanguage' => false,
+ 'expectedVary' => ['X-Foo'],
+ ];
+ yield 'response vary accept-language only, vary_by_language true (append new)' => [
+ 'responseVary' => ['Accept-Language'],
+ 'cacheVary' => ['X-Bar'],
+ 'varyByLanguage' => true,
+ 'expectedVary' => ['Accept-Language', 'X-Bar'], // X-Bar is added
+ ];
+ yield 'response vary accept-language only, vary_by_language false (no append)' => [
+ 'responseVary' => ['Accept-Language'],
+ 'cacheVary' => ['X-Bar'],
+ 'varyByLanguage' => false,
+ 'expectedVary' => ['Accept-Language'], // no append
+ ];
+ yield 'response vary multiple including accept-language, vary_by_language true (no append)' => [
+ 'responseVary' => ['Accept-Language', 'User-Agent'],
+ 'cacheVary' => ['X-Baz'],
+ 'varyByLanguage' => true,
+ 'expectedVary' => ['Accept-Language', 'User-Agent'], // no append
+ ];
+ yield 'cache vary is empty' => [
+ 'responseVary' => ['X-Existing'],
+ 'cacheVary' => [],
+ 'varyByLanguage' => true,
+ 'expectedVary' => ['X-Existing'], // nothing to add
+ ];
+ yield 'vary * (no append) — vary_by_language=true' => [
+ 'responseVary' => ['*'],
+ 'cacheVary' => ['X-Foo'],
+ 'varyByLanguage' => true,
+ 'expectedVary' => ['*'],
+ ];
+ yield 'vary * (no append) — vary_by_language=false' => [
+ 'responseVary' => ['*'],
+ 'cacheVary' => ['X-Foo'],
+ 'varyByLanguage' => false,
+ 'expectedVary' => ['*'],
+ ];
+ }
+
private function createRequest(Cache $cache): Request
{
return new Request([], [], ['_cache' => [$cache]]);