From 92d01724686241218c20a696ae362d8a02d4a5f9 Mon Sep 17 00:00:00 2001
From: Shyim
Date: Mon, 14 Oct 2024 15:41:45 +0200
Subject: [PATCH 01/19] [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/19] 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/19] [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/19] 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/19] 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/19] [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/19] 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/19] 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/19] [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/19] [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/19] 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/19] 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/19] 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/19] [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/19] [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/19] [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/19] [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/19] 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/19] 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"