From 810791737c44298a9ab930d5a1dddc275e0df38f Mon Sep 17 00:00:00 2001
From: Andrew Brown <browner12@gmail.com>
Date: Wed, 8 May 2024 16:40:28 -0500
Subject: [PATCH 01/48] inline variable

no need to create the temporary `$ipAddress` variable, we can access the array offset directly from the function.
---
 Request.php | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/Request.php b/Request.php
index df332479d..0dc5a9230 100644
--- a/Request.php
+++ b/Request.php
@@ -750,9 +750,7 @@ public function getClientIps(): array
      */
     public function getClientIp(): ?string
     {
-        $ipAddresses = $this->getClientIps();
-
-        return $ipAddresses[0];
+        return $this->getClientIps()[0];
     }
 
     /**

From 7e606f5de1862889274437c193e9e8b45eb0b936 Mon Sep 17 00:00:00 2001
From: Ian Irlen <45947370+kevinirlen@users.noreply.github.com>
Date: Sat, 1 Jun 2024 00:17:32 +0400
Subject: [PATCH 02/48] [HttpFoundation] A more precise comment for
 HeaderUtils::split method.

---
 HeaderUtils.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/HeaderUtils.php b/HeaderUtils.php
index 110896e17..421aefdb8 100644
--- a/HeaderUtils.php
+++ b/HeaderUtils.php
@@ -34,7 +34,7 @@ private function __construct()
      * Example:
      *
      *     HeaderUtils::split('da, en-gb;q=0.8', ',;')
-     *     // => ['da'], ['en-gb', 'q=0.8']]
+     *     # returns [['da'], ['en-gb', 'q=0.8']]
      *
      * @param string $separators List of characters to split on, ordered by
      *                           precedence, e.g. ',', ';=', or ',;='

From 5f81c6eebe36ed7319c7d0b98259d65abaf45705 Mon Sep 17 00:00:00 2001
From: Christian Flothmann <christian.flothmann@qossmic.com>
Date: Fri, 24 May 2024 12:26:22 +0200
Subject: [PATCH 03/48] use constructor property promotion

---
 AcceptHeaderItem.php                          |  8 +++---
 Cookie.php                                    | 28 ++++++++-----------
 File/UploadedFile.php                         | 11 +++++---
 ParameterBag.php                              |  8 ++----
 Session/Attribute/AttributeBag.php            |  7 ++---
 Session/Flash/AutoExpireFlashBag.php          |  7 ++---
 Session/Flash/FlashBag.php                    |  7 ++---
 Session/SessionBagProxy.php                   |  9 ++++--
 Session/SessionFactory.php                    | 11 ++++----
 .../Handler/MarshallingSessionHandler.php     | 11 +++-----
 .../Handler/MemcachedSessionHandler.php       | 10 +++----
 .../Storage/Handler/StrictSessionHandler.php  |  8 ++----
 Session/Storage/MetadataBag.php               | 10 +++----
 Session/Storage/MockArraySessionStorage.php   |  8 +++---
 .../Storage/MockFileSessionStorageFactory.php | 14 ++++------
 .../Storage/NativeSessionStorageFactory.php   | 17 ++++-------
 .../PhpBridgeSessionStorageFactory.php        | 14 ++++------
 Session/Storage/Proxy/SessionHandlerProxy.php |  8 ++----
 Test/Constraint/RequestAttributeValueSame.php | 11 +++-----
 Test/Constraint/ResponseCookieValueSame.php   | 17 ++++-------
 Test/Constraint/ResponseFormatSame.php        |  9 ++----
 Test/Constraint/ResponseHasCookie.php         | 14 ++++------
 Test/Constraint/ResponseHasHeader.php         |  8 ++----
 Test/Constraint/ResponseHeaderSame.php        | 11 +++-----
 Test/Constraint/ResponseStatusCodeSame.php    |  9 +++---
 UriSigner.php                                 | 15 ++++------
 26 files changed, 117 insertions(+), 173 deletions(-)

diff --git a/AcceptHeaderItem.php b/AcceptHeaderItem.php
index 35ecd4ea2..b8b2b8ad3 100644
--- a/AcceptHeaderItem.php
+++ b/AcceptHeaderItem.php
@@ -18,14 +18,14 @@
  */
 class AcceptHeaderItem
 {
-    private string $value;
     private float $quality = 1.0;
     private int $index = 0;
     private array $attributes = [];
 
-    public function __construct(string $value, array $attributes = [])
-    {
-        $this->value = $value;
+    public function __construct(
+        private string $value,
+        array $attributes = [],
+    ) {
         foreach ($attributes as $name => $value) {
             $this->setAttribute($name, $value);
         }
diff --git a/Cookie.php b/Cookie.php
index 46be14bae..034a1bf17 100644
--- a/Cookie.php
+++ b/Cookie.php
@@ -22,17 +22,10 @@ class Cookie
     public const SAMESITE_LAX = 'lax';
     public const SAMESITE_STRICT = 'strict';
 
-    protected string $name;
-    protected ?string $value;
-    protected ?string $domain;
     protected int $expire;
     protected string $path;
-    protected ?bool $secure;
-    protected bool $httpOnly;
 
-    private bool $raw;
     private ?string $sameSite = null;
-    private bool $partitioned = false;
     private bool $secureDefault = false;
 
     private const RESERVED_CHARS_LIST = "=,; \t\r\n\v\f";
@@ -94,8 +87,18 @@ public static function create(string $name, ?string $value = null, int|string|\D
      *
      * @throws \InvalidArgumentException
      */
-    public function __construct(string $name, ?string $value = null, int|string|\DateTimeInterface $expire = 0, ?string $path = '/', ?string $domain = null, ?bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = self::SAMESITE_LAX, bool $partitioned = false)
-    {
+    public function __construct(
+        protected string $name,
+        protected ?string $value = null,
+        int|string|\DateTimeInterface $expire = 0,
+        ?string $path = '/',
+        protected ?string $domain = null,
+        protected ?bool $secure = null,
+        protected bool $httpOnly = true,
+        private bool $raw = false,
+        ?string $sameSite = self::SAMESITE_LAX,
+        private bool $partitioned = false,
+    ) {
         // from PHP source code
         if ($raw && false !== strpbrk($name, self::RESERVED_CHARS_LIST)) {
             throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name));
@@ -105,16 +108,9 @@ public function __construct(string $name, ?string $value = null, int|string|\Dat
             throw new \InvalidArgumentException('The cookie name cannot be empty.');
         }
 
-        $this->name = $name;
-        $this->value = $value;
-        $this->domain = $domain;
         $this->expire = self::expiresTimestamp($expire);
         $this->path = $path ?: '/';
-        $this->secure = $secure;
-        $this->httpOnly = $httpOnly;
-        $this->raw = $raw;
         $this->sameSite = $this->withSameSite($sameSite)->sameSite;
-        $this->partitioned = $partitioned;
     }
 
     /**
diff --git a/File/UploadedFile.php b/File/UploadedFile.php
index 74e929f9b..3b050fb77 100644
--- a/File/UploadedFile.php
+++ b/File/UploadedFile.php
@@ -31,7 +31,6 @@
  */
 class UploadedFile extends File
 {
-    private bool $test;
     private string $originalName;
     private string $mimeType;
     private int $error;
@@ -61,13 +60,17 @@ class UploadedFile extends File
      * @throws FileException         If file_uploads is disabled
      * @throws FileNotFoundException If the file does not exist
      */
-    public function __construct(string $path, string $originalName, ?string $mimeType = null, ?int $error = null, bool $test = false)
-    {
+    public function __construct(
+        string $path,
+        string $originalName,
+        ?string $mimeType = null,
+        ?int $error = null,
+        private bool $test = false,
+    ) {
         $this->originalName = $this->getName($originalName);
         $this->originalPath = strtr($originalName, '\\', '/');
         $this->mimeType = $mimeType ?: 'application/octet-stream';
         $this->error = $error ?: \UPLOAD_ERR_OK;
-        $this->test = $test;
 
         parent::__construct($path, \UPLOAD_ERR_OK === $this->error);
     }
diff --git a/ParameterBag.php b/ParameterBag.php
index d60d3bda2..760e4f947 100644
--- a/ParameterBag.php
+++ b/ParameterBag.php
@@ -23,11 +23,9 @@
  */
 class ParameterBag implements \IteratorAggregate, \Countable
 {
-    protected array $parameters;
-
-    public function __construct(array $parameters = [])
-    {
-        $this->parameters = $parameters;
+    public function __construct(
+        protected array $parameters = [],
+    ) {
     }
 
     /**
diff --git a/Session/Attribute/AttributeBag.php b/Session/Attribute/AttributeBag.php
index 042f3bd90..e34a497c5 100644
--- a/Session/Attribute/AttributeBag.php
+++ b/Session/Attribute/AttributeBag.php
@@ -21,14 +21,13 @@ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Counta
     protected array $attributes = [];
 
     private string $name = 'attributes';
-    private string $storageKey;
 
     /**
      * @param string $storageKey The key used to store attributes in the session
      */
-    public function __construct(string $storageKey = '_sf2_attributes')
-    {
-        $this->storageKey = $storageKey;
+    public function __construct(
+        private string $storageKey = '_sf2_attributes',
+    ) {
     }
 
     public function getName(): string
diff --git a/Session/Flash/AutoExpireFlashBag.php b/Session/Flash/AutoExpireFlashBag.php
index 2eba84330..bfb856d51 100644
--- a/Session/Flash/AutoExpireFlashBag.php
+++ b/Session/Flash/AutoExpireFlashBag.php
@@ -20,14 +20,13 @@ class AutoExpireFlashBag implements FlashBagInterface
 {
     private string $name = 'flashes';
     private array $flashes = ['display' => [], 'new' => []];
-    private string $storageKey;
 
     /**
      * @param string $storageKey The key used to store flashes in the session
      */
-    public function __construct(string $storageKey = '_symfony_flashes')
-    {
-        $this->storageKey = $storageKey;
+    public function __construct(
+        private string $storageKey = '_symfony_flashes',
+    ) {
     }
 
     public function getName(): string
diff --git a/Session/Flash/FlashBag.php b/Session/Flash/FlashBag.php
index 044639b36..72753a66a 100644
--- a/Session/Flash/FlashBag.php
+++ b/Session/Flash/FlashBag.php
@@ -20,14 +20,13 @@ class FlashBag implements FlashBagInterface
 {
     private string $name = 'flashes';
     private array $flashes = [];
-    private string $storageKey;
 
     /**
      * @param string $storageKey The key used to store flashes in the session
      */
-    public function __construct(string $storageKey = '_symfony_flashes')
-    {
-        $this->storageKey = $storageKey;
+    public function __construct(
+        private string $storageKey = '_symfony_flashes',
+    ) {
     }
 
     public function getName(): string
diff --git a/Session/SessionBagProxy.php b/Session/SessionBagProxy.php
index e759d94db..a389bd8b1 100644
--- a/Session/SessionBagProxy.php
+++ b/Session/SessionBagProxy.php
@@ -18,13 +18,16 @@
  */
 final class SessionBagProxy implements SessionBagInterface
 {
-    private SessionBagInterface $bag;
     private array $data;
     private ?int $usageIndex;
     private ?\Closure $usageReporter;
 
-    public function __construct(SessionBagInterface $bag, array &$data, ?int &$usageIndex, ?callable $usageReporter)
-    {
+    public function __construct(
+        private SessionBagInterface $bag,
+        array &$data,
+        ?int &$usageIndex,
+        ?callable $usageReporter,
+    ) {
         $this->bag = $bag;
         $this->data = &$data;
         $this->usageIndex = &$usageIndex;
diff --git a/Session/SessionFactory.php b/Session/SessionFactory.php
index c06ed4b7d..b875a23c5 100644
--- a/Session/SessionFactory.php
+++ b/Session/SessionFactory.php
@@ -22,14 +22,13 @@ class_exists(Session::class);
  */
 class SessionFactory implements SessionFactoryInterface
 {
-    private RequestStack $requestStack;
-    private SessionStorageFactoryInterface $storageFactory;
     private ?\Closure $usageReporter;
 
-    public function __construct(RequestStack $requestStack, SessionStorageFactoryInterface $storageFactory, ?callable $usageReporter = null)
-    {
-        $this->requestStack = $requestStack;
-        $this->storageFactory = $storageFactory;
+    public function __construct(
+        private RequestStack $requestStack,
+        private SessionStorageFactoryInterface $storageFactory,
+        ?callable $usageReporter = null,
+    ) {
         $this->usageReporter = null === $usageReporter ? null : $usageReporter(...);
     }
 
diff --git a/Session/Storage/Handler/MarshallingSessionHandler.php b/Session/Storage/Handler/MarshallingSessionHandler.php
index 1567f5433..8e82f184d 100644
--- a/Session/Storage/Handler/MarshallingSessionHandler.php
+++ b/Session/Storage/Handler/MarshallingSessionHandler.php
@@ -18,13 +18,10 @@
  */
 class MarshallingSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface
 {
-    private AbstractSessionHandler $handler;
-    private MarshallerInterface $marshaller;
-
-    public function __construct(AbstractSessionHandler $handler, MarshallerInterface $marshaller)
-    {
-        $this->handler = $handler;
-        $this->marshaller = $marshaller;
+    public function __construct(
+        private AbstractSessionHandler $handler,
+        private MarshallerInterface $marshaller,
+    ) {
     }
 
     public function open(string $savePath, string $name): bool
diff --git a/Session/Storage/Handler/MemcachedSessionHandler.php b/Session/Storage/Handler/MemcachedSessionHandler.php
index 91a023ddb..ecee15f37 100644
--- a/Session/Storage/Handler/MemcachedSessionHandler.php
+++ b/Session/Storage/Handler/MemcachedSessionHandler.php
@@ -21,8 +21,6 @@
  */
 class MemcachedSessionHandler extends AbstractSessionHandler
 {
-    private \Memcached $memcached;
-
     /**
      * Time to live in seconds.
      */
@@ -42,10 +40,10 @@ class MemcachedSessionHandler extends AbstractSessionHandler
      *
      * @throws \InvalidArgumentException When unsupported options are passed
      */
-    public function __construct(\Memcached $memcached, array $options = [])
-    {
-        $this->memcached = $memcached;
-
+    public function __construct(
+        private \Memcached $memcached,
+        array $options = [],
+    ) {
         if ($diff = array_diff(array_keys($options), ['prefix', 'expiretime', 'ttl'])) {
             throw new \InvalidArgumentException(sprintf('The following options are not supported "%s".', implode(', ', $diff)));
         }
diff --git a/Session/Storage/Handler/StrictSessionHandler.php b/Session/Storage/Handler/StrictSessionHandler.php
index 1f8668744..74a9962c7 100644
--- a/Session/Storage/Handler/StrictSessionHandler.php
+++ b/Session/Storage/Handler/StrictSessionHandler.php
@@ -18,16 +18,14 @@
  */
 class StrictSessionHandler extends AbstractSessionHandler
 {
-    private \SessionHandlerInterface $handler;
     private bool $doDestroy;
 
-    public function __construct(\SessionHandlerInterface $handler)
-    {
+    public function __construct(
+        private \SessionHandlerInterface $handler,
+    ) {
         if ($handler instanceof \SessionUpdateTimestampHandlerInterface) {
             throw new \LogicException(sprintf('"%s" is already an instance of "SessionUpdateTimestampHandlerInterface", you cannot wrap it with "%s".', get_debug_type($handler), self::class));
         }
-
-        $this->handler = $handler;
     }
 
     /**
diff --git a/Session/Storage/MetadataBag.php b/Session/Storage/MetadataBag.php
index 3e80f7dd8..c9e0bdd33 100644
--- a/Session/Storage/MetadataBag.php
+++ b/Session/Storage/MetadataBag.php
@@ -29,18 +29,16 @@ class MetadataBag implements SessionBagInterface
     protected array $meta = [self::CREATED => 0, self::UPDATED => 0, self::LIFETIME => 0];
 
     private string $name = '__metadata';
-    private string $storageKey;
     private int $lastUsed;
-    private int $updateThreshold;
 
     /**
      * @param string $storageKey      The key used to store bag in the session
      * @param int    $updateThreshold The time to wait between two UPDATED updates
      */
-    public function __construct(string $storageKey = '_sf2_meta', int $updateThreshold = 0)
-    {
-        $this->storageKey = $storageKey;
-        $this->updateThreshold = $updateThreshold;
+    public function __construct(
+        private string $storageKey = '_sf2_meta',
+        private int $updateThreshold = 0,
+    ) {
     }
 
     public function initialize(array &$array): void
diff --git a/Session/Storage/MockArraySessionStorage.php b/Session/Storage/MockArraySessionStorage.php
index 97774ce0e..8e8e3109b 100644
--- a/Session/Storage/MockArraySessionStorage.php
+++ b/Session/Storage/MockArraySessionStorage.php
@@ -28,7 +28,6 @@
 class MockArraySessionStorage implements SessionStorageInterface
 {
     protected string $id = '';
-    protected string $name;
     protected bool $started = false;
     protected bool $closed = false;
     protected array $data = [];
@@ -39,9 +38,10 @@ class MockArraySessionStorage implements SessionStorageInterface
      */
     protected array $bags = [];
 
-    public function __construct(string $name = 'MOCKSESSID', ?MetadataBag $metaBag = null)
-    {
-        $this->name = $name;
+    public function __construct(
+        protected string $name = 'MOCKSESSID',
+        ?MetadataBag $metaBag = null,
+    ) {
         $this->setMetadataBag($metaBag);
     }
 
diff --git a/Session/Storage/MockFileSessionStorageFactory.php b/Session/Storage/MockFileSessionStorageFactory.php
index 6727cf14f..77ee7b658 100644
--- a/Session/Storage/MockFileSessionStorageFactory.php
+++ b/Session/Storage/MockFileSessionStorageFactory.php
@@ -21,18 +21,14 @@ class_exists(MockFileSessionStorage::class);
  */
 class MockFileSessionStorageFactory implements SessionStorageFactoryInterface
 {
-    private ?string $savePath;
-    private string $name;
-    private ?MetadataBag $metaBag;
-
     /**
      * @see MockFileSessionStorage constructor.
      */
-    public function __construct(?string $savePath = null, string $name = 'MOCKSESSID', ?MetadataBag $metaBag = null)
-    {
-        $this->savePath = $savePath;
-        $this->name = $name;
-        $this->metaBag = $metaBag;
+    public function __construct(
+        private ?string $savePath = null,
+        private string $name = 'MOCKSESSID',
+        private ?MetadataBag $metaBag = null,
+    ) {
     }
 
     public function createStorage(?Request $request): SessionStorageInterface
diff --git a/Session/Storage/NativeSessionStorageFactory.php b/Session/Storage/NativeSessionStorageFactory.php
index 6463a4c1b..cb8c53539 100644
--- a/Session/Storage/NativeSessionStorageFactory.php
+++ b/Session/Storage/NativeSessionStorageFactory.php
@@ -22,20 +22,15 @@ class_exists(NativeSessionStorage::class);
  */
 class NativeSessionStorageFactory implements SessionStorageFactoryInterface
 {
-    private array $options;
-    private AbstractProxy|\SessionHandlerInterface|null $handler;
-    private ?MetadataBag $metaBag;
-    private bool $secure;
-
     /**
      * @see NativeSessionStorage constructor.
      */
-    public function __construct(array $options = [], AbstractProxy|\SessionHandlerInterface|null $handler = null, ?MetadataBag $metaBag = null, bool $secure = false)
-    {
-        $this->options = $options;
-        $this->handler = $handler;
-        $this->metaBag = $metaBag;
-        $this->secure = $secure;
+    public function __construct(
+        private array $options = [],
+        private AbstractProxy|\SessionHandlerInterface|null $handler = null,
+        private ?MetadataBag $metaBag = null,
+        private bool $secure = false,
+    ) {
     }
 
     public function createStorage(?Request $request): SessionStorageInterface
diff --git a/Session/Storage/PhpBridgeSessionStorageFactory.php b/Session/Storage/PhpBridgeSessionStorageFactory.php
index aa4f800d3..357e5c713 100644
--- a/Session/Storage/PhpBridgeSessionStorageFactory.php
+++ b/Session/Storage/PhpBridgeSessionStorageFactory.php
@@ -22,15 +22,11 @@ class_exists(PhpBridgeSessionStorage::class);
  */
 class PhpBridgeSessionStorageFactory implements SessionStorageFactoryInterface
 {
-    private AbstractProxy|\SessionHandlerInterface|null $handler;
-    private ?MetadataBag $metaBag;
-    private bool $secure;
-
-    public function __construct(AbstractProxy|\SessionHandlerInterface|null $handler = null, ?MetadataBag $metaBag = null, bool $secure = false)
-    {
-        $this->handler = $handler;
-        $this->metaBag = $metaBag;
-        $this->secure = $secure;
+    public function __construct(
+        private AbstractProxy|\SessionHandlerInterface|null $handler = null,
+        private ?MetadataBag $metaBag = null,
+        private bool $secure = false,
+    ) {
     }
 
     public function createStorage(?Request $request): SessionStorageInterface
diff --git a/Session/Storage/Proxy/SessionHandlerProxy.php b/Session/Storage/Proxy/SessionHandlerProxy.php
index b8df97f45..0316362f0 100644
--- a/Session/Storage/Proxy/SessionHandlerProxy.php
+++ b/Session/Storage/Proxy/SessionHandlerProxy.php
@@ -18,11 +18,9 @@
  */
 class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface
 {
-    protected \SessionHandlerInterface $handler;
-
-    public function __construct(\SessionHandlerInterface $handler)
-    {
-        $this->handler = $handler;
+    public function __construct(
+        protected \SessionHandlerInterface $handler,
+    ) {
         $this->wrapper = $handler instanceof \SessionHandler;
         $this->saveHandlerName = $this->wrapper || ($handler instanceof StrictSessionHandler && $handler->isWrapper()) ? \ini_get('session.save_handler') : 'user';
     }
diff --git a/Test/Constraint/RequestAttributeValueSame.php b/Test/Constraint/RequestAttributeValueSame.php
index 6e1142681..fe910f063 100644
--- a/Test/Constraint/RequestAttributeValueSame.php
+++ b/Test/Constraint/RequestAttributeValueSame.php
@@ -16,13 +16,10 @@
 
 final class RequestAttributeValueSame extends Constraint
 {
-    private string $name;
-    private string $value;
-
-    public function __construct(string $name, string $value)
-    {
-        $this->name = $name;
-        $this->value = $value;
+    public function __construct(
+        private string $name,
+        private string $value,
+    ) {
     }
 
     public function toString(): string
diff --git a/Test/Constraint/ResponseCookieValueSame.php b/Test/Constraint/ResponseCookieValueSame.php
index 768007b95..936496a28 100644
--- a/Test/Constraint/ResponseCookieValueSame.php
+++ b/Test/Constraint/ResponseCookieValueSame.php
@@ -17,17 +17,12 @@
 
 final class ResponseCookieValueSame extends Constraint
 {
-    private string $name;
-    private string $value;
-    private string $path;
-    private ?string $domain;
-
-    public function __construct(string $name, string $value, string $path = '/', ?string $domain = null)
-    {
-        $this->name = $name;
-        $this->value = $value;
-        $this->path = $path;
-        $this->domain = $domain;
+    public function __construct(
+        private string $name,
+        private string $value,
+        private string $path = '/',
+        private ?string $domain = null,
+    ) {
     }
 
     public function toString(): string
diff --git a/Test/Constraint/ResponseFormatSame.php b/Test/Constraint/ResponseFormatSame.php
index c75321f24..cc3655aef 100644
--- a/Test/Constraint/ResponseFormatSame.php
+++ b/Test/Constraint/ResponseFormatSame.php
@@ -22,16 +22,11 @@
  */
 final class ResponseFormatSame extends Constraint
 {
-    private Request $request;
-    private ?string $format;
-
     public function __construct(
-        Request $request,
-        ?string $format,
+        private Request $request,
+        private ?string $format,
         private readonly bool $verbose = true,
     ) {
-        $this->request = $request;
-        $this->format = $format;
     }
 
     public function toString(): string
diff --git a/Test/Constraint/ResponseHasCookie.php b/Test/Constraint/ResponseHasCookie.php
index 8eccea9d1..aff7d4944 100644
--- a/Test/Constraint/ResponseHasCookie.php
+++ b/Test/Constraint/ResponseHasCookie.php
@@ -17,15 +17,11 @@
 
 final class ResponseHasCookie extends Constraint
 {
-    private string $name;
-    private string $path;
-    private ?string $domain;
-
-    public function __construct(string $name, string $path = '/', ?string $domain = null)
-    {
-        $this->name = $name;
-        $this->path = $path;
-        $this->domain = $domain;
+    public function __construct(
+        private string $name,
+        private string $path = '/',
+        private ?string $domain = null,
+    ) {
     }
 
     public function toString(): string
diff --git a/Test/Constraint/ResponseHasHeader.php b/Test/Constraint/ResponseHasHeader.php
index 08522c89c..4dbd0c997 100644
--- a/Test/Constraint/ResponseHasHeader.php
+++ b/Test/Constraint/ResponseHasHeader.php
@@ -16,11 +16,9 @@
 
 final class ResponseHasHeader extends Constraint
 {
-    private string $headerName;
-
-    public function __construct(string $headerName)
-    {
-        $this->headerName = $headerName;
+    public function __construct(
+        private string $headerName,
+    ) {
     }
 
     public function toString(): string
diff --git a/Test/Constraint/ResponseHeaderSame.php b/Test/Constraint/ResponseHeaderSame.php
index 8141df972..af1cb5fbb 100644
--- a/Test/Constraint/ResponseHeaderSame.php
+++ b/Test/Constraint/ResponseHeaderSame.php
@@ -16,13 +16,10 @@
 
 final class ResponseHeaderSame extends Constraint
 {
-    private string $headerName;
-    private string $expectedValue;
-
-    public function __construct(string $headerName, string $expectedValue)
-    {
-        $this->headerName = $headerName;
-        $this->expectedValue = $expectedValue;
+    public function __construct(
+        private string $headerName,
+        private string $expectedValue,
+    ) {
     }
 
     public function toString(): string
diff --git a/Test/Constraint/ResponseStatusCodeSame.php b/Test/Constraint/ResponseStatusCodeSame.php
index 5ca6373ce..1223608b9 100644
--- a/Test/Constraint/ResponseStatusCodeSame.php
+++ b/Test/Constraint/ResponseStatusCodeSame.php
@@ -16,11 +16,10 @@
 
 final class ResponseStatusCodeSame extends Constraint
 {
-    private int $statusCode;
-
-    public function __construct(int $statusCode, private readonly bool $verbose = true)
-    {
-        $this->statusCode = $statusCode;
+    public function __construct(
+        private int $statusCode,
+        private readonly bool $verbose = true,
+    ) {
     }
 
     public function toString(): string
diff --git a/UriSigner.php b/UriSigner.php
index 4415026b1..f9fba605b 100644
--- a/UriSigner.php
+++ b/UriSigner.php
@@ -18,23 +18,18 @@
  */
 class UriSigner
 {
-    private string $secret;
-    private string $hashParameter;
-    private string $expirationParameter;
-
     /**
      * @param string $hashParameter       Query string parameter to use
      * @param string $expirationParameter Query string parameter to use for expiration
      */
-    public function __construct(#[\SensitiveParameter] string $secret, string $hashParameter = '_hash', string $expirationParameter = '_expiration')
-    {
+    public function __construct(
+        #[\SensitiveParameter] private string $secret,
+        private string $hashParameter = '_hash',
+        private string $expirationParameter = '_expiration',
+    ) {
         if (!$secret) {
             throw new \InvalidArgumentException('A non-empty secret is required.');
         }
-
-        $this->secret = $secret;
-        $this->hashParameter = $hashParameter;
-        $this->expirationParameter = $expirationParameter;
     }
 
     /**

From 044d55526b302250b8c3bd1985124dcdca0f8518 Mon Sep 17 00:00:00 2001
From: "Alexander M. Turek" <me@derrabus.de>
Date: Thu, 20 Jun 2024 17:52:34 +0200
Subject: [PATCH 04/48] Prefix all sprintf() calls

---
 BinaryFileResponse.php                        |  4 ++--
 Cookie.php                                    |  4 ++--
 File/Exception/AccessDeniedException.php      |  2 +-
 File/Exception/FileNotFoundException.php      |  2 +-
 File/Exception/UnexpectedTypeException.php    |  2 +-
 File/File.php                                 |  8 ++++----
 File/UploadedFile.php                         |  4 ++--
 HeaderBag.php                                 |  4 ++--
 HeaderUtils.php                               |  2 +-
 InputBag.php                                  | 12 +++++------
 IpUtils.php                                   |  2 +-
 JsonResponse.php                              |  4 ++--
 ParameterBag.php                              | 12 +++++------
 RedirectResponse.php                          |  4 ++--
 Request.php                                   | 20 +++++++++----------
 Response.php                                  |  8 ++++----
 ResponseHeaderBag.php                         |  2 +-
 Session/SessionUtils.php                      |  4 ++--
 .../Handler/AbstractSessionHandler.php        |  4 ++--
 .../Storage/Handler/IdentityMarshaller.php    |  2 +-
 .../Handler/MemcachedSessionHandler.php       |  2 +-
 .../Handler/NativeFileSessionHandler.php      |  4 ++--
 Session/Storage/Handler/PdoSessionHandler.php | 12 +++++------
 .../Storage/Handler/RedisSessionHandler.php   |  2 +-
 .../Storage/Handler/SessionHandlerFactory.php |  4 ++--
 .../Storage/Handler/StrictSessionHandler.php  |  2 +-
 Session/Storage/MockArraySessionStorage.php   |  2 +-
 Session/Storage/MockFileSessionStorage.php    |  2 +-
 Session/Storage/NativeSessionStorage.php      |  6 +++---
 Test/Constraint/RequestAttributeValueSame.php |  2 +-
 Test/Constraint/ResponseCookieValueSame.php   |  8 ++++----
 Test/Constraint/ResponseHasCookie.php         |  6 +++---
 Test/Constraint/ResponseHasHeader.php         |  2 +-
 .../Constraint/ResponseHeaderLocationSame.php |  4 ++--
 Test/Constraint/ResponseHeaderSame.php        |  2 +-
 Tests/RequestTest.php                         |  2 +-
 Tests/ResponseFunctionalTest.php              |  4 ++--
 Tests/ResponseTest.php                        |  6 +++---
 .../Handler/AbstractSessionHandlerTest.php    |  4 ++--
 .../Handler/MongoDbSessionHandlerTest.php     |  2 +-
 UriSigner.php                                 |  6 +++---
 41 files changed, 95 insertions(+), 95 deletions(-)

diff --git a/BinaryFileResponse.php b/BinaryFileResponse.php
index e49b1c984..535aa6068 100644
--- a/BinaryFileResponse.php
+++ b/BinaryFileResponse.php
@@ -256,13 +256,13 @@ public function prepare(Request $request): static
                         $end = min($end, $fileSize - 1);
                         if ($start < 0 || $start > $end) {
                             $this->setStatusCode(416);
-                            $this->headers->set('Content-Range', sprintf('bytes */%s', $fileSize));
+                            $this->headers->set('Content-Range', \sprintf('bytes */%s', $fileSize));
                         } elseif ($end - $start < $fileSize - 1) {
                             $this->maxlen = $end < $fileSize ? $end - $start + 1 : -1;
                             $this->offset = $start;
 
                             $this->setStatusCode(206);
-                            $this->headers->set('Content-Range', sprintf('bytes %s-%s/%s', $start, $end, $fileSize));
+                            $this->headers->set('Content-Range', \sprintf('bytes %s-%s/%s', $start, $end, $fileSize));
                             $this->headers->set('Content-Length', $end - $start + 1);
                         }
                     }
diff --git a/Cookie.php b/Cookie.php
index 034a1bf17..199287057 100644
--- a/Cookie.php
+++ b/Cookie.php
@@ -101,7 +101,7 @@ public function __construct(
     ) {
         // from PHP source code
         if ($raw && false !== strpbrk($name, self::RESERVED_CHARS_LIST)) {
-            throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name));
+            throw new \InvalidArgumentException(\sprintf('The cookie name "%s" contains invalid characters.', $name));
         }
 
         if (!$name) {
@@ -204,7 +204,7 @@ public function withHttpOnly(bool $httpOnly = true): static
     public function withRaw(bool $raw = true): static
     {
         if ($raw && false !== strpbrk($this->name, self::RESERVED_CHARS_LIST)) {
-            throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $this->name));
+            throw new \InvalidArgumentException(\sprintf('The cookie name "%s" contains invalid characters.', $this->name));
         }
 
         $cookie = clone $this;
diff --git a/File/Exception/AccessDeniedException.php b/File/Exception/AccessDeniedException.php
index 136d2a9f5..79ab0fce3 100644
--- a/File/Exception/AccessDeniedException.php
+++ b/File/Exception/AccessDeniedException.php
@@ -20,6 +20,6 @@ class AccessDeniedException extends FileException
 {
     public function __construct(string $path)
     {
-        parent::__construct(sprintf('The file %s could not be accessed', $path));
+        parent::__construct(\sprintf('The file %s could not be accessed', $path));
     }
 }
diff --git a/File/Exception/FileNotFoundException.php b/File/Exception/FileNotFoundException.php
index 31bdf68fe..3a5eb039b 100644
--- a/File/Exception/FileNotFoundException.php
+++ b/File/Exception/FileNotFoundException.php
@@ -20,6 +20,6 @@ class FileNotFoundException extends FileException
 {
     public function __construct(string $path)
     {
-        parent::__construct(sprintf('The file "%s" does not exist', $path));
+        parent::__construct(\sprintf('The file "%s" does not exist', $path));
     }
 }
diff --git a/File/Exception/UnexpectedTypeException.php b/File/Exception/UnexpectedTypeException.php
index 905bd5962..09b1c7e18 100644
--- a/File/Exception/UnexpectedTypeException.php
+++ b/File/Exception/UnexpectedTypeException.php
@@ -15,6 +15,6 @@ class UnexpectedTypeException extends FileException
 {
     public function __construct(mixed $value, string $expectedType)
     {
-        parent::__construct(sprintf('Expected argument of type %s, %s given', $expectedType, get_debug_type($value)));
+        parent::__construct(\sprintf('Expected argument of type %s, %s given', $expectedType, get_debug_type($value)));
     }
 }
diff --git a/File/File.php b/File/File.php
index 34ca5a537..c369ecbfb 100644
--- a/File/File.php
+++ b/File/File.php
@@ -93,7 +93,7 @@ public function move(string $directory, ?string $name = null): self
             restore_error_handler();
         }
         if (!$renamed) {
-            throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s).', $this->getPathname(), $target, strip_tags($error)));
+            throw new FileException(\sprintf('Could not move the file "%s" to "%s" (%s).', $this->getPathname(), $target, strip_tags($error)));
         }
 
         @chmod($target, 0666 & ~umask());
@@ -106,7 +106,7 @@ public function getContent(): string
         $content = file_get_contents($this->getPathname());
 
         if (false === $content) {
-            throw new FileException(sprintf('Could not get the content of the file "%s".', $this->getPathname()));
+            throw new FileException(\sprintf('Could not get the content of the file "%s".', $this->getPathname()));
         }
 
         return $content;
@@ -116,10 +116,10 @@ protected function getTargetFile(string $directory, ?string $name = null): self
     {
         if (!is_dir($directory)) {
             if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) {
-                throw new FileException(sprintf('Unable to create the "%s" directory.', $directory));
+                throw new FileException(\sprintf('Unable to create the "%s" directory.', $directory));
             }
         } elseif (!is_writable($directory)) {
-            throw new FileException(sprintf('Unable to write in the "%s" directory.', $directory));
+            throw new FileException(\sprintf('Unable to write in the "%s" directory.', $directory));
         }
 
         $target = rtrim($directory, '/\\').\DIRECTORY_SEPARATOR.(null === $name ? $this->getBasename() : $this->getName($name));
diff --git a/File/UploadedFile.php b/File/UploadedFile.php
index 3b050fb77..a27a56f96 100644
--- a/File/UploadedFile.php
+++ b/File/UploadedFile.php
@@ -194,7 +194,7 @@ public function move(string $directory, ?string $name = null): File
                 restore_error_handler();
             }
             if (!$moved) {
-                throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s).', $this->getPathname(), $target, strip_tags($error)));
+                throw new FileException(\sprintf('Could not move the file "%s" to "%s" (%s).', $this->getPathname(), $target, strip_tags($error)));
             }
 
             @chmod($target, 0666 & ~umask());
@@ -284,6 +284,6 @@ public function getErrorMessage(): string
         $maxFilesize = \UPLOAD_ERR_INI_SIZE === $errorCode ? self::getMaxFilesize() / 1024 : 0;
         $message = $errors[$errorCode] ?? 'The file "%s" was not uploaded due to an unknown error.';
 
-        return sprintf($message, $this->getClientOriginalName(), $maxFilesize);
+        return \sprintf($message, $this->getClientOriginalName(), $maxFilesize);
     }
 }
diff --git a/HeaderBag.php b/HeaderBag.php
index 4bab3764b..12a2e1bdb 100644
--- a/HeaderBag.php
+++ b/HeaderBag.php
@@ -51,7 +51,7 @@ public function __toString(): string
         foreach ($headers as $name => $values) {
             $name = ucwords($name, '-');
             foreach ($values as $value) {
-                $content .= sprintf("%-{$max}s %s\r\n", $name.':', $value);
+                $content .= \sprintf("%-{$max}s %s\r\n", $name.':', $value);
             }
         }
 
@@ -194,7 +194,7 @@ public function getDate(string $key, ?\DateTimeInterface $default = null): ?\Dat
         }
 
         if (false === $date = \DateTimeImmutable::createFromFormat(\DATE_RFC2822, $value)) {
-            throw new \RuntimeException(sprintf('The "%s" HTTP header is not parseable (%s).', $key, $value));
+            throw new \RuntimeException(\sprintf('The "%s" HTTP header is not parseable (%s).', $key, $value));
         }
 
         return $date;
diff --git a/HeaderUtils.php b/HeaderUtils.php
index 421aefdb8..a7079be9a 100644
--- a/HeaderUtils.php
+++ b/HeaderUtils.php
@@ -165,7 +165,7 @@ public static function unquote(string $s): string
     public static function makeDisposition(string $disposition, string $filename, string $filenameFallback = ''): string
     {
         if (!\in_array($disposition, [self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE])) {
-            throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE));
+            throw new \InvalidArgumentException(\sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE));
         }
 
         if ('' === $filenameFallback) {
diff --git a/InputBag.php b/InputBag.php
index dc5e12756..97bd8b090 100644
--- a/InputBag.php
+++ b/InputBag.php
@@ -29,13 +29,13 @@ final class InputBag extends ParameterBag
     public function get(string $key, mixed $default = null): string|int|float|bool|null
     {
         if (null !== $default && !\is_scalar($default) && !$default instanceof \Stringable) {
-            throw new \InvalidArgumentException(sprintf('Expected a scalar value as a 2nd argument to "%s()", "%s" given.', __METHOD__, get_debug_type($default)));
+            throw new \InvalidArgumentException(\sprintf('Expected a scalar value as a 2nd argument to "%s()", "%s" given.', __METHOD__, get_debug_type($default)));
         }
 
         $value = parent::get($key, $this);
 
         if (null !== $value && $this !== $value && !\is_scalar($value) && !$value instanceof \Stringable) {
-            throw new BadRequestException(sprintf('Input value "%s" contains a non-scalar value.', $key));
+            throw new BadRequestException(\sprintf('Input value "%s" contains a non-scalar value.', $key));
         }
 
         return $this === $value ? $default : $value;
@@ -68,7 +68,7 @@ public function add(array $inputs = []): void
     public function set(string $key, mixed $value): void
     {
         if (null !== $value && !\is_scalar($value) && !\is_array($value) && !$value instanceof \Stringable) {
-            throw new \InvalidArgumentException(sprintf('Expected a scalar, or an array as a 2nd argument to "%s()", "%s" given.', __METHOD__, get_debug_type($value)));
+            throw new \InvalidArgumentException(\sprintf('Expected a scalar, or an array as a 2nd argument to "%s()", "%s" given.', __METHOD__, get_debug_type($value)));
         }
 
         $this->parameters[$key] = $value;
@@ -114,11 +114,11 @@ public function filter(string $key, mixed $default = null, int $filter = \FILTER
         }
 
         if (\is_array($value) && !(($options['flags'] ?? 0) & (\FILTER_REQUIRE_ARRAY | \FILTER_FORCE_ARRAY))) {
-            throw new BadRequestException(sprintf('Input value "%s" contains an array, but "FILTER_REQUIRE_ARRAY" or "FILTER_FORCE_ARRAY" flags were not set.', $key));
+            throw new BadRequestException(\sprintf('Input value "%s" contains an array, but "FILTER_REQUIRE_ARRAY" or "FILTER_FORCE_ARRAY" flags were not set.', $key));
         }
 
         if ((\FILTER_CALLBACK & $filter) && !(($options['options'] ?? null) instanceof \Closure)) {
-            throw new \InvalidArgumentException(sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null)));
+            throw new \InvalidArgumentException(\sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null)));
         }
 
         $options['flags'] ??= 0;
@@ -131,6 +131,6 @@ public function filter(string $key, mixed $default = null, int $filter = \FILTER
             return $value;
         }
 
-        throw new BadRequestException(sprintf('Input value "%s" is invalid and flag "FILTER_NULL_ON_FAILURE" was not set.', $key));
+        throw new BadRequestException(\sprintf('Input value "%s" is invalid and flag "FILTER_NULL_ON_FAILURE" was not set.', $key));
     }
 }
diff --git a/IpUtils.php b/IpUtils.php
index ceab620c2..db2c8efdc 100644
--- a/IpUtils.php
+++ b/IpUtils.php
@@ -102,7 +102,7 @@ public static function checkIp4(string $requestIp, string $ip): bool
             return self::setCacheResult($cacheKey, false);
         }
 
-        return self::setCacheResult($cacheKey, 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, $netmask));
+        return self::setCacheResult($cacheKey, 0 === substr_compare(\sprintf('%032b', ip2long($requestIp)), \sprintf('%032b', ip2long($address)), 0, $netmask));
     }
 
     /**
diff --git a/JsonResponse.php b/JsonResponse.php
index 4571d22c7..cd6f177df 100644
--- a/JsonResponse.php
+++ b/JsonResponse.php
@@ -41,7 +41,7 @@ public function __construct(mixed $data = null, int $status = 200, array $header
         parent::__construct('', $status, $headers);
 
         if ($json && !\is_string($data) && !is_numeric($data) && !\is_callable([$data, '__toString'])) {
-            throw new \TypeError(sprintf('"%s": If $json is set to true, argument $data must be a string or object implementing __toString(), "%s" given.', __METHOD__, get_debug_type($data)));
+            throw new \TypeError(\sprintf('"%s": If $json is set to true, argument $data must be a string or object implementing __toString(), "%s" given.', __METHOD__, get_debug_type($data)));
         }
 
         $data ??= new \ArrayObject();
@@ -173,7 +173,7 @@ protected function update(): static
             // Not using application/javascript for compatibility reasons with older browsers.
             $this->headers->set('Content-Type', 'text/javascript');
 
-            return $this->setContent(sprintf('/**/%s(%s);', $this->callback, $this->data));
+            return $this->setContent(\sprintf('/**/%s(%s);', $this->callback, $this->data));
         }
 
         // Only set the header when there is none or when it equals 'text/javascript' (from a previous update with callback)
diff --git a/ParameterBag.php b/ParameterBag.php
index 760e4f947..35a0f1819 100644
--- a/ParameterBag.php
+++ b/ParameterBag.php
@@ -40,7 +40,7 @@ public function all(?string $key = null): array
         }
 
         if (!\is_array($value = $this->parameters[$key] ?? [])) {
-            throw new BadRequestException(sprintf('Unexpected value for parameter "%s": expecting "array", got "%s".', $key, get_debug_type($value)));
+            throw new BadRequestException(\sprintf('Unexpected value for parameter "%s": expecting "array", got "%s".', $key, get_debug_type($value)));
         }
 
         return $value;
@@ -127,7 +127,7 @@ public function getString(string $key, string $default = ''): string
     {
         $value = $this->get($key, $default);
         if (!\is_scalar($value) && !$value instanceof \Stringable) {
-            throw new UnexpectedValueException(sprintf('Parameter value "%s" cannot be converted to "string".', $key));
+            throw new UnexpectedValueException(\sprintf('Parameter value "%s" cannot be converted to "string".', $key));
         }
 
         return (string) $value;
@@ -172,7 +172,7 @@ public function getEnum(string $key, string $class, ?\BackedEnum $default = null
         try {
             return $class::from($value);
         } catch (\ValueError|\TypeError $e) {
-            throw new UnexpectedValueException(sprintf('Parameter "%s" cannot be converted to enum: %s.', $key, $e->getMessage()), $e->getCode(), $e);
+            throw new UnexpectedValueException(\sprintf('Parameter "%s" cannot be converted to enum: %s.', $key, $e->getMessage()), $e->getCode(), $e);
         }
     }
 
@@ -199,11 +199,11 @@ public function filter(string $key, mixed $default = null, int $filter = \FILTER
         }
 
         if (\is_object($value) && !$value instanceof \Stringable) {
-            throw new UnexpectedValueException(sprintf('Parameter value "%s" cannot be filtered.', $key));
+            throw new UnexpectedValueException(\sprintf('Parameter value "%s" cannot be filtered.', $key));
         }
 
         if ((\FILTER_CALLBACK & $filter) && !(($options['options'] ?? null) instanceof \Closure)) {
-            throw new \InvalidArgumentException(sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null)));
+            throw new \InvalidArgumentException(\sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null)));
         }
 
         $options['flags'] ??= 0;
@@ -216,7 +216,7 @@ public function filter(string $key, mixed $default = null, int $filter = \FILTER
             return $value;
         }
 
-        throw new \UnexpectedValueException(sprintf('Parameter value "%s" is invalid and flag "FILTER_NULL_ON_FAILURE" was not set.', $key));
+        throw new \UnexpectedValueException(\sprintf('Parameter value "%s" is invalid and flag "FILTER_NULL_ON_FAILURE" was not set.', $key));
     }
 
     /**
diff --git a/RedirectResponse.php b/RedirectResponse.php
index 3c2e4b620..b1b1cf3c3 100644
--- a/RedirectResponse.php
+++ b/RedirectResponse.php
@@ -39,7 +39,7 @@ public function __construct(string $url, int $status = 302, array $headers = [])
         $this->setTargetUrl($url);
 
         if (!$this->isRedirect()) {
-            throw new \InvalidArgumentException(sprintf('The HTTP status code is not a redirect ("%s" given).', $status));
+            throw new \InvalidArgumentException(\sprintf('The HTTP status code is not a redirect ("%s" given).', $status));
         }
 
         if (301 == $status && !\array_key_exists('cache-control', array_change_key_case($headers, \CASE_LOWER))) {
@@ -71,7 +71,7 @@ public function setTargetUrl(string $url): static
         $this->targetUrl = $url;
 
         $this->setContent(
-            sprintf('<!DOCTYPE html>
+            \sprintf('<!DOCTYPE html>
 <html>
     <head>
         <meta charset="UTF-8" />
diff --git a/Request.php b/Request.php
index 0dc5a9230..75618e1c6 100644
--- a/Request.php
+++ b/Request.php
@@ -300,7 +300,7 @@ public static function create(string $uri, string $method = 'GET', array $parame
 
         $components = parse_url($uri);
         if (false === $components) {
-            throw new \InvalidArgumentException(sprintf('Malformed URI "%s".', $uri));
+            throw new \InvalidArgumentException(\sprintf('Malformed URI "%s".', $uri));
         }
         if (isset($components['host'])) {
             $server['SERVER_NAME'] = $components['host'];
@@ -472,7 +472,7 @@ public function __toString(): string
         }
 
         return
-            sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL'))."\r\n".
+            \sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL'))."\r\n".
             $this->headers.
             $cookieHeader."\r\n".
             $content;
@@ -567,7 +567,7 @@ public static function getTrustedHeaderSet(): int
      */
     public static function setTrustedHosts(array $hostPatterns): void
     {
-        self::$trustedHostPatterns = array_map(fn ($hostPattern) => sprintf('{%s}i', $hostPattern), $hostPatterns);
+        self::$trustedHostPatterns = array_map(fn ($hostPattern) => \sprintf('{%s}i', $hostPattern), $hostPatterns);
         // we need to reset trusted hosts on trusted host patterns change
         self::$trustedHosts = [];
     }
@@ -1082,7 +1082,7 @@ public function getHost(): string
             }
             $this->isHostValid = false;
 
-            throw new SuspiciousOperationException(sprintf('Invalid Host "%s".', $host));
+            throw new SuspiciousOperationException(\sprintf('Invalid Host "%s".', $host));
         }
 
         if (\count(self::$trustedHostPatterns) > 0) {
@@ -1105,7 +1105,7 @@ public function getHost(): string
             }
             $this->isHostValid = false;
 
-            throw new SuspiciousOperationException(sprintf('Untrusted Host "%s".', $host));
+            throw new SuspiciousOperationException(\sprintf('Untrusted Host "%s".', $host));
         }
 
         return $host;
@@ -1162,7 +1162,7 @@ public function getMethod(): string
         }
 
         if (!preg_match('/^[A-Z]++$/D', $method)) {
-            throw new SuspiciousOperationException(sprintf('Invalid method override "%s".', $method));
+            throw new SuspiciousOperationException(\sprintf('Invalid method override "%s".', $method));
         }
 
         return $this->method = $method;
@@ -1445,7 +1445,7 @@ public function getPayload(): InputBag
         }
 
         if (!\is_array($content)) {
-            throw new JsonException(sprintf('JSON content was expected to decode to an array, "%s" returned.', get_debug_type($content)));
+            throw new JsonException(\sprintf('JSON content was expected to decode to an array, "%s" returned.', get_debug_type($content)));
         }
 
         return new InputBag($content);
@@ -1471,7 +1471,7 @@ public function toArray(): array
         }
 
         if (!\is_array($content)) {
-            throw new JsonException(sprintf('JSON content was expected to decode to an array, "%s" returned.', get_debug_type($content)));
+            throw new JsonException(\sprintf('JSON content was expected to decode to an array, "%s" returned.', get_debug_type($content)));
         }
 
         return $content;
@@ -1937,7 +1937,7 @@ private function getUrlencodedPrefix(string $string, string $prefix): ?string
 
         $len = \strlen($prefix);
 
-        if (preg_match(sprintf('#^(%%[[:xdigit:]]{2}|.){%d}#', $len), $string, $match)) {
+        if (preg_match(\sprintf('#^(%%[[:xdigit:]]{2}|.){%d}#', $len), $string, $match)) {
             return $match[0];
         }
 
@@ -2029,7 +2029,7 @@ private function getTrustedValues(int $type, ?string $ip = null): array
         }
         $this->isForwardedValid = false;
 
-        throw new ConflictingHeadersException(sprintf('The request has both a trusted "%s" header and a trusted "%s" header, conflicting with each other. You should either configure your proxy to remove one of them, or configure your project to distrust the offending one.', self::TRUSTED_HEADERS[self::HEADER_FORWARDED], self::TRUSTED_HEADERS[$type]));
+        throw new ConflictingHeadersException(\sprintf('The request has both a trusted "%s" header and a trusted "%s" header, conflicting with each other. You should either configure your proxy to remove one of them, or configure your project to distrust the offending one.', self::TRUSTED_HEADERS[self::HEADER_FORWARDED], self::TRUSTED_HEADERS[$type]));
     }
 
     private function normalizeAndFilterClientIps(array $clientIps, string $ip): array
diff --git a/Response.php b/Response.php
index 22c09a01f..bf68d2741 100644
--- a/Response.php
+++ b/Response.php
@@ -217,7 +217,7 @@ public function __construct(?string $content = '', int $status = 200, array $hea
     public function __toString(): string
     {
         return
-            sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n".
+            \sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n".
             $this->headers."\r\n".
             $this->getContent();
     }
@@ -365,7 +365,7 @@ public function sendHeaders(?int $statusCode = null): static
         $statusCode ??= $this->statusCode;
 
         // status
-        header(sprintf('HTTP/%s %s %s', $this->version, $statusCode, $this->statusText), true, $statusCode);
+        header(\sprintf('HTTP/%s %s %s', $this->version, $statusCode, $this->statusText), true, $statusCode);
 
         return $this;
     }
@@ -470,7 +470,7 @@ public function setStatusCode(int $code, ?string $text = null): static
     {
         $this->statusCode = $code;
         if ($this->isInvalid()) {
-            throw new \InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $code));
+            throw new \InvalidArgumentException(\sprintf('The HTTP status code "%s" is not valid.', $code));
         }
 
         if (null === $text) {
@@ -973,7 +973,7 @@ public function setEtag(?string $etag, bool $weak = false): static
     public function setCache(array $options): static
     {
         if ($diff = array_diff(array_keys($options), array_keys(self::HTTP_RESPONSE_CACHE_CONTROL_DIRECTIVES))) {
-            throw new \InvalidArgumentException(sprintf('Response does not support the following options: "%s".', implode('", "', $diff)));
+            throw new \InvalidArgumentException(\sprintf('Response does not support the following options: "%s".', implode('", "', $diff)));
         }
 
         if (isset($options['etag'])) {
diff --git a/ResponseHeaderBag.php b/ResponseHeaderBag.php
index c8f08438d..023651efb 100644
--- a/ResponseHeaderBag.php
+++ b/ResponseHeaderBag.php
@@ -195,7 +195,7 @@ public function removeCookie(string $name, ?string $path = '/', ?string $domain
     public function getCookies(string $format = self::COOKIES_FLAT): array
     {
         if (!\in_array($format, [self::COOKIES_FLAT, self::COOKIES_ARRAY])) {
-            throw new \InvalidArgumentException(sprintf('Format "%s" invalid (%s).', $format, implode(', ', [self::COOKIES_FLAT, self::COOKIES_ARRAY])));
+            throw new \InvalidArgumentException(\sprintf('Format "%s" invalid (%s).', $format, implode(', ', [self::COOKIES_FLAT, self::COOKIES_ARRAY])));
         }
 
         if (self::COOKIES_ARRAY === $format) {
diff --git a/Session/SessionUtils.php b/Session/SessionUtils.php
index 504c5848e..57aa565ff 100644
--- a/Session/SessionUtils.php
+++ b/Session/SessionUtils.php
@@ -28,8 +28,8 @@ final class SessionUtils
     public static function popSessionCookie(string $sessionName, #[\SensitiveParameter] string $sessionId): ?string
     {
         $sessionCookie = null;
-        $sessionCookiePrefix = sprintf(' %s=', urlencode($sessionName));
-        $sessionCookieWithId = sprintf('%s%s;', $sessionCookiePrefix, urlencode($sessionId));
+        $sessionCookiePrefix = \sprintf(' %s=', urlencode($sessionName));
+        $sessionCookieWithId = \sprintf('%s%s;', $sessionCookiePrefix, urlencode($sessionId));
         $otherCookies = [];
         foreach (headers_list() as $h) {
             if (0 !== stripos($h, 'Set-Cookie:')) {
diff --git a/Session/Storage/Handler/AbstractSessionHandler.php b/Session/Storage/Handler/AbstractSessionHandler.php
index 288c24232..fd8562377 100644
--- a/Session/Storage/Handler/AbstractSessionHandler.php
+++ b/Session/Storage/Handler/AbstractSessionHandler.php
@@ -32,7 +32,7 @@ public function open(string $savePath, string $sessionName): bool
     {
         $this->sessionName = $sessionName;
         if (!headers_sent() && !\ini_get('session.cache_limiter') && '0' !== \ini_get('session.cache_limiter')) {
-            header(sprintf('Cache-Control: max-age=%d, private, must-revalidate', 60 * (int) \ini_get('session.cache_expire')));
+            header(\sprintf('Cache-Control: max-age=%d, private, must-revalidate', 60 * (int) \ini_get('session.cache_expire')));
         }
 
         return true;
@@ -88,7 +88,7 @@ public function destroy(#[\SensitiveParameter] string $sessionId): bool
     {
         if (!headers_sent() && filter_var(\ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOL)) {
             if (!isset($this->sessionName)) {
-                throw new \LogicException(sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', static::class));
+                throw new \LogicException(\sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', static::class));
             }
             $cookie = SessionUtils::popSessionCookie($this->sessionName, $sessionId);
 
diff --git a/Session/Storage/Handler/IdentityMarshaller.php b/Session/Storage/Handler/IdentityMarshaller.php
index 411a8d1f0..70ac76248 100644
--- a/Session/Storage/Handler/IdentityMarshaller.php
+++ b/Session/Storage/Handler/IdentityMarshaller.php
@@ -22,7 +22,7 @@ public function marshall(array $values, ?array &$failed): array
     {
         foreach ($values as $key => $value) {
             if (!\is_string($value)) {
-                throw new \LogicException(sprintf('%s accepts only string as data.', __METHOD__));
+                throw new \LogicException(\sprintf('%s accepts only string as data.', __METHOD__));
             }
         }
 
diff --git a/Session/Storage/Handler/MemcachedSessionHandler.php b/Session/Storage/Handler/MemcachedSessionHandler.php
index ecee15f37..4b95d8878 100644
--- a/Session/Storage/Handler/MemcachedSessionHandler.php
+++ b/Session/Storage/Handler/MemcachedSessionHandler.php
@@ -45,7 +45,7 @@ public function __construct(
         array $options = [],
     ) {
         if ($diff = array_diff(array_keys($options), ['prefix', 'expiretime', 'ttl'])) {
-            throw new \InvalidArgumentException(sprintf('The following options are not supported "%s".', implode(', ', $diff)));
+            throw new \InvalidArgumentException(\sprintf('The following options are not supported "%s".', implode(', ', $diff)));
         }
 
         $this->ttl = $options['expiretime'] ?? $options['ttl'] ?? null;
diff --git a/Session/Storage/Handler/NativeFileSessionHandler.php b/Session/Storage/Handler/NativeFileSessionHandler.php
index f8c6151a4..284cd869d 100644
--- a/Session/Storage/Handler/NativeFileSessionHandler.php
+++ b/Session/Storage/Handler/NativeFileSessionHandler.php
@@ -34,7 +34,7 @@ public function __construct(?string $savePath = null)
 
         if ($count = substr_count($savePath, ';')) {
             if ($count > 2) {
-                throw new \InvalidArgumentException(sprintf('Invalid argument $savePath \'%s\'.', $savePath));
+                throw new \InvalidArgumentException(\sprintf('Invalid argument $savePath \'%s\'.', $savePath));
             }
 
             // characters after last ';' are the path
@@ -42,7 +42,7 @@ public function __construct(?string $savePath = null)
         }
 
         if ($baseDir && !is_dir($baseDir) && !@mkdir($baseDir, 0777, true) && !is_dir($baseDir)) {
-            throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s".', $baseDir));
+            throw new \RuntimeException(\sprintf('Session Storage was not able to create directory "%s".', $baseDir));
         }
 
         if ($savePath !== \ini_get('session.save_path')) {
diff --git a/Session/Storage/Handler/PdoSessionHandler.php b/Session/Storage/Handler/PdoSessionHandler.php
index aa8ab5690..f08471e9b 100644
--- a/Session/Storage/Handler/PdoSessionHandler.php
+++ b/Session/Storage/Handler/PdoSessionHandler.php
@@ -155,7 +155,7 @@ public function __construct(#[\SensitiveParameter] \PDO|string|null $pdoOrDsn =
     {
         if ($pdoOrDsn instanceof \PDO) {
             if (\PDO::ERRMODE_EXCEPTION !== $pdoOrDsn->getAttribute(\PDO::ATTR_ERRMODE)) {
-                throw new \InvalidArgumentException(sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)).', __CLASS__));
+                throw new \InvalidArgumentException(\sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)).', __CLASS__));
             }
 
             $this->pdo = $pdoOrDsn;
@@ -222,7 +222,7 @@ public function configureSchema(Schema $schema, ?\Closure $isSameDatabase = null
                 $table->addColumn($this->timeCol, Types::INTEGER)->setUnsigned(true)->setNotnull(true);
                 break;
             default:
-                throw new \DomainException(sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver));
+                throw new \DomainException(\sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver));
         }
         $table->setPrimaryKey([$this->idCol]);
         $table->addIndex([$this->lifetimeCol], $this->lifetimeCol.'_idx');
@@ -255,7 +255,7 @@ public function createTable(): void
             'pgsql' => "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)",
             'oci' => "CREATE TABLE $this->table ($this->idCol VARCHAR2(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)",
             'sqlsrv' => "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol VARBINARY(MAX) NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)",
-            default => throw new \DomainException(sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver)),
+            default => throw new \DomainException(\sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver)),
         };
 
         try {
@@ -536,7 +536,7 @@ private function buildDsnFromUrl(#[\SensitiveParameter] string $dsnOrUrl): strin
                 return $dsn;
 
             default:
-                throw new \InvalidArgumentException(sprintf('The scheme "%s" is not supported by the PdoSessionHandler URL configuration. Pass a PDO DSN directly.', $params['scheme']));
+                throw new \InvalidArgumentException(\sprintf('The scheme "%s" is not supported by the PdoSessionHandler URL configuration. Pass a PDO DSN directly.', $params['scheme']));
         }
     }
 
@@ -732,7 +732,7 @@ private function doAdvisoryLock(#[\SensitiveParameter] string $sessionId): \PDOS
             case 'sqlite':
                 throw new \DomainException('SQLite does not support advisory locks.');
             default:
-                throw new \DomainException(sprintf('Advisory locks are currently not implemented for PDO driver "%s".', $this->driver));
+                throw new \DomainException(\sprintf('Advisory locks are currently not implemented for PDO driver "%s".', $this->driver));
         }
     }
 
@@ -774,7 +774,7 @@ private function getSelectSql(): string
                     // we already locked when starting transaction
                     break;
                 default:
-                    throw new \DomainException(sprintf('Transactional locks are currently not implemented for PDO driver "%s".', $this->driver));
+                    throw new \DomainException(\sprintf('Transactional locks are currently not implemented for PDO driver "%s".', $this->driver));
             }
         }
 
diff --git a/Session/Storage/Handler/RedisSessionHandler.php b/Session/Storage/Handler/RedisSessionHandler.php
index b696eee4b..78cd4e7c2 100644
--- a/Session/Storage/Handler/RedisSessionHandler.php
+++ b/Session/Storage/Handler/RedisSessionHandler.php
@@ -44,7 +44,7 @@ public function __construct(
         array $options = [],
     ) {
         if ($diff = array_diff(array_keys($options), ['prefix', 'ttl'])) {
-            throw new \InvalidArgumentException(sprintf('The following options are not supported "%s".', implode(', ', $diff)));
+            throw new \InvalidArgumentException(\sprintf('The following options are not supported "%s".', implode(', ', $diff)));
         }
 
         $this->prefix = $options['prefix'] ?? 'sf_s';
diff --git a/Session/Storage/Handler/SessionHandlerFactory.php b/Session/Storage/Handler/SessionHandlerFactory.php
index 3f1d03267..13af07c21 100644
--- a/Session/Storage/Handler/SessionHandlerFactory.php
+++ b/Session/Storage/Handler/SessionHandlerFactory.php
@@ -49,7 +49,7 @@ public static function createHandler(object|string $connection, array $options =
                 return new PdoSessionHandler($connection);
 
             case !\is_string($connection):
-                throw new \InvalidArgumentException(sprintf('Unsupported Connection: "%s".', get_debug_type($connection)));
+                throw new \InvalidArgumentException(\sprintf('Unsupported Connection: "%s".', get_debug_type($connection)));
             case str_starts_with($connection, 'file://'):
                 $savePath = substr($connection, 7);
 
@@ -90,6 +90,6 @@ public static function createHandler(object|string $connection, array $options =
                 return new PdoSessionHandler($connection, $options);
         }
 
-        throw new \InvalidArgumentException(sprintf('Unsupported Connection: "%s".', $connection));
+        throw new \InvalidArgumentException(\sprintf('Unsupported Connection: "%s".', $connection));
     }
 }
diff --git a/Session/Storage/Handler/StrictSessionHandler.php b/Session/Storage/Handler/StrictSessionHandler.php
index 74a9962c7..0d84eac34 100644
--- a/Session/Storage/Handler/StrictSessionHandler.php
+++ b/Session/Storage/Handler/StrictSessionHandler.php
@@ -24,7 +24,7 @@ public function __construct(
         private \SessionHandlerInterface $handler,
     ) {
         if ($handler instanceof \SessionUpdateTimestampHandlerInterface) {
-            throw new \LogicException(sprintf('"%s" is already an instance of "SessionUpdateTimestampHandlerInterface", you cannot wrap it with "%s".', get_debug_type($handler), self::class));
+            throw new \LogicException(\sprintf('"%s" is already an instance of "SessionUpdateTimestampHandlerInterface", you cannot wrap it with "%s".', get_debug_type($handler), self::class));
         }
     }
 
diff --git a/Session/Storage/MockArraySessionStorage.php b/Session/Storage/MockArraySessionStorage.php
index 8e8e3109b..7869d1be7 100644
--- a/Session/Storage/MockArraySessionStorage.php
+++ b/Session/Storage/MockArraySessionStorage.php
@@ -133,7 +133,7 @@ public function registerBag(SessionBagInterface $bag): void
     public function getBag(string $name): SessionBagInterface
     {
         if (!isset($this->bags[$name])) {
-            throw new \InvalidArgumentException(sprintf('The SessionBagInterface "%s" is not registered.', $name));
+            throw new \InvalidArgumentException(\sprintf('The SessionBagInterface "%s" is not registered.', $name));
         }
 
         if (!$this->started) {
diff --git a/Session/Storage/MockFileSessionStorage.php b/Session/Storage/MockFileSessionStorage.php
index 48dd74dfb..c230c701a 100644
--- a/Session/Storage/MockFileSessionStorage.php
+++ b/Session/Storage/MockFileSessionStorage.php
@@ -35,7 +35,7 @@ public function __construct(?string $savePath = null, string $name = 'MOCKSESSID
         $savePath ??= sys_get_temp_dir();
 
         if (!is_dir($savePath) && !@mkdir($savePath, 0777, true) && !is_dir($savePath)) {
-            throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s".', $savePath));
+            throw new \RuntimeException(\sprintf('Session Storage was not able to create directory "%s".', $savePath));
         }
 
         $this->savePath = $savePath;
diff --git a/Session/Storage/NativeSessionStorage.php b/Session/Storage/NativeSessionStorage.php
index d32292ae0..239064214 100644
--- a/Session/Storage/NativeSessionStorage.php
+++ b/Session/Storage/NativeSessionStorage.php
@@ -113,7 +113,7 @@ public function start(): bool
         }
 
         if (filter_var(\ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOL) && headers_sent($file, $line)) {
-            throw new \RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line));
+            throw new \RuntimeException(\sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line));
         }
 
         $sessionId = $_COOKIE[session_name()] ?? null;
@@ -224,7 +224,7 @@ public function save(): void
         $previousHandler = set_error_handler(function ($type, $msg, $file, $line) use (&$previousHandler) {
             if (\E_WARNING === $type && str_starts_with($msg, 'session_write_close():')) {
                 $handler = $this->saveHandler instanceof SessionHandlerProxy ? $this->saveHandler->getHandler() : $this->saveHandler;
-                $msg = sprintf('session_write_close(): Failed to write session data with "%s" handler', $handler::class);
+                $msg = \sprintf('session_write_close(): Failed to write session data with "%s" handler', $handler::class);
             }
 
             return $previousHandler ? $previousHandler($type, $msg, $file, $line) : false;
@@ -271,7 +271,7 @@ public function registerBag(SessionBagInterface $bag): void
     public function getBag(string $name): SessionBagInterface
     {
         if (!isset($this->bags[$name])) {
-            throw new \InvalidArgumentException(sprintf('The SessionBagInterface "%s" is not registered.', $name));
+            throw new \InvalidArgumentException(\sprintf('The SessionBagInterface "%s" is not registered.', $name));
         }
 
         if (!$this->started && $this->saveHandler->isActive()) {
diff --git a/Test/Constraint/RequestAttributeValueSame.php b/Test/Constraint/RequestAttributeValueSame.php
index fe910f063..c570848fb 100644
--- a/Test/Constraint/RequestAttributeValueSame.php
+++ b/Test/Constraint/RequestAttributeValueSame.php
@@ -24,7 +24,7 @@ public function __construct(
 
     public function toString(): string
     {
-        return sprintf('has attribute "%s" with value "%s"', $this->name, $this->value);
+        return \sprintf('has attribute "%s" with value "%s"', $this->name, $this->value);
     }
 
     /**
diff --git a/Test/Constraint/ResponseCookieValueSame.php b/Test/Constraint/ResponseCookieValueSame.php
index 936496a28..d93a46187 100644
--- a/Test/Constraint/ResponseCookieValueSame.php
+++ b/Test/Constraint/ResponseCookieValueSame.php
@@ -27,14 +27,14 @@ public function __construct(
 
     public function toString(): string
     {
-        $str = sprintf('has cookie "%s"', $this->name);
+        $str = \sprintf('has cookie "%s"', $this->name);
         if ('/' !== $this->path) {
-            $str .= sprintf(' with path "%s"', $this->path);
+            $str .= \sprintf(' with path "%s"', $this->path);
         }
         if ($this->domain) {
-            $str .= sprintf(' for domain "%s"', $this->domain);
+            $str .= \sprintf(' for domain "%s"', $this->domain);
         }
-        $str .= sprintf(' with value "%s"', $this->value);
+        $str .= \sprintf(' with value "%s"', $this->value);
 
         return $str;
     }
diff --git a/Test/Constraint/ResponseHasCookie.php b/Test/Constraint/ResponseHasCookie.php
index aff7d4944..0bc58036f 100644
--- a/Test/Constraint/ResponseHasCookie.php
+++ b/Test/Constraint/ResponseHasCookie.php
@@ -26,12 +26,12 @@ public function __construct(
 
     public function toString(): string
     {
-        $str = sprintf('has cookie "%s"', $this->name);
+        $str = \sprintf('has cookie "%s"', $this->name);
         if ('/' !== $this->path) {
-            $str .= sprintf(' with path "%s"', $this->path);
+            $str .= \sprintf(' with path "%s"', $this->path);
         }
         if ($this->domain) {
-            $str .= sprintf(' for domain "%s"', $this->domain);
+            $str .= \sprintf(' for domain "%s"', $this->domain);
         }
 
         return $str;
diff --git a/Test/Constraint/ResponseHasHeader.php b/Test/Constraint/ResponseHasHeader.php
index 4dbd0c997..52fd3c180 100644
--- a/Test/Constraint/ResponseHasHeader.php
+++ b/Test/Constraint/ResponseHasHeader.php
@@ -23,7 +23,7 @@ public function __construct(
 
     public function toString(): string
     {
-        return sprintf('has header "%s"', $this->headerName);
+        return \sprintf('has header "%s"', $this->headerName);
     }
 
     /**
diff --git a/Test/Constraint/ResponseHeaderLocationSame.php b/Test/Constraint/ResponseHeaderLocationSame.php
index 9286ec715..833ffd9f2 100644
--- a/Test/Constraint/ResponseHeaderLocationSame.php
+++ b/Test/Constraint/ResponseHeaderLocationSame.php
@@ -23,7 +23,7 @@ public function __construct(private Request $request, private string $expectedVa
 
     public function toString(): string
     {
-        return sprintf('has header "Location" matching "%s"', $this->expectedValue);
+        return \sprintf('has header "Location" matching "%s"', $this->expectedValue);
     }
 
     protected function matches($other): bool
@@ -53,7 +53,7 @@ private function toFullUrl(string $url): string
         }
 
         if (str_starts_with($url, '//')) {
-            return sprintf('%s:%s', $this->request->getScheme(), $url);
+            return \sprintf('%s:%s', $this->request->getScheme(), $url);
         }
 
         if (str_starts_with($url, '/')) {
diff --git a/Test/Constraint/ResponseHeaderSame.php b/Test/Constraint/ResponseHeaderSame.php
index af1cb5fbb..f2ae27f5b 100644
--- a/Test/Constraint/ResponseHeaderSame.php
+++ b/Test/Constraint/ResponseHeaderSame.php
@@ -24,7 +24,7 @@ public function __construct(
 
     public function toString(): string
     {
-        return sprintf('has header "%s" with value "%s"', $this->headerName, $this->expectedValue);
+        return \sprintf('has header "%s" with value "%s"', $this->headerName, $this->expectedValue);
     }
 
     /**
diff --git a/Tests/RequestTest.php b/Tests/RequestTest.php
index 89b38ae92..f1902ad8c 100644
--- a/Tests/RequestTest.php
+++ b/Tests/RequestTest.php
@@ -2652,7 +2652,7 @@ public static function preferSafeContentData()
     public function testReservedFlags()
     {
         foreach ((new \ReflectionClass(Request::class))->getConstants() as $constant => $value) {
-            $this->assertNotSame(0b10000000, $value, sprintf('The constant "%s" should not use the reserved value "0b10000000".', $constant));
+            $this->assertNotSame(0b10000000, $value, \sprintf('The constant "%s" should not use the reserved value "0b10000000".', $constant));
         }
     }
 
diff --git a/Tests/ResponseFunctionalTest.php b/Tests/ResponseFunctionalTest.php
index 1b3566a2c..e5c6c2428 100644
--- a/Tests/ResponseFunctionalTest.php
+++ b/Tests/ResponseFunctionalTest.php
@@ -45,9 +45,9 @@ public static function tearDownAfterClass(): void
      */
     public function testCookie($fixture)
     {
-        $result = file_get_contents(sprintf('http://localhost:8054/%s.php', $fixture));
+        $result = file_get_contents(\sprintf('http://localhost:8054/%s.php', $fixture));
         $result = preg_replace_callback('/expires=[^;]++/', fn ($m) => str_replace('-', ' ', $m[0]), $result);
-        $this->assertStringMatchesFormatFile(__DIR__.sprintf('/Fixtures/response-functional/%s.expected', $fixture), $result);
+        $this->assertStringMatchesFormatFile(__DIR__.\sprintf('/Fixtures/response-functional/%s.expected', $fixture), $result);
     }
 
     public static function provideCookie()
diff --git a/Tests/ResponseTest.php b/Tests/ResponseTest.php
index 007842476..491a50fd5 100644
--- a/Tests/ResponseTest.php
+++ b/Tests/ResponseTest.php
@@ -181,7 +181,7 @@ public function testIsNotModifiedEtag()
         $etagTwo = 'randomly_generated_etag_2';
 
         $request = new Request();
-        $request->headers->set('If-None-Match', sprintf('%s, %s, %s', $etagOne, $etagTwo, 'etagThree'));
+        $request->headers->set('If-None-Match', \sprintf('%s, %s, %s', $etagOne, $etagTwo, 'etagThree'));
 
         $response = new Response();
 
@@ -235,7 +235,7 @@ public function testIsNotModifiedLastModifiedAndEtag()
         $etag = 'randomly_generated_etag';
 
         $request = new Request();
-        $request->headers->set('If-None-Match', sprintf('%s, %s', $etag, 'etagThree'));
+        $request->headers->set('If-None-Match', \sprintf('%s, %s', $etag, 'etagThree'));
         $request->headers->set('If-Modified-Since', $modified);
 
         $response = new Response();
@@ -259,7 +259,7 @@ public function testIsNotModifiedIfModifiedSinceAndEtagWithoutLastModified()
         $etag = 'randomly_generated_etag';
 
         $request = new Request();
-        $request->headers->set('If-None-Match', sprintf('%s, %s', $etag, 'etagThree'));
+        $request->headers->set('If-None-Match', \sprintf('%s, %s', $etag, 'etagThree'));
         $request->headers->set('If-Modified-Since', $modified);
 
         $response = new Response();
diff --git a/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php b/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php
index 27fb57da4..361d3b15d 100644
--- a/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php
+++ b/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php
@@ -45,10 +45,10 @@ public function testSession($fixture)
     {
         $context = ['http' => ['header' => "Cookie: sid=123abc\r\n"]];
         $context = stream_context_create($context);
-        $result = file_get_contents(sprintf('http://localhost:8053/%s.php', $fixture), false, $context);
+        $result = file_get_contents(\sprintf('http://localhost:8053/%s.php', $fixture), false, $context);
         $result = preg_replace_callback('/expires=[^;]++/', fn ($m) => str_replace('-', ' ', $m[0]), $result);
 
-        $this->assertStringEqualsFile(__DIR__.sprintf('/Fixtures/%s.expected', $fixture), $result);
+        $this->assertStringEqualsFile(__DIR__.\sprintf('/Fixtures/%s.expected', $fixture), $result);
     }
 
     public static function provideSession()
diff --git a/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php b/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php
index 0c6de4c8d..0027b91fd 100644
--- a/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php
+++ b/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php
@@ -51,7 +51,7 @@ protected function setUp(): void
         try {
             $this->manager->executeCommand(self::DABASE_NAME, new Command(['ping' => 1]));
         } catch (ConnectionException $e) {
-            $this->markTestSkipped(sprintf('MongoDB Server "%s" not running: %s', getenv('MONGODB_HOST'), $e->getMessage()));
+            $this->markTestSkipped(\sprintf('MongoDB Server "%s" not running: %s', getenv('MONGODB_HOST'), $e->getMessage()));
         }
 
         $this->options = [
diff --git a/UriSigner.php b/UriSigner.php
index f9fba605b..dd7443489 100644
--- a/UriSigner.php
+++ b/UriSigner.php
@@ -55,7 +55,7 @@ public function sign(string $uri/*, \DateTimeInterface|\DateInterval|int|null $e
         }
 
         if (null !== $expiration && !$expiration instanceof \DateTimeInterface && !$expiration instanceof \DateInterval && !\is_int($expiration)) {
-            throw new \TypeError(sprintf('The second argument of %s() must be an instance of %s or %s, an integer or null (%s given).', __METHOD__, \DateTimeInterface::class, \DateInterval::class, get_debug_type($expiration)));
+            throw new \TypeError(\sprintf('The second argument of %s() must be an instance of %s or %s, an integer or null (%s given).', __METHOD__, \DateTimeInterface::class, \DateInterval::class, get_debug_type($expiration)));
         }
 
         $url = parse_url($uri);
@@ -66,11 +66,11 @@ public function sign(string $uri/*, \DateTimeInterface|\DateInterval|int|null $e
         }
 
         if (isset($params[$this->hashParameter])) {
-            throw new LogicException(sprintf('URI query parameter conflict: parameter name "%s" is reserved.', $this->hashParameter));
+            throw new LogicException(\sprintf('URI query parameter conflict: parameter name "%s" is reserved.', $this->hashParameter));
         }
 
         if (isset($params[$this->expirationParameter])) {
-            throw new LogicException(sprintf('URI query parameter conflict: parameter name "%s" is reserved.', $this->expirationParameter));
+            throw new LogicException(\sprintf('URI query parameter conflict: parameter name "%s" is reserved.', $this->expirationParameter));
         }
 
         if (null !== $expiration) {

From 1d115494660af99ab328feda260f72f48231afdc Mon Sep 17 00:00:00 2001
From: Dariusz Ruminski <dariusz.ruminski@gmail.com>
Date: Sun, 16 Jun 2024 17:17:26 +0200
Subject: [PATCH 05/48] chore: CS fixes

---
 Tests/ParameterBagTest.php                     |  2 +-
 Tests/Session/Flash/AutoExpireFlashBagTest.php | 12 ++++++------
 Tests/Session/Flash/FlashBagTest.php           |  4 ++--
 3 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/Tests/ParameterBagTest.php b/Tests/ParameterBagTest.php
index c73d70507..9a34b5f99 100644
--- a/Tests/ParameterBagTest.php
+++ b/Tests/ParameterBagTest.php
@@ -251,7 +251,7 @@ public function testFilter()
             'dec' => '256',
             'hex' => '0x100',
             'array' => ['bang'],
-            ]);
+        ]);
 
         $this->assertEmpty($bag->filter('nokey'), '->filter() should return empty by default if no key is found');
 
diff --git a/Tests/Session/Flash/AutoExpireFlashBagTest.php b/Tests/Session/Flash/AutoExpireFlashBagTest.php
index 6a6510a57..0b418c006 100644
--- a/Tests/Session/Flash/AutoExpireFlashBagTest.php
+++ b/Tests/Session/Flash/AutoExpireFlashBagTest.php
@@ -46,9 +46,9 @@ public function testInitialize()
         $bag->initialize($array);
         $this->assertEquals(['A previous flash message'], $bag->peek('notice'));
         $array = ['new' => [
-                'notice' => ['Something else'],
-                'error' => ['a'],
-            ]];
+            'notice' => ['Something else'],
+            'error' => ['a'],
+        ]];
         $bag->initialize($array);
         $this->assertEquals(['Something else'], $bag->peek('notice'));
         $this->assertEquals(['a'], $bag->peek('error'));
@@ -106,13 +106,13 @@ public function testPeekAll()
         $this->assertEquals([
             'notice' => 'Foo',
             'error' => 'Bar',
-            ], $this->bag->peekAll()
+        ], $this->bag->peekAll()
         );
 
         $this->assertEquals([
             'notice' => 'Foo',
             'error' => 'Bar',
-            ], $this->bag->peekAll()
+        ], $this->bag->peekAll()
         );
     }
 
@@ -137,7 +137,7 @@ public function testAll()
         $this->bag->set('error', 'Bar');
         $this->assertEquals([
             'notice' => ['A previous flash message'],
-            ], $this->bag->all()
+        ], $this->bag->all()
         );
 
         $this->assertEquals([], $this->bag->all());
diff --git a/Tests/Session/Flash/FlashBagTest.php b/Tests/Session/Flash/FlashBagTest.php
index 59e3f1f0e..8163ba769 100644
--- a/Tests/Session/Flash/FlashBagTest.php
+++ b/Tests/Session/Flash/FlashBagTest.php
@@ -141,14 +141,14 @@ public function testPeekAll()
         $this->assertEquals([
             'notice' => ['Foo'],
             'error' => ['Bar'],
-            ], $this->bag->peekAll()
+        ], $this->bag->peekAll()
         );
         $this->assertTrue($this->bag->has('notice'));
         $this->assertTrue($this->bag->has('error'));
         $this->assertEquals([
             'notice' => ['Foo'],
             'error' => ['Bar'],
-            ], $this->bag->peekAll()
+        ], $this->bag->peekAll()
         );
     }
 }

From 3c12220e5ab96e91dd7f03aaae6bc27458962246 Mon Sep 17 00:00:00 2001
From: Fabien Potencier <fabien@potencier.org>
Date: Sat, 6 Jul 2024 09:57:16 +0200
Subject: [PATCH 06/48] Update .gitattributes

---
 .gitattributes | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/.gitattributes b/.gitattributes
index 84c7add05..14c3c3594 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,4 +1,3 @@
 /Tests export-ignore
 /phpunit.xml.dist export-ignore
-/.gitattributes export-ignore
-/.gitignore export-ignore
+/.git* export-ignore

From 1a9b7d56cde7b234f5fa1230684be1bf655292f3 Mon Sep 17 00:00:00 2001
From: Alexandre Daubois <alex.daubois@gmail.com>
Date: Thu, 18 Jul 2024 11:54:36 +0200
Subject: [PATCH 07/48] [HttpFoundation] Remove always false condition in
 `BinaryFileResponse`

---
 BinaryFileResponse.php           |  4 ++--
 Tests/BinaryFileResponseTest.php | 10 ++++++++++
 2 files changed, 12 insertions(+), 2 deletions(-)

diff --git a/BinaryFileResponse.php b/BinaryFileResponse.php
index 535aa6068..cb03ea6d0 100644
--- a/BinaryFileResponse.php
+++ b/BinaryFileResponse.php
@@ -110,8 +110,8 @@ public function getFile(): File
      */
     public function setChunkSize(int $chunkSize): static
     {
-        if ($chunkSize < 1 || $chunkSize > \PHP_INT_MAX) {
-            throw new \LogicException('The chunk size of a BinaryFileResponse cannot be less than 1 or greater than PHP_INT_MAX.');
+        if ($chunkSize < 1) {
+            throw new \InvalidArgumentException('The chunk size of a BinaryFileResponse cannot be less than 1.');
         }
 
         $this->chunkSize = $chunkSize;
diff --git a/Tests/BinaryFileResponseTest.php b/Tests/BinaryFileResponseTest.php
index b58276cf7..bad3329d2 100644
--- a/Tests/BinaryFileResponseTest.php
+++ b/Tests/BinaryFileResponseTest.php
@@ -455,4 +455,14 @@ public function testCreateFromTemporaryFile()
         $string = ob_get_clean();
         $this->assertSame('foo,bar', $string);
     }
+
+    public function testSetChunkSizeTooSmall()
+    {
+        $response = new BinaryFileResponse(__DIR__.'/File/Fixtures/test.gif');
+
+        $this->expectException(\InvalidArgumentException::class);
+        $this->expectExceptionMessage('The chunk size of a BinaryFileResponse cannot be less than 1.');
+
+        $response->setChunkSize(0);
+    }
 }

From 7e85301bf46e912e3450316d3db49a5755af0be5 Mon Sep 17 00:00:00 2001
From: Alexandre Daubois <alex.daubois@gmail.com>
Date: Fri, 19 Jul 2024 10:48:20 +0200
Subject: [PATCH 08/48] [HttpFoundation][HttpKernel] Remove dead code and
 useless casts

---
 BinaryFileResponse.php                      | 2 +-
 File/File.php                               | 3 +--
 HeaderBag.php                               | 2 +-
 Request.php                                 | 2 +-
 Test/Constraint/ResponseCookieValueSame.php | 3 +--
 5 files changed, 5 insertions(+), 7 deletions(-)

diff --git a/BinaryFileResponse.php b/BinaryFileResponse.php
index cb03ea6d0..bf4e446e3 100644
--- a/BinaryFileResponse.php
+++ b/BinaryFileResponse.php
@@ -70,7 +70,7 @@ public function setFile(\SplFileInfo|string $file, ?string $contentDisposition =
             if ($file instanceof \SplFileInfo) {
                 $file = new File($file->getPathname(), !$isTemporaryFile);
             } else {
-                $file = new File((string) $file);
+                $file = new File($file);
             }
         }
 
diff --git a/File/File.php b/File/File.php
index c369ecbfb..2194c178a 100644
--- a/File/File.php
+++ b/File/File.php
@@ -134,8 +134,7 @@ protected function getName(string $name): string
     {
         $originalName = str_replace('\\', '/', $name);
         $pos = strrpos($originalName, '/');
-        $originalName = false === $pos ? $originalName : substr($originalName, $pos + 1);
 
-        return $originalName;
+        return false === $pos ? $originalName : substr($originalName, $pos + 1);
     }
 }
diff --git a/HeaderBag.php b/HeaderBag.php
index 12a2e1bdb..c2ede560b 100644
--- a/HeaderBag.php
+++ b/HeaderBag.php
@@ -118,7 +118,7 @@ public function get(string $key, ?string $default = null): ?string
             return null;
         }
 
-        return (string) $headers[0];
+        return $headers[0];
     }
 
     /**
diff --git a/Request.php b/Request.php
index 75618e1c6..3e20823de 100644
--- a/Request.php
+++ b/Request.php
@@ -1878,7 +1878,7 @@ protected function preparePathInfo(): string
         }
 
         $pathInfo = substr($requestUri, \strlen($baseUrl));
-        if (false === $pathInfo || '' === $pathInfo) {
+        if ('' === $pathInfo) {
             // If substr() returns false then PATH_INFO is set to an empty string
             return '/';
         }
diff --git a/Test/Constraint/ResponseCookieValueSame.php b/Test/Constraint/ResponseCookieValueSame.php
index d93a46187..dbf9add6f 100644
--- a/Test/Constraint/ResponseCookieValueSame.php
+++ b/Test/Constraint/ResponseCookieValueSame.php
@@ -34,9 +34,8 @@ public function toString(): string
         if ($this->domain) {
             $str .= \sprintf(' for domain "%s"', $this->domain);
         }
-        $str .= \sprintf(' with value "%s"', $this->value);
 
-        return $str;
+        return $str.\sprintf(' with value "%s"', $this->value);
     }
 
     /**

From 33500b2572f1d684d4447112ea87f5e679950c37 Mon Sep 17 00:00:00 2001
From: Alexander Schranz <alexander@sulu.io>
Date: Fri, 2 Aug 2024 10:47:01 +0200
Subject: [PATCH 09/48] [HttpFoundation] Add `$requests` parameter to
 `RequestStack` constructor

---
 CHANGELOG.md               |  5 +++++
 RequestStack.php           | 10 ++++++++++
 Tests/RequestStackTest.php |  7 +++++++
 3 files changed, 22 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 003470567..64c2d38f2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,11 @@
 CHANGELOG
 =========
 
+7.2
+---
+
+ * Add optional `$requests` argument to `RequestStack::__construct()`
+
 7.1
 ---
 
diff --git a/RequestStack.php b/RequestStack.php
index ac8263e91..c358ea68c 100644
--- a/RequestStack.php
+++ b/RequestStack.php
@@ -26,6 +26,16 @@ class RequestStack
      */
     private array $requests = [];
 
+    /**
+     * @param Request[] $requests
+     */
+    public function __construct(array $requests = [])
+    {
+        foreach ($requests as $request) {
+            $this->push($request);
+        }
+    }
+
     /**
      * Pushes a Request on the stack.
      *
diff --git a/Tests/RequestStackTest.php b/Tests/RequestStackTest.php
index 2b26ce5c6..6fba27589 100644
--- a/Tests/RequestStackTest.php
+++ b/Tests/RequestStackTest.php
@@ -17,6 +17,13 @@
 
 class RequestStackTest extends TestCase
 {
+    public function testConstruct()
+    {
+        $request = Request::create('/foo');
+        $requestStack = new RequestStack([$request]);
+        $this->assertSame($request, $requestStack->getCurrentRequest());
+    }
+
     public function testGetCurrentRequest()
     {
         $requestStack = new RequestStack();

From 79d451132e00b4c230398d5f7825fe6981ab1c1a Mon Sep 17 00:00:00 2001
From: Alexandre Daubois <alex.daubois@gmail.com>
Date: Mon, 5 Aug 2024 09:12:25 +0200
Subject: [PATCH 10/48] Fix multiple CS errors

---
 Tests/RateLimiter/AbstractRequestRateLimiterTest.php | 1 -
 Tests/Session/Storage/Proxy/AbstractProxyTest.php    | 1 -
 2 files changed, 2 deletions(-)

diff --git a/Tests/RateLimiter/AbstractRequestRateLimiterTest.php b/Tests/RateLimiter/AbstractRequestRateLimiterTest.php
index 26f2fac90..087d7aeae 100644
--- a/Tests/RateLimiter/AbstractRequestRateLimiterTest.php
+++ b/Tests/RateLimiter/AbstractRequestRateLimiterTest.php
@@ -14,7 +14,6 @@
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\RateLimiter\LimiterInterface;
-use Symfony\Component\RateLimiter\Policy\NoLimiter;
 use Symfony\Component\RateLimiter\RateLimit;
 
 class AbstractRequestRateLimiterTest extends TestCase
diff --git a/Tests/Session/Storage/Proxy/AbstractProxyTest.php b/Tests/Session/Storage/Proxy/AbstractProxyTest.php
index bb459bb9f..8d04830a7 100644
--- a/Tests/Session/Storage/Proxy/AbstractProxyTest.php
+++ b/Tests/Session/Storage/Proxy/AbstractProxyTest.php
@@ -11,7 +11,6 @@
 
 namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Proxy;
 
-use PHPUnit\Framework\MockObject\MockObject;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
 use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;

From cc18d66ec3ec568e26291209e32993bf6d61f72a Mon Sep 17 00:00:00 2001
From: Roy de Vos Burchart <bonroyage@users.noreply.github.com>
Date: Thu, 1 Aug 2024 17:21:17 +0200
Subject: [PATCH 11/48] Code style change in `@PER-CS2.0` affecting `@Symfony`
 (parentheses for anonymous classes)

---
 Tests/InputBagTest.php                            | 4 ++--
 Tests/JsonResponseTest.php                        | 2 +-
 Tests/ParameterBagTest.php                        | 2 +-
 Tests/RequestTest.php                             | 2 +-
 Tests/Session/Storage/Proxy/AbstractProxyTest.php | 2 +-
 Tests/StreamedJsonResponseTest.php                | 6 +++---
 6 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/Tests/InputBagTest.php b/Tests/InputBagTest.php
index 01df837b2..36eb9ead5 100644
--- a/Tests/InputBagTest.php
+++ b/Tests/InputBagTest.php
@@ -20,7 +20,7 @@ class InputBagTest extends TestCase
 {
     public function testGet()
     {
-        $bag = new InputBag(['foo' => 'bar', 'null' => null, 'int' => 1, 'float' => 1.0, 'bool' => false, 'stringable' => new class() implements \Stringable {
+        $bag = new InputBag(['foo' => 'bar', 'null' => null, 'int' => 1, 'float' => 1.0, 'bool' => false, 'stringable' => new class implements \Stringable {
             public function __toString(): string
             {
                 return 'strval';
@@ -58,7 +58,7 @@ public function testGetBooleanError()
 
     public function testGetString()
     {
-        $bag = new InputBag(['integer' => 123, 'bool_true' => true, 'bool_false' => false, 'string' => 'abc', 'stringable' => new class() implements \Stringable {
+        $bag = new InputBag(['integer' => 123, 'bool_true' => true, 'bool_false' => false, 'string' => 'abc', 'stringable' => new class implements \Stringable {
             public function __toString(): string
             {
                 return 'strval';
diff --git a/Tests/JsonResponseTest.php b/Tests/JsonResponseTest.php
index 2058280c5..67a038279 100644
--- a/Tests/JsonResponseTest.php
+++ b/Tests/JsonResponseTest.php
@@ -171,7 +171,7 @@ public function testConstructorWithNullAsDataThrowsAnUnexpectedValueException()
 
     public function testConstructorWithObjectWithToStringMethod()
     {
-        $class = new class() {
+        $class = new class {
             public function __toString(): string
             {
                 return '{}';
diff --git a/Tests/ParameterBagTest.php b/Tests/ParameterBagTest.php
index 9a34b5f99..ac2b4da5a 100644
--- a/Tests/ParameterBagTest.php
+++ b/Tests/ParameterBagTest.php
@@ -206,7 +206,7 @@ public function testGetIntExceptionWithInvalid()
 
     public function testGetString()
     {
-        $bag = new ParameterBag(['integer' => 123, 'bool_true' => true, 'bool_false' => false, 'string' => 'abc', 'stringable' => new class() implements \Stringable {
+        $bag = new ParameterBag(['integer' => 123, 'bool_true' => true, 'bool_false' => false, 'string' => 'abc', 'stringable' => new class implements \Stringable {
             public function __toString(): string
             {
                 return 'strval';
diff --git a/Tests/RequestTest.php b/Tests/RequestTest.php
index f1902ad8c..6f3543322 100644
--- a/Tests/RequestTest.php
+++ b/Tests/RequestTest.php
@@ -2213,7 +2213,7 @@ public function testFactory()
 
     public function testFactoryCallable()
     {
-        $requestFactory = new class() {
+        $requestFactory = new class {
             public function createRequest(): Request
             {
                 return new NewRequest();
diff --git a/Tests/Session/Storage/Proxy/AbstractProxyTest.php b/Tests/Session/Storage/Proxy/AbstractProxyTest.php
index bb459bb9f..1fcd9fd53 100644
--- a/Tests/Session/Storage/Proxy/AbstractProxyTest.php
+++ b/Tests/Session/Storage/Proxy/AbstractProxyTest.php
@@ -27,7 +27,7 @@ class AbstractProxyTest extends TestCase
 
     protected function setUp(): void
     {
-        $this->proxy = new class() extends AbstractProxy {};
+        $this->proxy = new class extends AbstractProxy {};
     }
 
     public function testGetSaveHandlerName()
diff --git a/Tests/StreamedJsonResponseTest.php b/Tests/StreamedJsonResponseTest.php
index db76cd3ae..3d94aa078 100644
--- a/Tests/StreamedJsonResponseTest.php
+++ b/Tests/StreamedJsonResponseTest.php
@@ -132,14 +132,14 @@ public function testResponseOtherTraversable()
     {
         $arrayObject = new \ArrayObject(['__symfony_json__' => '__symfony_json__']);
 
-        $iteratorAggregate = new class() implements \IteratorAggregate {
+        $iteratorAggregate = new class implements \IteratorAggregate {
             public function getIterator(): \Traversable
             {
                 return new \ArrayIterator(['__symfony_json__']);
             }
         };
 
-        $jsonSerializable = new class() implements \IteratorAggregate, \JsonSerializable {
+        $jsonSerializable = new class implements \IteratorAggregate, \JsonSerializable {
             public function getIterator(): \Traversable
             {
                 return new \ArrayIterator(['This should be ignored']);
@@ -187,7 +187,7 @@ public function testResponseStatusCode()
 
     public function testPlaceholderAsObjectStructure()
     {
-        $object = new class() {
+        $object = new class {
             public $__symfony_json__ = 'foo';
             public $bar = '__symfony_json__';
         };

From 79d0023c23d92d49c43be47c726e92366b4d3a81 Mon Sep 17 00:00:00 2001
From: "Alexander M. Turek" <me@derrabus.de>
Date: Tue, 13 Aug 2024 14:32:31 +0200
Subject: [PATCH 12/48] [httpFoundation] Use typed property

---
 Request.php | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/Request.php b/Request.php
index 3e20823de..c17d1d10c 100644
--- a/Request.php
+++ b/Request.php
@@ -193,8 +193,7 @@ class Request
         self::HEADER_X_FORWARDED_PREFIX => 'X_FORWARDED_PREFIX',
     ];
 
-    /** @var bool */
-    private $isIisRewrite = false;
+    private bool $isIisRewrite = false;
 
     /**
      * @param array                $query      The GET parameters

From 1579e3dfac42cf04201154041945750effd3bb58 Mon Sep 17 00:00:00 2001
From: Alexandre Daubois <alex.daubois@gmail.com>
Date: Tue, 20 Aug 2024 10:02:53 +0200
Subject: [PATCH 13/48] [HttpFoundation] Add optional `$v4Bytes` and `$v6Bytes`
 parameters to `IpUtils::anonymize()`

---
 CHANGELOG.md          |  3 +-
 IpUtils.php           | 34 +++++++++++++++++++----
 Tests/IpUtilsTest.php | 64 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 94 insertions(+), 7 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 64c2d38f2..ce1b33939 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,7 +4,8 @@ CHANGELOG
 7.2
 ---
 
- * Add optional `$requests` argument to `RequestStack::__construct()`
+ * Add optional `$requests` parameter to `RequestStack::__construct()`
+ * Add optional `$v4Bytes` and `$v6Bytes` parameters to `IpUtils::anonymize()`
 
 7.1
 ---
diff --git a/IpUtils.php b/IpUtils.php
index db2c8efdc..5e1e29c95 100644
--- a/IpUtils.php
+++ b/IpUtils.php
@@ -178,25 +178,47 @@ public static function checkIp6(string $requestIp, string $ip): bool
     /**
      * Anonymizes an IP/IPv6.
      *
-     * Removes the last byte for v4 and the last 8 bytes for v6 IPs
+     * Removes the last bytes of IPv4 and IPv6 addresses (1 byte for IPv4 and 8 bytes for IPv6 by default).
+     *
+     * @param int<0, 4>  $v4Bytes
+     * @param int<0, 16> $v6Bytes
      */
-    public static function anonymize(string $ip): string
+    public static function anonymize(string $ip/* , int $v4Bytes = 1, int $v6Bytes = 8 */): string
     {
+        $v4Bytes = 1 < \func_num_args() ? func_get_arg(1) : 1;
+        $v6Bytes = 2 < \func_num_args() ? func_get_arg(2) : 8;
+
+        if ($v4Bytes < 0 || $v6Bytes < 0) {
+            throw new \InvalidArgumentException('Cannot anonymize less than 0 bytes.');
+        }
+
+        if ($v4Bytes > 4 || $v6Bytes > 16) {
+            throw new \InvalidArgumentException('Cannot anonymize more than 4 bytes for IPv4 and 16 bytes for IPv6.');
+        }
+
         $wrappedIPv6 = false;
         if (str_starts_with($ip, '[') && str_ends_with($ip, ']')) {
             $wrappedIPv6 = true;
             $ip = substr($ip, 1, -1);
         }
 
+        $mappedIpV4MaskGenerator = function (string $mask, int $bytesToAnonymize) {
+            $mask .= str_repeat('ff', 4 - $bytesToAnonymize);
+            $mask .= str_repeat('00', $bytesToAnonymize);
+
+            return '::'.implode(':', str_split($mask, 4));
+        };
+
         $packedAddress = inet_pton($ip);
         if (4 === \strlen($packedAddress)) {
-            $mask = '255.255.255.0';
+            $mask = rtrim(str_repeat('255.', 4 - $v4Bytes).str_repeat('0.', $v4Bytes), '.');
         } elseif ($ip === inet_ntop($packedAddress & inet_pton('::ffff:ffff:ffff'))) {
-            $mask = '::ffff:ffff:ff00';
+            $mask = $mappedIpV4MaskGenerator('ffff', $v4Bytes);
         } elseif ($ip === inet_ntop($packedAddress & inet_pton('::ffff:ffff'))) {
-            $mask = '::ffff:ff00';
+            $mask = $mappedIpV4MaskGenerator('', $v4Bytes);
         } else {
-            $mask = 'ffff:ffff:ffff:ffff:0000:0000:0000:0000';
+            $mask = str_repeat('ff', 16 - $v6Bytes).str_repeat('00', $v6Bytes);
+            $mask = implode(':', str_split($mask, 4));
         }
         $ip = inet_ntop($packedAddress & inet_pton($mask));
 
diff --git a/Tests/IpUtilsTest.php b/Tests/IpUtilsTest.php
index ce93c69e9..95044106b 100644
--- a/Tests/IpUtilsTest.php
+++ b/Tests/IpUtilsTest.php
@@ -150,6 +150,70 @@ public static function anonymizedIpData()
         ];
     }
 
+    /**
+     * @dataProvider anonymizedIpDataWithBytes
+     */
+    public function testAnonymizeWithBytes($ip, $expected, $bytesForV4, $bytesForV6)
+    {
+        $this->assertSame($expected, IpUtils::anonymize($ip, $bytesForV4, $bytesForV6));
+    }
+
+    public static function anonymizedIpDataWithBytes(): array
+    {
+        return [
+            ['192.168.1.1', '192.168.0.0', 2, 8],
+            ['192.168.1.1', '192.0.0.0', 3, 8],
+            ['192.168.1.1', '0.0.0.0', 4, 8],
+            ['1.2.3.4', '1.2.3.0', 1, 8],
+            ['1.2.3.4', '1.2.3.4', 0, 8],
+            ['2a01:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0:396e:4789:8e99:890f', 1, 0],
+            ['2a01:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0:396e:4789::', 1, 4],
+            ['2a01:198:603:10:396e:4789:8e99:890f', '2a01:198:603:10:396e:4700::', 1, 5],
+            ['2a01:198:603:10:396e:4789:8e99:890f', '2a00::', 1, 15],
+            ['2a01:198:603:10:396e:4789:8e99:890f', '::', 1, 16],
+            ['::1', '::', 1, 1],
+            ['0:0:0:0:0:0:0:1', '::', 1, 1],
+            ['1:0:0:0:0:0:0:1', '1::', 1, 1],
+            ['0:0:603:50:396e:4789:8e99:0001', '0:0:603::', 1, 10],
+            ['[0:0:603:50:396e:4789:8e99:0001]', '[::603:50:396e:4789:8e00:0]', 1, 3],
+            ['[2a01:198::3]', '[2a01:198::]', 1, 2],
+            ['::ffff:123.234.235.236', '::ffff:123.234.235.0', 1, 8], // IPv4-mapped IPv6 addresses
+            ['::123.234.235.236', '::123.234.0.0', 2, 8], // deprecated IPv4-compatible IPv6 address
+        ];
+    }
+
+    public function testAnonymizeV4WithNegativeBytes()
+    {
+        $this->expectException(\InvalidArgumentException::class);
+        $this->expectExceptionMessage('Cannot anonymize less than 0 bytes.');
+
+        IpUtils::anonymize('anything', -1, 8);
+    }
+
+    public function testAnonymizeV6WithNegativeBytes()
+    {
+        $this->expectException(\InvalidArgumentException::class);
+        $this->expectExceptionMessage('Cannot anonymize less than 0 bytes.');
+
+        IpUtils::anonymize('anything', 1, -1);
+    }
+
+    public function testAnonymizeV4WithTooManyBytes()
+    {
+        $this->expectException(\InvalidArgumentException::class);
+        $this->expectExceptionMessage('Cannot anonymize more than 4 bytes for IPv4 and 16 bytes for IPv6.');
+
+        IpUtils::anonymize('anything', 5, 8);
+    }
+
+    public function testAnonymizeV6WithTooManyBytes()
+    {
+        $this->expectException(\InvalidArgumentException::class);
+        $this->expectExceptionMessage('Cannot anonymize more than 4 bytes for IPv4 and 16 bytes for IPv6.');
+
+        IpUtils::anonymize('anything', 1, 17);
+    }
+
     /**
      * @dataProvider getIp4SubnetMaskZeroData
      */

From 8359de79c85278958535311a7718a00b81827c7f Mon Sep 17 00:00:00 2001
From: Fabien Potencier <fabien@potencier.org>
Date: Mon, 26 Aug 2024 17:35:30 +0200
Subject: [PATCH 14/48] Use Stringable whenever possible

---
 JsonResponse.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/JsonResponse.php b/JsonResponse.php
index cd6f177df..187173b68 100644
--- a/JsonResponse.php
+++ b/JsonResponse.php
@@ -40,7 +40,7 @@ public function __construct(mixed $data = null, int $status = 200, array $header
     {
         parent::__construct('', $status, $headers);
 
-        if ($json && !\is_string($data) && !is_numeric($data) && !\is_callable([$data, '__toString'])) {
+        if ($json && !\is_string($data) && !is_numeric($data) && !$data instanceof \Stringable) {
             throw new \TypeError(\sprintf('"%s": If $json is set to true, argument $data must be a string or object implementing __toString(), "%s" given.', __METHOD__, get_debug_type($data)));
         }
 

From 981886d01290d8ed642c4b42ba25c5a2c5440377 Mon Sep 17 00:00:00 2001
From: Nicolas Grekas <nicolas.grekas@gmail.com>
Date: Tue, 3 Sep 2024 10:21:02 +0200
Subject: [PATCH 15/48] [HttpFoundation] Add `PRIVATE_SUBNETS` as a shortcut
 for private IP address ranges to `Request::setTrustedProxies()`

---
 CHANGELOG.md          |  1 +
 Request.php           | 24 +++++++++++++++---------
 Tests/RequestTest.php | 31 +++++++++++++++++++++----------
 3 files changed, 37 insertions(+), 19 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index ce1b33939..c3814fddd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@ CHANGELOG
 
  * Add optional `$requests` parameter to `RequestStack::__construct()`
  * Add optional `$v4Bytes` and `$v6Bytes` parameters to `IpUtils::anonymize()`
+ * Add `PRIVATE_SUBNETS` as a shortcut for private IP address ranges to `Request::setTrustedProxies()`
 
 7.1
 ---
diff --git a/Request.php b/Request.php
index c17d1d10c..6c670cb08 100644
--- a/Request.php
+++ b/Request.php
@@ -520,20 +520,26 @@ public function overrideGlobals(): void
      *
      * You should only list the reverse proxies that you manage directly.
      *
-     * @param array $proxies          A list of trusted proxies, the string 'REMOTE_ADDR' will be replaced with $_SERVER['REMOTE_ADDR']
-     * @param int   $trustedHeaderSet A bit field of Request::HEADER_*, to set which headers to trust from your proxies
+     * @param array                          $proxies          A list of trusted proxies, the string 'REMOTE_ADDR' will be replaced with $_SERVER['REMOTE_ADDR'] and 'PRIVATE_SUBNETS' by IpUtils::PRIVATE_SUBNETS
+     * @param int-mask-of<Request::HEADER_*> $trustedHeaderSet A bit field to set which headers to trust from your proxies
      */
     public static function setTrustedProxies(array $proxies, int $trustedHeaderSet): void
     {
-        self::$trustedProxies = array_reduce($proxies, function ($proxies, $proxy) {
-            if ('REMOTE_ADDR' !== $proxy) {
-                $proxies[] = $proxy;
-            } elseif (isset($_SERVER['REMOTE_ADDR'])) {
-                $proxies[] = $_SERVER['REMOTE_ADDR'];
+        if (false !== $i = array_search('REMOTE_ADDR', $proxies, true)) {
+            if (isset($_SERVER['REMOTE_ADDR'])) {
+                $proxies[$i] = $_SERVER['REMOTE_ADDR'];
+            } else {
+                unset($proxies[$i]);
+                $proxies = array_values($proxies);
             }
+        }
+
+        if (false !== ($i = array_search('PRIVATE_SUBNETS', $proxies, true)) || false !== ($i = array_search('private_ranges', $proxies, true))) {
+            unset($proxies[$i]);
+            $proxies = array_merge($proxies, IpUtils::PRIVATE_SUBNETS);
+        }
 
-            return $proxies;
-        }, []);
+        self::$trustedProxies = $proxies;
         self::$trustedHeaderSet = $trustedHeaderSet;
     }
 
diff --git a/Tests/RequestTest.php b/Tests/RequestTest.php
index 6f3543322..e23a88dde 100644
--- a/Tests/RequestTest.php
+++ b/Tests/RequestTest.php
@@ -16,6 +16,7 @@
 use Symfony\Component\HttpFoundation\Exception\JsonException;
 use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
 use Symfony\Component\HttpFoundation\InputBag;
+use Symfony\Component\HttpFoundation\IpUtils;
 use Symfony\Component\HttpFoundation\ParameterBag;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Session\Session;
@@ -2564,6 +2565,26 @@ public function testTrustedProxiesRemoteAddr($serverRemoteAddr, $trustedProxies,
         $this->assertSame($result, Request::getTrustedProxies());
     }
 
+    public static function trustedProxiesRemoteAddr()
+    {
+        return [
+            ['1.1.1.1', ['REMOTE_ADDR'], ['1.1.1.1']],
+            ['1.1.1.1', ['REMOTE_ADDR', '2.2.2.2'], ['1.1.1.1', '2.2.2.2']],
+            [null, ['REMOTE_ADDR'], []],
+            [null, ['REMOTE_ADDR', '2.2.2.2'], ['2.2.2.2']],
+        ];
+    }
+
+    /**
+     * @testWith ["PRIVATE_SUBNETS"]
+     *           ["private_ranges"]
+     */
+    public function testTrustedProxiesPrivateSubnets(string $key)
+    {
+        Request::setTrustedProxies([$key], Request::HEADER_X_FORWARDED_FOR);
+        $this->assertSame(IpUtils::PRIVATE_SUBNETS, Request::getTrustedProxies());
+    }
+
     public function testTrustedValuesCache()
     {
         $request = Request::create('http://example.com/');
@@ -2581,16 +2602,6 @@ public function testTrustedValuesCache()
         $this->assertFalse($request->isSecure());
     }
 
-    public static function trustedProxiesRemoteAddr()
-    {
-        return [
-            ['1.1.1.1', ['REMOTE_ADDR'], ['1.1.1.1']],
-            ['1.1.1.1', ['REMOTE_ADDR', '2.2.2.2'], ['1.1.1.1', '2.2.2.2']],
-            [null, ['REMOTE_ADDR'], []],
-            [null, ['REMOTE_ADDR', '2.2.2.2'], ['2.2.2.2']],
-        ];
-    }
-
     /**
      * @dataProvider preferSafeContentData
      */

From a9d64431e0518e0139e197bceeafe3ac0f5a0209 Mon Sep 17 00:00:00 2001
From: Christian Flothmann <christian.flothmann@qossmic.com>
Date: Tue, 3 Sep 2024 15:19:19 +0200
Subject: [PATCH 16/48] stop using TestCase::iniSet()

---
 Tests/UriSignerTest.php | 33 +++++++++++++++++++--------------
 1 file changed, 19 insertions(+), 14 deletions(-)

diff --git a/Tests/UriSignerTest.php b/Tests/UriSignerTest.php
index e39c0a292..949e34760 100644
--- a/Tests/UriSignerTest.php
+++ b/Tests/UriSignerTest.php
@@ -64,20 +64,25 @@ public function testCheck()
 
     public function testCheckWithDifferentArgSeparator()
     {
-        $this->iniSet('arg_separator.output', '&amp;');
-        $signer = new UriSigner('foobar');
-
-        $this->assertSame(
-            'http://example.com/foo?_hash=rIOcC%2FF3DoEGo%2FvnESjSp7uU9zA9S%2F%2BOLhxgMexoPUM%3D&baz=bay&foo=bar',
-            $signer->sign('http://example.com/foo?foo=bar&baz=bay')
-        );
-        $this->assertTrue($signer->check($signer->sign('http://example.com/foo?foo=bar&baz=bay')));
-
-        $this->assertSame(
-            'http://example.com/foo?_expiration=2145916800&_hash=xLhnPMzV3KqqHaaUffBUJvtRDAZ4%2FZ9Y8Sw%2BgmS%2B82Q%3D&baz=bay&foo=bar',
-            $signer->sign('http://example.com/foo?foo=bar&baz=bay', new \DateTimeImmutable('2038-01-01 00:00:00', new \DateTimeZone('UTC')))
-        );
-        $this->assertTrue($signer->check($signer->sign('http://example.com/foo?foo=bar&baz=bay', new \DateTimeImmutable('2099-01-01 00:00:00'))));
+        $oldArgSeparatorOutputValue = ini_set('arg_separator.output', '&amp;');
+
+        try {
+            $signer = new UriSigner('foobar');
+
+            $this->assertSame(
+                'http://example.com/foo?_hash=rIOcC%2FF3DoEGo%2FvnESjSp7uU9zA9S%2F%2BOLhxgMexoPUM%3D&baz=bay&foo=bar',
+                $signer->sign('http://example.com/foo?foo=bar&baz=bay')
+            );
+            $this->assertTrue($signer->check($signer->sign('http://example.com/foo?foo=bar&baz=bay')));
+
+            $this->assertSame(
+                'http://example.com/foo?_expiration=2145916800&_hash=xLhnPMzV3KqqHaaUffBUJvtRDAZ4%2FZ9Y8Sw%2BgmS%2B82Q%3D&baz=bay&foo=bar',
+                $signer->sign('http://example.com/foo?foo=bar&baz=bay', new \DateTimeImmutable('2038-01-01 00:00:00', new \DateTimeZone('UTC')))
+            );
+            $this->assertTrue($signer->check($signer->sign('http://example.com/foo?foo=bar&baz=bay', new \DateTimeImmutable('2099-01-01 00:00:00'))));
+        } finally {
+            ini_set('arg_separator.output', $oldArgSeparatorOutputValue);
+        }
     }
 
     public function testCheckWithRequest()

From fe6d48291699a78df5346c72095dec94f60ca682 Mon Sep 17 00:00:00 2001
From: Christian Flothmann <christian.flothmann@qossmic.com>
Date: Thu, 5 Sep 2024 08:55:30 +0200
Subject: [PATCH 17/48] no longer use the internal TestFailure class

---
 .../RequestAttributeValueSameTest.php         | 12 +++-------
 .../ResponseCookieValueSameTest.php           | 12 +++-------
 .../Constraint/ResponseFormatSameTest.php     | 23 +++++--------------
 .../Test/Constraint/ResponseHasCookieTest.php | 12 +++-------
 .../Test/Constraint/ResponseHasHeaderTest.php | 12 +++-------
 .../Constraint/ResponseHeaderSameTest.php     | 12 +++-------
 .../Constraint/ResponseIsRedirectedTest.php   | 19 ++++-----------
 .../Constraint/ResponseIsSuccessfulTest.php   | 19 ++++-----------
 .../ResponseIsUnprocessableTest.php           | 19 ++++-----------
 .../Constraint/ResponseStatusCodeSameTest.php | 18 ++++-----------
 10 files changed, 41 insertions(+), 117 deletions(-)

diff --git a/Tests/Test/Constraint/RequestAttributeValueSameTest.php b/Tests/Test/Constraint/RequestAttributeValueSameTest.php
index c7ee239f3..27dbd895e 100644
--- a/Tests/Test/Constraint/RequestAttributeValueSameTest.php
+++ b/Tests/Test/Constraint/RequestAttributeValueSameTest.php
@@ -13,7 +13,6 @@
 
 use PHPUnit\Framework\ExpectationFailedException;
 use PHPUnit\Framework\TestCase;
-use PHPUnit\Framework\TestFailure;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Test\Constraint\RequestAttributeValueSame;
 
@@ -28,14 +27,9 @@ public function testConstraint()
         $constraint = new RequestAttributeValueSame('bar', 'foo');
         $this->assertFalse($constraint->evaluate($request, '', true));
 
-        try {
-            $constraint->evaluate($request);
-        } catch (ExpectationFailedException $e) {
-            $this->assertEquals("Failed asserting that the Request has attribute \"bar\" with value \"foo\".\n", TestFailure::exceptionToString($e));
+        $this->expectException(ExpectationFailedException::class);
+        $this->expectExceptionMessage('Failed asserting that the Request has attribute "bar" with value "foo".');
 
-            return;
-        }
-
-        $this->fail();
+        $constraint->evaluate($request);
     }
 }
diff --git a/Tests/Test/Constraint/ResponseCookieValueSameTest.php b/Tests/Test/Constraint/ResponseCookieValueSameTest.php
index 1b68b20bd..e3ad61000 100644
--- a/Tests/Test/Constraint/ResponseCookieValueSameTest.php
+++ b/Tests/Test/Constraint/ResponseCookieValueSameTest.php
@@ -13,7 +13,6 @@
 
 use PHPUnit\Framework\ExpectationFailedException;
 use PHPUnit\Framework\TestCase;
-use PHPUnit\Framework\TestFailure;
 use Symfony\Component\HttpFoundation\Cookie;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpFoundation\Test\Constraint\ResponseCookieValueSame;
@@ -31,15 +30,10 @@ public function testConstraint()
         $constraint = new ResponseCookieValueSame('foo', 'babar', '/path');
         $this->assertFalse($constraint->evaluate($response, '', true));
 
-        try {
-            $constraint->evaluate($response);
-        } catch (ExpectationFailedException $e) {
-            $this->assertEquals("Failed asserting that the Response has cookie \"foo\" with path \"/path\" with value \"babar\".\n", TestFailure::exceptionToString($e));
+        $this->expectException(ExpectationFailedException::class);
+        $this->expectExceptionMessage('Failed asserting that the Response has cookie "foo" with path "/path" with value "babar".');
 
-            return;
-        }
-
-        $this->fail();
+        $constraint->evaluate($response);
     }
 
     public function testCookieWithNullValueIsComparedAsEmptyString()
diff --git a/Tests/Test/Constraint/ResponseFormatSameTest.php b/Tests/Test/Constraint/ResponseFormatSameTest.php
index aed9285f2..9ac6a1cf5 100644
--- a/Tests/Test/Constraint/ResponseFormatSameTest.php
+++ b/Tests/Test/Constraint/ResponseFormatSameTest.php
@@ -13,7 +13,6 @@
 
 use PHPUnit\Framework\ExpectationFailedException;
 use PHPUnit\Framework\TestCase;
-use PHPUnit\Framework\TestFailure;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpFoundation\Test\Constraint\ResponseFormatSame;
@@ -32,15 +31,10 @@ public function testConstraint()
         $this->assertTrue($constraint->evaluate(new Response('', 200, ['Content-Type' => 'application/vnd.myformat']), '', true));
         $this->assertFalse($constraint->evaluate(new Response(), '', true));
 
-        try {
-            $constraint->evaluate(new Response('', 200, ['Content-Type' => 'application/ld+json']));
-        } catch (ExpectationFailedException $e) {
-            $this->assertStringContainsString("Failed asserting that the Response format is custom.\nHTTP/1.0 200 OK", TestFailure::exceptionToString($e));
+        $this->expectException(ExpectationFailedException::class);
+        $this->expectExceptionMessage("Failed asserting that the Response format is custom.\nHTTP/1.0 200 OK");
 
-            return;
-        }
-
-        $this->fail();
+        $constraint->evaluate(new Response('', 200, ['Content-Type' => 'application/ld+json']));
     }
 
     public function testNullFormat()
@@ -48,14 +42,9 @@ public function testNullFormat()
         $constraint = new ResponseFormatSame(new Request(), null);
         $this->assertTrue($constraint->evaluate(new Response(), '', true));
 
-        try {
-            $constraint->evaluate(new Response('', 200, ['Content-Type' => 'application/ld+json']));
-        } catch (ExpectationFailedException $e) {
-            $this->assertStringContainsString("Failed asserting that the Response format is null.\nHTTP/1.0 200 OK", TestFailure::exceptionToString($e));
-
-            return;
-        }
+        $this->expectException(ExpectationFailedException::class);
+        $this->expectExceptionMessage("Failed asserting that the Response format is null.\nHTTP/1.0 200 OK");
 
-        $this->fail();
+        $constraint->evaluate(new Response('', 200, ['Content-Type' => 'application/ld+json']));
     }
 }
diff --git a/Tests/Test/Constraint/ResponseHasCookieTest.php b/Tests/Test/Constraint/ResponseHasCookieTest.php
index ba1d7f38a..380502aba 100644
--- a/Tests/Test/Constraint/ResponseHasCookieTest.php
+++ b/Tests/Test/Constraint/ResponseHasCookieTest.php
@@ -13,7 +13,6 @@
 
 use PHPUnit\Framework\ExpectationFailedException;
 use PHPUnit\Framework\TestCase;
-use PHPUnit\Framework\TestFailure;
 use Symfony\Component\HttpFoundation\Cookie;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpFoundation\Test\Constraint\ResponseHasCookie;
@@ -29,14 +28,9 @@ public function testConstraint()
         $constraint = new ResponseHasCookie('bar');
         $this->assertFalse($constraint->evaluate($response, '', true));
 
-        try {
-            $constraint->evaluate($response);
-        } catch (ExpectationFailedException $e) {
-            $this->assertEquals("Failed asserting that the Response has cookie \"bar\".\n", TestFailure::exceptionToString($e));
+        $this->expectException(ExpectationFailedException::class);
+        $this->expectExceptionMessage('Failed asserting that the Response has cookie "bar".');
 
-            return;
-        }
-
-        $this->fail();
+        $constraint->evaluate($response);
     }
 }
diff --git a/Tests/Test/Constraint/ResponseHasHeaderTest.php b/Tests/Test/Constraint/ResponseHasHeaderTest.php
index 9a8fc2560..5959451eb 100644
--- a/Tests/Test/Constraint/ResponseHasHeaderTest.php
+++ b/Tests/Test/Constraint/ResponseHasHeaderTest.php
@@ -13,7 +13,6 @@
 
 use PHPUnit\Framework\ExpectationFailedException;
 use PHPUnit\Framework\TestCase;
-use PHPUnit\Framework\TestFailure;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpFoundation\Test\Constraint\ResponseHasHeader;
 
@@ -26,14 +25,9 @@ public function testConstraint()
         $constraint = new ResponseHasHeader('X-Date');
         $this->assertFalse($constraint->evaluate(new Response(), '', true));
 
-        try {
-            $constraint->evaluate(new Response());
-        } catch (ExpectationFailedException $e) {
-            $this->assertEquals("Failed asserting that the Response has header \"X-Date\".\n", TestFailure::exceptionToString($e));
+        $this->expectException(ExpectationFailedException::class);
+        $this->expectExceptionMessage('Failed asserting that the Response has header "X-Date".');
 
-            return;
-        }
-
-        $this->fail();
+        $constraint->evaluate(new Response());
     }
 }
diff --git a/Tests/Test/Constraint/ResponseHeaderSameTest.php b/Tests/Test/Constraint/ResponseHeaderSameTest.php
index 17a3f2a99..7c8b4a80b 100644
--- a/Tests/Test/Constraint/ResponseHeaderSameTest.php
+++ b/Tests/Test/Constraint/ResponseHeaderSameTest.php
@@ -13,7 +13,6 @@
 
 use PHPUnit\Framework\ExpectationFailedException;
 use PHPUnit\Framework\TestCase;
-use PHPUnit\Framework\TestFailure;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpFoundation\Test\Constraint\ResponseHeaderSame;
 
@@ -26,14 +25,9 @@ public function testConstraint()
         $constraint = new ResponseHeaderSame('Cache-Control', 'public');
         $this->assertFalse($constraint->evaluate(new Response(), '', true));
 
-        try {
-            $constraint->evaluate(new Response());
-        } catch (ExpectationFailedException $e) {
-            $this->assertEquals("Failed asserting that the Response has header \"Cache-Control\" with value \"public\".\n", TestFailure::exceptionToString($e));
+        $this->expectException(ExpectationFailedException::class);
+        $this->expectExceptionMessage('Failed asserting that the Response has header "Cache-Control" with value "public".');
 
-            return;
-        }
-
-        $this->fail();
+        $constraint->evaluate(new Response());
     }
 }
diff --git a/Tests/Test/Constraint/ResponseIsRedirectedTest.php b/Tests/Test/Constraint/ResponseIsRedirectedTest.php
index c4df3bc93..7e011ca10 100644
--- a/Tests/Test/Constraint/ResponseIsRedirectedTest.php
+++ b/Tests/Test/Constraint/ResponseIsRedirectedTest.php
@@ -13,7 +13,6 @@
 
 use PHPUnit\Framework\ExpectationFailedException;
 use PHPUnit\Framework\TestCase;
-use PHPUnit\Framework\TestFailure;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpFoundation\Test\Constraint\ResponseIsRedirected;
 
@@ -26,17 +25,10 @@ public function testConstraint()
         $this->assertTrue($constraint->evaluate(new Response('', 301), '', true));
         $this->assertFalse($constraint->evaluate(new Response(), '', true));
 
-        try {
-            $constraint->evaluate(new Response('Body content'));
-        } catch (ExpectationFailedException $e) {
-            $exceptionMessage = TestFailure::exceptionToString($e);
-            $this->assertStringContainsString("Failed asserting that the Response is redirected.\nHTTP/1.0 200 OK", $exceptionMessage);
-            $this->assertStringContainsString('Body content', $exceptionMessage);
+        $this->expectException(ExpectationFailedException::class);
+        $this->expectExceptionMessageMatches('/Failed asserting that the Response is redirected.\nHTTP\/1.0 200 OK.+Body content/s');
 
-            return;
-        }
-
-        $this->fail();
+        $constraint->evaluate(new Response('Body content'));
     }
 
     public function testReducedVerbosity()
@@ -45,9 +37,8 @@ public function testReducedVerbosity()
         try {
             $constraint->evaluate(new Response('Body content'));
         } catch (ExpectationFailedException $e) {
-            $exceptionMessage = TestFailure::exceptionToString($e);
-            $this->assertStringContainsString("Failed asserting that the Response is redirected.\nHTTP/1.0 200 OK", $exceptionMessage);
-            $this->assertStringNotContainsString('Body content', $exceptionMessage);
+            $this->assertStringContainsString("Failed asserting that the Response is redirected.\nHTTP/1.0 200 OK", $e->getMessage());
+            $this->assertStringNotContainsString('Body content', $e->getMessage());
 
             return;
         }
diff --git a/Tests/Test/Constraint/ResponseIsSuccessfulTest.php b/Tests/Test/Constraint/ResponseIsSuccessfulTest.php
index 9ecafae7e..18e1c5e30 100644
--- a/Tests/Test/Constraint/ResponseIsSuccessfulTest.php
+++ b/Tests/Test/Constraint/ResponseIsSuccessfulTest.php
@@ -13,7 +13,6 @@
 
 use PHPUnit\Framework\ExpectationFailedException;
 use PHPUnit\Framework\TestCase;
-use PHPUnit\Framework\TestFailure;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpFoundation\Test\Constraint\ResponseIsSuccessful;
 
@@ -26,17 +25,10 @@ public function testConstraint()
         $this->assertTrue($constraint->evaluate(new Response(), '', true));
         $this->assertFalse($constraint->evaluate(new Response('', 404), '', true));
 
-        try {
-            $constraint->evaluate(new Response('Response body', 404));
-        } catch (ExpectationFailedException $e) {
-            $exceptionMessage = TestFailure::exceptionToString($e);
-            $this->assertStringContainsString("Failed asserting that the Response is successful.\nHTTP/1.0 404 Not Found", $exceptionMessage);
-            $this->assertStringContainsString('Response body', $exceptionMessage);
+        $this->expectException(ExpectationFailedException::class);
+        $this->expectExceptionMessageMatches('/Failed asserting that the Response is successful.\nHTTP\/1.0 404 Not Found.+Response body/s');
 
-            return;
-        }
-
-        $this->fail();
+        $constraint->evaluate(new Response('Response body', 404));
     }
 
     public function testReducedVerbosity()
@@ -46,9 +38,8 @@ public function testReducedVerbosity()
         try {
             $constraint->evaluate(new Response('Response body', 404));
         } catch (ExpectationFailedException $e) {
-            $exceptionMessage = TestFailure::exceptionToString($e);
-            $this->assertStringContainsString("Failed asserting that the Response is successful.\nHTTP/1.0 404 Not Found", $exceptionMessage);
-            $this->assertStringNotContainsString('Response body', $exceptionMessage);
+            $this->assertStringContainsString("Failed asserting that the Response is successful.\nHTTP/1.0 404 Not Found", $e->getMessage());
+            $this->assertStringNotContainsString('Response body', $e->getMessage());
 
             return;
         }
diff --git a/Tests/Test/Constraint/ResponseIsUnprocessableTest.php b/Tests/Test/Constraint/ResponseIsUnprocessableTest.php
index a142ec4b0..38454c1d0 100644
--- a/Tests/Test/Constraint/ResponseIsUnprocessableTest.php
+++ b/Tests/Test/Constraint/ResponseIsUnprocessableTest.php
@@ -13,7 +13,6 @@
 
 use PHPUnit\Framework\ExpectationFailedException;
 use PHPUnit\Framework\TestCase;
-use PHPUnit\Framework\TestFailure;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpFoundation\Test\Constraint\ResponseIsUnprocessable;
 
@@ -26,17 +25,10 @@ public function testConstraint()
         $this->assertTrue($constraint->evaluate(new Response('', 422), '', true));
         $this->assertFalse($constraint->evaluate(new Response(), '', true));
 
-        try {
-            $constraint->evaluate(new Response('Response body'));
-        } catch (ExpectationFailedException $e) {
-            $exceptionMessage = TestFailure::exceptionToString($e);
-            $this->assertStringContainsString("Failed asserting that the Response is unprocessable.\nHTTP/1.0 200 OK", $exceptionMessage);
-            $this->assertStringContainsString('Response body', $exceptionMessage);
+        $this->expectException(ExpectationFailedException::class);
+        $this->expectExceptionMessageMatches('/Failed asserting that the Response is unprocessable.\nHTTP\/1.0 200 OK.+Response body/s');
 
-            return;
-        }
-
-        $this->fail();
+        $constraint->evaluate(new Response('Response body'));
     }
 
     public function testReducedVerbosity()
@@ -46,9 +38,8 @@ public function testReducedVerbosity()
         try {
             $constraint->evaluate(new Response('Response body'));
         } catch (ExpectationFailedException $e) {
-            $exceptionMessage = TestFailure::exceptionToString($e);
-            $this->assertStringContainsString("Failed asserting that the Response is unprocessable.\nHTTP/1.0 200 OK", $exceptionMessage);
-            $this->assertStringNotContainsString('Response body', $exceptionMessage);
+            $this->assertStringContainsString("Failed asserting that the Response is unprocessable.\nHTTP/1.0 200 OK", $e->getMessage());
+            $this->assertStringNotContainsString('Response body', $e->getMessage());
 
             return;
         }
diff --git a/Tests/Test/Constraint/ResponseStatusCodeSameTest.php b/Tests/Test/Constraint/ResponseStatusCodeSameTest.php
index df840f1a8..f4e216018 100644
--- a/Tests/Test/Constraint/ResponseStatusCodeSameTest.php
+++ b/Tests/Test/Constraint/ResponseStatusCodeSameTest.php
@@ -13,7 +13,6 @@
 
 use PHPUnit\Framework\ExpectationFailedException;
 use PHPUnit\Framework\TestCase;
-use PHPUnit\Framework\TestFailure;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpFoundation\Test\Constraint\ResponseStatusCodeSame;
 
@@ -28,17 +27,11 @@ public function testConstraint()
         $this->assertTrue($constraint->evaluate(new Response('', 404), '', true));
 
         $constraint = new ResponseStatusCodeSame(200);
-        try {
-            $constraint->evaluate(new Response('Response body', 404));
-        } catch (ExpectationFailedException $e) {
-            $exceptionMessage = TestFailure::exceptionToString($e);
-            $this->assertStringContainsString("Failed asserting that the Response status code is 200.\nHTTP/1.0 404 Not Found", TestFailure::exceptionToString($e));
-            $this->assertStringContainsString('Response body', $exceptionMessage);
 
-            return;
-        }
+        $this->expectException(ExpectationFailedException::class);
+        $this->expectExceptionMessageMatches('/Failed asserting that the Response status code is 200.\nHTTP\/1.0 404 Not Found.+Response body/s');
 
-        $this->fail();
+        $constraint->evaluate(new Response('Response body', 404));
     }
 
     public function testReducedVerbosity()
@@ -48,9 +41,8 @@ public function testReducedVerbosity()
         try {
             $constraint->evaluate(new Response('Response body', 404));
         } catch (ExpectationFailedException $e) {
-            $exceptionMessage = TestFailure::exceptionToString($e);
-            $this->assertStringContainsString("Failed asserting that the Response status code is 200.\nHTTP/1.0 404 Not Found", TestFailure::exceptionToString($e));
-            $this->assertStringNotContainsString('Response body', $exceptionMessage);
+            $this->assertStringContainsString("Failed asserting that the Response status code is 200.\nHTTP/1.0 404 Not Found", $e->getMessage());
+            $this->assertStringNotContainsString('Response body', $e->getMessage());
 
             return;
         }

From 393f43f33e75540dacf8d73d529214f604ff830f Mon Sep 17 00:00:00 2001
From: Alexandre Daubois <alex.daubois@gmail.com>
Date: Thu, 12 Sep 2024 19:32:01 +0200
Subject: [PATCH 18/48] [HttpFoundation] Deprecate passing `referer_check`,
 `use_only_cookies`, `use_trans_sid`, `trans_sid_hosts` and `trans_sid_tags`
 options to `NativeSessionStorage`

---
 CHANGELOG.md                                  |  1 +
 Session/Storage/NativeSessionStorage.php      | 14 ++++++----
 .../Storage/Handler/Fixtures/common.inc       |  1 -
 .../Storage/NativeSessionStorageTest.php      | 27 +++++++++++++++++++
 composer.json                                 |  1 +
 5 files changed, 38 insertions(+), 6 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c3814fddd..6fe867ddf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ CHANGELOG
  * Add optional `$requests` parameter to `RequestStack::__construct()`
  * Add optional `$v4Bytes` and `$v6Bytes` parameters to `IpUtils::anonymize()`
  * Add `PRIVATE_SUBNETS` as a shortcut for private IP address ranges to `Request::setTrustedProxies()`
+ * Deprecate passing `referer_check`, `use_only_cookies`, `use_trans_sid`, `trans_sid_hosts` and `trans_sid_tags` options to `NativeSessionStorage`
 
 7.1
 ---
diff --git a/Session/Storage/NativeSessionStorage.php b/Session/Storage/NativeSessionStorage.php
index 239064214..0794fd269 100644
--- a/Session/Storage/NativeSessionStorage.php
+++ b/Session/Storage/NativeSessionStorage.php
@@ -62,16 +62,16 @@ class NativeSessionStorage implements SessionStorageInterface
      * gc_probability, "1"
      * lazy_write, "1"
      * name, "PHPSESSID"
-     * referer_check, ""
+     * referer_check, "" (deprecated since Symfony 7.2, to be removed in Symfony 8.0)
      * serialize_handler, "php"
      * use_strict_mode, "1"
      * use_cookies, "1"
-     * use_only_cookies, "1"
-     * use_trans_sid, "0"
+     * use_only_cookies, "1" (deprecated since Symfony 7.2, to be removed in Symfony 8.0)
+     * use_trans_sid, "0" (deprecated since Symfony 7.2, to be removed in Symfony 8.0)
      * sid_length, "32"
      * sid_bits_per_character, "5"
-     * trans_sid_hosts, $_SERVER['HTTP_HOST']
-     * trans_sid_tags, "a=href,area=href,frame=src,form="
+     * trans_sid_hosts, $_SERVER['HTTP_HOST'] (deprecated since Symfony 7.2, to be removed in Symfony 8.0)
+     * trans_sid_tags, "a=href,area=href,frame=src,form=" (deprecated since Symfony 7.2, to be removed in Symfony 8.0)
      */
     public function __construct(array $options = [], AbstractProxy|\SessionHandlerInterface|null $handler = null, ?MetadataBag $metaBag = null)
     {
@@ -328,6 +328,10 @@ public function setOptions(array $options): void
         ]);
 
         foreach ($options as $key => $value) {
+            if (\in_array($key, ['referer_check', 'use_only_cookies', 'use_trans_sid', 'trans_sid_hosts', 'trans_sid_tags'], true)) {
+                trigger_deprecation('symfony/http-foundation', '7.2', 'NativeSessionStorage\'s "%s" option is deprecated and will be ignored in Symfony 8.0.', $key);
+            }
+
             if (isset($validOptions[$key])) {
                 if ('cookie_secure' === $key && 'auto' === $value) {
                     continue;
diff --git a/Tests/Session/Storage/Handler/Fixtures/common.inc b/Tests/Session/Storage/Handler/Fixtures/common.inc
index 5f48d42cb..7aaedf7f8 100644
--- a/Tests/Session/Storage/Handler/Fixtures/common.inc
+++ b/Tests/Session/Storage/Handler/Fixtures/common.inc
@@ -28,7 +28,6 @@ ini_set('session.cookie_domain', '');
 ini_set('session.cookie_secure', '');
 ini_set('session.cookie_httponly', '');
 ini_set('session.use_cookies', 1);
-ini_set('session.use_only_cookies', 1);
 ini_set('session.cache_expire', 180);
 ini_set('session.cookie_path', '/');
 ini_set('session.cookie_domain', '');
diff --git a/Tests/Session/Storage/NativeSessionStorageTest.php b/Tests/Session/Storage/NativeSessionStorageTest.php
index a0d54deb7..a7189a37b 100644
--- a/Tests/Session/Storage/NativeSessionStorageTest.php
+++ b/Tests/Session/Storage/NativeSessionStorageTest.php
@@ -12,6 +12,7 @@
 namespace Symfony\Component\HttpFoundation\Tests\Session\Storage;
 
 use PHPUnit\Framework\TestCase;
+use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
 use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
 use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
 use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler;
@@ -32,6 +33,8 @@
  */
 class NativeSessionStorageTest extends TestCase
 {
+    use ExpectDeprecationTrait;
+
     private string $savePath;
 
     private $initialSessionSaveHandler;
@@ -215,10 +218,14 @@ public function testCacheExpireOption()
     }
 
     /**
+     * @group legacy
+     *
      * The test must only be removed when the "session.trans_sid_tags" option is removed from PHP or when the "trans_sid_tags" option is no longer supported by the native session storage.
      */
     public function testTransSidTagsOption()
     {
+        $this->expectDeprecation('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "trans_sid_tags" option is deprecated and will be ignored in Symfony 8.0.');
+
         $previousErrorHandler = set_error_handler(function ($errno, $errstr) use (&$previousErrorHandler) {
             if ('ini_set(): Usage of session.trans_sid_tags INI setting is deprecated' !== $errstr) {
                 return $previousErrorHandler ? $previousErrorHandler(...\func_get_args()) : false;
@@ -357,4 +364,24 @@ public function testSaveHandlesNullSessionGracefully()
 
         $this->addToAssertionCount(1);
     }
+
+    /**
+     * @group legacy
+     */
+    public function testPassingDeprecatedOptions()
+    {
+        $this->expectDeprecation('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "referer_check" option is deprecated and will be ignored in Symfony 8.0.');
+        $this->expectDeprecation('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "use_only_cookies" option is deprecated and will be ignored in Symfony 8.0.');
+        $this->expectDeprecation('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "use_trans_sid" option is deprecated and will be ignored in Symfony 8.0.');
+        $this->expectDeprecation('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "trans_sid_hosts" option is deprecated and will be ignored in Symfony 8.0.');
+        $this->expectDeprecation('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "trans_sid_tags" option is deprecated and will be ignored in Symfony 8.0.');
+
+        $this->getStorage([
+            'referer_check' => 'foo',
+            'use_only_cookies' => 'foo',
+            'use_trans_sid' => 'foo',
+            'trans_sid_hosts' => 'foo',
+            'trans_sid_tags' => 'foo',
+        ]);
+    }
 }
diff --git a/composer.json b/composer.json
index 6e88fc15b..45c13cc56 100644
--- a/composer.json
+++ b/composer.json
@@ -17,6 +17,7 @@
     ],
     "require": {
         "php": ">=8.2",
+        "symfony/deprecation-contracts": "^2.5|^3.0",
         "symfony/polyfill-mbstring": "~1.1",
         "symfony/polyfill-php83": "^1.27"
     },

From 54fec5006bb6c5aa81ab7b0095abf55cdce0a787 Mon Sep 17 00:00:00 2001
From: Alexandre Daubois <alex.daubois@gmail.com>
Date: Tue, 23 Jul 2024 09:29:37 +0200
Subject: [PATCH 19/48] [FrameworkBundle] Deprecate `session.sid_length` and
 `session.sid_bits_per_character` config options

---
 CHANGELOG.md                                       |  2 +-
 Session/Storage/NativeSessionStorage.php           | 12 +++++++-----
 Tests/Session/Storage/NativeSessionStorageTest.php |  4 ++++
 3 files changed, 12 insertions(+), 6 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6fe867ddf..6616aa0ad 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,7 +7,7 @@ CHANGELOG
  * Add optional `$requests` parameter to `RequestStack::__construct()`
  * Add optional `$v4Bytes` and `$v6Bytes` parameters to `IpUtils::anonymize()`
  * Add `PRIVATE_SUBNETS` as a shortcut for private IP address ranges to `Request::setTrustedProxies()`
- * Deprecate passing `referer_check`, `use_only_cookies`, `use_trans_sid`, `trans_sid_hosts` and `trans_sid_tags` options to `NativeSessionStorage`
+ * Deprecate passing `referer_check`, `use_only_cookies`, `use_trans_sid`, `trans_sid_hosts`, `trans_sid_tags`, `sid_bits_per_character` and `sid_length` options to `NativeSessionStorage`
 
 7.1
 ---
diff --git a/Session/Storage/NativeSessionStorage.php b/Session/Storage/NativeSessionStorage.php
index 0794fd269..3d08f5f6d 100644
--- a/Session/Storage/NativeSessionStorage.php
+++ b/Session/Storage/NativeSessionStorage.php
@@ -68,8 +68,8 @@ class NativeSessionStorage implements SessionStorageInterface
      * use_cookies, "1"
      * use_only_cookies, "1" (deprecated since Symfony 7.2, to be removed in Symfony 8.0)
      * use_trans_sid, "0" (deprecated since Symfony 7.2, to be removed in Symfony 8.0)
-     * sid_length, "32"
-     * sid_bits_per_character, "5"
+     * sid_length, "32" (@deprecated since Symfony 7.2, to be removed in 8.0)
+     * sid_bits_per_character, "5" (@deprecated since Symfony 7.2, to be removed in 8.0)
      * trans_sid_hosts, $_SERVER['HTTP_HOST'] (deprecated since Symfony 7.2, to be removed in Symfony 8.0)
      * trans_sid_tags, "a=href,area=href,frame=src,form=" (deprecated since Symfony 7.2, to be removed in Symfony 8.0)
      */
@@ -126,8 +126,8 @@ public function start(): bool
          * See https://www.php.net/manual/en/session.configuration.php#ini.session.sid-bits-per-character.
          * Allowed values are integers such as:
          * - 4 for range `a-f0-9`
-         * - 5 for range `a-v0-9`
-         * - 6 for range `a-zA-Z0-9,-`
+         * - 5 for range `a-v0-9` (@deprecated since Symfony 7.2, it will default to 4 and the option will be ignored in Symfony 8.0)
+         * - 6 for range `a-zA-Z0-9,-` (@deprecated since Symfony 7.2, it will default to 4 and the option will be ignored in Symfony 8.0)
          *
          * ---------- Part 2
          *
@@ -139,6 +139,8 @@ public function start(): bool
          * - The length of Windows and Linux filenames is limited to 255 bytes. Then the max must not exceed 255.
          * - The session filename prefix is `sess_`, a 5 bytes string. Then the max must not exceed 255 - 5 = 250.
          *
+         * This is @deprecated since Symfony 7.2, the sid length will default to 32 and the option will be ignored in Symfony 8.0.
+         *
          * ---------- Conclusion
          *
          * The parts 1 and 2 prevent the warning below:
@@ -328,7 +330,7 @@ public function setOptions(array $options): void
         ]);
 
         foreach ($options as $key => $value) {
-            if (\in_array($key, ['referer_check', 'use_only_cookies', 'use_trans_sid', 'trans_sid_hosts', 'trans_sid_tags'], true)) {
+            if (\in_array($key, ['referer_check', 'use_only_cookies', 'use_trans_sid', 'trans_sid_hosts', 'trans_sid_tags', 'sid_length', 'sid_bits_per_character'], true)) {
                 trigger_deprecation('symfony/http-foundation', '7.2', 'NativeSessionStorage\'s "%s" option is deprecated and will be ignored in Symfony 8.0.', $key);
             }
 
diff --git a/Tests/Session/Storage/NativeSessionStorageTest.php b/Tests/Session/Storage/NativeSessionStorageTest.php
index a7189a37b..d21ca888c 100644
--- a/Tests/Session/Storage/NativeSessionStorageTest.php
+++ b/Tests/Session/Storage/NativeSessionStorageTest.php
@@ -370,6 +370,8 @@ public function testSaveHandlesNullSessionGracefully()
      */
     public function testPassingDeprecatedOptions()
     {
+        $this->expectDeprecation('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "sid_length" option is deprecated and will be ignored in Symfony 8.0.');
+        $this->expectDeprecation('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "sid_bits_per_character" option is deprecated and will be ignored in Symfony 8.0.');
         $this->expectDeprecation('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "referer_check" option is deprecated and will be ignored in Symfony 8.0.');
         $this->expectDeprecation('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "use_only_cookies" option is deprecated and will be ignored in Symfony 8.0.');
         $this->expectDeprecation('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "use_trans_sid" option is deprecated and will be ignored in Symfony 8.0.');
@@ -377,6 +379,8 @@ public function testPassingDeprecatedOptions()
         $this->expectDeprecation('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "trans_sid_tags" option is deprecated and will be ignored in Symfony 8.0.');
 
         $this->getStorage([
+            'sid_length' => 42,
+            'sid_bits_per_character' => 6,
             'referer_check' => 'foo',
             'use_only_cookies' => 'foo',
             'use_trans_sid' => 'foo',

From d87b1218baa01fa0bdebfcfdbefa7f3f74fbbc1c Mon Sep 17 00:00:00 2001
From: "Alexander M. Turek" <me@derrabus.de>
Date: Thu, 19 Sep 2024 09:56:35 +0200
Subject: [PATCH 20/48] Switch to ExpectUserDeprecationMessageTrait

---
 .../Storage/NativeSessionStorageTest.php      | 20 +++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/Tests/Session/Storage/NativeSessionStorageTest.php b/Tests/Session/Storage/NativeSessionStorageTest.php
index d21ca888c..11c489f6b 100644
--- a/Tests/Session/Storage/NativeSessionStorageTest.php
+++ b/Tests/Session/Storage/NativeSessionStorageTest.php
@@ -12,7 +12,7 @@
 namespace Symfony\Component\HttpFoundation\Tests\Session\Storage;
 
 use PHPUnit\Framework\TestCase;
-use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
+use Symfony\Bridge\PhpUnit\ExpectUserDeprecationMessageTrait;
 use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
 use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
 use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler;
@@ -33,7 +33,7 @@
  */
 class NativeSessionStorageTest extends TestCase
 {
-    use ExpectDeprecationTrait;
+    use ExpectUserDeprecationMessageTrait;
 
     private string $savePath;
 
@@ -224,7 +224,7 @@ public function testCacheExpireOption()
      */
     public function testTransSidTagsOption()
     {
-        $this->expectDeprecation('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "trans_sid_tags" option is deprecated and will be ignored in Symfony 8.0.');
+        $this->expectUserDeprecationMessage('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "trans_sid_tags" option is deprecated and will be ignored in Symfony 8.0.');
 
         $previousErrorHandler = set_error_handler(function ($errno, $errstr) use (&$previousErrorHandler) {
             if ('ini_set(): Usage of session.trans_sid_tags INI setting is deprecated' !== $errstr) {
@@ -370,13 +370,13 @@ public function testSaveHandlesNullSessionGracefully()
      */
     public function testPassingDeprecatedOptions()
     {
-        $this->expectDeprecation('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "sid_length" option is deprecated and will be ignored in Symfony 8.0.');
-        $this->expectDeprecation('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "sid_bits_per_character" option is deprecated and will be ignored in Symfony 8.0.');
-        $this->expectDeprecation('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "referer_check" option is deprecated and will be ignored in Symfony 8.0.');
-        $this->expectDeprecation('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "use_only_cookies" option is deprecated and will be ignored in Symfony 8.0.');
-        $this->expectDeprecation('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "use_trans_sid" option is deprecated and will be ignored in Symfony 8.0.');
-        $this->expectDeprecation('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "trans_sid_hosts" option is deprecated and will be ignored in Symfony 8.0.');
-        $this->expectDeprecation('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "trans_sid_tags" option is deprecated and will be ignored in Symfony 8.0.');
+        $this->expectUserDeprecationMessage('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "sid_length" option is deprecated and will be ignored in Symfony 8.0.');
+        $this->expectUserDeprecationMessage('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "sid_bits_per_character" option is deprecated and will be ignored in Symfony 8.0.');
+        $this->expectUserDeprecationMessage('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "referer_check" option is deprecated and will be ignored in Symfony 8.0.');
+        $this->expectUserDeprecationMessage('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "use_only_cookies" option is deprecated and will be ignored in Symfony 8.0.');
+        $this->expectUserDeprecationMessage('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "use_trans_sid" option is deprecated and will be ignored in Symfony 8.0.');
+        $this->expectUserDeprecationMessage('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "trans_sid_hosts" option is deprecated and will be ignored in Symfony 8.0.');
+        $this->expectUserDeprecationMessage('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "trans_sid_tags" option is deprecated and will be ignored in Symfony 8.0.');
 
         $this->getStorage([
             'sid_length' => 42,

From 0c5ecc929852c99bf6b0dd118bfb375f526358c6 Mon Sep 17 00:00:00 2001
From: Alexandre Daubois <alex.daubois@gmail.com>
Date: Tue, 24 Sep 2024 12:58:43 +0200
Subject: [PATCH 21/48] Fix `$this` calls to static ones when relevant

---
 Request.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Request.php b/Request.php
index 2af29a52a..114010609 100644
--- a/Request.php
+++ b/Request.php
@@ -1572,7 +1572,7 @@ public function getLanguages(): array
         $this->languages = [];
         foreach ($languages as $acceptHeaderItem) {
             $lang = $acceptHeaderItem->getValue();
-            $this->languages[] = $this->formatLocale($lang);
+            $this->languages[] = self::formatLocale($lang);
         }
         $this->languages = array_unique($this->languages);
 

From 1028e55086ea8d31d6323a6b7b8f0f12901fd95c Mon Sep 17 00:00:00 2001
From: Alexandre Daubois <alex.daubois@gmail.com>
Date: Tue, 24 Sep 2024 13:28:07 +0200
Subject: [PATCH 22/48] Remove useless parent method calls in tests

---
 Tests/Session/Flash/AutoExpireFlashBagTest.php                  | 2 --
 Tests/Session/Flash/FlashBagTest.php                            | 2 --
 .../Storage/Handler/AbstractRedisSessionHandlerTestCase.php     | 2 --
 Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php   | 2 --
 Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php     | 2 --
 Tests/Session/Storage/Handler/PdoSessionHandlerTest.php         | 1 -
 Tests/Session/Storage/MetadataBagTest.php                       | 1 -
 7 files changed, 12 deletions(-)

diff --git a/Tests/Session/Flash/AutoExpireFlashBagTest.php b/Tests/Session/Flash/AutoExpireFlashBagTest.php
index 0b418c006..8e9ee780b 100644
--- a/Tests/Session/Flash/AutoExpireFlashBagTest.php
+++ b/Tests/Session/Flash/AutoExpireFlashBagTest.php
@@ -27,7 +27,6 @@ class AutoExpireFlashBagTest extends TestCase
 
     protected function setUp(): void
     {
-        parent::setUp();
         $this->bag = new FlashBag();
         $this->array = ['new' => ['notice' => ['A previous flash message']]];
         $this->bag->initialize($this->array);
@@ -36,7 +35,6 @@ protected function setUp(): void
     protected function tearDown(): void
     {
         unset($this->bag);
-        parent::tearDown();
     }
 
     public function testInitialize()
diff --git a/Tests/Session/Flash/FlashBagTest.php b/Tests/Session/Flash/FlashBagTest.php
index 8163ba769..efe9a4977 100644
--- a/Tests/Session/Flash/FlashBagTest.php
+++ b/Tests/Session/Flash/FlashBagTest.php
@@ -27,7 +27,6 @@ class FlashBagTest extends TestCase
 
     protected function setUp(): void
     {
-        parent::setUp();
         $this->bag = new FlashBag();
         $this->array = ['notice' => ['A previous flash message']];
         $this->bag->initialize($this->array);
@@ -36,7 +35,6 @@ protected function setUp(): void
     protected function tearDown(): void
     {
         unset($this->bag);
-        parent::tearDown();
     }
 
     public function testInitialize()
diff --git a/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php b/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php
index 4df1553c8..f6ab99514 100644
--- a/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php
+++ b/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php
@@ -31,8 +31,6 @@ abstract protected function createRedisClient(string $host): \Redis|Relay|\Redis
 
     protected function setUp(): void
     {
-        parent::setUp();
-
         if (!\extension_loaded('redis')) {
             self::markTestSkipped('Extension redis required.');
         }
diff --git a/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php b/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php
index 379fcb0d1..0f460e47a 100644
--- a/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php
+++ b/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php
@@ -30,8 +30,6 @@ class MemcachedSessionHandlerTest extends TestCase
 
     protected function setUp(): void
     {
-        parent::setUp();
-
         if (version_compare(phpversion('memcached'), '2.2.0', '>=') && version_compare(phpversion('memcached'), '3.0.0b1', '<')) {
             $this->markTestSkipped('Tests can only be run with memcached extension 2.1.0 or lower, or 3.0.0b1 or higher');
         }
diff --git a/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php b/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php
index 908acfe15..63b0263c2 100644
--- a/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php
+++ b/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php
@@ -44,8 +44,6 @@ class MongoDbSessionHandlerTest extends TestCase
 
     protected function setUp(): void
     {
-        parent::setUp();
-
         $this->manager = new Manager('mongodb://'.getenv('MONGODB_HOST'));
 
         try {
diff --git a/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php b/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php
index ede4703aa..8106f3da4 100644
--- a/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php
+++ b/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php
@@ -30,7 +30,6 @@ protected function tearDown(): void
         if ($this->dbFile) {
             @unlink($this->dbFile);
         }
-        parent::tearDown();
     }
 
     protected function getPersistentSqliteDsn()
diff --git a/Tests/Session/Storage/MetadataBagTest.php b/Tests/Session/Storage/MetadataBagTest.php
index b2f3de42b..3acdcfcac 100644
--- a/Tests/Session/Storage/MetadataBagTest.php
+++ b/Tests/Session/Storage/MetadataBagTest.php
@@ -26,7 +26,6 @@ class MetadataBagTest extends TestCase
 
     protected function setUp(): void
     {
-        parent::setUp();
         $this->bag = new MetadataBag();
         $this->array = [MetadataBag::CREATED => 1234567, MetadataBag::UPDATED => 12345678, MetadataBag::LIFETIME => 0];
         $this->bag->initialize($this->array);

From 53b79fbb27e5f847ab89b3f531a9d1440db2a49b Mon Sep 17 00:00:00 2001
From: Alexandre Daubois <alex.daubois@gmail.com>
Date: Fri, 4 Oct 2024 13:58:10 +0200
Subject: [PATCH 23/48] Fix test extension requirements

---
 .../Storage/Handler/AbstractRedisSessionHandlerTestCase.php     | 2 --
 1 file changed, 2 deletions(-)

diff --git a/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php b/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php
index f6ab99514..0b4c86edf 100644
--- a/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php
+++ b/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php
@@ -16,8 +16,6 @@
 use Symfony\Component\HttpFoundation\Session\Storage\Handler\RedisSessionHandler;
 
 /**
- * @requires extension redis
- *
  * @group time-sensitive
  */
 abstract class AbstractRedisSessionHandlerTestCase extends TestCase

From 4c415d52695870fe56d78e6b9ab88bb8d342eaf4 Mon Sep 17 00:00:00 2001
From: Alexandre Daubois <alex.daubois@gmail.com>
Date: Mon, 14 Oct 2024 20:03:05 +0200
Subject: [PATCH 24/48] Reduce common control flows

---
 Tests/RequestMatcher/SchemeRequestMatcherTest.php | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/Tests/RequestMatcher/SchemeRequestMatcherTest.php b/Tests/RequestMatcher/SchemeRequestMatcherTest.php
index 6614bfcc2..933b3d695 100644
--- a/Tests/RequestMatcher/SchemeRequestMatcherTest.php
+++ b/Tests/RequestMatcher/SchemeRequestMatcherTest.php
@@ -25,18 +25,16 @@ public function test(string $requestScheme, array|string $matcherScheme, bool $i
         $httpRequest = Request::create('');
         $httpsRequest = Request::create('', 'get', [], [], [], ['HTTPS' => 'on']);
 
+        $matcher = new SchemeRequestMatcher($matcherScheme);
         if ($isMatch) {
             if ('https' === $requestScheme) {
-                $matcher = new SchemeRequestMatcher($matcherScheme);
                 $this->assertFalse($matcher->matches($httpRequest));
                 $this->assertTrue($matcher->matches($httpsRequest));
             } else {
-                $matcher = new SchemeRequestMatcher($matcherScheme);
                 $this->assertFalse($matcher->matches($httpsRequest));
                 $this->assertTrue($matcher->matches($httpRequest));
             }
         } else {
-            $matcher = new SchemeRequestMatcher($matcherScheme);
             $this->assertFalse($matcher->matches($httpRequest));
             $this->assertFalse($matcher->matches($httpsRequest));
         }

From 735b8519a8bcbf603d3af0430fb1be5c8822af13 Mon Sep 17 00:00:00 2001
From: Alexandre Daubois <alex.daubois@gmail.com>
Date: Fri, 18 Oct 2024 16:04:52 +0200
Subject: [PATCH 25/48] Remove always true/false occurrences

---
 Request.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Request.php b/Request.php
index 114010609..e80bfa61f 100644
--- a/Request.php
+++ b/Request.php
@@ -1536,7 +1536,7 @@ public function getPreferredLanguage(?array $locales = null): ?string
             return $preferredLanguages[0] ?? null;
         }
 
-        $locales = array_map($this->formatLocale(...), $locales ?? []);
+        $locales = array_map($this->formatLocale(...), $locales);
         if (!$preferredLanguages) {
             return $locales[0];
         }

From 069924c165fb05f1d0860f21addff0f1d0778e44 Mon Sep 17 00:00:00 2001
From: Mathias Arlaud <mathias.arlaud@gmail.com>
Date: Tue, 10 Dec 2024 10:44:11 +0100
Subject: [PATCH 26/48] [HttpFoundation] Support iterable of string in
 `StreamedResponse`

---
 CHANGELOG.md                   |  7 ++++++-
 StreamedResponse.php           | 27 ++++++++++++++++++++++-----
 Tests/StreamedResponseTest.php | 11 +++++++++++
 3 files changed, 39 insertions(+), 6 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6616aa0ad..6861b3b36 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,11 @@
 CHANGELOG
 =========
 
+7.3
+---
+
+ * Add support for iterable of string in `StreamedResponse`
+
 7.2
 ---
 
@@ -40,7 +45,7 @@ CHANGELOG
  * Add `UriSigner` from the HttpKernel component
  * Add `partitioned` flag to `Cookie` (CHIPS Cookie)
  * Add argument `bool $flush = true` to `Response::send()`
-* Make `MongoDbSessionHandler` instantiable with the mongodb extension directly
+ * Make `MongoDbSessionHandler` instantiable with the mongodb extension directly
 
 6.3
 ---
diff --git a/StreamedResponse.php b/StreamedResponse.php
index 3acaade17..6eedf1c49 100644
--- a/StreamedResponse.php
+++ b/StreamedResponse.php
@@ -14,7 +14,7 @@
 /**
  * StreamedResponse represents a streamed HTTP response.
  *
- * A StreamedResponse uses a callback for its content.
+ * A StreamedResponse uses a callback or an iterable of strings for its content.
  *
  * The callback should use the standard PHP functions like echo
  * to stream the response back to the client. The flush() function
@@ -32,19 +32,36 @@ class StreamedResponse extends Response
     private bool $headersSent = false;
 
     /**
-     * @param int $status The HTTP status code (200 "OK" by default)
+     * @param callable|iterable<string>|null $callbackOrChunks
+     * @param int                            $status           The HTTP status code (200 "OK" by default)
      */
-    public function __construct(?callable $callback = null, int $status = 200, array $headers = [])
+    public function __construct(callable|iterable|null $callbackOrChunks = null, int $status = 200, array $headers = [])
     {
         parent::__construct(null, $status, $headers);
 
-        if (null !== $callback) {
-            $this->setCallback($callback);
+        if (\is_callable($callbackOrChunks)) {
+            $this->setCallback($callbackOrChunks);
+        } elseif ($callbackOrChunks) {
+            $this->setChunks($callbackOrChunks);
         }
         $this->streamed = false;
         $this->headersSent = false;
     }
 
+    /**
+     * @param iterable<string> $chunks
+     */
+    public function setChunks(iterable $chunks): static
+    {
+        $this->callback = static function () use ($chunks): void {
+            foreach ($chunks as $chunk) {
+                echo $chunk;
+            }
+        };
+
+        return $this;
+    }
+
     /**
      * Sets the PHP callback associated with this Response.
      *
diff --git a/Tests/StreamedResponseTest.php b/Tests/StreamedResponseTest.php
index 2a2b7e731..78a777aea 100644
--- a/Tests/StreamedResponseTest.php
+++ b/Tests/StreamedResponseTest.php
@@ -25,6 +25,17 @@ public function testConstructor()
         $this->assertEquals('text/plain', $response->headers->get('Content-Type'));
     }
 
+    public function testConstructorWithChunks()
+    {
+        $chunks = ['foo', 'bar', 'baz'];
+        $callback = (new StreamedResponse($chunks))->getCallback();
+
+        ob_start();
+        $callback();
+
+        $this->assertSame('foobarbaz', ob_get_clean());
+    }
+
     public function testPrepareWith11Protocol()
     {
         $response = new StreamedResponse(function () { echo 'foo'; });

From c2508e48b252f02b1364980ff79fb168a074e199 Mon Sep 17 00:00:00 2001
From: Dariusz Ruminski <dariusz.ruminski@gmail.com>
Date: Wed, 11 Dec 2024 14:08:35 +0100
Subject: [PATCH 27/48] chore: PHP CS Fixer fixes

---
 ResponseHeaderBag.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ResponseHeaderBag.php b/ResponseHeaderBag.php
index 023651efb..b2bdb500c 100644
--- a/ResponseHeaderBag.php
+++ b/ResponseHeaderBag.php
@@ -221,7 +221,7 @@ public function getCookies(string $format = self::COOKIES_FLAT): array
      */
     public function clearCookie(string $name, ?string $path = '/', ?string $domain = null, bool $secure = false, bool $httpOnly = true, ?string $sameSite = null /* , bool $partitioned = false */): void
     {
-        $partitioned = 6 < \func_num_args() ? \func_get_arg(6) : false;
+        $partitioned = 6 < \func_num_args() ? func_get_arg(6) : false;
 
         $this->setCookie(new Cookie($name, null, 1, $path, $domain, $secure, $httpOnly, false, $sameSite, $partitioned));
     }

From 9e485dd3094b0bcf2d9cd9f9b822ef7ebc0e5dbc Mon Sep 17 00:00:00 2001
From: valtzu <valtzu@gmail.com>
Date: Wed, 27 Nov 2024 22:28:12 +0200
Subject: [PATCH 28/48] Generate url-safe signatures

---
 Tests/UriSignerTest.php | 18 ++++++++++++------
 UriSigner.php           |  9 +++++----
 2 files changed, 17 insertions(+), 10 deletions(-)

diff --git a/Tests/UriSignerTest.php b/Tests/UriSignerTest.php
index 949e34760..927e2bda8 100644
--- a/Tests/UriSignerTest.php
+++ b/Tests/UriSignerTest.php
@@ -70,13 +70,13 @@ public function testCheckWithDifferentArgSeparator()
             $signer = new UriSigner('foobar');
 
             $this->assertSame(
-                'http://example.com/foo?_hash=rIOcC%2FF3DoEGo%2FvnESjSp7uU9zA9S%2F%2BOLhxgMexoPUM%3D&baz=bay&foo=bar',
+                'http://example.com/foo?_hash=rIOcC_F3DoEGo_vnESjSp7uU9zA9S_-OLhxgMexoPUM&baz=bay&foo=bar',
                 $signer->sign('http://example.com/foo?foo=bar&baz=bay')
             );
             $this->assertTrue($signer->check($signer->sign('http://example.com/foo?foo=bar&baz=bay')));
 
             $this->assertSame(
-                'http://example.com/foo?_expiration=2145916800&_hash=xLhnPMzV3KqqHaaUffBUJvtRDAZ4%2FZ9Y8Sw%2BgmS%2B82Q%3D&baz=bay&foo=bar',
+                'http://example.com/foo?_expiration=2145916800&_hash=xLhnPMzV3KqqHaaUffBUJvtRDAZ4_Z9Y8Sw-gmS-82Q&baz=bay&foo=bar',
                 $signer->sign('http://example.com/foo?foo=bar&baz=bay', new \DateTimeImmutable('2038-01-01 00:00:00', new \DateTimeZone('UTC')))
             );
             $this->assertTrue($signer->check($signer->sign('http://example.com/foo?foo=bar&baz=bay', new \DateTimeImmutable('2099-01-01 00:00:00'))));
@@ -103,13 +103,13 @@ public function testCheckWithDifferentParameter()
         $signer = new UriSigner('foobar', 'qux', 'abc');
 
         $this->assertSame(
-            'http://example.com/foo?baz=bay&foo=bar&qux=rIOcC%2FF3DoEGo%2FvnESjSp7uU9zA9S%2F%2BOLhxgMexoPUM%3D',
+            'http://example.com/foo?baz=bay&foo=bar&qux=rIOcC_F3DoEGo_vnESjSp7uU9zA9S_-OLhxgMexoPUM',
             $signer->sign('http://example.com/foo?foo=bar&baz=bay')
         );
         $this->assertTrue($signer->check($signer->sign('http://example.com/foo?foo=bar&baz=bay')));
 
         $this->assertSame(
-            'http://example.com/foo?abc=2145916800&baz=bay&foo=bar&qux=kE4rK2MzeiwrYAKy%2B%2FGKvKA6bnzqCbACBdpC3yGnPVU%3D',
+            'http://example.com/foo?abc=2145916800&baz=bay&foo=bar&qux=kE4rK2MzeiwrYAKy-_GKvKA6bnzqCbACBdpC3yGnPVU',
             $signer->sign('http://example.com/foo?foo=bar&baz=bay', new \DateTimeImmutable('2038-01-01 00:00:00', new \DateTimeZone('UTC')))
         );
         $this->assertTrue($signer->check($signer->sign('http://example.com/foo?foo=bar&baz=bay', new \DateTimeImmutable('2099-01-01 00:00:00'))));
@@ -120,14 +120,14 @@ public function testSignerWorksWithFragments()
         $signer = new UriSigner('foobar');
 
         $this->assertSame(
-            'http://example.com/foo?_hash=EhpAUyEobiM3QTrKxoLOtQq5IsWyWedoXDPqIjzNj5o%3D&bar=foo&foo=bar#foobar',
+            'http://example.com/foo?_hash=EhpAUyEobiM3QTrKxoLOtQq5IsWyWedoXDPqIjzNj5o&bar=foo&foo=bar#foobar',
             $signer->sign('http://example.com/foo?bar=foo&foo=bar#foobar')
         );
 
         $this->assertTrue($signer->check($signer->sign('http://example.com/foo?bar=foo&foo=bar#foobar')));
 
         $this->assertSame(
-            'http://example.com/foo?_expiration=2145916800&_hash=jTdrIE9MJSorNpQmkX6tmOtocxXtHDzIJawcAW4IFYo%3D&bar=foo&foo=bar#foobar',
+            'http://example.com/foo?_expiration=2145916800&_hash=jTdrIE9MJSorNpQmkX6tmOtocxXtHDzIJawcAW4IFYo&bar=foo&foo=bar#foobar',
             $signer->sign('http://example.com/foo?bar=foo&foo=bar#foobar', new \DateTimeImmutable('2038-01-01 00:00:00', new \DateTimeZone('UTC')))
         );
 
@@ -198,4 +198,10 @@ public function testCheckWithUriExpiration()
         $this->assertFalse($signer->check($relativeUriFromNow2));
         $this->assertFalse($signer->check($relativeUriFromNow3));
     }
+
+    public function testNonUrlSafeBase64()
+    {
+        $signer = new UriSigner('foobar');
+        $this->assertTrue($signer->check('http://example.com/foo?_hash=rIOcC%2FF3DoEGo%2FvnESjSp7uU9zA9S%2F%2BOLhxgMexoPUM%3D&baz=bay&foo=bar'));
+    }
 }
diff --git a/UriSigner.php b/UriSigner.php
index dd7443489..1c9e25a5c 100644
--- a/UriSigner.php
+++ b/UriSigner.php
@@ -46,7 +46,7 @@ public function __construct(
      *
      * The expiration is added as a query string parameter.
      */
-    public function sign(string $uri/*, \DateTimeInterface|\DateInterval|int|null $expiration = null*/): string
+    public function sign(string $uri/* , \DateTimeInterface|\DateInterval|int|null $expiration = null */): string
     {
         $expiration = null;
 
@@ -55,7 +55,7 @@ public function sign(string $uri/*, \DateTimeInterface|\DateInterval|int|null $e
         }
 
         if (null !== $expiration && !$expiration instanceof \DateTimeInterface && !$expiration instanceof \DateInterval && !\is_int($expiration)) {
-            throw new \TypeError(\sprintf('The second argument of %s() must be an instance of %s or %s, an integer or null (%s given).', __METHOD__, \DateTimeInterface::class, \DateInterval::class, get_debug_type($expiration)));
+            throw new \TypeError(\sprintf('The second argument of "%s()" must be an instance of "%s" or "%s", an integer or null (%s given).', __METHOD__, \DateTimeInterface::class, \DateInterval::class, get_debug_type($expiration)));
         }
 
         $url = parse_url($uri);
@@ -103,7 +103,8 @@ public function check(string $uri): bool
         $hash = $params[$this->hashParameter];
         unset($params[$this->hashParameter]);
 
-        if (!hash_equals($this->computeHash($this->buildUrl($url, $params)), $hash)) {
+        // In 8.0, remove support for non-url-safe tokens
+        if (!hash_equals($this->computeHash($this->buildUrl($url, $params)), strtr(rtrim($hash, '='), ['/' => '_', '+' => '-']))) {
             return false;
         }
 
@@ -124,7 +125,7 @@ public function checkRequest(Request $request): bool
 
     private function computeHash(string $uri): string
     {
-        return base64_encode(hash_hmac('sha256', $uri, $this->secret, true));
+        return strtr(rtrim(base64_encode(hash_hmac('sha256', $uri, $this->secret, true)), '='), ['/' => '_', '+' => '-']);
     }
 
     private function buildUrl(array $url, array $params = []): string

From 658b7a44304949f426c640531aaea00ec15ea7dc Mon Sep 17 00:00:00 2001
From: Vincent Langlet <vincentlanglet@hotmail.fr>
Date: Fri, 27 Dec 2024 10:36:28 +0100
Subject: [PATCH 29/48] [HttpFoundation] Document thrown exception by parameter
 and input bag

---
 InputBag.php     | 10 ++++++++++
 ParameterBag.php | 19 +++++++++++++++++++
 2 files changed, 29 insertions(+)

diff --git a/InputBag.php b/InputBag.php
index 97bd8b090..7411d755c 100644
--- a/InputBag.php
+++ b/InputBag.php
@@ -25,6 +25,8 @@ final class InputBag extends ParameterBag
      * Returns a scalar input value by name.
      *
      * @param string|int|float|bool|null $default The default value if the input key does not exist
+     *
+     * @throws BadRequestException if the input contains a non-scalar value
      */
     public function get(string $key, mixed $default = null): string|int|float|bool|null
     {
@@ -85,6 +87,8 @@ public function set(string $key, mixed $value): void
      * @return ?T
      *
      * @psalm-return ($default is null ? T|null : T)
+     *
+     * @throws BadRequestException if the input cannot be converted to an enum
      */
     public function getEnum(string $key, string $class, ?\BackedEnum $default = null): ?\BackedEnum
     {
@@ -97,6 +101,8 @@ public function getEnum(string $key, string $class, ?\BackedEnum $default = null
 
     /**
      * Returns the parameter value converted to string.
+     *
+     * @throws BadRequestException if the input contains a non-scalar value
      */
     public function getString(string $key, string $default = ''): string
     {
@@ -104,6 +110,10 @@ public function getString(string $key, string $default = ''): string
         return (string) $this->get($key, $default);
     }
 
+    /**
+     * @throws BadRequestException if the input value is an array and \FILTER_REQUIRE_ARRAY or \FILTER_FORCE_ARRAY is not set
+     * @throws BadRequestException if the input value is invalid and \FILTER_NULL_ON_FAILURE is not set
+     */
     public function filter(string $key, mixed $default = null, int $filter = \FILTER_DEFAULT, mixed $options = []): mixed
     {
         $value = $this->has($key) ? $this->all()[$key] : $default;
diff --git a/ParameterBag.php b/ParameterBag.php
index 35a0f1819..f37d7b3e2 100644
--- a/ParameterBag.php
+++ b/ParameterBag.php
@@ -32,6 +32,8 @@ public function __construct(
      * Returns the parameters.
      *
      * @param string|null $key The name of the parameter to return or null to get them all
+     *
+     * @throws BadRequestException if the value is not an array
      */
     public function all(?string $key = null): array
     {
@@ -98,6 +100,8 @@ public function remove(string $key): void
 
     /**
      * Returns the alphabetic characters of the parameter value.
+     *
+     * @throws UnexpectedValueException if the value cannot be converted to string
      */
     public function getAlpha(string $key, string $default = ''): string
     {
@@ -106,6 +110,8 @@ public function getAlpha(string $key, string $default = ''): string
 
     /**
      * Returns the alphabetic characters and digits of the parameter value.
+     *
+     * @throws UnexpectedValueException if the value cannot be converted to string
      */
     public function getAlnum(string $key, string $default = ''): string
     {
@@ -114,6 +120,8 @@ public function getAlnum(string $key, string $default = ''): string
 
     /**
      * Returns the digits of the parameter value.
+     *
+     * @throws UnexpectedValueException if the value cannot be converted to string
      */
     public function getDigits(string $key, string $default = ''): string
     {
@@ -122,6 +130,8 @@ public function getDigits(string $key, string $default = ''): string
 
     /**
      * Returns the parameter as string.
+     *
+     * @throws UnexpectedValueException if the value cannot be converted to string
      */
     public function getString(string $key, string $default = ''): string
     {
@@ -135,6 +145,8 @@ public function getString(string $key, string $default = ''): string
 
     /**
      * Returns the parameter value converted to integer.
+     *
+     * @throws UnexpectedValueException if the value cannot be converted to integer
      */
     public function getInt(string $key, int $default = 0): int
     {
@@ -143,6 +155,8 @@ public function getInt(string $key, int $default = 0): int
 
     /**
      * Returns the parameter value converted to boolean.
+     *
+     * @throws UnexpectedValueException if the value cannot be converted to a boolean
      */
     public function getBoolean(string $key, bool $default = false): bool
     {
@@ -160,6 +174,8 @@ public function getBoolean(string $key, bool $default = false): bool
      * @return ?T
      *
      * @psalm-return ($default is null ? T|null : T)
+     *
+     * @throws UnexpectedValueException if the parameter value cannot be converted to an enum
      */
     public function getEnum(string $key, string $class, ?\BackedEnum $default = null): ?\BackedEnum
     {
@@ -183,6 +199,9 @@ public function getEnum(string $key, string $class, ?\BackedEnum $default = null
      * @param int|array{flags?: int, options?: array} $options Flags from FILTER_* constants
      *
      * @see https://php.net/filter-var
+     *
+     * @throws UnexpectedValueException if the parameter value is a non-stringable object
+     * @throws UnexpectedValueException if the parameter value is invalid and \FILTER_NULL_ON_FAILURE is not set
      */
     public function filter(string $key, mixed $default = null, int $filter = \FILTER_DEFAULT, mixed $options = []): mixed
     {

From a4d4dd6587151385196a67ab5b2438a227d598cb Mon Sep 17 00:00:00 2001
From: Emilien Escalle <neilime@users.noreply.github.com>
Date: Mon, 6 Jan 2025 16:43:34 +0100
Subject: [PATCH 30/48] chore(HttpFoundation): define phpdoc type for Response
 "statusTexts"

As `Symfony\Component\HttpFoundation\Response::$statusTexts` is public, it can be used by everyone. Some of us can use type analysis like PSALM or PHPStan...

It is always useful, and for some of them mandatory to have a proper array typing to use this variable.
---
 Response.php | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Response.php b/Response.php
index bf68d2741..638b5bf60 100644
--- a/Response.php
+++ b/Response.php
@@ -121,6 +121,8 @@ class Response
      * (last updated 2021-10-01).
      *
      * Unless otherwise noted, the status code is defined in RFC2616.
+     *
+     * @var array<int, string>
      */
     public static array $statusTexts = [
         100 => 'Continue',

From 5c227d698654539a16ef2d149bbe8725c644327d Mon Sep 17 00:00:00 2001
From: Yonel Ceruto <yonel.ceruto@scnd.com>
Date: Fri, 1 Nov 2024 15:10:26 -0400
Subject: [PATCH 31/48] Streamlining server event streaming

---
 CHANGELOG.md                      |   1 +
 EventStreamResponse.php           | 110 ++++++++++++++++++++++
 ServerEvent.php                   | 147 ++++++++++++++++++++++++++++++
 Tests/EventStreamResponseTest.php | 127 ++++++++++++++++++++++++++
 4 files changed, 385 insertions(+)
 create mode 100644 EventStreamResponse.php
 create mode 100644 ServerEvent.php
 create mode 100644 Tests/EventStreamResponseTest.php

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6861b3b36..0841fa9ab 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@ CHANGELOG
 ---
 
  * Add support for iterable of string in `StreamedResponse`
+ * Add `EventStreamResponse` and `ServerEvent` classes to streamline server event streaming
 
 7.2
 ---
diff --git a/EventStreamResponse.php b/EventStreamResponse.php
new file mode 100644
index 000000000..fe1a2872e
--- /dev/null
+++ b/EventStreamResponse.php
@@ -0,0 +1,110 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Represents a streaming HTTP response for sending server events
+ * as part of the Server-Sent Events (SSE) streaming technique.
+ *
+ * To broadcast events to multiple users at once, for long-running
+ * connections and for high-traffic websites, prefer using the Mercure
+ * Symfony Component, which relies on Software designed for these use
+ * cases: https://symfony.com/doc/current/mercure.html
+ *
+ * @see ServerEvent
+ *
+ * @author Yonel Ceruto <open@yceruto.dev>
+ *
+ * Example usage:
+ *
+ *     return new EventStreamResponse(function () {
+ *         yield new ServerEvent(time());
+ *
+ *         sleep(1);
+ *
+ *         yield new ServerEvent(time());
+ *     });
+ */
+class EventStreamResponse extends StreamedResponse
+{
+    /**
+     * @param int|null $retry The number of milliseconds the client should wait
+     *                        before reconnecting in case of network failure
+     */
+    public function __construct(?callable $callback = null, int $status = 200, array $headers = [], private ?int $retry = null)
+    {
+        $headers += [
+            'Connection' => 'keep-alive',
+            'Content-Type' => 'text/event-stream',
+            'Cache-Control' => 'private, no-cache, no-store, must-revalidate, max-age=0',
+            'X-Accel-Buffering' => 'no',
+            'Pragma' => 'no-cache',
+            'Expire' => '0',
+        ];
+
+        parent::__construct($callback, $status, $headers);
+    }
+
+    public function setCallback(callable $callback): static
+    {
+        if ($this->callback) {
+            return parent::setCallback($callback);
+        }
+
+        $this->callback = function () use ($callback) {
+            if (is_iterable($events = $callback($this))) {
+                foreach ($events as $event) {
+                    $this->sendEvent($event);
+
+                    if (connection_aborted()) {
+                        break;
+                    }
+                }
+            }
+        };
+
+        return $this;
+    }
+
+    /**
+     * Sends a server event to the client.
+     *
+     * @return $this
+     */
+    public function sendEvent(ServerEvent $event): static
+    {
+        if ($this->retry > 0 && !$event->getRetry()) {
+            $event->setRetry($this->retry);
+        }
+
+        foreach ($event as $part) {
+            echo $part;
+
+            if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) {
+                static::closeOutputBuffers(0, true);
+                flush();
+            }
+        }
+
+        return $this;
+    }
+
+    public function getRetry(): ?int
+    {
+        return $this->retry;
+    }
+
+    public function setRetry(int $retry): void
+    {
+        $this->retry = $retry;
+    }
+}
diff --git a/ServerEvent.php b/ServerEvent.php
new file mode 100644
index 000000000..ea2b5c885
--- /dev/null
+++ b/ServerEvent.php
@@ -0,0 +1,147 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * An event generated on the server intended for streaming to the client
+ * as part of the SSE streaming technique.
+ *
+ * @implements \IteratorAggregate<string>
+ *
+ * @author Yonel Ceruto <open@yceruto.dev>
+ */
+class ServerEvent implements \IteratorAggregate
+{
+    /**
+     * @param string|iterable<string> $data    The event data field for the message
+     * @param string|null             $type    The event type
+     * @param int|null                $retry   The number of milliseconds the client should wait
+     *                                         before reconnecting in case of network failure
+     * @param string|null             $id      The event ID to set the EventSource object's last event ID value
+     * @param string|null             $comment The event comment
+     */
+    public function __construct(
+        private string|iterable $data,
+        private ?string $type = null,
+        private ?int $retry = null,
+        private ?string $id = null,
+        private ?string $comment = null,
+    ) {
+    }
+
+    public function getData(): iterable|string
+    {
+        return $this->data;
+    }
+
+    /**
+     * @return $this
+     */
+    public function setData(iterable|string $data): static
+    {
+        $this->data = $data;
+
+        return $this;
+    }
+
+    public function getType(): ?string
+    {
+        return $this->type;
+    }
+
+    /**
+     * @return $this
+     */
+    public function setType(string $type): static
+    {
+        $this->type = $type;
+
+        return $this;
+    }
+
+    public function getRetry(): ?int
+    {
+        return $this->retry;
+    }
+
+    /**
+     * @return $this
+     */
+    public function setRetry(?int $retry): static
+    {
+        $this->retry = $retry;
+
+        return $this;
+    }
+
+    public function getId(): ?string
+    {
+        return $this->id;
+    }
+
+    /**
+     * @return $this
+     */
+    public function setId(string $id): static
+    {
+        $this->id = $id;
+
+        return $this;
+    }
+
+    public function getComment(): ?string
+    {
+        return $this->comment;
+    }
+
+    public function setComment(string $comment): static
+    {
+        $this->comment = $comment;
+
+        return $this;
+    }
+
+    /**
+     * @return \Traversable<string>
+     */
+    public function getIterator(): \Traversable
+    {
+        static $lastRetry = null;
+
+        $head = '';
+        if ($this->comment) {
+            $head .= \sprintf(': %s', $this->comment)."\n";
+        }
+        if ($this->id) {
+            $head .= \sprintf('id: %s', $this->id)."\n";
+        }
+        if ($this->retry > 0 && $this->retry !== $lastRetry) {
+            $head .= \sprintf('retry: %s', $lastRetry = $this->retry)."\n";
+        }
+        if ($this->type) {
+            $head .= \sprintf('event: %s', $this->type)."\n";
+        }
+        yield $head;
+
+        if ($this->data) {
+            if (is_iterable($this->data)) {
+                foreach ($this->data as $data) {
+                    yield \sprintf('data: %s', $data)."\n";
+                }
+            } else {
+                yield \sprintf('data: %s', $this->data)."\n";
+            }
+        }
+
+        yield "\n";
+    }
+}
diff --git a/Tests/EventStreamResponseTest.php b/Tests/EventStreamResponseTest.php
new file mode 100644
index 000000000..4c430fbe8
--- /dev/null
+++ b/Tests/EventStreamResponseTest.php
@@ -0,0 +1,127 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\EventStreamResponse;
+use Symfony\Component\HttpFoundation\ServerEvent;
+
+class EventStreamResponseTest extends TestCase
+{
+    public function testInitializationWithDefaultValues()
+    {
+        $response = new EventStreamResponse();
+
+        $this->assertSame('text/event-stream', $response->headers->get('content-type'));
+        $this->assertSame('max-age=0, must-revalidate, no-cache, no-store, private', $response->headers->get('cache-control'));
+        $this->assertSame('keep-alive', $response->headers->get('connection'));
+
+        $this->assertSame(200, $response->getStatusCode());
+        $this->assertNull($response->getRetry());
+    }
+
+    public function testStreamSingleEvent()
+    {
+        $response = new EventStreamResponse(function () {
+            yield new ServerEvent(
+                data: 'foo',
+                type: 'bar',
+                retry: 100,
+                id: '1',
+                comment: 'bla bla',
+            );
+        });
+
+        $expected = <<<STR
+: bla bla
+id: 1
+retry: 100
+event: bar
+data: foo
+
+
+STR;
+
+        $this->assertSameResponseContent($expected, $response);
+    }
+
+    public function testStreamEventsAndData()
+    {
+        $data = static function (): iterable {
+            yield 'first line';
+            yield 'second line';
+            yield 'third line';
+        };
+
+        $response = new EventStreamResponse(function () use ($data) {
+            yield new ServerEvent('single line');
+            yield new ServerEvent(['first line', 'second line']);
+            yield new ServerEvent($data());
+        });
+
+        $expected = <<<STR
+data: single line
+
+data: first line
+data: second line
+
+data: first line
+data: second line
+data: third line
+
+
+STR;
+
+        $this->assertSameResponseContent($expected, $response);
+    }
+
+    public function testStreamEventsWithRetryFallback()
+    {
+        $response = new EventStreamResponse(function () {
+            yield new ServerEvent('foo');
+            yield new ServerEvent('bar');
+            yield new ServerEvent('baz', retry: 1000);
+        }, retry: 1500);
+
+        $expected = <<<STR
+retry: 1500
+data: foo
+
+data: bar
+
+retry: 1000
+data: baz
+
+
+STR;
+
+        $this->assertSameResponseContent($expected, $response);
+    }
+
+    public function testStreamEventWithSendMethod()
+    {
+        $response = new EventStreamResponse(function (EventStreamResponse $response) {
+            $response->sendEvent(new ServerEvent('foo'));
+        });
+
+        $this->assertSameResponseContent("data: foo\n\n", $response);
+    }
+
+    private function assertSameResponseContent(string $expected, EventStreamResponse $response): void
+    {
+        ob_start();
+        $response->send();
+        $actual = ob_get_clean();
+
+        $this->assertSame($expected, $actual);
+    }
+}

From ce130081367d3b2a4a722327f8b6c2b62da72a2a Mon Sep 17 00:00:00 2001
From: Nicolas Grekas <nicolas.grekas@gmail.com>
Date: Wed, 26 Feb 2025 18:04:06 +0100
Subject: [PATCH 32/48] Add support for `valkey:` / `valkeys:` schemes

---
 CHANGELOG.md                                      | 1 +
 Session/Storage/Handler/SessionHandlerFactory.php | 2 ++
 2 files changed, 3 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0841fa9ab..59070ee8b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@ CHANGELOG
 
  * Add support for iterable of string in `StreamedResponse`
  * Add `EventStreamResponse` and `ServerEvent` classes to streamline server event streaming
+ * Add support for `valkey:` / `valkeys:` schemes for sessions
 
 7.2
 ---
diff --git a/Session/Storage/Handler/SessionHandlerFactory.php b/Session/Storage/Handler/SessionHandlerFactory.php
index 13af07c21..cb0b6f8a9 100644
--- a/Session/Storage/Handler/SessionHandlerFactory.php
+++ b/Session/Storage/Handler/SessionHandlerFactory.php
@@ -57,6 +57,8 @@ public static function createHandler(object|string $connection, array $options =
 
             case str_starts_with($connection, 'redis:'):
             case str_starts_with($connection, 'rediss:'):
+            case str_starts_with($connection, 'valkey:'):
+            case str_starts_with($connection, 'valkeys:'):
             case str_starts_with($connection, 'memcached:'):
                 if (!class_exists(AbstractAdapter::class)) {
                     throw new \InvalidArgumentException('Unsupported Redis or Memcached DSN. Try running "composer require symfony/cache".');

From 28d6dfa2bf46226604c57c56f92e423a3d195352 Mon Sep 17 00:00:00 2001
From: Christian Flothmann <christian.flothmann@open.de>
Date: Sun, 2 Mar 2025 16:03:52 +0100
Subject: [PATCH 33/48] replace assertEmpty() with stricter assertions

---
 Tests/ParameterBagTest.php                              | 2 +-
 Tests/ResponseTest.php                                  | 2 +-
 Tests/Session/Storage/Handler/PdoSessionHandlerTest.php | 2 +-
 Tests/StreamedResponseTest.php                          | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/Tests/ParameterBagTest.php b/Tests/ParameterBagTest.php
index ac2b4da5a..5729af2c2 100644
--- a/Tests/ParameterBagTest.php
+++ b/Tests/ParameterBagTest.php
@@ -253,7 +253,7 @@ public function testFilter()
             'array' => ['bang'],
         ]);
 
-        $this->assertEmpty($bag->filter('nokey'), '->filter() should return empty by default if no key is found');
+        $this->assertSame('', $bag->filter('nokey'), '->filter() should return empty by default if no key is found');
 
         $this->assertEquals('0123', $bag->filter('digits', '', \FILTER_SANITIZE_NUMBER_INT), '->filter() gets a value of parameter as integer filtering out invalid characters');
 
diff --git a/Tests/ResponseTest.php b/Tests/ResponseTest.php
index 491a50fd5..2c761a4f8 100644
--- a/Tests/ResponseTest.php
+++ b/Tests/ResponseTest.php
@@ -127,7 +127,7 @@ public function testSetNotModified()
         ob_start();
         $modified->sendContent();
         $string = ob_get_clean();
-        $this->assertEmpty($string);
+        $this->assertSame('', $string);
     }
 
     public function testIsSuccessful()
diff --git a/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php b/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php
index 20ca3e269..0ee76ae0b 100644
--- a/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php
+++ b/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php
@@ -352,7 +352,7 @@ public function testConfigureSchemaTableExistsPdo()
         $pdoSessionHandler = new PdoSessionHandler($this->getMemorySqlitePdo());
         $pdoSessionHandler->configureSchema($schema, fn () => true);
         $table = $schema->getTable('sessions');
-        $this->assertEmpty($table->getColumns(), 'The table was not overwritten');
+        $this->assertSame([], $table->getColumns(), 'The table was not overwritten');
     }
 
     public static function provideUrlDsnPairs()
diff --git a/Tests/StreamedResponseTest.php b/Tests/StreamedResponseTest.php
index 78a777aea..2a8fe5825 100644
--- a/Tests/StreamedResponseTest.php
+++ b/Tests/StreamedResponseTest.php
@@ -133,7 +133,7 @@ public function testSetNotModified()
         ob_start();
         $modified->sendContent();
         $string = ob_get_clean();
-        $this->assertEmpty($string);
+        $this->assertSame('', $string);
     }
 
     public function testSendInformationalResponse()

From 371272aeb6286f8135e028ca535f8e4d6f114126 Mon Sep 17 00:00:00 2001
From: Christian Flothmann <christian.flothmann@open.de>
Date: Mon, 24 Mar 2025 17:30:13 +0100
Subject: [PATCH 34/48] use Table::addPrimaryKeyConstraint() with Doctrine DBAL
 4.3+

---
 Session/Storage/Handler/PdoSessionHandler.php | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/Session/Storage/Handler/PdoSessionHandler.php b/Session/Storage/Handler/PdoSessionHandler.php
index f08471e9b..e2fb4f129 100644
--- a/Session/Storage/Handler/PdoSessionHandler.php
+++ b/Session/Storage/Handler/PdoSessionHandler.php
@@ -11,6 +11,9 @@
 
 namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
 
+use Doctrine\DBAL\Schema\Name\Identifier;
+use Doctrine\DBAL\Schema\Name\UnqualifiedName;
+use Doctrine\DBAL\Schema\PrimaryKeyConstraint;
 use Doctrine\DBAL\Schema\Schema;
 use Doctrine\DBAL\Types\Types;
 
@@ -224,7 +227,13 @@ public function configureSchema(Schema $schema, ?\Closure $isSameDatabase = null
             default:
                 throw new \DomainException(\sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver));
         }
-        $table->setPrimaryKey([$this->idCol]);
+
+        if (class_exists(PrimaryKeyConstraint::class)) {
+            $table->addPrimaryKeyConstraint(new PrimaryKeyConstraint(null, [new UnqualifiedName(Identifier::unquoted($this->idCol))], true));
+        } else {
+            $table->setPrimaryKey([$this->idCol]);
+        }
+
         $table->addIndex([$this->lifetimeCol], $this->lifetimeCol.'_idx');
     }
 

From 6f7fb440036ce73bfb46ceac0bdefaae1ccc6b14 Mon Sep 17 00:00:00 2001
From: chillbram <7299762+chillbram@users.noreply.github.com>
Date: Thu, 3 Apr 2025 21:52:33 +0200
Subject: [PATCH 35/48] [HttpFoundation] Follow language preferences more
 accurately in `getPreferredLanguage()`

---
 CHANGELOG.md          |  1 +
 Request.php           |  4 ----
 Tests/RequestTest.php | 12 ++++++------
 3 files changed, 7 insertions(+), 10 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 59070ee8b..2d8065ba5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ CHANGELOG
  * Add support for iterable of string in `StreamedResponse`
  * Add `EventStreamResponse` and `ServerEvent` classes to streamline server event streaming
  * Add support for `valkey:` / `valkeys:` schemes for sessions
+ * `Request::getPreferredLanguage()` now favors a more preferred language above exactly matching a locale
 
 7.2
 ---
diff --git a/Request.php b/Request.php
index db78105cc..9f421525d 100644
--- a/Request.php
+++ b/Request.php
@@ -1553,10 +1553,6 @@ public function getPreferredLanguage(?array $locales = null): ?string
             return $locales[0];
         }
 
-        if ($matches = array_intersect($preferredLanguages, $locales)) {
-            return current($matches);
-        }
-
         $combinations = array_merge(...array_map($this->getLanguageCombinations(...), $preferredLanguages));
         foreach ($combinations as $combination) {
             foreach ($locales as $locale) {
diff --git a/Tests/RequestTest.php b/Tests/RequestTest.php
index d5a41390e..bb4eeb3b6 100644
--- a/Tests/RequestTest.php
+++ b/Tests/RequestTest.php
@@ -1550,16 +1550,16 @@ public static function providePreferredLanguage(): iterable
         yield '"fr" selected as first choice when no header is present' => ['fr', null, ['fr', 'en']];
         yield '"en" selected as first choice when no header is present' => ['en', null, ['en', 'fr']];
         yield '"fr_CH" selected as first choice when no header is present' => ['fr_CH', null, ['fr-ch', 'fr-fr']];
-        yield '"en_US" is selected as an exact match is found (1)' => ['en_US', 'zh, en-us; q=0.8, en; q=0.6', ['en', 'en-us']];
-        yield '"en_US" is selected as an exact match is found (2)' => ['en_US', 'ja-JP,fr_CA;q=0.7,fr;q=0.5,en_US;q=0.3', ['en_US', 'fr_FR']];
-        yield '"en" is selected as an exact match is found' => ['en', 'zh, en-us; q=0.8, en; q=0.6', ['fr', 'en']];
-        yield '"fr" is selected as an exact match is found' => ['fr', 'zh, en-us; q=0.8, fr-fr; q=0.6, fr; q=0.5', ['fr', 'en']];
+        yield '"en_US" is selected as an exact match is found' => ['en_US', 'zh, en-us; q=0.8, en; q=0.6', ['en', 'en-us']];
+        yield '"fr_FR" is selected as it has a higher priority than an exact match' => ['fr_FR', 'ja-JP,fr_CA;q=0.7,fr;q=0.5,en_US;q=0.3', ['en_US', 'fr_FR']];
+        yield '"en" is selected as an exact match is found (1)' => ['en', 'zh, en-us; q=0.8, en; q=0.6', ['fr', 'en']];
+        yield '"en" is selected as an exact match is found (2)' => ['en', 'zh, en-us; q=0.8, fr-fr; q=0.6, fr; q=0.5', ['fr', 'en']];
         yield '"en" is selected as "en-us" is a similar dialect' => ['en', 'zh, en-us; q=0.8', ['fr', 'en']];
         yield '"fr_FR" is selected as "fr_CA" is a similar dialect (1)' => ['fr_FR', 'ja-JP,fr_CA;q=0.7,fr;q=0.5', ['en_US', 'fr_FR']];
         yield '"fr_FR" is selected as "fr_CA" is a similar dialect (2)' => ['fr_FR', 'ja-JP,fr_CA;q=0.7', ['en_US', 'fr_FR']];
-        yield '"fr_FR" is selected as "fr" is a similar dialect' => ['fr_FR', 'ja-JP,fr;q=0.5', ['en_US', 'fr_FR']];
+        yield '"fr_FR" is selected as "fr" is a similar dialect (1)' => ['fr_FR', 'ja-JP,fr;q=0.5', ['en_US', 'fr_FR']];
+        yield '"fr_FR" is selected as "fr" is a similar dialect (2)' => ['fr_FR', 'ja-JP,fr;q=0.5,en_US;q=0.3', ['en_US', 'fr_FR']];
         yield '"fr_FR" is selected as "fr_CA" is a similar dialect and has a greater "q" compared to "en_US" (2)' => ['fr_FR', 'ja-JP,fr_CA;q=0.7,ru-ru;q=0.3', ['en_US', 'fr_FR']];
-        yield '"en_US" is selected it is an exact match' => ['en_US', 'ja-JP,fr;q=0.5,en_US;q=0.3', ['en_US', 'fr_FR']];
         yield '"fr_FR" is selected as "fr_CA" is a similar dialect and has a greater "q" compared to "en"' => ['fr_FR', 'ja-JP,fr_CA;q=0.7,en;q=0.5', ['en_US', 'fr_FR']];
         yield '"fr_FR" is selected as is is an exact match as well as "en_US", but with a greater "q" parameter' => ['fr_FR', 'en-us;q=0.5,fr-fr', ['en_US', 'fr_FR']];
         yield '"hi_IN" is selected as "hi_Latn_IN" is a similar dialect' => ['hi_IN', 'fr-fr,hi_Latn_IN;q=0.5', ['hi_IN', 'en_US']];

From b0e234d4907616eda45c00ed479bc4074017b7df Mon Sep 17 00:00:00 2001
From: timesince <seekseat@icloud.com>
Date: Wed, 9 Apr 2025 13:59:35 +0800
Subject: [PATCH 36/48] chore: fix some typos

Signed-off-by: timesince <seekseat@icloud.com>
---
 Tests/InputBagTest.php     | 2 +-
 Tests/ParameterBagTest.php | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/Tests/InputBagTest.php b/Tests/InputBagTest.php
index e2112726e..d1e9015f1 100644
--- a/Tests/InputBagTest.php
+++ b/Tests/InputBagTest.php
@@ -78,7 +78,7 @@ public function __toString(): string
         $this->assertSame('foo', $bag->getString('unknown', 'foo'), '->getString() returns the default if a parameter is not defined');
         $this->assertSame('1', $bag->getString('bool_true'), '->getString() returns "1" if a parameter is true');
         $this->assertSame('', $bag->getString('bool_false', 'foo'), '->getString() returns an empty empty string if a parameter is false');
-        $this->assertSame('strval', $bag->getString('stringable'), '->getString() gets a value of a stringable paramater as string');
+        $this->assertSame('strval', $bag->getString('stringable'), '->getString() gets a value of a stringable parameter as string');
     }
 
     public function testGetStringExceptionWithArray()
diff --git a/Tests/ParameterBagTest.php b/Tests/ParameterBagTest.php
index 42c1b67da..ad0cf99bf 100644
--- a/Tests/ParameterBagTest.php
+++ b/Tests/ParameterBagTest.php
@@ -226,7 +226,7 @@ public function __toString(): string
         $this->assertSame('foo', $bag->getString('unknown', 'foo'), '->getString() returns the default if a parameter is not defined');
         $this->assertSame('1', $bag->getString('bool_true'), '->getString() returns "1" if a parameter is true');
         $this->assertSame('', $bag->getString('bool_false', 'foo'), '->getString() returns an empty empty string if a parameter is false');
-        $this->assertSame('strval', $bag->getString('stringable'), '->getString() gets a value of a stringable paramater as string');
+        $this->assertSame('strval', $bag->getString('stringable'), '->getString() gets a value of a stringable parameter as string');
     }
 
     public function testGetStringExceptionWithArray()

From 54169d58f79677af67bde50657f0f7620075bd47 Mon Sep 17 00:00:00 2001
From: Kevin Bond <kevinbond@gmail.com>
Date: Tue, 15 Apr 2025 11:04:08 -0400
Subject: [PATCH 37/48] [HttpFoundation][FrameworkBundle] clock support for
 `UriSigner`

---
 CHANGELOG.md            |  1 +
 Tests/UriSignerTest.php | 24 ++++++++++++++++++++++++
 UriSigner.php           | 11 +++++++++--
 composer.json           |  1 +
 4 files changed, 35 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2d8065ba5..5410cba63 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ CHANGELOG
  * Add `EventStreamResponse` and `ServerEvent` classes to streamline server event streaming
  * Add support for `valkey:` / `valkeys:` schemes for sessions
  * `Request::getPreferredLanguage()` now favors a more preferred language above exactly matching a locale
+ * Allow `UriSigner` to use a `ClockInterface`
 
 7.2
 ---
diff --git a/Tests/UriSignerTest.php b/Tests/UriSignerTest.php
index 927e2bda8..85a0b727c 100644
--- a/Tests/UriSignerTest.php
+++ b/Tests/UriSignerTest.php
@@ -12,6 +12,7 @@
 namespace Symfony\Component\HttpFoundation\Tests;
 
 use PHPUnit\Framework\TestCase;
+use Symfony\Component\Clock\MockClock;
 use Symfony\Component\HttpFoundation\Exception\LogicException;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\UriSigner;
@@ -199,6 +200,29 @@ public function testCheckWithUriExpiration()
         $this->assertFalse($signer->check($relativeUriFromNow3));
     }
 
+    public function testCheckWithUriExpirationWithClock()
+    {
+        $clock = new MockClock();
+        $signer = new UriSigner('foobar', clock: $clock);
+
+        $this->assertFalse($signer->check($signer->sign('http://example.com/foo', new \DateTimeImmutable('2000-01-01 00:00:00'))));
+        $this->assertFalse($signer->check($signer->sign('http://example.com/foo?foo=bar', new \DateTimeImmutable('2000-01-01 00:00:00'))));
+        $this->assertFalse($signer->check($signer->sign('http://example.com/foo?foo=bar&0=integer', new \DateTimeImmutable('2000-01-01 00:00:00'))));
+
+        $this->assertFalse($signer->check($signer->sign('http://example.com/foo', 1577836800))); // 2000-01-01
+        $this->assertFalse($signer->check($signer->sign('http://example.com/foo?foo=bar', 1577836800))); // 2000-01-01
+        $this->assertFalse($signer->check($signer->sign('http://example.com/foo?foo=bar&0=integer', 1577836800))); // 2000-01-01
+
+        $relativeUriFromNow1 = $signer->sign('http://example.com/foo', new \DateInterval('PT3S'));
+        $relativeUriFromNow2 = $signer->sign('http://example.com/foo?foo=bar', new \DateInterval('PT3S'));
+        $relativeUriFromNow3 = $signer->sign('http://example.com/foo?foo=bar&0=integer', new \DateInterval('PT3S'));
+        $clock->sleep(10);
+
+        $this->assertFalse($signer->check($relativeUriFromNow1));
+        $this->assertFalse($signer->check($relativeUriFromNow2));
+        $this->assertFalse($signer->check($relativeUriFromNow3));
+    }
+
     public function testNonUrlSafeBase64()
     {
         $signer = new UriSigner('foobar');
diff --git a/UriSigner.php b/UriSigner.php
index 1c9e25a5c..b1109ae69 100644
--- a/UriSigner.php
+++ b/UriSigner.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\HttpFoundation;
 
+use Psr\Clock\ClockInterface;
 use Symfony\Component\HttpFoundation\Exception\LogicException;
 
 /**
@@ -26,6 +27,7 @@ public function __construct(
         #[\SensitiveParameter] private string $secret,
         private string $hashParameter = '_hash',
         private string $expirationParameter = '_expiration',
+        private ?ClockInterface $clock = null,
     ) {
         if (!$secret) {
             throw new \InvalidArgumentException('A non-empty secret is required.');
@@ -109,7 +111,7 @@ public function check(string $uri): bool
         }
 
         if ($expiration = $params[$this->expirationParameter] ?? false) {
-            return time() < $expiration;
+            return $this->now()->getTimestamp() < $expiration;
         }
 
         return true;
@@ -153,9 +155,14 @@ private function getExpirationTime(\DateTimeInterface|\DateInterval|int $expirat
         }
 
         if ($expiration instanceof \DateInterval) {
-            return \DateTimeImmutable::createFromFormat('U', time())->add($expiration)->format('U');
+            return $this->now()->add($expiration)->format('U');
         }
 
         return (string) $expiration;
     }
+
+    private function now(): \DateTimeImmutable
+    {
+        return $this->clock?->now() ?? \DateTimeImmutable::createFromFormat('U', time());
+    }
 }
diff --git a/composer.json b/composer.json
index cb2bbf8cb..a86b21b7c 100644
--- a/composer.json
+++ b/composer.json
@@ -25,6 +25,7 @@
         "doctrine/dbal": "^3.6|^4",
         "predis/predis": "^1.1|^2.0",
         "symfony/cache": "^6.4.12|^7.1.5",
+        "symfony/clock": "^6.4|^7.0",
         "symfony/dependency-injection": "^6.4|^7.0",
         "symfony/http-kernel": "^6.4|^7.0",
         "symfony/mime": "^6.4|^7.0",

From 5a69e812075d8bae5da68d8e0ffa66699e556590 Mon Sep 17 00:00:00 2001
From: Kevin Bond <kevinbond@gmail.com>
Date: Mon, 31 Mar 2025 18:06:51 -0400
Subject: [PATCH 38/48] [HttpFoundation] Add `UriSigner::verify()` that throws
 named exceptions

---
 CHANGELOG.md                               |   1 +
 Exception/ExpiredSignedUriException.php    |  26 ++++++
 Exception/SignedUriException.php           |  19 ++++
 Exception/UnsignedUriException.php         |  26 ++++++
 Exception/UnverifiedSignedUriException.php |  26 ++++++
 Tests/UriSignerTest.php                    |  33 +++++++
 UriSigner.php                              | 103 ++++++++++++++++-----
 7 files changed, 210 insertions(+), 24 deletions(-)
 create mode 100644 Exception/ExpiredSignedUriException.php
 create mode 100644 Exception/SignedUriException.php
 create mode 100644 Exception/UnsignedUriException.php
 create mode 100644 Exception/UnverifiedSignedUriException.php

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5410cba63..374c31889 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ CHANGELOG
  * Add support for `valkey:` / `valkeys:` schemes for sessions
  * `Request::getPreferredLanguage()` now favors a more preferred language above exactly matching a locale
  * Allow `UriSigner` to use a `ClockInterface`
+ * Add `UriSigner::verify()`
 
 7.2
 ---
diff --git a/Exception/ExpiredSignedUriException.php b/Exception/ExpiredSignedUriException.php
new file mode 100644
index 000000000..613e08ef4
--- /dev/null
+++ b/Exception/ExpiredSignedUriException.php
@@ -0,0 +1,26 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Exception;
+
+/**
+ * @author Kevin Bond <kevinbond@gmail.com>
+ */
+final class ExpiredSignedUriException extends SignedUriException
+{
+    /**
+     * @internal
+     */
+    public function __construct()
+    {
+        parent::__construct('The URI has expired.');
+    }
+}
diff --git a/Exception/SignedUriException.php b/Exception/SignedUriException.php
new file mode 100644
index 000000000..17b729d31
--- /dev/null
+++ b/Exception/SignedUriException.php
@@ -0,0 +1,19 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Exception;
+
+/**
+ * @author Kevin Bond <kevinbond@gmail.com>
+ */
+abstract class SignedUriException extends \RuntimeException implements ExceptionInterface
+{
+}
diff --git a/Exception/UnsignedUriException.php b/Exception/UnsignedUriException.php
new file mode 100644
index 000000000..5eabb806b
--- /dev/null
+++ b/Exception/UnsignedUriException.php
@@ -0,0 +1,26 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Exception;
+
+/**
+ * @author Kevin Bond <kevinbond@gmail.com>
+ */
+final class UnsignedUriException extends SignedUriException
+{
+    /**
+     * @internal
+     */
+    public function __construct()
+    {
+        parent::__construct('The URI is not signed.');
+    }
+}
diff --git a/Exception/UnverifiedSignedUriException.php b/Exception/UnverifiedSignedUriException.php
new file mode 100644
index 000000000..cc7e98bf2
--- /dev/null
+++ b/Exception/UnverifiedSignedUriException.php
@@ -0,0 +1,26 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Exception;
+
+/**
+ * @author Kevin Bond <kevinbond@gmail.com>
+ */
+final class UnverifiedSignedUriException extends SignedUriException
+{
+    /**
+     * @internal
+     */
+    public function __construct()
+    {
+        parent::__construct('The URI signature is invalid.');
+    }
+}
diff --git a/Tests/UriSignerTest.php b/Tests/UriSignerTest.php
index 85a0b727c..81b35c28e 100644
--- a/Tests/UriSignerTest.php
+++ b/Tests/UriSignerTest.php
@@ -13,7 +13,10 @@
 
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Clock\MockClock;
+use Symfony\Component\HttpFoundation\Exception\ExpiredSignedUriException;
 use Symfony\Component\HttpFoundation\Exception\LogicException;
+use Symfony\Component\HttpFoundation\Exception\UnsignedUriException;
+use Symfony\Component\HttpFoundation\Exception\UnverifiedSignedUriException;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\UriSigner;
 
@@ -228,4 +231,34 @@ public function testNonUrlSafeBase64()
         $signer = new UriSigner('foobar');
         $this->assertTrue($signer->check('http://example.com/foo?_hash=rIOcC%2FF3DoEGo%2FvnESjSp7uU9zA9S%2F%2BOLhxgMexoPUM%3D&baz=bay&foo=bar'));
     }
+
+    public function testVerifyUnSignedUri()
+    {
+        $signer = new UriSigner('foobar');
+        $uri = 'http://example.com/foo';
+
+        $this->expectException(UnsignedUriException::class);
+
+        $signer->verify($uri);
+    }
+
+    public function testVerifyUnverifiedUri()
+    {
+        $signer = new UriSigner('foobar');
+        $uri = 'http://example.com/foo?_hash=invalid';
+
+        $this->expectException(UnverifiedSignedUriException::class);
+
+        $signer->verify($uri);
+    }
+
+    public function testVerifyExpiredUri()
+    {
+        $signer = new UriSigner('foobar');
+        $uri = $signer->sign('http://example.com/foo', 123456);
+
+        $this->expectException(ExpiredSignedUriException::class);
+
+        $signer->verify($uri);
+    }
 }
diff --git a/UriSigner.php b/UriSigner.php
index b1109ae69..bb870e43c 100644
--- a/UriSigner.php
+++ b/UriSigner.php
@@ -12,13 +12,22 @@
 namespace Symfony\Component\HttpFoundation;
 
 use Psr\Clock\ClockInterface;
+use Symfony\Component\HttpFoundation\Exception\ExpiredSignedUriException;
 use Symfony\Component\HttpFoundation\Exception\LogicException;
+use Symfony\Component\HttpFoundation\Exception\SignedUriException;
+use Symfony\Component\HttpFoundation\Exception\UnsignedUriException;
+use Symfony\Component\HttpFoundation\Exception\UnverifiedSignedUriException;
 
 /**
  * @author Fabien Potencier <fabien@symfony.com>
  */
 class UriSigner
 {
+    private const STATUS_VALID = 1;
+    private const STATUS_INVALID = 2;
+    private const STATUS_MISSING = 3;
+    private const STATUS_EXPIRED = 4;
+
     /**
      * @param string $hashParameter       Query string parameter to use
      * @param string $expirationParameter Query string parameter to use for expiration
@@ -91,38 +100,40 @@ public function sign(string $uri/* , \DateTimeInterface|\DateInterval|int|null $
      */
     public function check(string $uri): bool
     {
-        $url = parse_url($uri);
-        $params = [];
-
-        if (isset($url['query'])) {
-            parse_str($url['query'], $params);
-        }
+        return self::STATUS_VALID === $this->doVerify($uri);
+    }
 
-        if (empty($params[$this->hashParameter])) {
-            return false;
-        }
+    public function checkRequest(Request $request): bool
+    {
+        return self::STATUS_VALID === $this->doVerify(self::normalize($request));
+    }
 
-        $hash = $params[$this->hashParameter];
-        unset($params[$this->hashParameter]);
+    /**
+     * Verify a Request or string URI.
+     *
+     * @throws UnsignedUriException         If the URI is not signed
+     * @throws UnverifiedSignedUriException If the signature is invalid
+     * @throws ExpiredSignedUriException    If the URI has expired
+     * @throws SignedUriException
+     */
+    public function verify(Request|string $uri): void
+    {
+        $uri = self::normalize($uri);
+        $status = $this->doVerify($uri);
 
-        // In 8.0, remove support for non-url-safe tokens
-        if (!hash_equals($this->computeHash($this->buildUrl($url, $params)), strtr(rtrim($hash, '='), ['/' => '_', '+' => '-']))) {
-            return false;
+        if (self::STATUS_VALID === $status) {
+            return;
         }
 
-        if ($expiration = $params[$this->expirationParameter] ?? false) {
-            return $this->now()->getTimestamp() < $expiration;
+        if (self::STATUS_MISSING === $status) {
+            throw new UnsignedUriException();
         }
 
-        return true;
-    }
-
-    public function checkRequest(Request $request): bool
-    {
-        $qs = ($qs = $request->server->get('QUERY_STRING')) ? '?'.$qs : '';
+        if (self::STATUS_INVALID === $status) {
+            throw new UnverifiedSignedUriException();
+        }
 
-        // we cannot use $request->getUri() here as we want to work with the original URI (no query string reordering)
-        return $this->check($request->getSchemeAndHttpHost().$request->getBaseUrl().$request->getPathInfo().$qs);
+        throw new ExpiredSignedUriException();
     }
 
     private function computeHash(string $uri): string
@@ -165,4 +176,48 @@ private function now(): \DateTimeImmutable
     {
         return $this->clock?->now() ?? \DateTimeImmutable::createFromFormat('U', time());
     }
+
+    /**
+     * @return self::STATUS_*
+     */
+    private function doVerify(string $uri): int
+    {
+        $url = parse_url($uri);
+        $params = [];
+
+        if (isset($url['query'])) {
+            parse_str($url['query'], $params);
+        }
+
+        if (empty($params[$this->hashParameter])) {
+            return self::STATUS_MISSING;
+        }
+
+        $hash = $params[$this->hashParameter];
+        unset($params[$this->hashParameter]);
+
+        if (!hash_equals($this->computeHash($this->buildUrl($url, $params)), strtr(rtrim($hash, '='), ['/' => '_', '+' => '-']))) {
+            return self::STATUS_INVALID;
+        }
+
+        if (!$expiration = $params[$this->expirationParameter] ?? false) {
+            return self::STATUS_VALID;
+        }
+
+        if ($this->now()->getTimestamp() < $expiration) {
+            return self::STATUS_VALID;
+        }
+
+        return self::STATUS_EXPIRED;
+    }
+
+    private static function normalize(Request|string $uri): string
+    {
+        if ($uri instanceof Request) {
+            $qs = ($qs = $uri->server->get('QUERY_STRING')) ? '?'.$qs : '';
+            $uri = $uri->getSchemeAndHttpHost().$uri->getBaseUrl().$uri->getPathInfo().$qs;
+        }
+
+        return $uri;
+    }
 }

From abbe5faf754aebc557c4da9c8e2780b4f094c5ce Mon Sep 17 00:00:00 2001
From: Mathias Arlaud <mathias.arlaud@gmail.com>
Date: Thu, 24 Apr 2025 08:52:37 +0200
Subject: [PATCH 39/48] [HttpFoundation] Flush after each echo in
 `StreamedResponse`

---
 StreamedResponse.php           | 2 ++
 Tests/StreamedResponseTest.php | 8 ++++++--
 2 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/StreamedResponse.php b/StreamedResponse.php
index 6eedf1c49..4e755a7cd 100644
--- a/StreamedResponse.php
+++ b/StreamedResponse.php
@@ -56,6 +56,8 @@ public function setChunks(iterable $chunks): static
         $this->callback = static function () use ($chunks): void {
             foreach ($chunks as $chunk) {
                 echo $chunk;
+                @ob_flush();
+                flush();
             }
         };
 
diff --git a/Tests/StreamedResponseTest.php b/Tests/StreamedResponseTest.php
index 2a8fe5825..fdaee3a35 100644
--- a/Tests/StreamedResponseTest.php
+++ b/Tests/StreamedResponseTest.php
@@ -30,10 +30,14 @@ public function testConstructorWithChunks()
         $chunks = ['foo', 'bar', 'baz'];
         $callback = (new StreamedResponse($chunks))->getCallback();
 
-        ob_start();
+        $buffer = '';
+        ob_start(function (string $chunk) use (&$buffer) {
+            $buffer .= $chunk;
+        });
         $callback();
 
-        $this->assertSame('foobarbaz', ob_get_clean());
+        ob_get_clean();
+        $this->assertSame('foobarbaz', $buffer);
     }
 
     public function testPrepareWith11Protocol()

From 3f0c7ea41db479383b81d436b836d37168fd5b99 Mon Sep 17 00:00:00 2001
From: Fabien Potencier <fabien@potencier.org>
Date: Sun, 27 Apr 2025 15:26:02 +0200
Subject: [PATCH 40/48] Remove unneeded use statements

---
 Tests/RateLimiter/AbstractRequestRateLimiterTest.php | 1 -
 Tests/Session/Storage/Proxy/AbstractProxyTest.php    | 1 -
 2 files changed, 2 deletions(-)

diff --git a/Tests/RateLimiter/AbstractRequestRateLimiterTest.php b/Tests/RateLimiter/AbstractRequestRateLimiterTest.php
index 26f2fac90..087d7aeae 100644
--- a/Tests/RateLimiter/AbstractRequestRateLimiterTest.php
+++ b/Tests/RateLimiter/AbstractRequestRateLimiterTest.php
@@ -14,7 +14,6 @@
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\RateLimiter\LimiterInterface;
-use Symfony\Component\RateLimiter\Policy\NoLimiter;
 use Symfony\Component\RateLimiter\RateLimit;
 
 class AbstractRequestRateLimiterTest extends TestCase
diff --git a/Tests/Session/Storage/Proxy/AbstractProxyTest.php b/Tests/Session/Storage/Proxy/AbstractProxyTest.php
index bb459bb9f..8d04830a7 100644
--- a/Tests/Session/Storage/Proxy/AbstractProxyTest.php
+++ b/Tests/Session/Storage/Proxy/AbstractProxyTest.php
@@ -11,7 +11,6 @@
 
 namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Proxy;
 
-use PHPUnit\Framework\MockObject\MockObject;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
 use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;

From 7965dc6fde8d57a626988a94f5447dcb47e8dca0 Mon Sep 17 00:00:00 2001
From: wkania <wojciech.kania+github@gmail.com>
Date: Sun, 27 Apr 2025 16:24:15 +0200
Subject: [PATCH 41/48] Fix overwriting an array element

---
 Tests/RequestTest.php | 2 --
 1 file changed, 2 deletions(-)

diff --git a/Tests/RequestTest.php b/Tests/RequestTest.php
index 7a4807ecf..f1aa0ebea 100644
--- a/Tests/RequestTest.php
+++ b/Tests/RequestTest.php
@@ -604,7 +604,6 @@ public function testGetUri()
 
         $server['REDIRECT_QUERY_STRING'] = 'query=string';
         $server['REDIRECT_URL'] = '/path/info';
-        $server['SCRIPT_NAME'] = '/index.php';
         $server['QUERY_STRING'] = 'query=string';
         $server['REQUEST_URI'] = '/path/info?toto=test&1=1';
         $server['SCRIPT_NAME'] = '/index.php';
@@ -731,7 +730,6 @@ public function testGetUriForPath()
 
         $server['REDIRECT_QUERY_STRING'] = 'query=string';
         $server['REDIRECT_URL'] = '/path/info';
-        $server['SCRIPT_NAME'] = '/index.php';
         $server['QUERY_STRING'] = 'query=string';
         $server['REQUEST_URI'] = '/path/info?toto=test&1=1';
         $server['SCRIPT_NAME'] = '/index.php';

From 90313c6e0b8955dbde57f450ac7a484c2dc592b9 Mon Sep 17 00:00:00 2001
From: ivelin vasilev <i.d.vasilevv@gmail.com>
Date: Thu, 8 May 2025 01:06:34 +0300
Subject: [PATCH 42/48] [HttpFoundation] Emit PHP warning when
 Response::sendHeaders() while output has already been sent

---
 Response.php | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/Response.php b/Response.php
index 638b5bf60..6766f2c77 100644
--- a/Response.php
+++ b/Response.php
@@ -317,6 +317,11 @@ public function sendHeaders(?int $statusCode = null): static
     {
         // headers have already been sent by the developer
         if (headers_sent()) {
+            if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) {
+                $statusCode ??= $this->statusCode;
+                header(\sprintf('HTTP/%s %s %s', $this->version, $statusCode, $this->statusText), true, $statusCode);
+            }
+
             return $this;
         }
 

From 6b7c97fe1ddac8df3cc9ba6410c8abc683e148ae Mon Sep 17 00:00:00 2001
From: Athorcis <Athorcis@users.noreply.github.com>
Date: Mon, 28 Apr 2025 13:34:00 +0200
Subject: [PATCH 43/48] [HttpFoundation] Fix: Encode path in X-Accel-Redirect
 header

we need to encode the path in X-Accel-Redirect header, otherwise nginx fail when certain characters are present in it (like % or ?)

https://github.com/rack/rack/issues/1306
---
 BinaryFileResponse.php           |  2 +-
 Tests/BinaryFileResponseTest.php | 11 ++++++++++-
 2 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/BinaryFileResponse.php b/BinaryFileResponse.php
index 41a244b81..c22f283cb 100644
--- a/BinaryFileResponse.php
+++ b/BinaryFileResponse.php
@@ -229,7 +229,7 @@ public function prepare(Request $request): static
                         $path = $location.substr($path, \strlen($pathPrefix));
                         // Only set X-Accel-Redirect header if a valid URI can be produced
                         // as nginx does not serve arbitrary file paths.
-                        $this->headers->set($type, $path);
+                        $this->headers->set($type, rawurlencode($path));
                         $this->maxlen = 0;
                         break;
                     }
diff --git a/Tests/BinaryFileResponseTest.php b/Tests/BinaryFileResponseTest.php
index c7d47a4d7..8f298b77f 100644
--- a/Tests/BinaryFileResponseTest.php
+++ b/Tests/BinaryFileResponseTest.php
@@ -314,7 +314,15 @@ public function testXAccelMapping($realpath, $mapping, $virtual)
         $property->setValue($response, $file);
 
         $response->prepare($request);
-        $this->assertEquals($virtual, $response->headers->get('X-Accel-Redirect'));
+        $header = $response->headers->get('X-Accel-Redirect');
+
+        if ($virtual) {
+            // Making sure the path doesn't contain characters unsupported by nginx
+            $this->assertMatchesRegularExpression('/^([^?%]|%[0-9A-F]{2})*$/', $header);
+            $header = rawurldecode($header);
+        }
+
+        $this->assertEquals($virtual, $header);
     }
 
     public function testDeleteFileAfterSend()
@@ -361,6 +369,7 @@ public static function getSampleXAccelMappings()
             ['/home/Foo/bar.txt', '/var/www/=/files/,/home/Foo/=/baz/', '/baz/bar.txt'],
             ['/home/Foo/bar.txt', '"/var/www/"="/files/", "/home/Foo/"="/baz/"', '/baz/bar.txt'],
             ['/tmp/bar.txt', '"/var/www/"="/files/", "/home/Foo/"="/baz/"', null],
+            ['/var/www/var/www/files/foo%.txt', '/var/www/=/files/', '/files/var/www/files/foo%.txt'],
         ];
     }
 

From 452d19f945ee41345fd8a50c18b60783546b7bd3 Mon Sep 17 00:00:00 2001
From: thecaliskan <bilgi@emrecaliskan.com.tr>
Date: Mon, 26 May 2025 11:39:29 +0300
Subject: [PATCH 44/48] fixed Via regex

---
 Request.php           | 2 +-
 Tests/RequestTest.php | 2 ++
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/Request.php b/Request.php
index 922014133..42a3a8a2c 100644
--- a/Request.php
+++ b/Request.php
@@ -1466,7 +1466,7 @@ public function isMethodCacheable(): bool
     public function getProtocolVersion(): ?string
     {
         if ($this->isFromTrustedProxy()) {
-            preg_match('~^(HTTP/)?([1-9]\.[0-9]) ~', $this->headers->get('Via') ?? '', $matches);
+            preg_match('~^(HTTP/)?([1-9]\.[0-9])\b~', $this->headers->get('Via') ?? '', $matches);
 
             if ($matches) {
                 return 'HTTP/'.$matches[2];
diff --git a/Tests/RequestTest.php b/Tests/RequestTest.php
index f1aa0ebea..a2eace70e 100644
--- a/Tests/RequestTest.php
+++ b/Tests/RequestTest.php
@@ -2402,6 +2402,8 @@ public static function protocolVersionProvider()
             'trusted with via and protocol name' => ['HTTP/2.0', true, 'HTTP/1.0 fred, HTTP/1.1 nowhere.com (Apache/1.1)', 'HTTP/1.0'],
             'trusted with broken via' => ['HTTP/2.0', true, 'HTTP/1^0 foo', 'HTTP/2.0'],
             'trusted with partially-broken via' => ['HTTP/2.0', true, '1.0 fred, foo', 'HTTP/1.0'],
+            'trusted with simple via' => ['HTTP/2.0', true, 'HTTP/1.0', 'HTTP/1.0'],
+            'trusted with only version via' => ['HTTP/2.0', true, '1.0', 'HTTP/1.0'],
         ];
     }
 

From 23dd60256610c86a3414575b70c596e5deff6ed9 Mon Sep 17 00:00:00 2001
From: Nicolas Grekas <nicolas.grekas@gmail.com>
Date: Mon, 23 Jun 2025 17:07:14 +0200
Subject: [PATCH 45/48] Revert "minor #60377 [HttpFoundation] Emit PHP warning
 when `Response::sendHeaders()` is called while output has already been sent
 (ivo95v)"

This reverts commit cf554e1be6338c1176832dd4cdf713d88d3144ad, reversing
changes made to 392d0c9c407d6b06a072600fe9dc2a779b231ce0.
---
 Response.php | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/Response.php b/Response.php
index 6766f2c77..638b5bf60 100644
--- a/Response.php
+++ b/Response.php
@@ -317,11 +317,6 @@ public function sendHeaders(?int $statusCode = null): static
     {
         // headers have already been sent by the developer
         if (headers_sent()) {
-            if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) {
-                $statusCode ??= $this->statusCode;
-                header(\sprintf('HTTP/%s %s %s', $this->version, $statusCode, $this->statusText), true, $statusCode);
-            }
-
             return $this;
         }
 

From 95f9645b246360f94dfb6df99f9c8a4a666df1f6 Mon Sep 17 00:00:00 2001
From: Benjamin Pick <benjaminpick@users.noreply.github.com>
Date: Fri, 4 Jul 2025 21:54:13 +0200
Subject: [PATCH 46/48] Fix php.net links

---
 Session/Storage/NativeSessionStorage.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Session/Storage/NativeSessionStorage.php b/Session/Storage/NativeSessionStorage.php
index f63de5740..7b0c5beca 100644
--- a/Session/Storage/NativeSessionStorage.php
+++ b/Session/Storage/NativeSessionStorage.php
@@ -139,7 +139,7 @@ public function start(): bool
          * ---------- Part 1
          *
          * The part `[a-zA-Z0-9,-]` is related to the PHP ini directive `session.sid_bits_per_character` defined as 6.
-         * See https://www.php.net/manual/en/session.configuration.php#ini.session.sid-bits-per-character.
+         * See https://php.net/session.configuration#ini.session.sid-bits-per-character
          * Allowed values are integers such as:
          * - 4 for range `a-f0-9`
          * - 5 for range `a-v0-9`
@@ -148,7 +148,7 @@ public function start(): bool
          * ---------- Part 2
          *
          * The part `{22,250}` is related to the PHP ini directive `session.sid_length`.
-         * See https://www.php.net/manual/en/session.configuration.php#ini.session.sid-length.
+         * See https://php.net/session.configuration#ini.session.sid-length
          * Allowed values are integers between 22 and 256, but we use 250 for the max.
          *
          * Where does the 250 come from?

From bc55079ffe0e769739ebaf6576fc696b551a0cf5 Mon Sep 17 00:00:00 2001
From: Nicolas Grekas <nicolas.grekas@gmail.com>
Date: Wed, 9 Jul 2025 11:39:35 +0200
Subject: [PATCH 47/48] [HttpFoundation] Fix deprecation in tests on PHP 8.5

---
 Tests/StreamedResponseTest.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Tests/StreamedResponseTest.php b/Tests/StreamedResponseTest.php
index fdaee3a35..584353b7f 100644
--- a/Tests/StreamedResponseTest.php
+++ b/Tests/StreamedResponseTest.php
@@ -32,7 +32,7 @@ public function testConstructorWithChunks()
 
         $buffer = '';
         ob_start(function (string $chunk) use (&$buffer) {
-            $buffer .= $chunk;
+            return $buffer .= $chunk;
         });
         $callback();
 

From 0341e41d8d8830c31a1dff5cbc5bdb3ec872a073 Mon Sep 17 00:00:00 2001
From: Nicolas Grekas <nicolas.grekas@gmail.com>
Date: Thu, 10 Jul 2025 09:12:18 +0200
Subject: [PATCH 48/48] CS fixes

---
 BinaryFileResponse.php                           |  4 ++--
 Cookie.php                                       |  4 ++--
 File/Exception/AccessDeniedException.php         |  2 +-
 File/Exception/FileNotFoundException.php         |  2 +-
 File/Exception/UnexpectedTypeException.php       |  2 +-
 File/File.php                                    |  8 ++++----
 File/UploadedFile.php                            |  4 ++--
 HeaderBag.php                                    |  4 ++--
 HeaderUtils.php                                  |  2 +-
 InputBag.php                                     | 10 +++++-----
 IpUtils.php                                      |  4 ++--
 JsonResponse.php                                 |  4 ++--
 ParameterBag.php                                 | 10 +++++-----
 RedirectResponse.php                             |  4 ++--
 Request.php                                      | 16 ++++++++--------
 Response.php                                     |  8 ++++----
 ResponseHeaderBag.php                            |  4 ++--
 Session/SessionUtils.php                         |  4 ++--
 .../Storage/Handler/AbstractSessionHandler.php   |  4 ++--
 Session/Storage/Handler/IdentityMarshaller.php   |  2 +-
 .../Storage/Handler/MemcachedSessionHandler.php  |  2 +-
 .../Storage/Handler/NativeFileSessionHandler.php |  4 ++--
 Session/Storage/Handler/PdoSessionHandler.php    | 12 ++++++------
 Session/Storage/Handler/RedisSessionHandler.php  |  2 +-
 .../Storage/Handler/SessionHandlerFactory.php    |  4 ++--
 Session/Storage/Handler/StrictSessionHandler.php |  2 +-
 Session/Storage/MockArraySessionStorage.php      |  2 +-
 Session/Storage/MockFileSessionStorage.php       |  2 +-
 Session/Storage/NativeSessionStorage.php         |  6 +++---
 Test/Constraint/RequestAttributeValueSame.php    |  2 +-
 Test/Constraint/ResponseCookieValueSame.php      |  8 ++++----
 Test/Constraint/ResponseHasCookie.php            |  6 +++---
 Test/Constraint/ResponseHasHeader.php            |  2 +-
 Test/Constraint/ResponseHeaderLocationSame.php   |  4 ++--
 Test/Constraint/ResponseHeaderSame.php           |  2 +-
 Tests/InputBagTest.php                           |  4 ++--
 Tests/JsonResponseTest.php                       |  2 +-
 Tests/ParameterBagTest.php                       | 10 +++++-----
 Tests/RequestTest.php                            |  2 +-
 Tests/ResponseFunctionalTest.php                 |  4 ++--
 Tests/ResponseTest.php                           |  6 +++---
 Tests/Session/Flash/AutoExpireFlashBagTest.php   | 12 ++++++------
 Tests/Session/Flash/FlashBagTest.php             |  4 ++--
 .../Handler/AbstractSessionHandlerTest.php       |  4 ++--
 .../Storage/Handler/IdentityMarshallerTest.php   |  2 +-
 .../Handler/MongoDbSessionHandlerTest.php        |  2 +-
 .../Session/Storage/Proxy/AbstractProxyTest.php  |  2 +-
 Tests/StreamedJsonResponseTest.php               |  6 +++---
 48 files changed, 111 insertions(+), 111 deletions(-)

diff --git a/BinaryFileResponse.php b/BinaryFileResponse.php
index c22f283cb..2c14d3668 100644
--- a/BinaryFileResponse.php
+++ b/BinaryFileResponse.php
@@ -259,13 +259,13 @@ public function prepare(Request $request): static
                         $end = min($end, $fileSize - 1);
                         if ($start < 0 || $start > $end) {
                             $this->setStatusCode(416);
-                            $this->headers->set('Content-Range', sprintf('bytes */%s', $fileSize));
+                            $this->headers->set('Content-Range', \sprintf('bytes */%s', $fileSize));
                         } elseif ($end - $start < $fileSize - 1) {
                             $this->maxlen = $end < $fileSize ? $end - $start + 1 : -1;
                             $this->offset = $start;
 
                             $this->setStatusCode(206);
-                            $this->headers->set('Content-Range', sprintf('bytes %s-%s/%s', $start, $end, $fileSize));
+                            $this->headers->set('Content-Range', \sprintf('bytes %s-%s/%s', $start, $end, $fileSize));
                             $this->headers->set('Content-Length', $end - $start + 1);
                         }
                     }
diff --git a/Cookie.php b/Cookie.php
index 4a3b73608..05c6c62d5 100644
--- a/Cookie.php
+++ b/Cookie.php
@@ -101,7 +101,7 @@ public function __construct(string $name, ?string $value = null, int|string|\Dat
     {
         // from PHP source code
         if ($raw && false !== strpbrk($name, self::RESERVED_CHARS_LIST)) {
-            throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name));
+            throw new \InvalidArgumentException(\sprintf('The cookie name "%s" contains invalid characters.', $name));
         }
 
         if (empty($name)) {
@@ -211,7 +211,7 @@ public function withHttpOnly(bool $httpOnly = true): static
     public function withRaw(bool $raw = true): static
     {
         if ($raw && false !== strpbrk($this->name, self::RESERVED_CHARS_LIST)) {
-            throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $this->name));
+            throw new \InvalidArgumentException(\sprintf('The cookie name "%s" contains invalid characters.', $this->name));
         }
 
         $cookie = clone $this;
diff --git a/File/Exception/AccessDeniedException.php b/File/Exception/AccessDeniedException.php
index 136d2a9f5..79ab0fce3 100644
--- a/File/Exception/AccessDeniedException.php
+++ b/File/Exception/AccessDeniedException.php
@@ -20,6 +20,6 @@ class AccessDeniedException extends FileException
 {
     public function __construct(string $path)
     {
-        parent::__construct(sprintf('The file %s could not be accessed', $path));
+        parent::__construct(\sprintf('The file %s could not be accessed', $path));
     }
 }
diff --git a/File/Exception/FileNotFoundException.php b/File/Exception/FileNotFoundException.php
index 31bdf68fe..3a5eb039b 100644
--- a/File/Exception/FileNotFoundException.php
+++ b/File/Exception/FileNotFoundException.php
@@ -20,6 +20,6 @@ class FileNotFoundException extends FileException
 {
     public function __construct(string $path)
     {
-        parent::__construct(sprintf('The file "%s" does not exist', $path));
+        parent::__construct(\sprintf('The file "%s" does not exist', $path));
     }
 }
diff --git a/File/Exception/UnexpectedTypeException.php b/File/Exception/UnexpectedTypeException.php
index 905bd5962..09b1c7e18 100644
--- a/File/Exception/UnexpectedTypeException.php
+++ b/File/Exception/UnexpectedTypeException.php
@@ -15,6 +15,6 @@ class UnexpectedTypeException extends FileException
 {
     public function __construct(mixed $value, string $expectedType)
     {
-        parent::__construct(sprintf('Expected argument of type %s, %s given', $expectedType, get_debug_type($value)));
+        parent::__construct(\sprintf('Expected argument of type %s, %s given', $expectedType, get_debug_type($value)));
     }
 }
diff --git a/File/File.php b/File/File.php
index 34ca5a537..c369ecbfb 100644
--- a/File/File.php
+++ b/File/File.php
@@ -93,7 +93,7 @@ public function move(string $directory, ?string $name = null): self
             restore_error_handler();
         }
         if (!$renamed) {
-            throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s).', $this->getPathname(), $target, strip_tags($error)));
+            throw new FileException(\sprintf('Could not move the file "%s" to "%s" (%s).', $this->getPathname(), $target, strip_tags($error)));
         }
 
         @chmod($target, 0666 & ~umask());
@@ -106,7 +106,7 @@ public function getContent(): string
         $content = file_get_contents($this->getPathname());
 
         if (false === $content) {
-            throw new FileException(sprintf('Could not get the content of the file "%s".', $this->getPathname()));
+            throw new FileException(\sprintf('Could not get the content of the file "%s".', $this->getPathname()));
         }
 
         return $content;
@@ -116,10 +116,10 @@ protected function getTargetFile(string $directory, ?string $name = null): self
     {
         if (!is_dir($directory)) {
             if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) {
-                throw new FileException(sprintf('Unable to create the "%s" directory.', $directory));
+                throw new FileException(\sprintf('Unable to create the "%s" directory.', $directory));
             }
         } elseif (!is_writable($directory)) {
-            throw new FileException(sprintf('Unable to write in the "%s" directory.', $directory));
+            throw new FileException(\sprintf('Unable to write in the "%s" directory.', $directory));
         }
 
         $target = rtrim($directory, '/\\').\DIRECTORY_SEPARATOR.(null === $name ? $this->getBasename() : $this->getName($name));
diff --git a/File/UploadedFile.php b/File/UploadedFile.php
index f475d028d..85aab2872 100644
--- a/File/UploadedFile.php
+++ b/File/UploadedFile.php
@@ -174,7 +174,7 @@ public function move(string $directory, ?string $name = null): File
                 restore_error_handler();
             }
             if (!$moved) {
-                throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s).', $this->getPathname(), $target, strip_tags($error)));
+                throw new FileException(\sprintf('Could not move the file "%s" to "%s" (%s).', $this->getPathname(), $target, strip_tags($error)));
             }
 
             @chmod($target, 0666 & ~umask());
@@ -264,6 +264,6 @@ public function getErrorMessage(): string
         $maxFilesize = \UPLOAD_ERR_INI_SIZE === $errorCode ? self::getMaxFilesize() / 1024 : 0;
         $message = $errors[$errorCode] ?? 'The file "%s" was not uploaded due to an unknown error.';
 
-        return sprintf($message, $this->getClientOriginalName(), $maxFilesize);
+        return \sprintf($message, $this->getClientOriginalName(), $maxFilesize);
     }
 }
diff --git a/HeaderBag.php b/HeaderBag.php
index 4dd777f16..e8072addd 100644
--- a/HeaderBag.php
+++ b/HeaderBag.php
@@ -51,7 +51,7 @@ public function __toString(): string
         foreach ($headers as $name => $values) {
             $name = ucwords($name, '-');
             foreach ($values as $value) {
-                $content .= sprintf("%-{$max}s %s\r\n", $name.':', $value);
+                $content .= \sprintf("%-{$max}s %s\r\n", $name.':', $value);
             }
         }
 
@@ -204,7 +204,7 @@ public function getDate(string $key, ?\DateTimeInterface $default = null): ?\Dat
         }
 
         if (false === $date = \DateTimeImmutable::createFromFormat(\DATE_RFC2822, $value)) {
-            throw new \RuntimeException(sprintf('The "%s" HTTP header is not parseable (%s).', $key, $value));
+            throw new \RuntimeException(\sprintf('The "%s" HTTP header is not parseable (%s).', $key, $value));
         }
 
         return $date;
diff --git a/HeaderUtils.php b/HeaderUtils.php
index 110896e17..ad47f2204 100644
--- a/HeaderUtils.php
+++ b/HeaderUtils.php
@@ -165,7 +165,7 @@ public static function unquote(string $s): string
     public static function makeDisposition(string $disposition, string $filename, string $filenameFallback = ''): string
     {
         if (!\in_array($disposition, [self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE])) {
-            throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE));
+            throw new \InvalidArgumentException(\sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE));
         }
 
         if ('' === $filenameFallback) {
diff --git a/InputBag.php b/InputBag.php
index 5acf35fec..08b927571 100644
--- a/InputBag.php
+++ b/InputBag.php
@@ -29,13 +29,13 @@ final class InputBag extends ParameterBag
     public function get(string $key, mixed $default = null): string|int|float|bool|null
     {
         if (null !== $default && !\is_scalar($default) && !$default instanceof \Stringable) {
-            throw new \InvalidArgumentException(sprintf('Expected a scalar value as a 2nd argument to "%s()", "%s" given.', __METHOD__, get_debug_type($default)));
+            throw new \InvalidArgumentException(\sprintf('Expected a scalar value as a 2nd argument to "%s()", "%s" given.', __METHOD__, get_debug_type($default)));
         }
 
         $value = parent::get($key, $this);
 
         if (null !== $value && $this !== $value && !\is_scalar($value) && !$value instanceof \Stringable) {
-            throw new BadRequestException(sprintf('Input value "%s" contains a non-scalar value.', $key));
+            throw new BadRequestException(\sprintf('Input value "%s" contains a non-scalar value.', $key));
         }
 
         return $this === $value ? $default : $value;
@@ -68,7 +68,7 @@ public function add(array $inputs = []): void
     public function set(string $key, mixed $value): void
     {
         if (null !== $value && !\is_scalar($value) && !\is_array($value) && !$value instanceof \Stringable) {
-            throw new \InvalidArgumentException(sprintf('Expected a scalar, or an array as a 2nd argument to "%s()", "%s" given.', __METHOD__, get_debug_type($value)));
+            throw new \InvalidArgumentException(\sprintf('Expected a scalar, or an array as a 2nd argument to "%s()", "%s" given.', __METHOD__, get_debug_type($value)));
         }
 
         $this->parameters[$key] = $value;
@@ -112,11 +112,11 @@ public function filter(string $key, mixed $default = null, int $filter = \FILTER
         }
 
         if (\is_array($value) && !(($options['flags'] ?? 0) & (\FILTER_REQUIRE_ARRAY | \FILTER_FORCE_ARRAY))) {
-            throw new BadRequestException(sprintf('Input value "%s" contains an array, but "FILTER_REQUIRE_ARRAY" or "FILTER_FORCE_ARRAY" flags were not set.', $key));
+            throw new BadRequestException(\sprintf('Input value "%s" contains an array, but "FILTER_REQUIRE_ARRAY" or "FILTER_FORCE_ARRAY" flags were not set.', $key));
         }
 
         if ((\FILTER_CALLBACK & $filter) && !(($options['options'] ?? null) instanceof \Closure)) {
-            throw new \InvalidArgumentException(sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null)));
+            throw new \InvalidArgumentException(\sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null)));
         }
 
         $options['flags'] ??= 0;
diff --git a/IpUtils.php b/IpUtils.php
index 18b1c5faf..8b52d2a9d 100644
--- a/IpUtils.php
+++ b/IpUtils.php
@@ -102,7 +102,7 @@ public static function checkIp4(string $requestIp, string $ip): bool
             return self::setCacheResult($cacheKey, false);
         }
 
-        return self::setCacheResult($cacheKey, 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, $netmask));
+        return self::setCacheResult($cacheKey, 0 === substr_compare(\sprintf('%032b', ip2long($requestIp)), \sprintf('%032b', ip2long($address)), 0, $netmask));
     }
 
     /**
@@ -182,7 +182,7 @@ public static function checkIp6(string $requestIp, string $ip): bool
      */
     public static function anonymize(string $ip): string
     {
-        /**
+        /*
          * If the IP contains a % symbol, then it is a local-link address with scoping according to RFC 4007
          * In that case, we only care about the part before the % symbol, as the following functions, can only work with
          * the IP address itself. As the scope can leak information (containing interface name), we do not want to
diff --git a/JsonResponse.php b/JsonResponse.php
index 93c5751f2..616bccfee 100644
--- a/JsonResponse.php
+++ b/JsonResponse.php
@@ -41,7 +41,7 @@ public function __construct(mixed $data = null, int $status = 200, array $header
         parent::__construct('', $status, $headers);
 
         if ($json && !\is_string($data) && !is_numeric($data) && !\is_callable([$data, '__toString'])) {
-            throw new \TypeError(sprintf('"%s": If $json is set to true, argument $data must be a string or object implementing __toString(), "%s" given.', __METHOD__, get_debug_type($data)));
+            throw new \TypeError(\sprintf('"%s": If $json is set to true, argument $data must be a string or object implementing __toString(), "%s" given.', __METHOD__, get_debug_type($data)));
         }
 
         $data ??= new \ArrayObject();
@@ -176,7 +176,7 @@ protected function update(): static
             // Not using application/javascript for compatibility reasons with older browsers.
             $this->headers->set('Content-Type', 'text/javascript');
 
-            return $this->setContent(sprintf('/**/%s(%s);', $this->callback, $this->data));
+            return $this->setContent(\sprintf('/**/%s(%s);', $this->callback, $this->data));
         }
 
         // Only set the header when there is none or when it equals 'text/javascript' (from a previous update with callback)
diff --git a/ParameterBag.php b/ParameterBag.php
index 48fa4b233..2bd8cb15f 100644
--- a/ParameterBag.php
+++ b/ParameterBag.php
@@ -45,7 +45,7 @@ public function all(?string $key = null): array
         }
 
         if (!\is_array($value = $this->parameters[$key] ?? [])) {
-            throw new BadRequestException(sprintf('Unexpected value for parameter "%s": expecting "array", got "%s".', $key, get_debug_type($value)));
+            throw new BadRequestException(\sprintf('Unexpected value for parameter "%s": expecting "array", got "%s".', $key, get_debug_type($value)));
         }
 
         return $value;
@@ -141,7 +141,7 @@ public function getString(string $key, string $default = ''): string
     {
         $value = $this->get($key, $default);
         if (!\is_scalar($value) && !$value instanceof \Stringable) {
-            throw new UnexpectedValueException(sprintf('Parameter value "%s" cannot be converted to "string".', $key));
+            throw new UnexpectedValueException(\sprintf('Parameter value "%s" cannot be converted to "string".', $key));
         }
 
         return (string) $value;
@@ -185,7 +185,7 @@ public function getEnum(string $key, string $class, ?\BackedEnum $default = null
         try {
             return $class::from($value);
         } catch (\ValueError|\TypeError $e) {
-            throw new UnexpectedValueException(sprintf('Parameter "%s" cannot be converted to enum: %s.', $key, $e->getMessage()), $e->getCode(), $e);
+            throw new UnexpectedValueException(\sprintf('Parameter "%s" cannot be converted to enum: %s.', $key, $e->getMessage()), $e->getCode(), $e);
         }
     }
 
@@ -212,11 +212,11 @@ public function filter(string $key, mixed $default = null, int $filter = \FILTER
         }
 
         if (\is_object($value) && !$value instanceof \Stringable) {
-            throw new UnexpectedValueException(sprintf('Parameter value "%s" cannot be filtered.', $key));
+            throw new UnexpectedValueException(\sprintf('Parameter value "%s" cannot be filtered.', $key));
         }
 
         if ((\FILTER_CALLBACK & $filter) && !(($options['options'] ?? null) instanceof \Closure)) {
-            throw new \InvalidArgumentException(sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null)));
+            throw new \InvalidArgumentException(\sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null)));
         }
 
         $options['flags'] ??= 0;
diff --git a/RedirectResponse.php b/RedirectResponse.php
index 408629e36..220dcf616 100644
--- a/RedirectResponse.php
+++ b/RedirectResponse.php
@@ -39,7 +39,7 @@ public function __construct(string $url, int $status = 302, array $headers = [])
         $this->setTargetUrl($url);
 
         if (!$this->isRedirect()) {
-            throw new \InvalidArgumentException(sprintf('The HTTP status code is not a redirect ("%s" given).', $status));
+            throw new \InvalidArgumentException(\sprintf('The HTTP status code is not a redirect ("%s" given).', $status));
         }
 
         if (301 == $status && !\array_key_exists('cache-control', array_change_key_case($headers, \CASE_LOWER))) {
@@ -71,7 +71,7 @@ public function setTargetUrl(string $url): static
         $this->targetUrl = $url;
 
         $this->setContent(
-            sprintf('<!DOCTYPE html>
+            \sprintf('<!DOCTYPE html>
 <html>
     <head>
         <meta charset="UTF-8" />
diff --git a/Request.php b/Request.php
index 42a3a8a2c..ec2d08d1a 100644
--- a/Request.php
+++ b/Request.php
@@ -537,7 +537,7 @@ public function __toString(): string
         }
 
         return
-            sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL'))."\r\n".
+            \sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL'))."\r\n".
             $this->headers.
             $cookieHeader."\r\n".
             $content;
@@ -638,7 +638,7 @@ public static function getTrustedHeaderSet(): int
      */
     public static function setTrustedHosts(array $hostPatterns)
     {
-        self::$trustedHostPatterns = array_map(fn ($hostPattern) => sprintf('{%s}i', $hostPattern), $hostPatterns);
+        self::$trustedHostPatterns = array_map(fn ($hostPattern) => \sprintf('{%s}i', $hostPattern), $hostPatterns);
         // we need to reset trusted hosts on trusted host patterns change
         self::$trustedHosts = [];
     }
@@ -1160,7 +1160,7 @@ public function getHost(): string
             }
             $this->isHostValid = false;
 
-            throw new SuspiciousOperationException(sprintf('Invalid Host "%s".', $host));
+            throw new SuspiciousOperationException(\sprintf('Invalid Host "%s".', $host));
         }
 
         if (\count(self::$trustedHostPatterns) > 0) {
@@ -1183,7 +1183,7 @@ public function getHost(): string
             }
             $this->isHostValid = false;
 
-            throw new SuspiciousOperationException(sprintf('Untrusted Host "%s".', $host));
+            throw new SuspiciousOperationException(\sprintf('Untrusted Host "%s".', $host));
         }
 
         return $host;
@@ -1545,7 +1545,7 @@ public function getPayload(): InputBag
         }
 
         if (!\is_array($content)) {
-            throw new JsonException(sprintf('JSON content was expected to decode to an array, "%s" returned.', get_debug_type($content)));
+            throw new JsonException(\sprintf('JSON content was expected to decode to an array, "%s" returned.', get_debug_type($content)));
         }
 
         return new InputBag($content);
@@ -1571,7 +1571,7 @@ public function toArray(): array
         }
 
         if (!\is_array($content)) {
-            throw new JsonException(sprintf('JSON content was expected to decode to an array, "%s" returned.', get_debug_type($content)));
+            throw new JsonException(\sprintf('JSON content was expected to decode to an array, "%s" returned.', get_debug_type($content)));
         }
 
         return $content;
@@ -1978,7 +1978,7 @@ private function getUrlencodedPrefix(string $string, string $prefix): ?string
 
         $len = \strlen($prefix);
 
-        if (preg_match(sprintf('#^(%%[[:xdigit:]]{2}|.){%d}#', $len), $string, $match)) {
+        if (preg_match(\sprintf('#^(%%[[:xdigit:]]{2}|.){%d}#', $len), $string, $match)) {
             return $match[0];
         }
 
@@ -2070,7 +2070,7 @@ private function getTrustedValues(int $type, ?string $ip = null): array
         }
         $this->isForwardedValid = false;
 
-        throw new ConflictingHeadersException(sprintf('The request has both a trusted "%s" header and a trusted "%s" header, conflicting with each other. You should either configure your proxy to remove one of them, or configure your project to distrust the offending one.', self::TRUSTED_HEADERS[self::HEADER_FORWARDED], self::TRUSTED_HEADERS[$type]));
+        throw new ConflictingHeadersException(\sprintf('The request has both a trusted "%s" header and a trusted "%s" header, conflicting with each other. You should either configure your proxy to remove one of them, or configure your project to distrust the offending one.', self::TRUSTED_HEADERS[self::HEADER_FORWARDED], self::TRUSTED_HEADERS[$type]));
     }
 
     private function normalizeAndFilterClientIps(array $clientIps, string $ip): array
diff --git a/Response.php b/Response.php
index a43e7a9ac..e476e29d1 100644
--- a/Response.php
+++ b/Response.php
@@ -241,7 +241,7 @@ public function __construct(?string $content = '', int $status = 200, array $hea
     public function __toString(): string
     {
         return
-            sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n".
+            \sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n".
             $this->headers."\r\n".
             $this->getContent();
     }
@@ -393,7 +393,7 @@ public function sendHeaders(/* int $statusCode = null */): static
         $statusCode ??= $this->statusCode;
 
         // status
-        header(sprintf('HTTP/%s %s %s', $this->version, $statusCode, $this->statusText), true, $statusCode);
+        header(\sprintf('HTTP/%s %s %s', $this->version, $statusCode, $this->statusText), true, $statusCode);
 
         return $this;
     }
@@ -499,7 +499,7 @@ public function setStatusCode(int $code, ?string $text = null): static
     {
         $this->statusCode = $code;
         if ($this->isInvalid()) {
-            throw new \InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $code));
+            throw new \InvalidArgumentException(\sprintf('The HTTP status code "%s" is not valid.', $code));
         }
 
         if (null === $text) {
@@ -1011,7 +1011,7 @@ public function setEtag(?string $etag = null, bool $weak = false): static
     public function setCache(array $options): static
     {
         if ($diff = array_diff(array_keys($options), array_keys(self::HTTP_RESPONSE_CACHE_CONTROL_DIRECTIVES))) {
-            throw new \InvalidArgumentException(sprintf('Response does not support the following options: "%s".', implode('", "', $diff)));
+            throw new \InvalidArgumentException(\sprintf('Response does not support the following options: "%s".', implode('", "', $diff)));
         }
 
         if (isset($options['etag'])) {
diff --git a/ResponseHeaderBag.php b/ResponseHeaderBag.php
index 376357d01..8db8dce4e 100644
--- a/ResponseHeaderBag.php
+++ b/ResponseHeaderBag.php
@@ -212,7 +212,7 @@ public function removeCookie(string $name, ?string $path = '/', ?string $domain
     public function getCookies(string $format = self::COOKIES_FLAT): array
     {
         if (!\in_array($format, [self::COOKIES_FLAT, self::COOKIES_ARRAY])) {
-            throw new \InvalidArgumentException(sprintf('Format "%s" invalid (%s).', $format, implode(', ', [self::COOKIES_FLAT, self::COOKIES_ARRAY])));
+            throw new \InvalidArgumentException(\sprintf('Format "%s" invalid (%s).', $format, implode(', ', [self::COOKIES_FLAT, self::COOKIES_ARRAY])));
         }
 
         if (self::COOKIES_ARRAY === $format) {
@@ -240,7 +240,7 @@ public function getCookies(string $format = self::COOKIES_FLAT): array
      */
     public function clearCookie(string $name, ?string $path = '/', ?string $domain = null, bool $secure = false, bool $httpOnly = true, ?string $sameSite = null /* , bool $partitioned = false */)
     {
-        $partitioned = 6 < \func_num_args() ? \func_get_arg(6) : false;
+        $partitioned = 6 < \func_num_args() ? func_get_arg(6) : false;
 
         $this->setCookie(new Cookie($name, null, 1, $path, $domain, $secure, $httpOnly, false, $sameSite, $partitioned));
     }
diff --git a/Session/SessionUtils.php b/Session/SessionUtils.php
index 504c5848e..57aa565ff 100644
--- a/Session/SessionUtils.php
+++ b/Session/SessionUtils.php
@@ -28,8 +28,8 @@ final class SessionUtils
     public static function popSessionCookie(string $sessionName, #[\SensitiveParameter] string $sessionId): ?string
     {
         $sessionCookie = null;
-        $sessionCookiePrefix = sprintf(' %s=', urlencode($sessionName));
-        $sessionCookieWithId = sprintf('%s%s;', $sessionCookiePrefix, urlencode($sessionId));
+        $sessionCookiePrefix = \sprintf(' %s=', urlencode($sessionName));
+        $sessionCookieWithId = \sprintf('%s%s;', $sessionCookiePrefix, urlencode($sessionId));
         $otherCookies = [];
         foreach (headers_list() as $h) {
             if (0 !== stripos($h, 'Set-Cookie:')) {
diff --git a/Session/Storage/Handler/AbstractSessionHandler.php b/Session/Storage/Handler/AbstractSessionHandler.php
index 288c24232..fd8562377 100644
--- a/Session/Storage/Handler/AbstractSessionHandler.php
+++ b/Session/Storage/Handler/AbstractSessionHandler.php
@@ -32,7 +32,7 @@ public function open(string $savePath, string $sessionName): bool
     {
         $this->sessionName = $sessionName;
         if (!headers_sent() && !\ini_get('session.cache_limiter') && '0' !== \ini_get('session.cache_limiter')) {
-            header(sprintf('Cache-Control: max-age=%d, private, must-revalidate', 60 * (int) \ini_get('session.cache_expire')));
+            header(\sprintf('Cache-Control: max-age=%d, private, must-revalidate', 60 * (int) \ini_get('session.cache_expire')));
         }
 
         return true;
@@ -88,7 +88,7 @@ public function destroy(#[\SensitiveParameter] string $sessionId): bool
     {
         if (!headers_sent() && filter_var(\ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOL)) {
             if (!isset($this->sessionName)) {
-                throw new \LogicException(sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', static::class));
+                throw new \LogicException(\sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', static::class));
             }
             $cookie = SessionUtils::popSessionCookie($this->sessionName, $sessionId);
 
diff --git a/Session/Storage/Handler/IdentityMarshaller.php b/Session/Storage/Handler/IdentityMarshaller.php
index 411a8d1f0..70ac76248 100644
--- a/Session/Storage/Handler/IdentityMarshaller.php
+++ b/Session/Storage/Handler/IdentityMarshaller.php
@@ -22,7 +22,7 @@ public function marshall(array $values, ?array &$failed): array
     {
         foreach ($values as $key => $value) {
             if (!\is_string($value)) {
-                throw new \LogicException(sprintf('%s accepts only string as data.', __METHOD__));
+                throw new \LogicException(\sprintf('%s accepts only string as data.', __METHOD__));
             }
         }
 
diff --git a/Session/Storage/Handler/MemcachedSessionHandler.php b/Session/Storage/Handler/MemcachedSessionHandler.php
index 91a023ddb..9647f42bb 100644
--- a/Session/Storage/Handler/MemcachedSessionHandler.php
+++ b/Session/Storage/Handler/MemcachedSessionHandler.php
@@ -47,7 +47,7 @@ public function __construct(\Memcached $memcached, array $options = [])
         $this->memcached = $memcached;
 
         if ($diff = array_diff(array_keys($options), ['prefix', 'expiretime', 'ttl'])) {
-            throw new \InvalidArgumentException(sprintf('The following options are not supported "%s".', implode(', ', $diff)));
+            throw new \InvalidArgumentException(\sprintf('The following options are not supported "%s".', implode(', ', $diff)));
         }
 
         $this->ttl = $options['expiretime'] ?? $options['ttl'] ?? null;
diff --git a/Session/Storage/Handler/NativeFileSessionHandler.php b/Session/Storage/Handler/NativeFileSessionHandler.php
index f8c6151a4..284cd869d 100644
--- a/Session/Storage/Handler/NativeFileSessionHandler.php
+++ b/Session/Storage/Handler/NativeFileSessionHandler.php
@@ -34,7 +34,7 @@ public function __construct(?string $savePath = null)
 
         if ($count = substr_count($savePath, ';')) {
             if ($count > 2) {
-                throw new \InvalidArgumentException(sprintf('Invalid argument $savePath \'%s\'.', $savePath));
+                throw new \InvalidArgumentException(\sprintf('Invalid argument $savePath \'%s\'.', $savePath));
             }
 
             // characters after last ';' are the path
@@ -42,7 +42,7 @@ public function __construct(?string $savePath = null)
         }
 
         if ($baseDir && !is_dir($baseDir) && !@mkdir($baseDir, 0777, true) && !is_dir($baseDir)) {
-            throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s".', $baseDir));
+            throw new \RuntimeException(\sprintf('Session Storage was not able to create directory "%s".', $baseDir));
         }
 
         if ($savePath !== \ini_get('session.save_path')) {
diff --git a/Session/Storage/Handler/PdoSessionHandler.php b/Session/Storage/Handler/PdoSessionHandler.php
index 9cee76ddf..af5ce10b6 100644
--- a/Session/Storage/Handler/PdoSessionHandler.php
+++ b/Session/Storage/Handler/PdoSessionHandler.php
@@ -155,7 +155,7 @@ public function __construct(#[\SensitiveParameter] \PDO|string|null $pdoOrDsn =
     {
         if ($pdoOrDsn instanceof \PDO) {
             if (\PDO::ERRMODE_EXCEPTION !== $pdoOrDsn->getAttribute(\PDO::ATTR_ERRMODE)) {
-                throw new \InvalidArgumentException(sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)).', __CLASS__));
+                throw new \InvalidArgumentException(\sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)).', __CLASS__));
             }
 
             $this->pdo = $pdoOrDsn;
@@ -222,7 +222,7 @@ public function configureSchema(Schema $schema, ?\Closure $isSameDatabase = null
                 $table->addColumn($this->timeCol, Types::INTEGER)->setUnsigned(true)->setNotnull(true);
                 break;
             default:
-                throw new \DomainException(sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver));
+                throw new \DomainException(\sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver));
         }
         $table->setPrimaryKey([$this->idCol]);
         $table->addIndex([$this->lifetimeCol], $this->lifetimeCol.'_idx');
@@ -257,7 +257,7 @@ public function createTable()
             'pgsql' => "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)",
             'oci' => "CREATE TABLE $this->table ($this->idCol VARCHAR2(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)",
             'sqlsrv' => "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol VARBINARY(MAX) NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)",
-            default => throw new \DomainException(sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver)),
+            default => throw new \DomainException(\sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver)),
         };
 
         try {
@@ -538,7 +538,7 @@ private function buildDsnFromUrl(#[\SensitiveParameter] string $dsnOrUrl): strin
                 return $dsn;
 
             default:
-                throw new \InvalidArgumentException(sprintf('The scheme "%s" is not supported by the PdoSessionHandler URL configuration. Pass a PDO DSN directly.', $params['scheme']));
+                throw new \InvalidArgumentException(\sprintf('The scheme "%s" is not supported by the PdoSessionHandler URL configuration. Pass a PDO DSN directly.', $params['scheme']));
         }
     }
 
@@ -734,7 +734,7 @@ private function doAdvisoryLock(#[\SensitiveParameter] string $sessionId): \PDOS
             case 'sqlite':
                 throw new \DomainException('SQLite does not support advisory locks.');
             default:
-                throw new \DomainException(sprintf('Advisory locks are currently not implemented for PDO driver "%s".', $this->driver));
+                throw new \DomainException(\sprintf('Advisory locks are currently not implemented for PDO driver "%s".', $this->driver));
         }
     }
 
@@ -776,7 +776,7 @@ private function getSelectSql(): string
                     // we already locked when starting transaction
                     break;
                 default:
-                    throw new \DomainException(sprintf('Transactional locks are currently not implemented for PDO driver "%s".', $this->driver));
+                    throw new \DomainException(\sprintf('Transactional locks are currently not implemented for PDO driver "%s".', $this->driver));
             }
         }
 
diff --git a/Session/Storage/Handler/RedisSessionHandler.php b/Session/Storage/Handler/RedisSessionHandler.php
index b696eee4b..78cd4e7c2 100644
--- a/Session/Storage/Handler/RedisSessionHandler.php
+++ b/Session/Storage/Handler/RedisSessionHandler.php
@@ -44,7 +44,7 @@ public function __construct(
         array $options = [],
     ) {
         if ($diff = array_diff(array_keys($options), ['prefix', 'ttl'])) {
-            throw new \InvalidArgumentException(sprintf('The following options are not supported "%s".', implode(', ', $diff)));
+            throw new \InvalidArgumentException(\sprintf('The following options are not supported "%s".', implode(', ', $diff)));
         }
 
         $this->prefix = $options['prefix'] ?? 'sf_s';
diff --git a/Session/Storage/Handler/SessionHandlerFactory.php b/Session/Storage/Handler/SessionHandlerFactory.php
index ff5b70d81..43a9eb84e 100644
--- a/Session/Storage/Handler/SessionHandlerFactory.php
+++ b/Session/Storage/Handler/SessionHandlerFactory.php
@@ -49,7 +49,7 @@ public static function createHandler(object|string $connection, array $options =
                 return new PdoSessionHandler($connection);
 
             case !\is_string($connection):
-                throw new \InvalidArgumentException(sprintf('Unsupported Connection: "%s".', get_debug_type($connection)));
+                throw new \InvalidArgumentException(\sprintf('Unsupported Connection: "%s".', get_debug_type($connection)));
             case str_starts_with($connection, 'file://'):
                 $savePath = substr($connection, 7);
 
@@ -94,6 +94,6 @@ public static function createHandler(object|string $connection, array $options =
                 return new PdoSessionHandler($connection, $options);
         }
 
-        throw new \InvalidArgumentException(sprintf('Unsupported Connection: "%s".', $connection));
+        throw new \InvalidArgumentException(\sprintf('Unsupported Connection: "%s".', $connection));
     }
 }
diff --git a/Session/Storage/Handler/StrictSessionHandler.php b/Session/Storage/Handler/StrictSessionHandler.php
index 1f8668744..38afc1257 100644
--- a/Session/Storage/Handler/StrictSessionHandler.php
+++ b/Session/Storage/Handler/StrictSessionHandler.php
@@ -24,7 +24,7 @@ class StrictSessionHandler extends AbstractSessionHandler
     public function __construct(\SessionHandlerInterface $handler)
     {
         if ($handler instanceof \SessionUpdateTimestampHandlerInterface) {
-            throw new \LogicException(sprintf('"%s" is already an instance of "SessionUpdateTimestampHandlerInterface", you cannot wrap it with "%s".', get_debug_type($handler), self::class));
+            throw new \LogicException(\sprintf('"%s" is already an instance of "SessionUpdateTimestampHandlerInterface", you cannot wrap it with "%s".', get_debug_type($handler), self::class));
         }
 
         $this->handler = $handler;
diff --git a/Session/Storage/MockArraySessionStorage.php b/Session/Storage/MockArraySessionStorage.php
index f02793d3e..65ab34f91 100644
--- a/Session/Storage/MockArraySessionStorage.php
+++ b/Session/Storage/MockArraySessionStorage.php
@@ -174,7 +174,7 @@ public function registerBag(SessionBagInterface $bag)
     public function getBag(string $name): SessionBagInterface
     {
         if (!isset($this->bags[$name])) {
-            throw new \InvalidArgumentException(sprintf('The SessionBagInterface "%s" is not registered.', $name));
+            throw new \InvalidArgumentException(\sprintf('The SessionBagInterface "%s" is not registered.', $name));
         }
 
         if (!$this->started) {
diff --git a/Session/Storage/MockFileSessionStorage.php b/Session/Storage/MockFileSessionStorage.php
index ef6d9d8f8..84c2c4363 100644
--- a/Session/Storage/MockFileSessionStorage.php
+++ b/Session/Storage/MockFileSessionStorage.php
@@ -35,7 +35,7 @@ public function __construct(?string $savePath = null, string $name = 'MOCKSESSID
         $savePath ??= sys_get_temp_dir();
 
         if (!is_dir($savePath) && !@mkdir($savePath, 0777, true) && !is_dir($savePath)) {
-            throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s".', $savePath));
+            throw new \RuntimeException(\sprintf('Session Storage was not able to create directory "%s".', $savePath));
         }
 
         $this->savePath = $savePath;
diff --git a/Session/Storage/NativeSessionStorage.php b/Session/Storage/NativeSessionStorage.php
index 7b0c5beca..c8801cc25 100644
--- a/Session/Storage/NativeSessionStorage.php
+++ b/Session/Storage/NativeSessionStorage.php
@@ -129,7 +129,7 @@ public function start(): bool
         }
 
         if (filter_var(\ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOL) && headers_sent($file, $line)) {
-            throw new \RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line));
+            throw new \RuntimeException(\sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line));
         }
 
         $sessionId = $_COOKIE[session_name()] ?? null;
@@ -249,7 +249,7 @@ public function save()
         $previousHandler = set_error_handler(function ($type, $msg, $file, $line) use (&$previousHandler) {
             if (\E_WARNING === $type && str_starts_with($msg, 'session_write_close():')) {
                 $handler = $this->saveHandler instanceof SessionHandlerProxy ? $this->saveHandler->getHandler() : $this->saveHandler;
-                $msg = sprintf('session_write_close(): Failed to write session data with "%s" handler', $handler::class);
+                $msg = \sprintf('session_write_close(): Failed to write session data with "%s" handler', $handler::class);
             }
 
             return $previousHandler ? $previousHandler($type, $msg, $file, $line) : false;
@@ -302,7 +302,7 @@ public function registerBag(SessionBagInterface $bag)
     public function getBag(string $name): SessionBagInterface
     {
         if (!isset($this->bags[$name])) {
-            throw new \InvalidArgumentException(sprintf('The SessionBagInterface "%s" is not registered.', $name));
+            throw new \InvalidArgumentException(\sprintf('The SessionBagInterface "%s" is not registered.', $name));
         }
 
         if (!$this->started && $this->saveHandler->isActive()) {
diff --git a/Test/Constraint/RequestAttributeValueSame.php b/Test/Constraint/RequestAttributeValueSame.php
index 6e1142681..8fc9aa2a0 100644
--- a/Test/Constraint/RequestAttributeValueSame.php
+++ b/Test/Constraint/RequestAttributeValueSame.php
@@ -27,7 +27,7 @@ public function __construct(string $name, string $value)
 
     public function toString(): string
     {
-        return sprintf('has attribute "%s" with value "%s"', $this->name, $this->value);
+        return \sprintf('has attribute "%s" with value "%s"', $this->name, $this->value);
     }
 
     /**
diff --git a/Test/Constraint/ResponseCookieValueSame.php b/Test/Constraint/ResponseCookieValueSame.php
index 768007b95..285f45bf7 100644
--- a/Test/Constraint/ResponseCookieValueSame.php
+++ b/Test/Constraint/ResponseCookieValueSame.php
@@ -32,14 +32,14 @@ public function __construct(string $name, string $value, string $path = '/', ?st
 
     public function toString(): string
     {
-        $str = sprintf('has cookie "%s"', $this->name);
+        $str = \sprintf('has cookie "%s"', $this->name);
         if ('/' !== $this->path) {
-            $str .= sprintf(' with path "%s"', $this->path);
+            $str .= \sprintf(' with path "%s"', $this->path);
         }
         if ($this->domain) {
-            $str .= sprintf(' for domain "%s"', $this->domain);
+            $str .= \sprintf(' for domain "%s"', $this->domain);
         }
-        $str .= sprintf(' with value "%s"', $this->value);
+        $str .= \sprintf(' with value "%s"', $this->value);
 
         return $str;
     }
diff --git a/Test/Constraint/ResponseHasCookie.php b/Test/Constraint/ResponseHasCookie.php
index 8eccea9d1..a11809d04 100644
--- a/Test/Constraint/ResponseHasCookie.php
+++ b/Test/Constraint/ResponseHasCookie.php
@@ -30,12 +30,12 @@ public function __construct(string $name, string $path = '/', ?string $domain =
 
     public function toString(): string
     {
-        $str = sprintf('has cookie "%s"', $this->name);
+        $str = \sprintf('has cookie "%s"', $this->name);
         if ('/' !== $this->path) {
-            $str .= sprintf(' with path "%s"', $this->path);
+            $str .= \sprintf(' with path "%s"', $this->path);
         }
         if ($this->domain) {
-            $str .= sprintf(' for domain "%s"', $this->domain);
+            $str .= \sprintf(' for domain "%s"', $this->domain);
         }
 
         return $str;
diff --git a/Test/Constraint/ResponseHasHeader.php b/Test/Constraint/ResponseHasHeader.php
index 08522c89c..e5cb48adf 100644
--- a/Test/Constraint/ResponseHasHeader.php
+++ b/Test/Constraint/ResponseHasHeader.php
@@ -25,7 +25,7 @@ public function __construct(string $headerName)
 
     public function toString(): string
     {
-        return sprintf('has header "%s"', $this->headerName);
+        return \sprintf('has header "%s"', $this->headerName);
     }
 
     /**
diff --git a/Test/Constraint/ResponseHeaderLocationSame.php b/Test/Constraint/ResponseHeaderLocationSame.php
index 9286ec715..833ffd9f2 100644
--- a/Test/Constraint/ResponseHeaderLocationSame.php
+++ b/Test/Constraint/ResponseHeaderLocationSame.php
@@ -23,7 +23,7 @@ public function __construct(private Request $request, private string $expectedVa
 
     public function toString(): string
     {
-        return sprintf('has header "Location" matching "%s"', $this->expectedValue);
+        return \sprintf('has header "Location" matching "%s"', $this->expectedValue);
     }
 
     protected function matches($other): bool
@@ -53,7 +53,7 @@ private function toFullUrl(string $url): string
         }
 
         if (str_starts_with($url, '//')) {
-            return sprintf('%s:%s', $this->request->getScheme(), $url);
+            return \sprintf('%s:%s', $this->request->getScheme(), $url);
         }
 
         if (str_starts_with($url, '/')) {
diff --git a/Test/Constraint/ResponseHeaderSame.php b/Test/Constraint/ResponseHeaderSame.php
index 8141df972..02b5921d2 100644
--- a/Test/Constraint/ResponseHeaderSame.php
+++ b/Test/Constraint/ResponseHeaderSame.php
@@ -27,7 +27,7 @@ public function __construct(string $headerName, string $expectedValue)
 
     public function toString(): string
     {
-        return sprintf('has header "%s" with value "%s"', $this->headerName, $this->expectedValue);
+        return \sprintf('has header "%s" with value "%s"', $this->headerName, $this->expectedValue);
     }
 
     /**
diff --git a/Tests/InputBagTest.php b/Tests/InputBagTest.php
index d1e9015f1..cb0d449c5 100644
--- a/Tests/InputBagTest.php
+++ b/Tests/InputBagTest.php
@@ -23,7 +23,7 @@ class InputBagTest extends TestCase
 
     public function testGet()
     {
-        $bag = new InputBag(['foo' => 'bar', 'null' => null, 'int' => 1, 'float' => 1.0, 'bool' => false, 'stringable' => new class() implements \Stringable {
+        $bag = new InputBag(['foo' => 'bar', 'null' => null, 'int' => 1, 'float' => 1.0, 'bool' => false, 'stringable' => new class implements \Stringable {
             public function __toString(): string
             {
                 return 'strval';
@@ -65,7 +65,7 @@ public function testGetBooleanError()
 
     public function testGetString()
     {
-        $bag = new InputBag(['integer' => 123, 'bool_true' => true, 'bool_false' => false, 'string' => 'abc', 'stringable' => new class() implements \Stringable {
+        $bag = new InputBag(['integer' => 123, 'bool_true' => true, 'bool_false' => false, 'string' => 'abc', 'stringable' => new class implements \Stringable {
             public function __toString(): string
             {
                 return 'strval';
diff --git a/Tests/JsonResponseTest.php b/Tests/JsonResponseTest.php
index 2058280c5..67a038279 100644
--- a/Tests/JsonResponseTest.php
+++ b/Tests/JsonResponseTest.php
@@ -171,7 +171,7 @@ public function testConstructorWithNullAsDataThrowsAnUnexpectedValueException()
 
     public function testConstructorWithObjectWithToStringMethod()
     {
-        $class = new class() {
+        $class = new class {
             public function __toString(): string
             {
                 return '{}';
diff --git a/Tests/ParameterBagTest.php b/Tests/ParameterBagTest.php
index ad0cf99bf..0d9ae476e 100644
--- a/Tests/ParameterBagTest.php
+++ b/Tests/ParameterBagTest.php
@@ -192,7 +192,7 @@ public function testGetInt()
      */
     public function testGetIntExceptionWithArray()
     {
-        $this->expectDeprecation(sprintf('Since symfony/http-foundation 6.3: Ignoring invalid values when using "%s::getInt(\'digits\')" is deprecated and will throw an "%s" in 7.0; use method "filter()" with flag "FILTER_NULL_ON_FAILURE" to keep ignoring them.', ParameterBag::class, UnexpectedValueException::class));
+        $this->expectDeprecation(\sprintf('Since symfony/http-foundation 6.3: Ignoring invalid values when using "%s::getInt(\'digits\')" is deprecated and will throw an "%s" in 7.0; use method "filter()" with flag "FILTER_NULL_ON_FAILURE" to keep ignoring them.', ParameterBag::class, UnexpectedValueException::class));
 
         $bag = new ParameterBag(['digits' => ['123']]);
         $result = $bag->getInt('digits', 0);
@@ -204,7 +204,7 @@ public function testGetIntExceptionWithArray()
      */
     public function testGetIntExceptionWithInvalid()
     {
-        $this->expectDeprecation(sprintf('Since symfony/http-foundation 6.3: Ignoring invalid values when using "%s::getInt(\'word\')" is deprecated and will throw an "%s" in 7.0; use method "filter()" with flag "FILTER_NULL_ON_FAILURE" to keep ignoring them.', ParameterBag::class, UnexpectedValueException::class));
+        $this->expectDeprecation(\sprintf('Since symfony/http-foundation 6.3: Ignoring invalid values when using "%s::getInt(\'word\')" is deprecated and will throw an "%s" in 7.0; use method "filter()" with flag "FILTER_NULL_ON_FAILURE" to keep ignoring them.', ParameterBag::class, UnexpectedValueException::class));
 
         $bag = new ParameterBag(['word' => 'foo_BAR_012']);
         $result = $bag->getInt('word', 0);
@@ -213,7 +213,7 @@ public function testGetIntExceptionWithInvalid()
 
     public function testGetString()
     {
-        $bag = new ParameterBag(['integer' => 123, 'bool_true' => true, 'bool_false' => false, 'string' => 'abc', 'stringable' => new class() implements \Stringable {
+        $bag = new ParameterBag(['integer' => 123, 'bool_true' => true, 'bool_false' => false, 'string' => 'abc', 'stringable' => new class implements \Stringable {
             public function __toString(): string
             {
                 return 'strval';
@@ -258,7 +258,7 @@ public function testFilter()
             'dec' => '256',
             'hex' => '0x100',
             'array' => ['bang'],
-            ]);
+        ]);
 
         $this->assertEmpty($bag->filter('nokey'), '->filter() should return empty by default if no key is found');
 
@@ -339,7 +339,7 @@ public function testGetBoolean()
      */
     public function testGetBooleanExceptionWithInvalid()
     {
-        $this->expectDeprecation(sprintf('Since symfony/http-foundation 6.3: Ignoring invalid values when using "%s::getBoolean(\'invalid\')" is deprecated and will throw an "%s" in 7.0; use method "filter()" with flag "FILTER_NULL_ON_FAILURE" to keep ignoring them.', ParameterBag::class, UnexpectedValueException::class));
+        $this->expectDeprecation(\sprintf('Since symfony/http-foundation 6.3: Ignoring invalid values when using "%s::getBoolean(\'invalid\')" is deprecated and will throw an "%s" in 7.0; use method "filter()" with flag "FILTER_NULL_ON_FAILURE" to keep ignoring them.', ParameterBag::class, UnexpectedValueException::class));
 
         $bag = new ParameterBag(['invalid' => 'foo']);
         $result = $bag->getBoolean('invalid', 0);
diff --git a/Tests/RequestTest.php b/Tests/RequestTest.php
index a2eace70e..3ab13d1f2 100644
--- a/Tests/RequestTest.php
+++ b/Tests/RequestTest.php
@@ -2664,7 +2664,7 @@ public static function preferSafeContentData()
     public function testReservedFlags()
     {
         foreach ((new \ReflectionClass(Request::class))->getConstants() as $constant => $value) {
-            $this->assertNotSame(0b10000000, $value, sprintf('The constant "%s" should not use the reserved value "0b10000000".', $constant));
+            $this->assertNotSame(0b10000000, $value, \sprintf('The constant "%s" should not use the reserved value "0b10000000".', $constant));
         }
     }
 }
diff --git a/Tests/ResponseFunctionalTest.php b/Tests/ResponseFunctionalTest.php
index 1b3566a2c..e5c6c2428 100644
--- a/Tests/ResponseFunctionalTest.php
+++ b/Tests/ResponseFunctionalTest.php
@@ -45,9 +45,9 @@ public static function tearDownAfterClass(): void
      */
     public function testCookie($fixture)
     {
-        $result = file_get_contents(sprintf('http://localhost:8054/%s.php', $fixture));
+        $result = file_get_contents(\sprintf('http://localhost:8054/%s.php', $fixture));
         $result = preg_replace_callback('/expires=[^;]++/', fn ($m) => str_replace('-', ' ', $m[0]), $result);
-        $this->assertStringMatchesFormatFile(__DIR__.sprintf('/Fixtures/response-functional/%s.expected', $fixture), $result);
+        $this->assertStringMatchesFormatFile(__DIR__.\sprintf('/Fixtures/response-functional/%s.expected', $fixture), $result);
     }
 
     public static function provideCookie()
diff --git a/Tests/ResponseTest.php b/Tests/ResponseTest.php
index 007842476..491a50fd5 100644
--- a/Tests/ResponseTest.php
+++ b/Tests/ResponseTest.php
@@ -181,7 +181,7 @@ public function testIsNotModifiedEtag()
         $etagTwo = 'randomly_generated_etag_2';
 
         $request = new Request();
-        $request->headers->set('If-None-Match', sprintf('%s, %s, %s', $etagOne, $etagTwo, 'etagThree'));
+        $request->headers->set('If-None-Match', \sprintf('%s, %s, %s', $etagOne, $etagTwo, 'etagThree'));
 
         $response = new Response();
 
@@ -235,7 +235,7 @@ public function testIsNotModifiedLastModifiedAndEtag()
         $etag = 'randomly_generated_etag';
 
         $request = new Request();
-        $request->headers->set('If-None-Match', sprintf('%s, %s', $etag, 'etagThree'));
+        $request->headers->set('If-None-Match', \sprintf('%s, %s', $etag, 'etagThree'));
         $request->headers->set('If-Modified-Since', $modified);
 
         $response = new Response();
@@ -259,7 +259,7 @@ public function testIsNotModifiedIfModifiedSinceAndEtagWithoutLastModified()
         $etag = 'randomly_generated_etag';
 
         $request = new Request();
-        $request->headers->set('If-None-Match', sprintf('%s, %s', $etag, 'etagThree'));
+        $request->headers->set('If-None-Match', \sprintf('%s, %s', $etag, 'etagThree'));
         $request->headers->set('If-Modified-Since', $modified);
 
         $response = new Response();
diff --git a/Tests/Session/Flash/AutoExpireFlashBagTest.php b/Tests/Session/Flash/AutoExpireFlashBagTest.php
index 6a6510a57..0b418c006 100644
--- a/Tests/Session/Flash/AutoExpireFlashBagTest.php
+++ b/Tests/Session/Flash/AutoExpireFlashBagTest.php
@@ -46,9 +46,9 @@ public function testInitialize()
         $bag->initialize($array);
         $this->assertEquals(['A previous flash message'], $bag->peek('notice'));
         $array = ['new' => [
-                'notice' => ['Something else'],
-                'error' => ['a'],
-            ]];
+            'notice' => ['Something else'],
+            'error' => ['a'],
+        ]];
         $bag->initialize($array);
         $this->assertEquals(['Something else'], $bag->peek('notice'));
         $this->assertEquals(['a'], $bag->peek('error'));
@@ -106,13 +106,13 @@ public function testPeekAll()
         $this->assertEquals([
             'notice' => 'Foo',
             'error' => 'Bar',
-            ], $this->bag->peekAll()
+        ], $this->bag->peekAll()
         );
 
         $this->assertEquals([
             'notice' => 'Foo',
             'error' => 'Bar',
-            ], $this->bag->peekAll()
+        ], $this->bag->peekAll()
         );
     }
 
@@ -137,7 +137,7 @@ public function testAll()
         $this->bag->set('error', 'Bar');
         $this->assertEquals([
             'notice' => ['A previous flash message'],
-            ], $this->bag->all()
+        ], $this->bag->all()
         );
 
         $this->assertEquals([], $this->bag->all());
diff --git a/Tests/Session/Flash/FlashBagTest.php b/Tests/Session/Flash/FlashBagTest.php
index 59e3f1f0e..8163ba769 100644
--- a/Tests/Session/Flash/FlashBagTest.php
+++ b/Tests/Session/Flash/FlashBagTest.php
@@ -141,14 +141,14 @@ public function testPeekAll()
         $this->assertEquals([
             'notice' => ['Foo'],
             'error' => ['Bar'],
-            ], $this->bag->peekAll()
+        ], $this->bag->peekAll()
         );
         $this->assertTrue($this->bag->has('notice'));
         $this->assertTrue($this->bag->has('error'));
         $this->assertEquals([
             'notice' => ['Foo'],
             'error' => ['Bar'],
-            ], $this->bag->peekAll()
+        ], $this->bag->peekAll()
         );
     }
 }
diff --git a/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php b/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php
index 27fb57da4..361d3b15d 100644
--- a/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php
+++ b/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php
@@ -45,10 +45,10 @@ public function testSession($fixture)
     {
         $context = ['http' => ['header' => "Cookie: sid=123abc\r\n"]];
         $context = stream_context_create($context);
-        $result = file_get_contents(sprintf('http://localhost:8053/%s.php', $fixture), false, $context);
+        $result = file_get_contents(\sprintf('http://localhost:8053/%s.php', $fixture), false, $context);
         $result = preg_replace_callback('/expires=[^;]++/', fn ($m) => str_replace('-', ' ', $m[0]), $result);
 
-        $this->assertStringEqualsFile(__DIR__.sprintf('/Fixtures/%s.expected', $fixture), $result);
+        $this->assertStringEqualsFile(__DIR__.\sprintf('/Fixtures/%s.expected', $fixture), $result);
     }
 
     public static function provideSession()
diff --git a/Tests/Session/Storage/Handler/IdentityMarshallerTest.php b/Tests/Session/Storage/Handler/IdentityMarshallerTest.php
index 8019bd2e9..6f15f96cb 100644
--- a/Tests/Session/Storage/Handler/IdentityMarshallerTest.php
+++ b/Tests/Session/Storage/Handler/IdentityMarshallerTest.php
@@ -17,7 +17,7 @@
 /**
  * @author Ahmed TAILOULOUTE <ahmed.tailouloute@gmail.com>
  */
-class IdentityMarshallerTest extends Testcase
+class IdentityMarshallerTest extends TestCase
 {
     public function testMarshall()
     {
diff --git a/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php b/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php
index 04dfdbef8..d607e19d2 100644
--- a/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php
+++ b/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php
@@ -51,7 +51,7 @@ protected function setUp(): void
         try {
             $this->manager->executeCommand(self::DABASE_NAME, new Command(['ping' => 1]));
         } catch (ConnectionException $e) {
-            $this->markTestSkipped(sprintf('MongoDB Server "%s" not running: %s', getenv('MONGODB_HOST'), $e->getMessage()));
+            $this->markTestSkipped(\sprintf('MongoDB Server "%s" not running: %s', getenv('MONGODB_HOST'), $e->getMessage()));
         }
 
         $this->options = [
diff --git a/Tests/Session/Storage/Proxy/AbstractProxyTest.php b/Tests/Session/Storage/Proxy/AbstractProxyTest.php
index 8d04830a7..9551d52b4 100644
--- a/Tests/Session/Storage/Proxy/AbstractProxyTest.php
+++ b/Tests/Session/Storage/Proxy/AbstractProxyTest.php
@@ -26,7 +26,7 @@ class AbstractProxyTest extends TestCase
 
     protected function setUp(): void
     {
-        $this->proxy = new class() extends AbstractProxy {};
+        $this->proxy = new class extends AbstractProxy {};
     }
 
     public function testGetSaveHandlerName()
diff --git a/Tests/StreamedJsonResponseTest.php b/Tests/StreamedJsonResponseTest.php
index db76cd3ae..3d94aa078 100644
--- a/Tests/StreamedJsonResponseTest.php
+++ b/Tests/StreamedJsonResponseTest.php
@@ -132,14 +132,14 @@ public function testResponseOtherTraversable()
     {
         $arrayObject = new \ArrayObject(['__symfony_json__' => '__symfony_json__']);
 
-        $iteratorAggregate = new class() implements \IteratorAggregate {
+        $iteratorAggregate = new class implements \IteratorAggregate {
             public function getIterator(): \Traversable
             {
                 return new \ArrayIterator(['__symfony_json__']);
             }
         };
 
-        $jsonSerializable = new class() implements \IteratorAggregate, \JsonSerializable {
+        $jsonSerializable = new class implements \IteratorAggregate, \JsonSerializable {
             public function getIterator(): \Traversable
             {
                 return new \ArrayIterator(['This should be ignored']);
@@ -187,7 +187,7 @@ public function testResponseStatusCode()
 
     public function testPlaceholderAsObjectStructure()
     {
-        $object = new class() {
+        $object = new class {
             public $__symfony_json__ = 'foo';
             public $bar = '__symfony_json__';
         };
