diff --git a/AbstractExtension.php b/AbstractExtension.php
index 908f4f86f..27665727c 100644
--- a/AbstractExtension.php
+++ b/AbstractExtension.php
@@ -50,7 +50,7 @@ public function getType(string $name): FormTypeInterface
         }
 
         if (!isset($this->types[$name])) {
-            throw new InvalidArgumentException(sprintf('The type "%s" cannot be loaded by this extension.', $name));
+            throw new InvalidArgumentException(\sprintf('The type "%s" cannot be loaded by this extension.', $name));
         }
 
         return $this->types[$name];
diff --git a/AbstractTypeExtension.php b/AbstractTypeExtension.php
index 9f6da0a33..b32f3b522 100644
--- a/AbstractTypeExtension.php
+++ b/AbstractTypeExtension.php
@@ -18,9 +18,6 @@
  */
 abstract class AbstractTypeExtension implements FormTypeExtensionInterface
 {
-    /**
-     * @return void
-     */
     public function configureOptions(OptionsResolver $resolver): void
     {
     }
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0420af341..3b1fabfd1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,15 @@
 CHANGELOG
 =========
 
+7.2
+---
+
+ * Deprecate the `VersionAwareTest` trait, use feature detection instead
+ * Add support for the `calendar` option in `DateType`
+ * Add `LazyChoiceLoader` and `choice_lazy` option in `ChoiceType` for loading and rendering choices on demand
+ * Use `form.post_set_data` instead of `form.pre_set_data` in `ResizeFormListener`
+ * Change the priority of `DataCollectorListener` from 255 to -255
+
 7.1
 ---
 
diff --git a/ChoiceList/Factory/CachingFactoryDecorator.php b/ChoiceList/Factory/CachingFactoryDecorator.php
index 687fcec1e..1f373228b 100644
--- a/ChoiceList/Factory/CachingFactoryDecorator.php
+++ b/ChoiceList/Factory/CachingFactoryDecorator.php
@@ -27,8 +27,6 @@
  */
 class CachingFactoryDecorator implements ChoiceListFactoryInterface, ResetInterface
 {
-    private ChoiceListFactoryInterface $decoratedFactory;
-
     /**
      * @var ChoiceListInterface[]
      */
@@ -64,9 +62,9 @@ public static function generateHash(mixed $value, string $namespace = ''): strin
         return hash('sha256', $namespace.':'.serialize($value));
     }
 
-    public function __construct(ChoiceListFactoryInterface $decoratedFactory)
-    {
-        $this->decoratedFactory = $decoratedFactory;
+    public function __construct(
+        private ChoiceListFactoryInterface $decoratedFactory,
+    ) {
     }
 
     /**
diff --git a/ChoiceList/Factory/PropertyAccessDecorator.php b/ChoiceList/Factory/PropertyAccessDecorator.php
index c83ef17e9..f73a8fc2a 100644
--- a/ChoiceList/Factory/PropertyAccessDecorator.php
+++ b/ChoiceList/Factory/PropertyAccessDecorator.php
@@ -38,12 +38,12 @@
  */
 class PropertyAccessDecorator implements ChoiceListFactoryInterface
 {
-    private ChoiceListFactoryInterface $decoratedFactory;
     private PropertyAccessorInterface $propertyAccessor;
 
-    public function __construct(ChoiceListFactoryInterface $decoratedFactory, ?PropertyAccessorInterface $propertyAccessor = null)
-    {
-        $this->decoratedFactory = $decoratedFactory;
+    public function __construct(
+        private ChoiceListFactoryInterface $decoratedFactory,
+        ?PropertyAccessorInterface $propertyAccessor = null,
+    ) {
         $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
     }
 
diff --git a/ChoiceList/Loader/LazyChoiceLoader.php b/ChoiceList/Loader/LazyChoiceLoader.php
new file mode 100644
index 000000000..03451be36
--- /dev/null
+++ b/ChoiceList/Loader/LazyChoiceLoader.php
@@ -0,0 +1,54 @@
+<?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\Form\ChoiceList\Loader;
+
+use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
+use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
+
+/**
+ * A choice loader that loads its choices and values lazily, only when necessary.
+ *
+ * @author Yonel Ceruto <yonelceruto@gmail.com>
+ */
+class LazyChoiceLoader implements ChoiceLoaderInterface
+{
+    private ?ChoiceListInterface $choiceList = null;
+
+    public function __construct(
+        private readonly ChoiceLoaderInterface $loader,
+    ) {
+    }
+
+    public function loadChoiceList(?callable $value = null): ChoiceListInterface
+    {
+        return $this->choiceList ??= new ArrayChoiceList([], $value);
+    }
+
+    public function loadChoicesForValues(array $values, ?callable $value = null): array
+    {
+        $choices = $this->loader->loadChoicesForValues($values, $value);
+        $this->choiceList = new ArrayChoiceList($choices, $value);
+
+        return $choices;
+    }
+
+    public function loadValuesForChoices(array $choices, ?callable $value = null): array
+    {
+        $values = $this->loader->loadValuesForChoices($choices, $value);
+
+        if ($this->choiceList?->getValuesForChoices($choices) !== $values) {
+            $this->loadChoicesForValues($values, $value);
+        }
+
+        return $values;
+    }
+}
diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php
index 4d9901229..91db6f1a9 100644
--- a/Command/DebugCommand.php
+++ b/Command/DebugCommand.php
@@ -53,7 +53,7 @@ protected function configure(): void
                 new InputArgument('class', InputArgument::OPTIONAL, 'The form type class'),
                 new InputArgument('option', InputArgument::OPTIONAL, 'The form type option'),
                 new InputOption('show-deprecated', null, InputOption::VALUE_NONE, 'Display deprecated options in form types'),
-                new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'txt'),
+                new InputOption('format', null, InputOption::VALUE_REQUIRED, \sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'txt'),
             ])
             ->setHelp(<<<'EOF'
 The <info>%command.name%</info> command displays information about form types.
@@ -114,7 +114,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
                 $object = $resolvedType->getOptionsResolver();
 
                 if (!$object->isDefined($option)) {
-                    $message = sprintf('Option "%s" is not defined in "%s".', $option, $resolvedType->getInnerType()::class);
+                    $message = \sprintf('Option "%s" is not defined in "%s".', $option, $resolvedType->getInnerType()::class);
 
                     if ($alternatives = $this->findAlternatives($option, $object->getDefinedOptions())) {
                         if (1 === \count($alternatives)) {
@@ -148,7 +148,7 @@ private function getFqcnTypeClass(InputInterface $input, SymfonyStyle $io, strin
         $classes = $this->getFqcnTypeClasses($shortClassName);
 
         if (0 === $count = \count($classes)) {
-            $message = sprintf("Could not find type \"%s\" into the following namespaces:\n    %s", $shortClassName, implode("\n    ", $this->namespaces));
+            $message = \sprintf("Could not find type \"%s\" into the following namespaces:\n    %s", $shortClassName, implode("\n    ", $this->namespaces));
 
             $allTypes = array_merge($this->getCoreTypes(), $this->types);
             if ($alternatives = $this->findAlternatives($shortClassName, $allTypes)) {
@@ -166,10 +166,10 @@ private function getFqcnTypeClass(InputInterface $input, SymfonyStyle $io, strin
             return $classes[0];
         }
         if (!$input->isInteractive()) {
-            throw new InvalidArgumentException(sprintf("The type \"%s\" is ambiguous.\n\nDid you mean one of these?\n    %s.", $shortClassName, implode("\n    ", $classes)));
+            throw new InvalidArgumentException(\sprintf("The type \"%s\" is ambiguous.\n\nDid you mean one of these?\n    %s.", $shortClassName, implode("\n    ", $classes)));
         }
 
-        return $io->choice(sprintf("The type \"%s\" is ambiguous.\n\nSelect one of the following form types to display its information:", $shortClassName), $classes, $classes[0]);
+        return $io->choice(\sprintf("The type \"%s\" is ambiguous.\n\nSelect one of the following form types to display its information:", $shortClassName), $classes, $classes[0]);
     }
 
     private function getFqcnTypeClasses(string $shortClassName): array
@@ -272,6 +272,7 @@ private function completeOptions(string $class, CompletionSuggestions $suggestio
         $suggestions->suggestValues($resolvedType->getOptionsResolver()->getDefinedOptions());
     }
 
+    /** @return string[] */
     private function getAvailableFormatOptions(): array
     {
         return (new DescriptorHelper())->getFormats();
diff --git a/Console/Descriptor/Descriptor.php b/Console/Descriptor/Descriptor.php
index f4835fb1e..c910acdf4 100644
--- a/Console/Descriptor/Descriptor.php
+++ b/Console/Descriptor/Descriptor.php
@@ -46,7 +46,7 @@ public function describe(OutputInterface $output, ?object $object, array $option
             null === $object => $this->describeDefaults($options),
             $object instanceof ResolvedFormTypeInterface => $this->describeResolvedFormType($object, $options),
             $object instanceof OptionsResolver => $this->describeOption($object, $options),
-            default => throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_debug_type($object))),
+            default => throw new \InvalidArgumentException(\sprintf('Object of type "%s" is not describable.', get_debug_type($object))),
         };
     }
 
diff --git a/Console/Descriptor/TextDescriptor.php b/Console/Descriptor/TextDescriptor.php
index 7b723a0af..630b87253 100644
--- a/Console/Descriptor/TextDescriptor.php
+++ b/Console/Descriptor/TextDescriptor.php
@@ -80,7 +80,7 @@ protected function describeResolvedFormType(ResolvedFormTypeInterface $resolvedF
             'extension' => 'Extension options',
         ], $formOptions);
 
-        $this->output->title(sprintf('%s (Block prefix: "%s")', $resolvedFormType->getInnerType()::class, $resolvedFormType->getInnerType()->getBlockPrefix()));
+        $this->output->title(\sprintf('%s (Block prefix: "%s")', $resolvedFormType->getInnerType()::class, $resolvedFormType->getInnerType()->getBlockPrefix()));
 
         if ($formOptions) {
             $this->output->table($tableHeaders, $this->buildTableRows($tableHeaders, $formOptions));
@@ -131,7 +131,7 @@ protected function describeOption(OptionsResolver $optionsResolver, array $optio
         }
         array_pop($rows);
 
-        $this->output->title(sprintf('%s (%s)', $options['type']::class, $options['option']));
+        $this->output->title(\sprintf('%s (%s)', $options['type']::class, $options['option']));
         $this->output->table([], $rows);
     }
 
@@ -172,7 +172,7 @@ private function normalizeAndSortOptionsColumns(array $options): array
                 } else {
                     $options[$group][] = null;
                 }
-                $options[$group][] = sprintf('<info>%s</info>', (new \ReflectionClass($class))->getShortName());
+                $options[$group][] = \sprintf('<info>%s</info>', (new \ReflectionClass($class))->getShortName());
                 $options[$group][] = new TableSeparator();
 
                 sort($opt);
@@ -196,7 +196,7 @@ private function formatClassLink(string $class, ?string $text = null): string
             return $text;
         }
 
-        return sprintf('<href=%s>%s</>', $fileLink, $text);
+        return \sprintf('<href=%s>%s</>', $fileLink, $text);
     }
 
     private function getFileLink(string $class): string
diff --git a/DependencyInjection/FormPass.php b/DependencyInjection/FormPass.php
index 408731163..bec1782d4 100644
--- a/DependencyInjection/FormPass.php
+++ b/DependencyInjection/FormPass.php
@@ -47,6 +47,7 @@ private function processFormTypes(ContainerBuilder $container): Reference
         // Get service locator argument
         $servicesMap = [];
         $namespaces = ['Symfony\Component\Form\Extension\Core\Type' => true];
+        $csrfTokenIds = [];
 
         // Builds an array with fully-qualified type class names as keys and service IDs as values
         foreach ($container->findTaggedServiceIds('form.type', true) as $serviceId => $tag) {
@@ -54,6 +55,10 @@ private function processFormTypes(ContainerBuilder $container): Reference
             $serviceDefinition = $container->getDefinition($serviceId);
             $servicesMap[$formType = $serviceDefinition->getClass()] = new Reference($serviceId);
             $namespaces[substr($formType, 0, strrpos($formType, '\\'))] = true;
+
+            if (isset($tag[0]['csrf_token_id'])) {
+                $csrfTokenIds[$formType] = $tag[0]['csrf_token_id'];
+            }
         }
 
         if ($container->hasDefinition('console.command.form_debug')) {
@@ -62,6 +67,14 @@ private function processFormTypes(ContainerBuilder $container): Reference
             $commandDefinition->setArgument(2, array_keys($servicesMap));
         }
 
+        if ($csrfTokenIds && $container->hasDefinition('form.type_extension.csrf')) {
+            $csrfExtension = $container->getDefinition('form.type_extension.csrf');
+
+            if (8 <= \count($csrfExtension->getArguments())) {
+                $csrfExtension->replaceArgument(7, $csrfTokenIds);
+            }
+        }
+
         return ServiceLocatorTagPass::register($container, $servicesMap);
     }
 
@@ -89,7 +102,7 @@ private function processFormTypeExtensions(ContainerBuilder $container): array
                 }
 
                 if (!$extendsTypes) {
-                    throw new InvalidArgumentException(sprintf('The getExtendedTypes() method for service "%s" does not return any extended types.', $serviceId));
+                    throw new InvalidArgumentException(\sprintf('The getExtendedTypes() method for service "%s" does not return any extended types.', $serviceId));
                 }
             }
         }
diff --git a/Exception/UnexpectedTypeException.php b/Exception/UnexpectedTypeException.php
index 7a4dc295c..223061b77 100644
--- a/Exception/UnexpectedTypeException.php
+++ b/Exception/UnexpectedTypeException.php
@@ -15,6 +15,6 @@ class UnexpectedTypeException extends InvalidArgumentException
 {
     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/Extension/Core/DataTransformer/ArrayToPartsTransformer.php b/Extension/Core/DataTransformer/ArrayToPartsTransformer.php
index 828bd811e..92f59c906 100644
--- a/Extension/Core/DataTransformer/ArrayToPartsTransformer.php
+++ b/Extension/Core/DataTransformer/ArrayToPartsTransformer.php
@@ -72,7 +72,7 @@ public function reverseTransform(mixed $array): mixed
                 return null;
             }
 
-            throw new TransformationFailedException(sprintf('The keys "%s" should not be empty.', implode('", "', $emptyKeys)));
+            throw new TransformationFailedException(\sprintf('The keys "%s" should not be empty.', implode('", "', $emptyKeys)));
         }
 
         return $result;
diff --git a/Extension/Core/DataTransformer/BaseDateTimeTransformer.php b/Extension/Core/DataTransformer/BaseDateTimeTransformer.php
index 8d311b3f8..ca85fd44d 100644
--- a/Extension/Core/DataTransformer/BaseDateTimeTransformer.php
+++ b/Extension/Core/DataTransformer/BaseDateTimeTransformer.php
@@ -47,13 +47,13 @@ public function __construct(?string $inputTimezone = null, ?string $outputTimezo
         try {
             new \DateTimeZone($this->inputTimezone);
         } catch (\Exception $e) {
-            throw new InvalidArgumentException(sprintf('Input timezone is invalid: "%s".', $this->inputTimezone), $e->getCode(), $e);
+            throw new InvalidArgumentException(\sprintf('Input timezone is invalid: "%s".', $this->inputTimezone), $e->getCode(), $e);
         }
 
         try {
             new \DateTimeZone($this->outputTimezone);
         } catch (\Exception $e) {
-            throw new InvalidArgumentException(sprintf('Output timezone is invalid: "%s".', $this->outputTimezone), $e->getCode(), $e);
+            throw new InvalidArgumentException(\sprintf('Output timezone is invalid: "%s".', $this->outputTimezone), $e->getCode(), $e);
         }
     }
 }
diff --git a/Extension/Core/DataTransformer/BooleanToStringTransformer.php b/Extension/Core/DataTransformer/BooleanToStringTransformer.php
index e91bdb4db..7ef84bb44 100644
--- a/Extension/Core/DataTransformer/BooleanToStringTransformer.php
+++ b/Extension/Core/DataTransformer/BooleanToStringTransformer.php
@@ -25,17 +25,13 @@
  */
 class BooleanToStringTransformer implements DataTransformerInterface
 {
-    private string $trueValue;
-
-    private array $falseValues;
-
     /**
      * @param string $trueValue The value emitted upon transform if the input is true
      */
-    public function __construct(string $trueValue, array $falseValues = [null])
-    {
-        $this->trueValue = $trueValue;
-        $this->falseValues = $falseValues;
+    public function __construct(
+        private string $trueValue,
+        private array $falseValues = [null],
+    ) {
         if (\in_array($this->trueValue, $this->falseValues, true)) {
             throw new InvalidArgumentException('The specified "true" value is contained in the false-values.');
         }
diff --git a/Extension/Core/DataTransformer/ChoiceToValueTransformer.php b/Extension/Core/DataTransformer/ChoiceToValueTransformer.php
index 52ee25e93..e75bc31de 100644
--- a/Extension/Core/DataTransformer/ChoiceToValueTransformer.php
+++ b/Extension/Core/DataTransformer/ChoiceToValueTransformer.php
@@ -45,7 +45,7 @@ public function reverseTransform(mixed $value): mixed
                 return null;
             }
 
-            throw new TransformationFailedException(sprintf('The choice "%s" does not exist or is not unique.', $value));
+            throw new TransformationFailedException(\sprintf('The choice "%s" does not exist or is not unique.', $value));
         }
 
         return current($choices);
diff --git a/Extension/Core/DataTransformer/DateIntervalToArrayTransformer.php b/Extension/Core/DataTransformer/DateIntervalToArrayTransformer.php
index 08c058597..8591db191 100644
--- a/Extension/Core/DataTransformer/DateIntervalToArrayTransformer.php
+++ b/Extension/Core/DataTransformer/DateIntervalToArrayTransformer.php
@@ -93,9 +93,8 @@ public function transform(mixed $dateInterval): array
             }
         }
         $result['invert'] = '-' === $result['invert'];
-        $result = array_intersect_key($result, array_flip($this->fields));
 
-        return $result;
+        return array_intersect_key($result, array_flip($this->fields));
     }
 
     /**
@@ -124,29 +123,29 @@ public function reverseTransform(mixed $value): ?\DateInterval
             }
         }
         if (\count($emptyFields) > 0) {
-            throw new TransformationFailedException(sprintf('The fields "%s" should not be empty.', implode('", "', $emptyFields)));
+            throw new TransformationFailedException(\sprintf('The fields "%s" should not be empty.', implode('", "', $emptyFields)));
         }
         if (isset($value['invert']) && !\is_bool($value['invert'])) {
             throw new TransformationFailedException('The value of "invert" must be boolean.');
         }
         foreach (self::AVAILABLE_FIELDS as $field => $char) {
             if ('invert' !== $field && isset($value[$field]) && !ctype_digit((string) $value[$field])) {
-                throw new TransformationFailedException(sprintf('This amount of "%s" is invalid.', $field));
+                throw new TransformationFailedException(\sprintf('This amount of "%s" is invalid.', $field));
             }
         }
         try {
             if (!empty($value['weeks'])) {
-                $interval = sprintf(
+                $interval = \sprintf(
                     'P%sY%sM%sWT%sH%sM%sS',
                     empty($value['years']) ? '0' : $value['years'],
                     empty($value['months']) ? '0' : $value['months'],
-                    empty($value['weeks']) ? '0' : $value['weeks'],
+                    $value['weeks'],
                     empty($value['hours']) ? '0' : $value['hours'],
                     empty($value['minutes']) ? '0' : $value['minutes'],
                     empty($value['seconds']) ? '0' : $value['seconds']
                 );
             } else {
-                $interval = sprintf(
+                $interval = \sprintf(
                     'P%sY%sM%sDT%sH%sM%sS',
                     empty($value['years']) ? '0' : $value['years'],
                     empty($value['months']) ? '0' : $value['months'],
diff --git a/Extension/Core/DataTransformer/DateIntervalToStringTransformer.php b/Extension/Core/DataTransformer/DateIntervalToStringTransformer.php
index e1a3d9724..527836bf5 100644
--- a/Extension/Core/DataTransformer/DateIntervalToStringTransformer.php
+++ b/Extension/Core/DataTransformer/DateIntervalToStringTransformer.php
@@ -79,7 +79,7 @@ public function reverseTransform(mixed $value): ?\DateInterval
         }
         $valuePattern = '/^'.preg_replace('/%([yYmMdDhHiIsSwW])(\w)/', '(?P<$1>\d+)$2', $this->format).'$/';
         if (!preg_match($valuePattern, $value)) {
-            throw new TransformationFailedException(sprintf('Value "%s" contains intervals not accepted by format "%s".', $value, $this->format));
+            throw new TransformationFailedException(\sprintf('Value "%s" contains intervals not accepted by format "%s".', $value, $this->format));
         }
         try {
             $dateInterval = new \DateInterval($value);
diff --git a/Extension/Core/DataTransformer/DateTimeToArrayTransformer.php b/Extension/Core/DataTransformer/DateTimeToArrayTransformer.php
index 8c3d2d226..06d24cf8e 100644
--- a/Extension/Core/DataTransformer/DateTimeToArrayTransformer.php
+++ b/Extension/Core/DataTransformer/DateTimeToArrayTransformer.php
@@ -126,7 +126,7 @@ public function reverseTransform(mixed $value): ?\DateTime
         }
 
         if (\count($emptyFields) > 0) {
-            throw new TransformationFailedException(sprintf('The fields "%s" should not be empty.', implode('", "', $emptyFields)));
+            throw new TransformationFailedException(\sprintf('The fields "%s" should not be empty.', implode('", "', $emptyFields)));
         }
 
         if (isset($value['month']) && !ctype_digit((string) $value['month'])) {
@@ -158,7 +158,7 @@ public function reverseTransform(mixed $value): ?\DateTime
         }
 
         try {
-            $dateTime = new \DateTime(sprintf(
+            $dateTime = new \DateTime(\sprintf(
                 '%s-%s-%s %s:%s:%s',
                 empty($value['year']) ? $this->referenceDate->format('Y') : $value['year'],
                 empty($value['month']) ? $this->referenceDate->format('m') : $value['month'],
diff --git a/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformer.php b/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformer.php
index 855b22a49..dbeed8e49 100644
--- a/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformer.php
+++ b/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformer.php
@@ -85,7 +85,7 @@ public function reverseTransform(mixed $dateTimeLocal): ?\DateTime
         // to maintain backwards compatibility we do not strictly validate the submitted date
         // see https://github.com/symfony/symfony/issues/28699
         if (!preg_match('/^(\d{4})-(\d{2})-(\d{2})[T ]\d{2}:\d{2}(?::\d{2})?/', $dateTimeLocal, $matches)) {
-            throw new TransformationFailedException(sprintf('The date "%s" is not a valid date.', $dateTimeLocal));
+            throw new TransformationFailedException(\sprintf('The date "%s" is not a valid date.', $dateTimeLocal));
         }
 
         try {
@@ -99,7 +99,7 @@ public function reverseTransform(mixed $dateTimeLocal): ?\DateTime
         }
 
         if (!checkdate($matches[2], $matches[3], $matches[1])) {
-            throw new TransformationFailedException(sprintf('The date "%s-%s-%s" is not a valid date.', $matches[1], $matches[2], $matches[3]));
+            throw new TransformationFailedException(\sprintf('The date "%s-%s-%s" is not a valid date.', $matches[1], $matches[2], $matches[3]));
         }
 
         return $dateTime;
diff --git a/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php b/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php
index b7ea09259..426c4bf89 100644
--- a/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php
+++ b/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Form\Extension\Core\DataTransformer;
 
+use Symfony\Component\Form\Exception\InvalidArgumentException;
 use Symfony\Component\Form\Exception\TransformationFailedException;
 use Symfony\Component\Form\Exception\UnexpectedTypeException;
 
@@ -30,12 +31,12 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer
     /**
      * @see BaseDateTimeTransformer::formats for available format options
      *
-     * @param string|null $inputTimezone  The name of the input timezone
-     * @param string|null $outputTimezone The name of the output timezone
-     * @param int|null    $dateFormat     The date format
-     * @param int|null    $timeFormat     The time format
-     * @param int         $calendar       One of the \IntlDateFormatter calendar constants
-     * @param string|null $pattern        A pattern to pass to \IntlDateFormatter
+     * @param string|null       $inputTimezone  The name of the input timezone
+     * @param string|null       $outputTimezone The name of the output timezone
+     * @param int|null          $dateFormat     The date format
+     * @param int|null          $timeFormat     The time format
+     * @param int|\IntlCalendar $calendar       One of the \IntlDateFormatter calendar constants or an \IntlCalendar instance
+     * @param string|null       $pattern        A pattern to pass to \IntlDateFormatter
      *
      * @throws UnexpectedTypeException If a format is not supported or if a timezone is not a string
      */
@@ -44,7 +45,7 @@ public function __construct(
         ?string $outputTimezone = null,
         ?int $dateFormat = null,
         ?int $timeFormat = null,
-        private int $calendar = \IntlDateFormatter::GREGORIAN,
+        private int|\IntlCalendar $calendar = \IntlDateFormatter::GREGORIAN,
         private ?string $pattern = null,
     ) {
         parent::__construct($inputTimezone, $outputTimezone);
@@ -60,6 +61,10 @@ public function __construct(
             throw new UnexpectedTypeException($timeFormat, implode('", "', self::$formats));
         }
 
+        if (\is_int($calendar) && !\in_array($calendar, [\IntlDateFormatter::GREGORIAN, \IntlDateFormatter::TRADITIONAL], true)) {
+            throw new InvalidArgumentException('The "calendar" option should be either an \IntlDateFormatter constant or an \IntlCalendar instance.');
+        }
+
         $this->dateFormat = $dateFormat;
         $this->timeFormat = $timeFormat;
     }
@@ -128,7 +133,7 @@ public function reverseTransform(mixed $value): ?\DateTime
         } elseif (false === $timestamp) {
             // the value couldn't be parsed but the Intl extension didn't report an error code, this
             // could be the case when the Intl polyfill is used which always returns 0 as the error code
-            throw new TransformationFailedException(sprintf('"%s" could not be parsed as a date.', $value));
+            throw new TransformationFailedException(\sprintf('"%s" could not be parsed as a date.', $value));
         }
 
         try {
@@ -137,7 +142,7 @@ public function reverseTransform(mixed $value): ?\DateTime
                 $dateTime = new \DateTime(gmdate('Y-m-d', $timestamp), new \DateTimeZone($this->outputTimezone));
             } else {
                 // read timestamp into DateTime object - the formatter delivers a timestamp
-                $dateTime = new \DateTime(sprintf('@%s', $timestamp));
+                $dateTime = new \DateTime(\sprintf('@%s', $timestamp));
             }
             // set timezone separately, as it would be ignored if set via the constructor,
             // see https://php.net/datetime.construct
@@ -157,8 +162,6 @@ public function reverseTransform(mixed $value): ?\DateTime
      * Returns a preconfigured IntlDateFormatter instance.
      *
      * @param bool $ignoreTimezone Use UTC regardless of the configured timezone
-     *
-     * @throws TransformationFailedException in case the date formatter cannot be constructed
      */
     protected function getIntlDateFormatter(bool $ignoreTimezone = false): \IntlDateFormatter
     {
@@ -170,12 +173,6 @@ protected function getIntlDateFormatter(bool $ignoreTimezone = false): \IntlDate
         $pattern = $this->pattern;
 
         $intlDateFormatter = new \IntlDateFormatter(\Locale::getDefault(), $dateFormat, $timeFormat, $timezone, $calendar, $pattern ?? '');
-
-        // new \intlDateFormatter may return null instead of false in case of failure, see https://bugs.php.net/66323
-        if (!$intlDateFormatter) {
-            throw new TransformationFailedException(intl_get_error_message(), intl_get_error_code());
-        }
-
         $intlDateFormatter->setLenient(false);
 
         return $intlDateFormatter;
diff --git a/Extension/Core/DataTransformer/DateTimeToRfc3339Transformer.php b/Extension/Core/DataTransformer/DateTimeToRfc3339Transformer.php
index 41e63e57c..d32b3eae2 100644
--- a/Extension/Core/DataTransformer/DateTimeToRfc3339Transformer.php
+++ b/Extension/Core/DataTransformer/DateTimeToRfc3339Transformer.php
@@ -64,7 +64,7 @@ public function reverseTransform(mixed $rfc3339): ?\DateTime
         }
 
         if (!preg_match('/^(\d{4})-(\d{2})-(\d{2})T\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?(?:Z|(?:(?:\+|-)\d{2}:\d{2}))$/', $rfc3339, $matches)) {
-            throw new TransformationFailedException(sprintf('The date "%s" is not a valid date.', $rfc3339));
+            throw new TransformationFailedException(\sprintf('The date "%s" is not a valid date.', $rfc3339));
         }
 
         try {
@@ -78,7 +78,7 @@ public function reverseTransform(mixed $rfc3339): ?\DateTime
         }
 
         if (!checkdate($matches[2], $matches[3], $matches[1])) {
-            throw new TransformationFailedException(sprintf('The date "%s-%s-%s" is not a valid date.', $matches[1], $matches[2], $matches[3]));
+            throw new TransformationFailedException(\sprintf('The date "%s-%s-%s" is not a valid date.', $matches[1], $matches[2], $matches[3]));
         }
 
         return $dateTime;
diff --git a/Extension/Core/DataTransformer/IntegerToLocalizedStringTransformer.php b/Extension/Core/DataTransformer/IntegerToLocalizedStringTransformer.php
index eb5a2d6ff..d83e31b42 100644
--- a/Extension/Core/DataTransformer/IntegerToLocalizedStringTransformer.php
+++ b/Extension/Core/DataTransformer/IntegerToLocalizedStringTransformer.php
@@ -38,7 +38,7 @@ public function reverseTransform(mixed $value): int|float|null
         $decimalSeparator = $this->getNumberFormatter()->getSymbol(\NumberFormatter::DECIMAL_SEPARATOR_SYMBOL);
 
         if (\is_string($value) && str_contains($value, $decimalSeparator)) {
-            throw new TransformationFailedException(sprintf('The value "%s" is not a valid integer.', $value));
+            throw new TransformationFailedException(\sprintf('The value "%s" is not a valid integer.', $value));
         }
 
         $result = parent::reverseTransform($value);
diff --git a/Extension/Core/DataTransformer/IntlTimeZoneToStringTransformer.php b/Extension/Core/DataTransformer/IntlTimeZoneToStringTransformer.php
index 446a95f70..19626e49c 100644
--- a/Extension/Core/DataTransformer/IntlTimeZoneToStringTransformer.php
+++ b/Extension/Core/DataTransformer/IntlTimeZoneToStringTransformer.php
@@ -70,7 +70,7 @@ public function reverseTransform(mixed $value): mixed
         $intlTimeZone = \IntlTimeZone::createTimeZone($value);
 
         if ('Etc/Unknown' === $intlTimeZone->getID()) {
-            throw new TransformationFailedException(sprintf('Unknown timezone identifier "%s".', $value));
+            throw new TransformationFailedException(\sprintf('Unknown timezone identifier "%s".', $value));
         }
 
         return $intlTimeZone;
diff --git a/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformer.php b/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformer.php
index caf6750e9..b03f8da44 100644
--- a/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformer.php
+++ b/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformer.php
@@ -75,7 +75,7 @@ public function reverseTransform(mixed $value): int|float|null
             }
 
             if ($value > \PHP_INT_MAX || $value < \PHP_INT_MIN) {
-                throw new TransformationFailedException(sprintf('Cannot cast "%s" to an integer. Try setting the input to "float" instead.', $value));
+                throw new TransformationFailedException(\sprintf('Cannot cast "%s" to an integer. Try setting the input to "float" instead.', $value));
             }
 
             $value = (int) $value;
diff --git a/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php b/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php
index c1132d60d..3020dd148 100644
--- a/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php
+++ b/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php
@@ -64,9 +64,7 @@ public function transform(mixed $value): string
         }
 
         // Convert non-breaking and narrow non-breaking spaces to normal ones
-        $value = str_replace(["\xc2\xa0", "\xe2\x80\xaf"], ' ', $value);
-
-        return $value;
+        return str_replace(["\xc2\xa0", "\xe2\x80\xaf"], ' ', $value);
     }
 
     /**
@@ -145,7 +143,7 @@ public function reverseTransform(mixed $value): int|float|null
             $remainder = trim($remainder, " \t\n\r\0\x0b\xc2\xa0");
 
             if ('' !== $remainder) {
-                throw new TransformationFailedException(sprintf('The number contains unrecognized characters: "%s".', $remainder));
+                throw new TransformationFailedException(\sprintf('The number contains unrecognized characters: "%s".', $remainder));
             }
         }
 
diff --git a/Extension/Core/DataTransformer/PercentToLocalizedStringTransformer.php b/Extension/Core/DataTransformer/PercentToLocalizedStringTransformer.php
index a5ff54bc1..093c3ebe2 100644
--- a/Extension/Core/DataTransformer/PercentToLocalizedStringTransformer.php
+++ b/Extension/Core/DataTransformer/PercentToLocalizedStringTransformer.php
@@ -162,7 +162,7 @@ public function reverseTransform(mixed $value): int|float|null
             $remainder = trim($remainder, " \t\n\r\0\x0b\xc2\xa0");
 
             if ('' !== $remainder) {
-                throw new TransformationFailedException(sprintf('The number contains unrecognized characters: "%s".', $remainder));
+                throw new TransformationFailedException(\sprintf('The number contains unrecognized characters: "%s".', $remainder));
             }
         }
 
@@ -214,8 +214,6 @@ private function round(int|float $number): int|float
             \NumberFormatter::ROUND_HALFDOWN => round($number, 0, \PHP_ROUND_HALF_DOWN),
         };
 
-        $number = 1 === $roundingCoef ? (int) $number : $number / $roundingCoef;
-
-        return $number;
+        return 1 === $roundingCoef ? (int) $number : $number / $roundingCoef;
     }
 }
diff --git a/Extension/Core/DataTransformer/UlidToStringTransformer.php b/Extension/Core/DataTransformer/UlidToStringTransformer.php
index 7ace73ad0..9365cab15 100644
--- a/Extension/Core/DataTransformer/UlidToStringTransformer.php
+++ b/Extension/Core/DataTransformer/UlidToStringTransformer.php
@@ -65,7 +65,7 @@ public function reverseTransform(mixed $value): ?Ulid
         try {
             $ulid = new Ulid($value);
         } catch (\InvalidArgumentException $e) {
-            throw new TransformationFailedException(sprintf('The value "%s" is not a valid ULID.', $value), $e->getCode(), $e);
+            throw new TransformationFailedException(\sprintf('The value "%s" is not a valid ULID.', $value), $e->getCode(), $e);
         }
 
         return $ulid;
diff --git a/Extension/Core/DataTransformer/UuidToStringTransformer.php b/Extension/Core/DataTransformer/UuidToStringTransformer.php
index cc794a024..43326eb64 100644
--- a/Extension/Core/DataTransformer/UuidToStringTransformer.php
+++ b/Extension/Core/DataTransformer/UuidToStringTransformer.php
@@ -63,13 +63,13 @@ public function reverseTransform(mixed $value): ?Uuid
         }
 
         if (!Uuid::isValid($value)) {
-            throw new TransformationFailedException(sprintf('The value "%s" is not a valid UUID.', $value));
+            throw new TransformationFailedException(\sprintf('The value "%s" is not a valid UUID.', $value));
         }
 
         try {
             return Uuid::fromString($value);
         } catch (\InvalidArgumentException $e) {
-            throw new TransformationFailedException(sprintf('The value "%s" is not a valid UUID.', $value), $e->getCode(), $e);
+            throw new TransformationFailedException(\sprintf('The value "%s" is not a valid UUID.', $value), $e->getCode(), $e);
         }
     }
 }
diff --git a/Extension/Core/DataTransformer/ValueToDuplicatesTransformer.php b/Extension/Core/DataTransformer/ValueToDuplicatesTransformer.php
index e50f30c45..6f8ea4fbe 100644
--- a/Extension/Core/DataTransformer/ValueToDuplicatesTransformer.php
+++ b/Extension/Core/DataTransformer/ValueToDuplicatesTransformer.php
@@ -71,7 +71,7 @@ public function reverseTransform(mixed $array): mixed
                 return null;
             }
 
-            throw new TransformationFailedException(sprintf('The keys "%s" should not be empty.', implode('", "', $emptyKeys)));
+            throw new TransformationFailedException(\sprintf('The keys "%s" should not be empty.', implode('", "', $emptyKeys)));
         }
 
         return $result;
diff --git a/Extension/Core/DataTransformer/WeekToArrayTransformer.php b/Extension/Core/DataTransformer/WeekToArrayTransformer.php
index c10bc735f..448ae4278 100644
--- a/Extension/Core/DataTransformer/WeekToArrayTransformer.php
+++ b/Extension/Core/DataTransformer/WeekToArrayTransformer.php
@@ -40,7 +40,7 @@ public function transform(mixed $value): array
         }
 
         if (!\is_string($value)) {
-            throw new TransformationFailedException(sprintf('Value is expected to be a string but was "%s".', get_debug_type($value)));
+            throw new TransformationFailedException(\sprintf('Value is expected to be a string but was "%s".', get_debug_type($value)));
         }
 
         if (0 === preg_match('/^(?P<year>\d{4})-W(?P<week>\d{2})$/', $value, $matches)) {
@@ -70,7 +70,7 @@ public function reverseTransform(mixed $value): ?string
         }
 
         if (!\is_array($value)) {
-            throw new TransformationFailedException(sprintf('Value is expected to be an array, but was "%s".', get_debug_type($value)));
+            throw new TransformationFailedException(\sprintf('Value is expected to be an array, but was "%s".', get_debug_type($value)));
         }
 
         if (!\array_key_exists('year', $value)) {
@@ -82,7 +82,7 @@ public function reverseTransform(mixed $value): ?string
         }
 
         if ($additionalKeys = array_diff(array_keys($value), ['year', 'week'])) {
-            throw new TransformationFailedException(sprintf('Expected only keys "year" and "week" to be present, but also got ["%s"].', implode('", "', $additionalKeys)));
+            throw new TransformationFailedException(\sprintf('Expected only keys "year" and "week" to be present, but also got ["%s"].', implode('", "', $additionalKeys)));
         }
 
         if (null === $value['year'] && null === $value['week']) {
@@ -90,18 +90,18 @@ public function reverseTransform(mixed $value): ?string
         }
 
         if (!\is_int($value['year'])) {
-            throw new TransformationFailedException(sprintf('Year is expected to be an integer, but was "%s".', get_debug_type($value['year'])));
+            throw new TransformationFailedException(\sprintf('Year is expected to be an integer, but was "%s".', get_debug_type($value['year'])));
         }
 
         if (!\is_int($value['week'])) {
-            throw new TransformationFailedException(sprintf('Week is expected to be an integer, but was "%s".', get_debug_type($value['week'])));
+            throw new TransformationFailedException(\sprintf('Week is expected to be an integer, but was "%s".', get_debug_type($value['week'])));
         }
 
         // The 28th December is always in the last week of the year
         if (date('W', strtotime('28th December '.$value['year'])) < $value['week']) {
-            throw new TransformationFailedException(sprintf('Week "%d" does not exist for year "%d".', $value['week'], $value['year']));
+            throw new TransformationFailedException(\sprintf('Week "%d" does not exist for year "%d".', $value['week'], $value['year']));
         }
 
-        return sprintf('%d-W%02d', $value['year'], $value['week']);
+        return \sprintf('%d-W%02d', $value['year'], $value['week']);
     }
 }
diff --git a/Extension/Core/EventListener/ResizeFormListener.php b/Extension/Core/EventListener/ResizeFormListener.php
index d67efab31..299f91937 100644
--- a/Extension/Core/EventListener/ResizeFormListener.php
+++ b/Extension/Core/EventListener/ResizeFormListener.php
@@ -12,6 +12,7 @@
 namespace Symfony\Component\Form\Extension\Core\EventListener;
 
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Form\Event\PostSetDataEvent;
 use Symfony\Component\Form\Exception\UnexpectedTypeException;
 use Symfony\Component\Form\FormEvent;
 use Symfony\Component\Form\FormEvents;
@@ -27,6 +28,9 @@ class ResizeFormListener implements EventSubscriberInterface
     protected array $prototypeOptions;
 
     private \Closure|bool $deleteEmpty;
+    // BC, to be removed in 8.0
+    private bool $overridden = true;
+    private bool $usePreSetData = false;
 
     public function __construct(
         private string $type,
@@ -44,15 +48,57 @@ public function __construct(
     public static function getSubscribedEvents(): array
     {
         return [
-            FormEvents::PRE_SET_DATA => 'preSetData',
+            FormEvents::PRE_SET_DATA => 'preSetData', // deprecated
+            FormEvents::POST_SET_DATA => ['postSetData', 255], // as early as possible
             FormEvents::PRE_SUBMIT => 'preSubmit',
             // (MergeCollectionListener, MergeDoctrineCollectionListener)
             FormEvents::SUBMIT => ['onSubmit', 50],
         ];
     }
 
+    /**
+     * @deprecated Since Symfony 7.2, use {@see postSetData()} instead.
+     */
     public function preSetData(FormEvent $event): void
     {
+        if (__CLASS__ === static::class
+            || __CLASS__ === (new \ReflectionClass($this))->getMethod('preSetData')->getDeclaringClass()->name
+        ) {
+            // not a child class, or child class does not overload PRE_SET_DATA
+            return;
+        }
+
+        trigger_deprecation('symfony/form', '7.2', 'Calling "%s()" is deprecated, use "%s::postSetData()" instead.', __METHOD__, __CLASS__);
+        // parent::preSetData() has been called
+        $this->overridden = false;
+        try {
+            $this->postSetData($event);
+        } finally {
+            $this->usePreSetData = true;
+        }
+    }
+
+    /**
+     * Remove FormEvent type hint in 8.0.
+     *
+     * @final since Symfony 7.2
+     */
+    public function postSetData(FormEvent|PostSetDataEvent $event): void
+    {
+        if (__CLASS__ !== static::class) {
+            if ($this->overridden) {
+                trigger_deprecation('symfony/form', '7.2', 'Calling "%s::preSetData()" is deprecated, use "%s::postSetData()" instead.', static::class, __CLASS__);
+                // parent::preSetData() has not been called, noop
+
+                return;
+            }
+
+            if ($this->usePreSetData) {
+                // nothing else to do
+                return;
+            }
+        }
+
         $form = $event->getForm();
         $data = $event->getData() ?? [];
 
diff --git a/Extension/Core/Type/BaseType.php b/Extension/Core/Type/BaseType.php
index 68190c742..a7f745d78 100644
--- a/Extension/Core/Type/BaseType.php
+++ b/Extension/Core/Type/BaseType.php
@@ -46,9 +46,9 @@ public function buildView(FormView $view, FormInterface $form, array $options):
 
         if ($view->parent) {
             if ('' !== ($parentFullName = $view->parent->vars['full_name'])) {
-                $id = sprintf('%s_%s', $view->parent->vars['id'], $name);
-                $fullName = sprintf('%s[%s]', $parentFullName, $name);
-                $uniqueBlockPrefix = sprintf('%s_%s', $view->parent->vars['unique_block_prefix'], $blockName);
+                $id = \sprintf('%s_%s', $view->parent->vars['id'], $name);
+                $fullName = \sprintf('%s[%s]', $parentFullName, $name);
+                $uniqueBlockPrefix = \sprintf('%s_%s', $view->parent->vars['unique_block_prefix'], $blockName);
             } else {
                 $id = $name;
                 $fullName = $name;
diff --git a/Extension/Core/Type/ChoiceType.php b/Extension/Core/Type/ChoiceType.php
index 2e9cb7087..fc083ee40 100644
--- a/Extension/Core/Type/ChoiceType.php
+++ b/Extension/Core/Type/ChoiceType.php
@@ -27,10 +27,12 @@
 use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
 use Symfony\Component\Form\ChoiceList\Factory\PropertyAccessDecorator;
 use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
+use Symfony\Component\Form\ChoiceList\Loader\LazyChoiceLoader;
 use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
 use Symfony\Component\Form\ChoiceList\View\ChoiceListView;
 use Symfony\Component\Form\ChoiceList\View\ChoiceView;
 use Symfony\Component\Form\Event\PreSubmitEvent;
+use Symfony\Component\Form\Exception\LogicException;
 use Symfony\Component\Form\Exception\TransformationFailedException;
 use Symfony\Component\Form\Extension\Core\DataMapper\CheckboxListMapper;
 use Symfony\Component\Form\Extension\Core\DataMapper\RadioListMapper;
@@ -51,16 +53,16 @@
 class ChoiceType extends AbstractType
 {
     private ChoiceListFactoryInterface $choiceListFactory;
-    private ?TranslatorInterface $translator;
 
-    public function __construct(?ChoiceListFactoryInterface $choiceListFactory = null, ?TranslatorInterface $translator = null)
-    {
+    public function __construct(
+        ?ChoiceListFactoryInterface $choiceListFactory = null,
+        private ?TranslatorInterface $translator = null,
+    ) {
         $this->choiceListFactory = $choiceListFactory ?? new CachingFactoryDecorator(
             new PropertyAccessDecorator(
                 new DefaultChoiceListFactory()
             )
         );
-        $this->translator = $translator;
     }
 
     public function buildForm(FormBuilderInterface $builder, array $options): void
@@ -141,9 +143,9 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
                             $knownValues[$child->getName()] = $value;
                             unset($unknownValues[$value]);
                             continue;
-                        } else {
-                            $knownValues[$child->getName()] = null;
                         }
+
+                        $knownValues[$child->getName()] = null;
                     }
                 } else {
                     foreach ($choiceList->getChoicesForValues($data) as $key => $choice) {
@@ -158,7 +160,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
 
                 // Throw exception if unknown values were submitted (multiple choices will be handled in a different event listener below)
                 if (\count($unknownValues) > 0 && !$options['multiple']) {
-                    throw new TransformationFailedException(sprintf('The choices "%s" do not exist in the choice list.', implode('", "', array_keys($unknownValues))));
+                    throw new TransformationFailedException(\sprintf('The choices "%s" do not exist in the choice list.', implode('", "', array_keys($unknownValues))));
                 }
 
                 $event->setData($knownValues);
@@ -182,7 +184,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
                         $message = strtr($messageTemplate, ['{{ value }}' => $clientDataAsString]);
                     }
 
-                    $form->addError(new FormError($message, $messageTemplate, ['{{ value }}' => $clientDataAsString], null, new TransformationFailedException(sprintf('The choices "%s" do not exist in the choice list.', $clientDataAsString))));
+                    $form->addError(new FormError($message, $messageTemplate, ['{{ value }}' => $clientDataAsString], null, new TransformationFailedException(\sprintf('The choices "%s" do not exist in the choice list.', $clientDataAsString))));
                 }
             });
 
@@ -273,6 +275,8 @@ public function buildView(FormView $view, FormInterface $form, array $options):
 
     public function finishView(FormView $view, FormInterface $form, array $options): void
     {
+        $view->vars['duplicate_preferred_choices'] = $options['duplicate_preferred_choices'];
+
         if ($options['expanded']) {
             // Radio buttons should have the same name as the parent
             $childName = $view->vars['full_name'];
@@ -333,11 +337,24 @@ public function configureOptions(OptionsResolver $resolver): void
             return $choiceTranslationDomain;
         };
 
+        $choiceLoaderNormalizer = static function (Options $options, ?ChoiceLoaderInterface $choiceLoader) {
+            if (!$options['choice_lazy']) {
+                return $choiceLoader;
+            }
+
+            if (null === $choiceLoader) {
+                throw new LogicException('The "choice_lazy" option can only be used if the "choice_loader" option is set.');
+            }
+
+            return new LazyChoiceLoader($choiceLoader);
+        };
+
         $resolver->setDefaults([
             'multiple' => false,
             'expanded' => false,
             'choices' => [],
             'choice_filter' => null,
+            'choice_lazy' => false,
             'choice_loader' => null,
             'choice_label' => null,
             'choice_name' => null,
@@ -365,9 +382,11 @@ public function configureOptions(OptionsResolver $resolver): void
 
         $resolver->setNormalizer('placeholder', $placeholderNormalizer);
         $resolver->setNormalizer('choice_translation_domain', $choiceTranslationDomainNormalizer);
+        $resolver->setNormalizer('choice_loader', $choiceLoaderNormalizer);
 
         $resolver->setAllowedTypes('choices', ['null', 'array', \Traversable::class]);
         $resolver->setAllowedTypes('choice_translation_domain', ['null', 'bool', 'string']);
+        $resolver->setAllowedTypes('choice_lazy', 'bool');
         $resolver->setAllowedTypes('choice_loader', ['null', ChoiceLoaderInterface::class, ChoiceLoader::class]);
         $resolver->setAllowedTypes('choice_filter', ['null', 'callable', 'string', PropertyPath::class, ChoiceFilter::class]);
         $resolver->setAllowedTypes('choice_label', ['null', 'bool', 'callable', 'string', PropertyPath::class, ChoiceLabel::class]);
@@ -381,6 +400,8 @@ public function configureOptions(OptionsResolver $resolver): void
         $resolver->setAllowedTypes('separator_html', ['bool']);
         $resolver->setAllowedTypes('duplicate_preferred_choices', 'bool');
         $resolver->setAllowedTypes('group_by', ['null', 'callable', 'string', PropertyPath::class, GroupBy::class]);
+
+        $resolver->setInfo('choice_lazy', 'Load choices on demand. When set to true, only the selected choices are loaded and rendered.');
     }
 
     public function getBlockPrefix(): string
diff --git a/Extension/Core/Type/CountryType.php b/Extension/Core/Type/CountryType.php
index adef745c5..503bc8327 100644
--- a/Extension/Core/Type/CountryType.php
+++ b/Extension/Core/Type/CountryType.php
@@ -27,7 +27,7 @@ public function configureOptions(OptionsResolver $resolver): void
         $resolver->setDefaults([
             'choice_loader' => function (Options $options) {
                 if (!class_exists(Intl::class)) {
-                    throw new LogicException(sprintf('The "symfony/intl" component is required to use "%s". Try running "composer require symfony/intl".', static::class));
+                    throw new LogicException(\sprintf('The "symfony/intl" component is required to use "%s". Try running "composer require symfony/intl".', static::class));
                 }
 
                 $choiceTranslationLocale = $options['choice_translation_locale'];
diff --git a/Extension/Core/Type/CurrencyType.php b/Extension/Core/Type/CurrencyType.php
index 3581a77d9..abfa39729 100644
--- a/Extension/Core/Type/CurrencyType.php
+++ b/Extension/Core/Type/CurrencyType.php
@@ -27,7 +27,7 @@ public function configureOptions(OptionsResolver $resolver): void
         $resolver->setDefaults([
             'choice_loader' => function (Options $options) {
                 if (!class_exists(Intl::class)) {
-                    throw new LogicException(sprintf('The "symfony/intl" component is required to use "%s". Try running "composer require symfony/intl".', static::class));
+                    throw new LogicException(\sprintf('The "symfony/intl" component is required to use "%s". Try running "composer require symfony/intl".', static::class));
                 }
 
                 $choiceTranslationLocale = $options['choice_translation_locale'];
diff --git a/Extension/Core/Type/DateTimeType.php b/Extension/Core/Type/DateTimeType.php
index 1c67eeb15..cf4c2b741 100644
--- a/Extension/Core/Type/DateTimeType.php
+++ b/Extension/Core/Type/DateTimeType.php
@@ -203,7 +203,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
                 }
 
                 if ($date->getTimezone()->getName() !== $options['model_timezone']) {
-                    throw new LogicException(sprintf('Using a "%s" instance with a timezone ("%s") not matching the configured model timezone "%s" is not supported.', get_debug_type($date), $date->getTimezone()->getName(), $options['model_timezone']));
+                    throw new LogicException(\sprintf('Using a "%s" instance with a timezone ("%s") not matching the configured model timezone "%s" is not supported.', get_debug_type($date), $date->getTimezone()->getName(), $options['model_timezone']));
                 }
             });
         }
@@ -311,7 +311,7 @@ public function configureOptions(OptionsResolver $resolver): void
 
         $resolver->setNormalizer('date_format', static function (Options $options, $dateFormat) {
             if (null !== $dateFormat && 'single_text' === $options['widget'] && self::HTML5_FORMAT === $options['format']) {
-                throw new LogicException(sprintf('Cannot use the "date_format" option of the "%s" with an HTML5 date.', self::class));
+                throw new LogicException(\sprintf('Cannot use the "date_format" option of the "%s" with an HTML5 date.', self::class));
             }
 
             return $dateFormat;
@@ -319,10 +319,10 @@ public function configureOptions(OptionsResolver $resolver): void
         $resolver->setNormalizer('widget', static function (Options $options, $widget) {
             if ('single_text' === $widget) {
                 if (null !== $options['date_widget']) {
-                    throw new LogicException(sprintf('Cannot use the "date_widget" option of the "%s" when the "widget" option is set to "single_text".', self::class));
+                    throw new LogicException(\sprintf('Cannot use the "date_widget" option of the "%s" when the "widget" option is set to "single_text".', self::class));
                 }
                 if (null !== $options['time_widget']) {
-                    throw new LogicException(sprintf('Cannot use the "time_widget" option of the "%s" when the "widget" option is set to "single_text".', self::class));
+                    throw new LogicException(\sprintf('Cannot use the "time_widget" option of the "%s" when the "widget" option is set to "single_text".', self::class));
                 }
             } elseif (null === $widget && null === $options['date_widget'] && null === $options['time_widget']) {
                 return 'single_text';
@@ -332,7 +332,7 @@ public function configureOptions(OptionsResolver $resolver): void
         });
         $resolver->setNormalizer('html5', static function (Options $options, $html5) {
             if ($html5 && self::HTML5_FORMAT !== $options['format']) {
-                throw new LogicException(sprintf('Cannot use the "format" option of "%s" when the "html5" option is enabled.', self::class));
+                throw new LogicException(\sprintf('Cannot use the "format" option of "%s" when the "html5" option is enabled.', self::class));
             }
 
             return $html5;
diff --git a/Extension/Core/Type/DateType.php b/Extension/Core/Type/DateType.php
index cfbbaf4c0..36b430e14 100644
--- a/Extension/Core/Type/DateType.php
+++ b/Extension/Core/Type/DateType.php
@@ -49,7 +49,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
     {
         $dateFormat = \is_int($options['format']) ? $options['format'] : self::DEFAULT_FORMAT;
         $timeFormat = \IntlDateFormatter::NONE;
-        $calendar = \IntlDateFormatter::GREGORIAN;
+        $calendar = $options['calendar'] ?? \IntlDateFormatter::GREGORIAN;
         $pattern = \is_string($options['format']) ? $options['format'] : '';
 
         if (!\in_array($dateFormat, self::ACCEPTED_FORMATS, true)) {
@@ -58,7 +58,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
 
         if ('single_text' === $options['widget']) {
             if ('' !== $pattern && !str_contains($pattern, 'y') && !str_contains($pattern, 'M') && !str_contains($pattern, 'd')) {
-                throw new InvalidOptionsException(sprintf('The "format" option should contain the letters "y", "M" or "d". Its current value is "%s".', $pattern));
+                throw new InvalidOptionsException(\sprintf('The "format" option should contain the letters "y", "M" or "d". Its current value is "%s".', $pattern));
             }
 
             $builder->addViewTransformer(new DateTimeToLocalizedStringTransformer(
@@ -71,7 +71,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
             ));
         } else {
             if ('' !== $pattern && (!str_contains($pattern, 'y') || !str_contains($pattern, 'M') || !str_contains($pattern, 'd'))) {
-                throw new InvalidOptionsException(sprintf('The "format" option should contain the letters "y", "M" and "d". Its current value is "%s".', $pattern));
+                throw new InvalidOptionsException(\sprintf('The "format" option should contain the letters "y", "M" and "d". Its current value is "%s".', $pattern));
             }
 
             $yearOptions = $monthOptions = $dayOptions = [
@@ -120,17 +120,11 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
                 \Locale::getDefault(),
                 $dateFormat,
                 $timeFormat,
-                // see https://bugs.php.net/66323
-                class_exists(\IntlTimeZone::class, false) ? \IntlTimeZone::createDefault() : null,
+                null,
                 $calendar,
                 $pattern
             );
 
-            // new \IntlDateFormatter may return null instead of false in case of failure, see https://bugs.php.net/66323
-            if (!$formatter) {
-                throw new InvalidOptionsException(intl_get_error_message(), intl_get_error_code());
-            }
-
             $formatter->setLenient(false);
 
             if ('choice' === $options['widget']) {
@@ -187,7 +181,7 @@ class_exists(\IntlTimeZone::class, false) ? \IntlTimeZone::createDefault() : nul
                 }
 
                 if ($date->getTimezone()->getName() !== $options['model_timezone']) {
-                    throw new LogicException(sprintf('Using a "%s" instance with a timezone ("%s") not matching the configured model timezone "%s" is not supported.', get_debug_type($date), $date->getTimezone()->getName(), $options['model_timezone']));
+                    throw new LogicException(\sprintf('Using a "%s" instance with a timezone ("%s") not matching the configured model timezone "%s" is not supported.', get_debug_type($date), $date->getTimezone()->getName(), $options['model_timezone']));
                 }
             });
         }
@@ -255,10 +249,8 @@ public function configureOptions(OptionsResolver $resolver): void
 
         $choiceTranslationDomainNormalizer = static function (Options $options, $choiceTranslationDomain) {
             if (\is_array($choiceTranslationDomain)) {
-                $default = false;
-
                 return array_replace(
-                    ['year' => $default, 'month' => $default, 'day' => $default],
+                    ['year' => false, 'month' => false, 'day' => false],
                     $choiceTranslationDomain
                 );
             }
@@ -281,6 +273,7 @@ public function configureOptions(OptionsResolver $resolver): void
             'format' => $format,
             'model_timezone' => null,
             'view_timezone' => null,
+            'calendar' => null,
             'placeholder' => $placeholderDefault,
             'html5' => true,
             // Don't modify \DateTime classes by reference, we treat
@@ -320,10 +313,13 @@ public function configureOptions(OptionsResolver $resolver): void
         $resolver->setAllowedTypes('months', 'array');
         $resolver->setAllowedTypes('days', 'array');
         $resolver->setAllowedTypes('input_format', 'string');
+        $resolver->setAllowedTypes('calendar', ['null', 'int', \IntlCalendar::class]);
+
+        $resolver->setInfo('calendar', 'The calendar to use for formatting and parsing the date. The value should be an instance of \IntlCalendar. By default, the Gregorian calendar with the default locale is used.');
 
         $resolver->setNormalizer('html5', static function (Options $options, $html5) {
             if ($html5 && 'single_text' === $options['widget'] && self::HTML5_FORMAT !== $options['format']) {
-                throw new LogicException(sprintf('Cannot use the "format" option of "%s" when the "html5" option is enabled.', self::class));
+                throw new LogicException(\sprintf('Cannot use the "format" option of "%s" when the "html5" option is enabled.', self::class));
             }
 
             return $html5;
diff --git a/Extension/Core/Type/LanguageType.php b/Extension/Core/Type/LanguageType.php
index e81571f8c..638976a8c 100644
--- a/Extension/Core/Type/LanguageType.php
+++ b/Extension/Core/Type/LanguageType.php
@@ -28,7 +28,7 @@ public function configureOptions(OptionsResolver $resolver): void
         $resolver->setDefaults([
             'choice_loader' => function (Options $options) {
                 if (!class_exists(Intl::class)) {
-                    throw new LogicException(sprintf('The "symfony/intl" component is required to use "%s". Try running "composer require symfony/intl".', static::class));
+                    throw new LogicException(\sprintf('The "symfony/intl" component is required to use "%s". Try running "composer require symfony/intl".', static::class));
                 }
                 $choiceTranslationLocale = $options['choice_translation_locale'];
                 $useAlpha3Codes = $options['alpha3'];
diff --git a/Extension/Core/Type/LocaleType.php b/Extension/Core/Type/LocaleType.php
index d0124e600..1529ca83f 100644
--- a/Extension/Core/Type/LocaleType.php
+++ b/Extension/Core/Type/LocaleType.php
@@ -27,7 +27,7 @@ public function configureOptions(OptionsResolver $resolver): void
         $resolver->setDefaults([
             'choice_loader' => function (Options $options) {
                 if (!class_exists(Intl::class)) {
-                    throw new LogicException(sprintf('The "symfony/intl" component is required to use "%s". Try running "composer require symfony/intl".', static::class));
+                    throw new LogicException(\sprintf('The "symfony/intl" component is required to use "%s". Try running "composer require symfony/intl".', static::class));
                 }
 
                 $choiceTranslationLocale = $options['choice_translation_locale'];
diff --git a/Extension/Core/Type/TimeType.php b/Extension/Core/Type/TimeType.php
index 4e01489e7..92cf42d96 100644
--- a/Extension/Core/Type/TimeType.php
+++ b/Extension/Core/Type/TimeType.php
@@ -12,7 +12,6 @@
 namespace Symfony\Component\Form\Extension\Core\Type;
 
 use Symfony\Component\Form\AbstractType;
-use Symfony\Component\Form\Event\PreSubmitEvent;
 use Symfony\Component\Form\Exception\InvalidConfigurationException;
 use Symfony\Component\Form\Exception\LogicException;
 use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeImmutableToDateTimeTransformer;
@@ -45,7 +44,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
         }
 
         if (null !== $options['reference_date'] && $options['reference_date']->getTimezone()->getName() !== $options['model_timezone']) {
-            throw new InvalidConfigurationException(sprintf('The configured "model_timezone" (%s) must match the timezone of the "reference_date" (%s).', $options['model_timezone'], $options['reference_date']->getTimezone()->getName()));
+            throw new InvalidConfigurationException(\sprintf('The configured "model_timezone" (%s) must match the timezone of the "reference_date" (%s).', $options['model_timezone'], $options['reference_date']->getTimezone()->getName()));
         }
 
         if ($options['with_minutes']) {
@@ -60,15 +59,14 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
 
         if ('single_text' === $options['widget']) {
             $builder->addEventListener(FormEvents::PRE_SUBMIT, static function (FormEvent $e) use ($options) {
-                /** @var PreSubmitEvent $event */
                 $data = $e->getData();
                 if ($data && preg_match('/^(?P<hours>\d{2}):(?P<minutes>\d{2})(?::(?P<seconds>\d{2})(?:\.\d+)?)?$/', $data, $matches)) {
                     if ($options['with_seconds']) {
                         // handle seconds ignored by user's browser when with_seconds enabled
                         // https://codereview.chromium.org/450533009/
-                        $e->setData(sprintf('%s:%s:%s', $matches['hours'], $matches['minutes'], $matches['seconds'] ?? '00'));
+                        $e->setData(\sprintf('%s:%s:%s', $matches['hours'], $matches['minutes'], $matches['seconds'] ?? '00'));
                     } else {
-                        $e->setData(sprintf('%s:%s', $matches['hours'], $matches['minutes']));
+                        $e->setData(\sprintf('%s:%s', $matches['hours'], $matches['minutes']));
                     }
                 }
             });
@@ -217,7 +215,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
                 }
 
                 if ($date->getTimezone()->getName() !== $options['model_timezone']) {
-                    throw new LogicException(sprintf('Using a "%s" instance with a timezone ("%s") not matching the configured model timezone "%s" is not supported.', get_debug_type($date), $date->getTimezone()->getName(), $options['model_timezone']));
+                    throw new LogicException(\sprintf('Using a "%s" instance with a timezone ("%s") not matching the configured model timezone "%s" is not supported.', get_debug_type($date), $date->getTimezone()->getName(), $options['model_timezone']));
                 }
             });
         }
@@ -276,10 +274,8 @@ public function configureOptions(OptionsResolver $resolver): void
 
         $choiceTranslationDomainNormalizer = static function (Options $options, $choiceTranslationDomain) {
             if (\is_array($choiceTranslationDomain)) {
-                $default = false;
-
                 return array_replace(
-                    ['hour' => $default, 'minute' => $default, 'second' => $default],
+                    ['hour' => false, 'minute' => false, 'second' => false],
                     $choiceTranslationDomain
                 );
             }
diff --git a/Extension/Core/Type/TimezoneType.php b/Extension/Core/Type/TimezoneType.php
index 01ce68ce3..2316d666b 100644
--- a/Extension/Core/Type/TimezoneType.php
+++ b/Extension/Core/Type/TimezoneType.php
@@ -43,7 +43,7 @@ public function configureOptions(OptionsResolver $resolver): void
 
                 if ($options['intl']) {
                     if (!class_exists(Intl::class)) {
-                        throw new LogicException(sprintf('The "symfony/intl" component is required to use "%s" with option "intl=true". Try running "composer require symfony/intl".', static::class));
+                        throw new LogicException(\sprintf('The "symfony/intl" component is required to use "%s" with option "intl=true". Try running "composer require symfony/intl".', static::class));
                     }
 
                     $choiceTranslationLocale = $options['choice_translation_locale'];
diff --git a/Extension/Core/Type/WeekType.php b/Extension/Core/Type/WeekType.php
index c3ffae061..aea4a6c56 100644
--- a/Extension/Core/Type/WeekType.php
+++ b/Extension/Core/Type/WeekType.php
@@ -113,10 +113,8 @@ public function configureOptions(OptionsResolver $resolver): void
 
         $choiceTranslationDomainNormalizer = static function (Options $options, $choiceTranslationDomain) {
             if (\is_array($choiceTranslationDomain)) {
-                $default = false;
-
                 return array_replace(
-                    ['year' => $default, 'week' => $default],
+                    ['year' => false, 'week' => false],
                     $choiceTranslationDomain
                 );
             }
@@ -145,7 +143,7 @@ public function configureOptions(OptionsResolver $resolver): void
         $resolver->setNormalizer('choice_translation_domain', $choiceTranslationDomainNormalizer);
         $resolver->setNormalizer('html5', static function (Options $options, $html5) {
             if ($html5 && 'single_text' !== $options['widget']) {
-                throw new LogicException(sprintf('The "widget" option of "%s" must be set to "single_text" when the "html5" option is enabled.', self::class));
+                throw new LogicException(\sprintf('The "widget" option of "%s" must be set to "single_text" when the "html5" option is enabled.', self::class));
             }
 
             return $html5;
diff --git a/Extension/Csrf/Type/FormTypeCsrfExtension.php b/Extension/Csrf/Type/FormTypeCsrfExtension.php
index 0ad4daeb3..a12b9a41e 100644
--- a/Extension/Csrf/Type/FormTypeCsrfExtension.php
+++ b/Extension/Csrf/Type/FormTypeCsrfExtension.php
@@ -19,6 +19,7 @@
 use Symfony\Component\Form\FormInterface;
 use Symfony\Component\Form\FormView;
 use Symfony\Component\Form\Util\ServerParams;
+use Symfony\Component\OptionsResolver\Options;
 use Symfony\Component\OptionsResolver\OptionsResolver;
 use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
 use Symfony\Contracts\Translation\TranslatorInterface;
@@ -35,6 +36,8 @@ public function __construct(
         private ?TranslatorInterface $translator = null,
         private ?string $translationDomain = null,
         private ?ServerParams $serverParams = null,
+        private array $fieldAttr = [],
+        private string|array|null $defaultTokenId = null,
     ) {
     }
 
@@ -47,11 +50,17 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
             return;
         }
 
+        $csrfTokenId = $options['csrf_token_id']
+            ?: $this->defaultTokenId[$builder->getType()->getInnerType()::class]
+            ?? $builder->getName()
+            ?: $builder->getType()->getInnerType()::class;
+        $builder->setAttribute('csrf_token_id', $csrfTokenId);
+
         $builder
             ->addEventSubscriber(new CsrfValidationListener(
                 $options['csrf_field_name'],
                 $options['csrf_token_manager'],
-                $options['csrf_token_id'] ?: ($builder->getName() ?: $builder->getType()->getInnerType()::class),
+                $csrfTokenId,
                 $options['csrf_message'],
                 $this->translator,
                 $this->translationDomain,
@@ -67,12 +76,13 @@ public function finishView(FormView $view, FormInterface $form, array $options):
     {
         if ($options['csrf_protection'] && !$view->parent && $options['compound']) {
             $factory = $form->getConfig()->getFormFactory();
-            $tokenId = $options['csrf_token_id'] ?: ($form->getName() ?: $form->getConfig()->getType()->getInnerType()::class);
+            $tokenId = $form->getConfig()->getAttribute('csrf_token_id');
             $data = (string) $options['csrf_token_manager']->getToken($tokenId);
 
             $csrfForm = $factory->createNamed($options['csrf_field_name'], HiddenType::class, $data, [
                 'block_prefix' => 'csrf_token',
                 'mapped' => false,
+                'attr' => $this->fieldAttr,
             ]);
 
             $view->children[$options['csrf_field_name']] = $csrfForm->createView($view);
@@ -81,13 +91,26 @@ public function finishView(FormView $view, FormInterface $form, array $options):
 
     public function configureOptions(OptionsResolver $resolver): void
     {
+        if (\is_string($defaultTokenId = $this->defaultTokenId) && $defaultTokenId) {
+            $defaultTokenManager = $this->defaultTokenManager;
+            $defaultTokenId = static fn (Options $options) => $options['csrf_token_manager'] === $defaultTokenManager ? $defaultTokenId : null;
+        } else {
+            $defaultTokenId = null;
+        }
+
         $resolver->setDefaults([
             'csrf_protection' => $this->defaultEnabled,
             'csrf_field_name' => $this->defaultFieldName,
             'csrf_message' => 'The CSRF token is invalid. Please try to resubmit the form.',
             'csrf_token_manager' => $this->defaultTokenManager,
-            'csrf_token_id' => null,
+            'csrf_token_id' => $defaultTokenId,
         ]);
+
+        $resolver->setAllowedTypes('csrf_protection', 'bool');
+        $resolver->setAllowedTypes('csrf_field_name', 'string');
+        $resolver->setAllowedTypes('csrf_message', 'string');
+        $resolver->setAllowedTypes('csrf_token_manager', CsrfTokenManagerInterface::class);
+        $resolver->setAllowedTypes('csrf_token_id', ['null', 'string']);
     }
 
     public static function getExtendedTypes(): iterable
diff --git a/Extension/DataCollector/EventListener/DataCollectorListener.php b/Extension/DataCollector/EventListener/DataCollectorListener.php
index 02cffbeff..c541efec2 100644
--- a/Extension/DataCollector/EventListener/DataCollectorListener.php
+++ b/Extension/DataCollector/EventListener/DataCollectorListener.php
@@ -32,8 +32,8 @@ public function __construct(
     public static function getSubscribedEvents(): array
     {
         return [
-            // High priority in order to be called as soon as possible
-            FormEvents::POST_SET_DATA => ['postSetData', 255],
+            // Low priority in order to be called as late as possible
+            FormEvents::POST_SET_DATA => ['postSetData', -255],
             // Low priority in order to be called as late as possible
             FormEvents::POST_SUBMIT => ['postSubmit', -255],
         ];
diff --git a/Extension/DataCollector/FormDataCollector.php b/Extension/DataCollector/FormDataCollector.php
index 348be44aa..e6cae863e 100644
--- a/Extension/DataCollector/FormDataCollector.php
+++ b/Extension/DataCollector/FormDataCollector.php
@@ -64,7 +64,7 @@ public function __construct(
         private FormDataExtractorInterface $dataExtractor,
     ) {
         if (!class_exists(ClassStub::class)) {
-            throw new \LogicException(sprintf('The VarDumper component is needed for using the "%s" class. Install symfony/var-dumper version 3.4 or above.', __CLASS__));
+            throw new \LogicException(\sprintf('The VarDumper component is needed for using the "%s" class. Install symfony/var-dumper version 3.4 or above.', __CLASS__));
         }
 
         $this->reset();
diff --git a/Extension/DataCollector/FormDataExtractor.php b/Extension/DataCollector/FormDataExtractor.php
index 158cf3210..f56fe911f 100644
--- a/Extension/DataCollector/FormDataExtractor.php
+++ b/Extension/DataCollector/FormDataExtractor.php
@@ -103,15 +103,13 @@ public function extractSubmittedData(FormInterface $form): array
                     continue;
                 }
 
+                $errorData['trace'][] = $cause;
                 if ($cause instanceof \Exception) {
-                    $errorData['trace'][] = $cause;
                     $cause = $cause->getPrevious();
 
                     continue;
                 }
 
-                $errorData['trace'][] = $cause;
-
                 break;
             }
 
diff --git a/Extension/DependencyInjection/DependencyInjectionExtension.php b/Extension/DependencyInjection/DependencyInjectionExtension.php
index 420f26b74..f986bda41 100644
--- a/Extension/DependencyInjection/DependencyInjectionExtension.php
+++ b/Extension/DependencyInjection/DependencyInjectionExtension.php
@@ -37,7 +37,7 @@ public function __construct(
     public function getType(string $name): FormTypeInterface
     {
         if (!$this->typeContainer->has($name)) {
-            throw new InvalidArgumentException(sprintf('The field type "%s" is not registered in the service container.', $name));
+            throw new InvalidArgumentException(\sprintf('The field type "%s" is not registered in the service container.', $name));
         }
 
         return $this->typeContainer->get($name);
@@ -63,7 +63,7 @@ public function getTypeExtensions(string $name): array
 
                 // validate the result of getExtendedTypes() to ensure it is consistent with the service definition
                 if (!\in_array($name, $extendedTypes, true)) {
-                    throw new InvalidArgumentException(sprintf('The extended type "%s" specified for the type extension class "%s" does not match any of the actual extended types (["%s"]).', $name, $extension::class, implode('", "', $extendedTypes)));
+                    throw new InvalidArgumentException(\sprintf('The extended type "%s" specified for the type extension class "%s" does not match any of the actual extended types (["%s"]).', $name, $extension::class, implode('", "', $extendedTypes)));
                 }
             }
         }
diff --git a/Extension/PasswordHasher/EventListener/PasswordHasherListener.php b/Extension/PasswordHasher/EventListener/PasswordHasherListener.php
index 3ddac5ff3..0144cc3ff 100644
--- a/Extension/PasswordHasher/EventListener/PasswordHasherListener.php
+++ b/Extension/PasswordHasher/EventListener/PasswordHasherListener.php
@@ -95,7 +95,7 @@ private function getUser(FormInterface $form): PasswordAuthenticatedUserInterfac
         $parent = $this->getTargetForm($form)->getParent();
 
         if (!($user = $parent?->getData()) || !$user instanceof PasswordAuthenticatedUserInterface) {
-            throw new InvalidConfigurationException(sprintf('The "hash_property_path" option only supports "%s" objects, "%s" given.', PasswordAuthenticatedUserInterface::class, get_debug_type($user)));
+            throw new InvalidConfigurationException(\sprintf('The "hash_property_path" option only supports "%s" objects, "%s" given.', PasswordAuthenticatedUserInterface::class, get_debug_type($user)));
         }
 
         return $user;
diff --git a/Extension/Validator/Constraints/FormValidator.php b/Extension/Validator/Constraints/FormValidator.php
index 416358757..8f4ec60f2 100644
--- a/Extension/Validator/Constraints/FormValidator.php
+++ b/Extension/Validator/Constraints/FormValidator.php
@@ -56,7 +56,7 @@ public function validate(mixed $form, Constraint $formConstraint): void
             // Validate the data against its own constraints
             $validateDataGraph = $form->isRoot()
                 && (\is_object($data) || \is_array($data))
-                && (($groups && \is_array($groups)) || ($groups instanceof GroupSequence && $groups->groups))
+                && (\is_array($groups) || ($groups instanceof GroupSequence && $groups->groups))
             ;
 
             // Validate the data against the constraints defined in the form
@@ -92,7 +92,7 @@ public function validate(mixed $form, Constraint $formConstraint): void
                             $fieldFormConstraint = new Form();
                             $fieldFormConstraint->groups = $group;
                             $this->context->setNode($this->context->getValue(), $field, $this->context->getMetadata(), $this->context->getPropertyPath());
-                            $validator->atPath(sprintf('children[%s]', $field->getName()))->validate($field, $fieldFormConstraint, $group);
+                            $validator->atPath(\sprintf('children[%s]', $field->getName()))->validate($field, $fieldFormConstraint, $group);
                         }
                     }
 
@@ -139,7 +139,7 @@ public function validate(mixed $form, Constraint $formConstraint): void
                     if ($field->isSubmitted()) {
                         $this->resolvedGroups[$field] = $groups;
                         $this->context->setNode($this->context->getValue(), $field, $this->context->getMetadata(), $this->context->getPropertyPath());
-                        $validator->atPath(sprintf('children[%s]', $field->getName()))->validate($field, $formConstraint);
+                        $validator->atPath(\sprintf('children[%s]', $field->getName()))->validate($field, $formConstraint);
                     }
                 }
             }
@@ -156,7 +156,7 @@ public function validate(mixed $form, Constraint $formConstraint): void
                 if (!$child->isSynchronized()) {
                     $childrenSynchronized = false;
                     $this->context->setNode($this->context->getValue(), $child, $this->context->getMetadata(), $this->context->getPropertyPath());
-                    $validator->atPath(sprintf('children[%s]', $child->getName()))->validate($child, $formConstraint);
+                    $validator->atPath(\sprintf('children[%s]', $child->getName()))->validate($child, $formConstraint);
                 }
             }
 
diff --git a/Extension/Validator/ValidatorExtension.php b/Extension/Validator/ValidatorExtension.php
index 522a76962..2c534481c 100644
--- a/Extension/Validator/ValidatorExtension.php
+++ b/Extension/Validator/ValidatorExtension.php
@@ -40,7 +40,7 @@ public function __construct(
         // the DIC, where the XML file is loaded automatically. Thus the following
         // code must be kept synchronized with validation.xml
 
-        /* @var $metadata ClassMetadata */
+        /* @var ClassMetadata $metadata */
         $metadata->addConstraint(new Form());
         $metadata->addConstraint(new Traverse(false));
     }
diff --git a/Extension/Validator/ValidatorTypeGuesser.php b/Extension/Validator/ValidatorTypeGuesser.php
index 72ae8ddd5..08dc6e2d5 100644
--- a/Extension/Validator/ValidatorTypeGuesser.php
+++ b/Extension/Validator/ValidatorTypeGuesser.php
@@ -230,7 +230,7 @@ public function guessPatternForConstraint(Constraint $constraint): ?ValueGuess
         switch ($constraint::class) {
             case Length::class:
                 if (is_numeric($constraint->min)) {
-                    return new ValueGuess(sprintf('.{%s,}', (string) $constraint->min), Guess::LOW_CONFIDENCE);
+                    return new ValueGuess(\sprintf('.{%s,}', (string) $constraint->min), Guess::LOW_CONFIDENCE);
                 }
                 break;
 
@@ -244,7 +244,7 @@ public function guessPatternForConstraint(Constraint $constraint): ?ValueGuess
 
             case Range::class:
                 if (is_numeric($constraint->min)) {
-                    return new ValueGuess(sprintf('.{%s,}', \strlen((string) $constraint->min)), Guess::LOW_CONFIDENCE);
+                    return new ValueGuess(\sprintf('.{%s,}', \strlen((string) $constraint->min)), Guess::LOW_CONFIDENCE);
                 }
                 break;
 
diff --git a/Extension/Validator/ViolationMapper/MappingRule.php b/Extension/Validator/ViolationMapper/MappingRule.php
index f9a61cc81..3263f66df 100644
--- a/Extension/Validator/ViolationMapper/MappingRule.php
+++ b/Extension/Validator/ViolationMapper/MappingRule.php
@@ -64,7 +64,7 @@ public function getTarget(): FormInterface
 
         foreach ($childNames as $childName) {
             if (!$target->has($childName)) {
-                throw new ErrorMappingException(sprintf('The child "%s" of "%s" mapped by the rule "%s" in "%s" does not exist.', $childName, $target->getName(), $this->targetPath, $this->origin->getName()));
+                throw new ErrorMappingException(\sprintf('The child "%s" of "%s" mapped by the rule "%s" in "%s" does not exist.', $childName, $target->getName(), $this->targetPath, $this->origin->getName()));
             }
             $target = $target->get($childName);
         }
diff --git a/Extension/Validator/ViolationMapper/ViolationPath.php b/Extension/Validator/ViolationMapper/ViolationPath.php
index a9a0f15d6..0c2a130cc 100644
--- a/Extension/Validator/ViolationMapper/ViolationPath.php
+++ b/Extension/Validator/ViolationMapper/ViolationPath.php
@@ -132,7 +132,7 @@ public function getElements(): array
     public function getElement(int $index): string
     {
         if (!isset($this->elements[$index])) {
-            throw new OutOfBoundsException(sprintf('The index "%s" is not within the violation path.', $index));
+            throw new OutOfBoundsException(\sprintf('The index "%s" is not within the violation path.', $index));
         }
 
         return $this->elements[$index];
@@ -141,7 +141,7 @@ public function getElement(int $index): string
     public function isProperty(int $index): bool
     {
         if (!isset($this->isIndex[$index])) {
-            throw new OutOfBoundsException(sprintf('The index "%s" is not within the violation path.', $index));
+            throw new OutOfBoundsException(\sprintf('The index "%s" is not within the violation path.', $index));
         }
 
         return !$this->isIndex[$index];
@@ -150,7 +150,7 @@ public function isProperty(int $index): bool
     public function isIndex(int $index): bool
     {
         if (!isset($this->isIndex[$index])) {
-            throw new OutOfBoundsException(sprintf('The index "%s" is not within the violation path.', $index));
+            throw new OutOfBoundsException(\sprintf('The index "%s" is not within the violation path.', $index));
         }
 
         return $this->isIndex[$index];
@@ -176,7 +176,7 @@ public function isNullSafe(int $index): bool
     public function mapsForm(int $index): bool
     {
         if (!isset($this->mapsForm[$index])) {
-            throw new OutOfBoundsException(sprintf('The index "%s" is not within the violation path.', $index));
+            throw new OutOfBoundsException(\sprintf('The index "%s" is not within the violation path.', $index));
         }
 
         return $this->mapsForm[$index];
diff --git a/Form.php b/Form.php
index 44b97261b..72c60ee41 100644
--- a/Form.php
+++ b/Form.php
@@ -738,7 +738,7 @@ public function add(FormInterface|string $child, ?string $type = null, array $op
                 $child = $this->config->getFormFactory()->createNamed($child, $type, null, $options);
             }
         } elseif ($child->getConfig()->getAutoInitialize()) {
-            throw new RuntimeException(sprintf('Automatic initialization is only supported on root forms. You should set the "auto_initialize" option to false on the field "%s".', $child->getName()));
+            throw new RuntimeException(\sprintf('Automatic initialization is only supported on root forms. You should set the "auto_initialize" option to false on the field "%s".', $child->getName()));
         }
 
         $this->children[$child->getName()] = $child;
@@ -800,7 +800,7 @@ public function get(string $name): FormInterface
             return $this->children[$name];
         }
 
-        throw new OutOfBoundsException(sprintf('Child "%s" does not exist.', $name));
+        throw new OutOfBoundsException(\sprintf('Child "%s" does not exist.', $name));
     }
 
     /**
@@ -933,7 +933,7 @@ private function modelToNorm(mixed $value): mixed
                 $value = $transformer->transform($value);
             }
         } catch (TransformationFailedException $exception) {
-            throw new TransformationFailedException(sprintf('Unable to transform data for property path "%s": ', $this->getPropertyPath()).$exception->getMessage(), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters());
+            throw new TransformationFailedException(\sprintf('Unable to transform data for property path "%s": ', $this->getPropertyPath()).$exception->getMessage(), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters());
         }
 
         return $value;
@@ -953,7 +953,7 @@ private function normToModel(mixed $value): mixed
                 $value = $transformers[$i]->reverseTransform($value);
             }
         } catch (TransformationFailedException $exception) {
-            throw new TransformationFailedException(sprintf('Unable to reverse value for property path "%s": ', $this->getPropertyPath()).$exception->getMessage(), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters());
+            throw new TransformationFailedException(\sprintf('Unable to reverse value for property path "%s": ', $this->getPropertyPath()).$exception->getMessage(), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters());
         }
 
         return $value;
@@ -980,7 +980,7 @@ private function normToView(mixed $value): mixed
                 $value = $transformer->transform($value);
             }
         } catch (TransformationFailedException $exception) {
-            throw new TransformationFailedException(sprintf('Unable to transform value for property path "%s": ', $this->getPropertyPath()).$exception->getMessage(), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters());
+            throw new TransformationFailedException(\sprintf('Unable to transform value for property path "%s": ', $this->getPropertyPath()).$exception->getMessage(), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters());
         }
 
         return $value;
@@ -1002,7 +1002,7 @@ private function viewToNorm(mixed $value): mixed
                 $value = $transformers[$i]->reverseTransform($value);
             }
         } catch (TransformationFailedException $exception) {
-            throw new TransformationFailedException(sprintf('Unable to reverse value for property path "%s": ', $this->getPropertyPath()).$exception->getMessage(), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters());
+            throw new TransformationFailedException(\sprintf('Unable to reverse value for property path "%s": ', $this->getPropertyPath()).$exception->getMessage(), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters());
         }
 
         return $value;
diff --git a/FormBuilder.php b/FormBuilder.php
index 58bc9c86d..f34c2f76e 100644
--- a/FormBuilder.php
+++ b/FormBuilder.php
@@ -97,7 +97,7 @@ public function get(string $name): FormBuilderInterface
             return $this->children[$name];
         }
 
-        throw new InvalidArgumentException(sprintf('The child with the name "%s" does not exist.', $name));
+        throw new InvalidArgumentException(\sprintf('The child with the name "%s" does not exist.', $name));
     }
 
     public function remove(string $name): static
@@ -142,7 +142,7 @@ public function count(): int
 
     public function getFormConfig(): FormConfigInterface
     {
-        /** @var $config self */
+        /** @var self $config */
         $config = parent::getFormConfig();
 
         $config->children = [];
diff --git a/FormConfigBuilder.php b/FormConfigBuilder.php
index e0fb01dad..5d6bd653d 100644
--- a/FormConfigBuilder.php
+++ b/FormConfigBuilder.php
@@ -76,7 +76,7 @@ public function __construct(
         self::validateName($name);
 
         if (null !== $dataClass && !class_exists($dataClass) && !interface_exists($dataClass, false)) {
-            throw new InvalidArgumentException(sprintf('Class "%s" not found. Is the "data_class" form option set correctly?', $dataClass));
+            throw new InvalidArgumentException(\sprintf('Class "%s" not found. Is the "data_class" form option set correctly?', $dataClass));
         }
 
         $this->name = (string) $name;
@@ -632,7 +632,7 @@ public function setIsEmptyCallback(?callable $isEmptyCallback): static
     final public static function validateName(?string $name): void
     {
         if (!self::isValidName($name)) {
-            throw new InvalidArgumentException(sprintf('The name "%s" contains illegal characters. Names should start with a letter, digit or underscore and only contain letters, digits, numbers, underscores ("_"), hyphens ("-") and colons (":").', $name));
+            throw new InvalidArgumentException(\sprintf('The name "%s" contains illegal characters. Names should start with a letter, digit or underscore and only contain letters, digits, numbers, underscores ("_"), hyphens ("-") and colons (":").', $name));
         }
     }
 
diff --git a/FormErrorIterator.php b/FormErrorIterator.php
index a614e72c2..4f4a2574d 100644
--- a/FormErrorIterator.php
+++ b/FormErrorIterator.php
@@ -58,7 +58,7 @@ public function __construct(
     ) {
         foreach ($errors as $error) {
             if (!($error instanceof FormError || $error instanceof self)) {
-                throw new InvalidArgumentException(sprintf('The errors must be instances of "Symfony\Component\Form\FormError" or "%s". Got: "%s".', __CLASS__, get_debug_type($error)));
+                throw new InvalidArgumentException(\sprintf('The errors must be instances of "Symfony\Component\Form\FormError" or "%s". Got: "%s".', __CLASS__, get_debug_type($error)));
             }
         }
 
@@ -198,7 +198,7 @@ public function hasChildren(): bool
     public function getChildren(): self
     {
         if (!$this->hasChildren()) {
-            throw new LogicException(sprintf('The current element is not iterable. Use "%s" to get the current element.', self::class.'::current()'));
+            throw new LogicException(\sprintf('The current element is not iterable. Use "%s" to get the current element.', self::class.'::current()'));
         }
 
         /** @var self $children */
diff --git a/FormRegistry.php b/FormRegistry.php
index 95a007737..ecf654a2a 100644
--- a/FormRegistry.php
+++ b/FormRegistry.php
@@ -69,10 +69,10 @@ public function getType(string $name): ResolvedFormTypeInterface
             if (!$type) {
                 // Support fully-qualified class names
                 if (!class_exists($name)) {
-                    throw new InvalidArgumentException(sprintf('Could not load type "%s": class does not exist.', $name));
+                    throw new InvalidArgumentException(\sprintf('Could not load type "%s": class does not exist.', $name));
                 }
                 if (!is_subclass_of($name, FormTypeInterface::class)) {
-                    throw new InvalidArgumentException(sprintf('Could not load type "%s": class does not implement "Symfony\Component\Form\FormTypeInterface".', $name));
+                    throw new InvalidArgumentException(\sprintf('Could not load type "%s": class does not implement "Symfony\Component\Form\FormTypeInterface".', $name));
                 }
 
                 $type = new $name();
@@ -94,7 +94,7 @@ private function resolveType(FormTypeInterface $type): ResolvedFormTypeInterface
 
         if (isset($this->checkedTypes[$fqcn])) {
             $types = implode(' > ', array_merge(array_keys($this->checkedTypes), [$fqcn]));
-            throw new LogicException(sprintf('Circular reference detected for form type "%s" (%s).', $fqcn, $types));
+            throw new LogicException(\sprintf('Circular reference detected for form type "%s" (%s).', $fqcn, $types));
         }
 
         $this->checkedTypes[$fqcn] = true;
diff --git a/FormRenderer.php b/FormRenderer.php
index a9ffd4f41..4478432b2 100644
--- a/FormRenderer.php
+++ b/FormRenderer.php
@@ -59,7 +59,7 @@ public function renderBlock(FormView $view, string $blockName, array $variables
         $resource = $this->engine->getResourceForBlockName($view, $blockName);
 
         if (!$resource) {
-            throw new LogicException(sprintf('No block "%s" found while rendering the form.', $blockName));
+            throw new LogicException(\sprintf('No block "%s" found while rendering the form.', $blockName));
         }
 
         $viewCacheKey = $view->vars[self::CACHE_KEY_VAR];
@@ -116,7 +116,7 @@ public function searchAndRenderBlock(FormView $view, string $blockNameSuffix, ar
 
         if ($renderOnlyOnce && $view->isRendered()) {
             // This is not allowed, because it would result in rendering same IDs multiple times, which is not valid.
-            throw new BadMethodCallException(sprintf('Field "%s" has already been rendered, save the result of previous render call to a variable and output that instead.', $view->vars['name']));
+            throw new BadMethodCallException(\sprintf('Field "%s" has already been rendered, save the result of previous render call to a variable and output that instead.', $view->vars['name']));
         }
 
         // The cache key for storing the variables and types
@@ -203,10 +203,10 @@ public function searchAndRenderBlock(FormView $view, string $blockNameSuffix, ar
         // Escape if no resource exists for this block
         if (!$resource) {
             if (\count($blockNameHierarchy) !== \count(array_unique($blockNameHierarchy))) {
-                throw new LogicException(sprintf('Unable to render the form because the block names array contains duplicates: "%s".', implode('", "', array_reverse($blockNameHierarchy))));
+                throw new LogicException(\sprintf('Unable to render the form because the block names array contains duplicates: "%s".', implode('", "', array_reverse($blockNameHierarchy))));
             }
 
-            throw new LogicException(sprintf('Unable to render the form as none of the following blocks exist: "%s".', implode('", "', array_reverse($blockNameHierarchy))));
+            throw new LogicException(\sprintf('Unable to render the form as none of the following blocks exist: "%s".', implode('", "', array_reverse($blockNameHierarchy))));
         }
 
         // Merge the passed with the existing attributes
diff --git a/FormTypeExtensionInterface.php b/FormTypeExtensionInterface.php
index 3e2d2d03e..838406d2a 100644
--- a/FormTypeExtensionInterface.php
+++ b/FormTypeExtensionInterface.php
@@ -25,9 +25,6 @@ interface FormTypeExtensionInterface
      */
     public static function getExtendedTypes(): iterable;
 
-    /**
-     * @return void
-     */
     public function configureOptions(OptionsResolver $resolver): void;
 
     /**
diff --git a/PreloadedExtension.php b/PreloadedExtension.php
index 58d8f13b1..26090e00d 100644
--- a/PreloadedExtension.php
+++ b/PreloadedExtension.php
@@ -41,7 +41,7 @@ public function __construct(
     public function getType(string $name): FormTypeInterface
     {
         if (!isset($this->types[$name])) {
-            throw new InvalidArgumentException(sprintf('The type "%s" cannot be loaded by this extension.', $name));
+            throw new InvalidArgumentException(\sprintf('The type "%s" cannot be loaded by this extension.', $name));
         }
 
         return $this->types[$name];
diff --git a/ResolvedFormType.php b/ResolvedFormType.php
index 964619c39..82065f651 100644
--- a/ResolvedFormType.php
+++ b/ResolvedFormType.php
@@ -72,7 +72,7 @@ public function createBuilder(FormFactoryInterface $factory, string $name, array
         try {
             $options = $this->getOptionsResolver()->resolve($options);
         } catch (ExceptionInterface $e) {
-            throw new $e(sprintf('An error has occurred resolving the options of the form "%s": ', get_debug_type($this->getInnerType())).$e->getMessage(), $e->getCode(), $e);
+            throw new $e(\sprintf('An error has occurred resolving the options of the form "%s": ', get_debug_type($this->getInnerType())).$e->getMessage(), $e->getCode(), $e);
         }
 
         // Should be decoupled from the specific option at some point
diff --git a/Resources/translations/validators.es.xlf b/Resources/translations/validators.es.xlf
index 301e2b33f..a9989737c 100644
--- a/Resources/translations/validators.es.xlf
+++ b/Resources/translations/validators.es.xlf
@@ -52,7 +52,7 @@
             </trans-unit>
             <trans-unit id="108">
                 <source>Please enter a valid date.</source>
-                <target>Por favor, ingrese una fecha valida.</target>
+                <target>Por favor, ingrese una fecha válida.</target>
             </trans-unit>
             <trans-unit id="109">
                 <source>Please select a valid file.</source>
diff --git a/Resources/translations/validators.pt.xlf b/Resources/translations/validators.pt.xlf
index 755108f35..673e79f42 100644
--- a/Resources/translations/validators.pt.xlf
+++ b/Resources/translations/validators.pt.xlf
@@ -24,7 +24,7 @@
             </trans-unit>
             <trans-unit id="101">
                 <source>The selected choice is invalid.</source>
-                <target>A escolha seleccionada é inválida.</target>
+                <target>A escolha selecionada é inválida.</target>
             </trans-unit>
             <trans-unit id="102">
                 <source>The collection is invalid.</source>
diff --git a/Test/FormIntegrationTestCase.php b/Test/FormIntegrationTestCase.php
index 5bf37fd48..8756d9968 100644
--- a/Test/FormIntegrationTestCase.php
+++ b/Test/FormIntegrationTestCase.php
@@ -12,8 +12,12 @@
 namespace Symfony\Component\Form\Test;
 
 use PHPUnit\Framework\TestCase;
+use Symfony\Component\Form\FormExtensionInterface;
 use Symfony\Component\Form\FormFactoryInterface;
 use Symfony\Component\Form\Forms;
+use Symfony\Component\Form\FormTypeExtensionInterface;
+use Symfony\Component\Form\FormTypeGuesserInterface;
+use Symfony\Component\Form\FormTypeInterface;
 
 /**
  * @author Bernhard Schussek <bschussek@gmail.com>
@@ -32,21 +36,33 @@ protected function setUp(): void
             ->getFormFactory();
     }
 
+    /**
+     * @return FormExtensionInterface[]
+     */
     protected function getExtensions()
     {
         return [];
     }
 
+    /**
+     * @return FormTypeExtensionInterface[]
+     */
     protected function getTypeExtensions()
     {
         return [];
     }
 
+    /**
+     * @return FormTypeInterface[]
+     */
     protected function getTypes()
     {
         return [];
     }
 
+    /**
+     * @return FormTypeGuesserInterface[]
+     */
     protected function getTypeGuessers()
     {
         return [];
diff --git a/Test/FormPerformanceTestCase.php b/Test/FormPerformanceTestCase.php
index f5ad61907..9247f57e7 100644
--- a/Test/FormPerformanceTestCase.php
+++ b/Test/FormPerformanceTestCase.php
@@ -21,21 +21,27 @@
  */
 abstract class FormPerformanceTestCase extends FormIntegrationTestCase
 {
+    private float $startTime;
     protected int $maxRunningTime = 0;
 
-    protected function runTest(): mixed
+    protected function setUp(): void
     {
-        $s = microtime(true);
-        $result = parent::runTest();
-        $time = microtime(true) - $s;
+        parent::setUp();
+
+        $this->startTime = microtime(true);
+    }
+
+    protected function assertPostConditions(): void
+    {
+        parent::assertPostConditions();
+
+        $time = microtime(true) - $this->startTime;
 
         if (0 != $this->maxRunningTime && $time > $this->maxRunningTime) {
-            $this->fail(sprintf('expected running time: <= %s but was: %s', $this->maxRunningTime, $time));
+            $this->fail(\sprintf('expected running time: <= %s but was: %s', $this->maxRunningTime, $time));
         }
 
         $this->expectNotToPerformAssertions();
-
-        return $result;
     }
 
     /**
diff --git a/Test/Traits/ValidatorExtensionTrait.php b/Test/Traits/ValidatorExtensionTrait.php
index b89095de7..5d0486e8c 100644
--- a/Test/Traits/ValidatorExtensionTrait.php
+++ b/Test/Traits/ValidatorExtensionTrait.php
@@ -28,7 +28,7 @@ protected function getValidatorExtension(): ValidatorExtension
         }
 
         if (!$this instanceof TypeTestCase) {
-            throw new \Exception(sprintf('The trait "ValidatorExtensionTrait" can only be added to a class that extends "%s".', TypeTestCase::class));
+            throw new \Exception(\sprintf('The trait "ValidatorExtensionTrait" can only be added to a class that extends "%s".', TypeTestCase::class));
         }
 
         $this->validator = $this->createMock(ValidatorInterface::class);
diff --git a/Test/TypeTestCase.php b/Test/TypeTestCase.php
index 960b44228..1bbb66d25 100644
--- a/Test/TypeTestCase.php
+++ b/Test/TypeTestCase.php
@@ -13,6 +13,7 @@
 
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 use Symfony\Component\Form\FormBuilder;
+use Symfony\Component\Form\FormExtensionInterface;
 use Symfony\Component\Form\Test\Traits\ValidatorExtensionTrait;
 
 abstract class TypeTestCase extends FormIntegrationTestCase
@@ -28,6 +29,9 @@ protected function setUp(): void
         $this->builder = new FormBuilder('', null, $this->dispatcher, $this->factory);
     }
 
+    /**
+     * @return FormExtensionInterface[]
+     */
     protected function getExtensions()
     {
         $extensions = [];
@@ -39,11 +43,17 @@ protected function getExtensions()
         return $extensions;
     }
 
+    /**
+     * @return void
+     */
     public static function assertDateTimeEquals(\DateTime $expected, \DateTime $actual)
     {
         self::assertEquals($expected->format('c'), $actual->format('c'));
     }
 
+    /**
+     * @return void
+     */
     public static function assertDateIntervalEquals(\DateInterval $expected, \DateInterval $actual)
     {
         self::assertEquals($expected->format('%RP%yY%mM%dDT%hH%iM%sS'), $actual->format('%RP%yY%mM%dDT%hH%iM%sS'));
diff --git a/Tests/AbstractRequestHandlerTestCase.php b/Tests/AbstractRequestHandlerTestCase.php
index d050edb41..f80efffb7 100644
--- a/Tests/AbstractRequestHandlerTestCase.php
+++ b/Tests/AbstractRequestHandlerTestCase.php
@@ -39,7 +39,7 @@ abstract class AbstractRequestHandlerTestCase extends TestCase
 
     protected function setUp(): void
     {
-        $this->serverParams = new class() extends ServerParams {
+        $this->serverParams = new class extends ServerParams {
             public ?int $contentLength = null;
             public string $postMaxSize = '';
 
diff --git a/Tests/ChoiceList/AbstractChoiceListTestCase.php b/Tests/ChoiceList/AbstractChoiceListTestCase.php
index 0b0cb8e79..5ddae5614 100644
--- a/Tests/ChoiceList/AbstractChoiceListTestCase.php
+++ b/Tests/ChoiceList/AbstractChoiceListTestCase.php
@@ -39,8 +39,6 @@ abstract class AbstractChoiceListTestCase extends TestCase
 
     protected function setUp(): void
     {
-        parent::setUp();
-
         $this->list = $this->createChoiceList();
 
         $choices = $this->getChoices();
diff --git a/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php b/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php
index e7bf26d17..2b1b239e5 100644
--- a/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php
+++ b/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php
@@ -159,9 +159,9 @@ public function testCreateFromChoicesGroupedTraversable()
     {
         $list = $this->factory->createListFromChoices(
             new \ArrayIterator([
-                    'Group 1' => ['A' => $this->obj1, 'B' => $this->obj2],
-                    'Group 2' => ['C' => $this->obj3, 'D' => $this->obj4],
-                ])
+                'Group 1' => ['A' => $this->obj1, 'B' => $this->obj2],
+                'Group 2' => ['C' => $this->obj3, 'D' => $this->obj4],
+            ])
         );
 
         $this->assertObjectListWithGeneratedValues($list);
@@ -728,7 +728,7 @@ public function testPassTranslatableMessageAsLabelDoesntCastItToString()
 
     public function testPassTranslatableInterfaceAsLabelDoesntCastItToString()
     {
-        $message = new class() implements TranslatableInterface {
+        $message = new class implements TranslatableInterface {
             public function trans(TranslatorInterface $translator, ?string $locale = null): string
             {
                 return 'my_message';
@@ -941,7 +941,7 @@ private function assertFlatViewWithAttr($view)
                     'C',
                     ['attr2' => 'value2']
                 ),
-                ]
+            ]
         ), $view);
     }
 
@@ -987,7 +987,7 @@ private function assertGroupedView($view)
                     'Group 2',
                     [2 => new ChoiceView($this->obj3, '2', 'C')]
                 ),
-                ]
+            ]
         ), $view);
     }
 
diff --git a/Tests/ChoiceList/Loader/CallbackChoiceLoaderTest.php b/Tests/ChoiceList/Loader/CallbackChoiceLoaderTest.php
index d394196ee..791a6f006 100644
--- a/Tests/ChoiceList/Loader/CallbackChoiceLoaderTest.php
+++ b/Tests/ChoiceList/Loader/CallbackChoiceLoaderTest.php
@@ -69,8 +69,8 @@ public function testLoadChoicesForValuesLoadsChoiceListOnFirstCall()
     public function testLoadValuesForChoicesCastsCallbackItemsToString()
     {
         $choices = [
-           (object) ['id' => 2],
-           (object) ['id' => 3],
+            (object) ['id' => 2],
+            (object) ['id' => 3],
         ];
 
         $value = fn ($item) => $item->id;
diff --git a/Tests/ChoiceList/Loader/LazyChoiceLoaderTest.php b/Tests/ChoiceList/Loader/LazyChoiceLoaderTest.php
new file mode 100644
index 000000000..0c1bcf3c2
--- /dev/null
+++ b/Tests/ChoiceList/Loader/LazyChoiceLoaderTest.php
@@ -0,0 +1,50 @@
+<?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\Form\Tests\ChoiceList\Loader;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Form\ChoiceList\Loader\LazyChoiceLoader;
+use Symfony\Component\Form\Tests\Fixtures\ArrayChoiceLoader;
+
+class LazyChoiceLoaderTest extends TestCase
+{
+    private LazyChoiceLoader $loader;
+
+    protected function setUp(): void
+    {
+        $this->loader = new LazyChoiceLoader(new ArrayChoiceLoader(['A', 'B', 'C']));
+    }
+
+    public function testInitialEmptyChoiceListLoading()
+    {
+        $this->assertSame([], $this->loader->loadChoiceList()->getChoices());
+    }
+
+    public function testOnDemandChoiceListAfterLoadingValuesForChoices()
+    {
+        $this->loader->loadValuesForChoices(['A']);
+        $this->assertSame(['A' => 'A'], $this->loader->loadChoiceList()->getChoices());
+    }
+
+    public function testOnDemandChoiceListAfterLoadingChoicesForValues()
+    {
+        $this->loader->loadChoicesForValues(['B']);
+        $this->assertSame(['B' => 'B'], $this->loader->loadChoiceList()->getChoices());
+    }
+
+    public function testOnDemandChoiceList()
+    {
+        $this->loader->loadValuesForChoices(['A']);
+        $this->loader->loadChoicesForValues(['B']);
+        $this->assertSame(['B' => 'B'], $this->loader->loadChoiceList()->getChoices());
+    }
+}
diff --git a/Tests/Console/Descriptor/AbstractDescriptorTestCase.php b/Tests/Console/Descriptor/AbstractDescriptorTestCase.php
index fa8745b58..8a99c205c 100644
--- a/Tests/Console/Descriptor/AbstractDescriptorTestCase.php
+++ b/Tests/Console/Descriptor/AbstractDescriptorTestCase.php
@@ -153,7 +153,7 @@ private function getExpectedDescription($name)
 
     private function getFixtureFilename($name)
     {
-        return sprintf('%s/../../Fixtures/Descriptor/%s.%s', __DIR__, $name, $this->getFormat());
+        return \sprintf('%s/../../Fixtures/Descriptor/%s.%s', __DIR__, $name, $this->getFormat());
     }
 }
 
diff --git a/Tests/DependencyInjection/FormPassTest.php b/Tests/DependencyInjection/FormPassTest.php
index e9a7b5034..f0ccd3f09 100644
--- a/Tests/DependencyInjection/FormPassTest.php
+++ b/Tests/DependencyInjection/FormPassTest.php
@@ -21,6 +21,7 @@
 use Symfony\Component\Form\AbstractTypeExtension;
 use Symfony\Component\Form\Command\DebugCommand;
 use Symfony\Component\Form\DependencyInjection\FormPass;
+use Symfony\Component\Form\Extension\Csrf\Type\FormTypeCsrfExtension;
 use Symfony\Component\Form\FormRegistry;
 
 /**
@@ -95,6 +96,25 @@ public function testAddTaggedTypesToDebugCommand()
         );
     }
 
+    public function testAddTaggedTypesToCsrfTypeExtension()
+    {
+        $container = $this->createContainerBuilder();
+
+        $container->register('form.registry', FormRegistry::class);
+        $container->register('form.type_extension.csrf', FormTypeCsrfExtension::class)
+            ->setArguments([null, true, '_token', null, 'validator.translation_domain', null, [], null])
+            ->setPublic(true);
+
+        $container->setDefinition('form.extension', $this->createExtensionDefinition());
+        $container->register('my.type1', __CLASS__.'_Type1')->addTag('form.type', ['csrf_token_id' => 'the_token_id']);
+        $container->register('my.type2', __CLASS__.'_Type2')->addTag('form.type');
+
+        $container->compile();
+
+        $csrfDefinition = $container->getDefinition('form.type_extension.csrf');
+        $this->assertSame([__CLASS__.'_Type1' => 'the_token_id'], $csrfDefinition->getArgument(7));
+    }
+
     /**
      * @dataProvider addTaggedTypeExtensionsDataProvider
      */
diff --git a/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php b/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php
index 189c409f4..ee8c480cb 100644
--- a/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php
+++ b/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php
@@ -11,10 +11,12 @@
 
 namespace Symfony\Component\Form\Tests\Extension\Core\DataTransformer;
 
+use Symfony\Component\Form\Exception\InvalidArgumentException;
 use Symfony\Component\Form\Exception\TransformationFailedException;
 use Symfony\Component\Form\Extension\Core\DataTransformer\BaseDateTimeTransformer;
 use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToLocalizedStringTransformer;
 use Symfony\Component\Form\Tests\Extension\Core\DataTransformer\Traits\DateTimeEqualsTrait;
+use Symfony\Component\Intl\Intl;
 use Symfony\Component\Intl\Util\IntlTestHelper;
 
 class DateTimeToLocalizedStringTransformerTest extends BaseDateTimeTransformerTestCase
@@ -30,8 +32,6 @@ class DateTimeToLocalizedStringTransformerTest extends BaseDateTimeTransformerTe
 
     protected function setUp(): void
     {
-        parent::setUp();
-
         // Normalize intl. configuration settings.
         if (\extension_loaded('intl')) {
             $this->initialTestCaseUseException = ini_set('intl.use_exceptions', 0);
@@ -236,6 +236,10 @@ public function testReverseTransformFullTime()
 
     public function testReverseTransformFromDifferentLocale()
     {
+        if (version_compare(Intl::getIcuVersion(), '71.1', '>')) {
+            $this->markTestSkipped('ICU version 71.1 or lower is required.');
+        };
+
         \Locale::setDefault('en_US');
 
         $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC');
@@ -389,6 +393,68 @@ public function testReverseTransformWrapsIntlErrorsWithExceptionsAndErrorLevel()
         }
     }
 
+    public function testTransformDateTimeWithCustomCalendar()
+    {
+        $dateTime = new \DateTimeImmutable('2024-03-31');
+
+        $weekBeginsOnSunday = \IntlCalendar::createInstance();
+        $weekBeginsOnSunday->setFirstDayOfWeek(\IntlCalendar::DOW_SUNDAY);
+
+        $this->assertSame(
+            '2024-03-31 2024w14',
+            (new DateTimeToLocalizedStringTransformer(calendar: $weekBeginsOnSunday, pattern: "y-MM-dd y'w'w"))->transform($dateTime),
+        );
+
+        $weekBeginsOnMonday = \IntlCalendar::createInstance();
+        $weekBeginsOnMonday->setFirstDayOfWeek(\IntlCalendar::DOW_MONDAY);
+
+        $this->assertSame(
+            '2024-03-31 2024w13',
+            (new DateTimeToLocalizedStringTransformer(calendar: $weekBeginsOnMonday, pattern: "y-MM-dd y'w'w"))->transform($dateTime),
+        );
+    }
+
+    public function testReverseTransformDateTimeWithCustomCalendar()
+    {
+        $weekBeginsOnSunday = \IntlCalendar::createInstance();
+        $weekBeginsOnSunday->setFirstDayOfWeek(\IntlCalendar::DOW_SUNDAY);
+
+        $this->assertSame(
+            '2024-03-31',
+            (new DateTimeToLocalizedStringTransformer(calendar: $weekBeginsOnSunday, pattern: "y-MM-dd y'w'w"))
+                ->reverseTransform('2024-03-31 2024w14')
+                ->format('Y-m-d'),
+        );
+
+        $weekBeginsOnMonday = \IntlCalendar::createInstance();
+        $weekBeginsOnMonday->setFirstDayOfWeek(\IntlCalendar::DOW_MONDAY);
+
+        $this->assertSame(
+            '2024-03-31',
+            (new DateTimeToLocalizedStringTransformer(calendar: $weekBeginsOnMonday, pattern: "y-MM-dd y'w'w"))
+                ->reverseTransform('2024-03-31 2024w13')
+                ->format('Y-m-d'),
+        );
+    }
+
+    public function testDefaultCalendarIsGregorian()
+    {
+        $now = new \DateTimeImmutable();
+
+        $this->assertSame(
+            (new DateTimeToLocalizedStringTransformer(calendar: \IntlDateFormatter::GREGORIAN, pattern: "y-MM-dd y'w'w"))->transform($now),
+            (new DateTimeToLocalizedStringTransformer(pattern: "y-MM-dd y'w'w"))->transform($now),
+        );
+    }
+
+    public function testInvalidCalendar()
+    {
+        $this->expectException(InvalidArgumentException::class);
+        $this->expectExceptionMessage('The "calendar" option should be either an \IntlDateFormatter constant or an \IntlCalendar instance.');
+
+        new DateTimeToLocalizedStringTransformer(calendar: 123456);
+    }
+
     protected function createDateTimeTransformer(?string $inputTimezone = null, ?string $outputTimezone = null): BaseDateTimeTransformer
     {
         return new DateTimeToLocalizedStringTransformer($inputTimezone, $outputTimezone);
diff --git a/Tests/Extension/Core/DataTransformer/DateTimeToRfc3339TransformerTest.php b/Tests/Extension/Core/DataTransformer/DateTimeToRfc3339TransformerTest.php
index 6a4d77039..c69ba31be 100644
--- a/Tests/Extension/Core/DataTransformer/DateTimeToRfc3339TransformerTest.php
+++ b/Tests/Extension/Core/DataTransformer/DateTimeToRfc3339TransformerTest.php
@@ -25,8 +25,6 @@ class DateTimeToRfc3339TransformerTest extends BaseDateTimeTransformerTestCase
 
     protected function setUp(): void
     {
-        parent::setUp();
-
         $this->dateTime = new \DateTime('2010-02-03 04:05:06 UTC');
         $this->dateTimeWithoutSeconds = new \DateTime('2010-02-03 04:05:00 UTC');
     }
diff --git a/Tests/Extension/Core/EventListener/ResizeFormListenerTest.php b/Tests/Extension/Core/EventListener/ResizeFormListenerTest.php
index f63a5c154..934460c8f 100644
--- a/Tests/Extension/Core/EventListener/ResizeFormListenerTest.php
+++ b/Tests/Extension/Core/EventListener/ResizeFormListenerTest.php
@@ -14,28 +14,28 @@
 use Doctrine\Common\Collections\ArrayCollection;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\Form\AbstractType;
 use Symfony\Component\Form\Exception\UnexpectedTypeException;
 use Symfony\Component\Form\Extension\Core\DataMapper\DataMapper;
 use Symfony\Component\Form\Extension\Core\EventListener\ResizeFormListener;
 use Symfony\Component\Form\Extension\Core\Type\TextType;
 use Symfony\Component\Form\FormBuilder;
+use Symfony\Component\Form\FormBuilderInterface;
 use Symfony\Component\Form\FormEvent;
 use Symfony\Component\Form\FormFactoryBuilder;
 use Symfony\Component\Form\FormFactoryInterface;
-use Symfony\Component\Form\FormInterface;
 
 class ResizeFormListenerTest extends TestCase
 {
     private FormFactoryInterface $factory;
-    private FormInterface $form;
+    private FormBuilderInterface $builder;
 
     protected function setUp(): void
     {
         $this->factory = (new FormFactoryBuilder())->getFormFactory();
-        $this->form = $this->getBuilder()
+        $this->builder = $this->getBuilder()
             ->setCompound(true)
-            ->setDataMapper(new DataMapper())
-            ->getForm();
+            ->setDataMapper(new DataMapper());
     }
 
     protected function getBuilder($name = 'name')
@@ -43,142 +43,221 @@ protected function getBuilder($name = 'name')
         return new FormBuilder($name, null, new EventDispatcher(), $this->factory);
     }
 
-    protected function getForm($name = 'name')
+    /**
+     * @group legacy
+     */
+    public function testPreSetDataResizesForm()
     {
-        return $this->getBuilder($name)->getForm();
+        $this->builder->add($this->getBuilder('0'));
+        $this->builder->add($this->getBuilder('1'));
+        $this->builder->addEventSubscriber(new class(TextType::class, ['attr' => ['maxlength' => 10]], false, false) extends ResizeFormListener {
+            public function preSetData(FormEvent $event): void
+            {
+                parent::preSetData($event);
+            }
+        });
+
+        $form = $this->builder->getForm();
+
+        $this->assertTrue($form->has('0'));
+
+        // initialize the form
+        $form->setData([1 => 'string', 2 => 'string']);
+
+        $this->assertFalse($form->has('0'));
+        $this->assertTrue($form->has('1'));
+        $this->assertTrue($form->has('2'));
+
+        $this->assertSame('string', $form->get('1')->getData());
+        $this->assertSame('string', $form->get('2')->getData());
     }
 
-    public function testPreSetDataResizesForm()
+    public function testPostSetDataResizesForm()
     {
-        $this->form->add($this->getForm('0'));
-        $this->form->add($this->getForm('1'));
+        $this->builder->add($this->getBuilder('0'));
+        $this->builder->add($this->getBuilder('1'));
+        $this->builder->addEventSubscriber(new ResizeFormListener(TextType::class, ['attr' => ['maxlength' => 10]], false, false));
 
-        $data = [1 => 'string', 2 => 'string'];
-        $event = new FormEvent($this->form, $data);
-        $listener = new ResizeFormListener(TextType::class, ['attr' => ['maxlength' => 10]], false, false);
-        $listener->preSetData($event);
+        $form = $this->builder->getForm();
+
+        $this->assertTrue($form->has('0'));
 
-        $this->assertFalse($this->form->has('0'));
-        $this->assertTrue($this->form->has('1'));
-        $this->assertTrue($this->form->has('2'));
+        // initialize the form
+        $form->setData([1 => 'string', 2 => 'string']);
+
+        $this->assertFalse($form->has('0'));
+        $this->assertTrue($form->has('1'));
+        $this->assertTrue($form->has('2'));
+
+        $this->assertSame('string', $form->get('1')->getData());
+        $this->assertSame('string', $form->get('2')->getData());
     }
 
+    /**
+     * @group legacy
+     */
     public function testPreSetDataRequiresArrayOrTraversable()
     {
         $this->expectException(UnexpectedTypeException::class);
         $data = 'no array or traversable';
-        $event = new FormEvent($this->form, $data);
-        $listener = new ResizeFormListener('text', [], false, false);
+        $event = new FormEvent($this->builder->getForm(), $data);
+        $listener = new class(TextType::class, [], false, false) extends ResizeFormListener {
+            public function preSetData(FormEvent $event): void
+            {
+                parent::preSetData($event);
+            }
+        };
         $listener->preSetData($event);
     }
 
+    public function testPostSetDataRequiresArrayOrTraversable()
+    {
+        $this->expectException(UnexpectedTypeException::class);
+        $data = 'no array or traversable';
+        $event = new FormEvent($this->builder->getForm(), $data);
+        $listener = new ResizeFormListener(TextType::class, [], false, false);
+        $listener->postSetData($event);
+    }
+
+    /**
+     * @group legacy
+     */
     public function testPreSetDataDealsWithNullData()
     {
         $data = null;
-        $event = new FormEvent($this->form, $data);
-        $listener = new ResizeFormListener(TextType::class, [], false, false);
+        $event = new FormEvent($this->builder->getForm(), $data);
+        $listener = new class(TextType::class, [], false, false) extends ResizeFormListener {
+            public function preSetData(FormEvent $event): void
+            {
+                parent::preSetData($event);
+            }
+        };
         $listener->preSetData($event);
 
-        $this->assertSame(0, $this->form->count());
+        $this->assertSame(0, $this->builder->count());
+    }
+
+    public function testPostSetDataDealsWithNullData()
+    {
+        $data = null;
+        $event = new FormEvent($this->builder->getForm(), $data);
+        $listener = new ResizeFormListener(TextType::class, [], false, false);
+        $listener->postSetData($event);
+
+        $this->assertSame(0, $this->builder->count());
     }
 
     public function testPreSubmitResizesUpIfAllowAdd()
     {
-        $this->form->add($this->getForm('0'));
+        $this->builder->add($this->getBuilder('0'));
+        $this->builder->addEventSubscriber(new ResizeFormListener(TextType::class, ['attr' => ['maxlength' => 10]], true, false));
 
-        $data = [0 => 'string', 1 => 'string'];
-        $event = new FormEvent($this->form, $data);
-        $listener = new ResizeFormListener(TextType::class, ['attr' => ['maxlength' => 10]], true, false);
-        $listener->preSubmit($event);
+        $form = $this->builder->getForm();
+
+        $this->assertTrue($form->has('0'));
+        $this->assertFalse($form->has('1'));
 
-        $this->assertTrue($this->form->has('0'));
-        $this->assertTrue($this->form->has('1'));
+        $form->submit([0 => 'string', 1 => 'string']);
+
+        $this->assertTrue($form->has('0'));
+        $this->assertTrue($form->has('1'));
     }
 
     public function testPreSubmitResizesDownIfAllowDelete()
     {
-        $this->form->add($this->getForm('0'));
-        $this->form->add($this->getForm('1'));
+        $this->builder->add($this->getBuilder('0'));
+        $this->builder->add($this->getBuilder('1'));
+        $this->builder->addEventSubscriber(new ResizeFormListener(TextType::class, [], false, true));
 
-        $data = [0 => 'string'];
-        $event = new FormEvent($this->form, $data);
-        $listener = new ResizeFormListener('text', [], false, true);
-        $listener->preSubmit($event);
+        $form = $this->builder->getForm();
+        // initialize the form
+        $form->setData([0 => 'string', 1 => 'string']);
+
+        $this->assertTrue($form->has('0'));
+        $this->assertTrue($form->has('1'));
 
-        $this->assertTrue($this->form->has('0'));
-        $this->assertFalse($this->form->has('1'));
+        $form->submit([0 => 'string']);
+
+        $this->assertTrue($form->has('0'));
+        $this->assertFalse($form->has('1'));
     }
 
     // fix for https://github.com/symfony/symfony/pull/493
     public function testPreSubmitRemovesZeroKeys()
     {
-        $this->form->add($this->getForm('0'));
+        $this->builder->add($this->getBuilder('0'));
 
         $data = [];
-        $event = new FormEvent($this->form, $data);
-        $listener = new ResizeFormListener('text', [], false, true);
+        $form = $this->builder->getForm();
+        $event = new FormEvent($form, $data);
+        $listener = new ResizeFormListener(TextType::class, [], false, true);
         $listener->preSubmit($event);
 
-        $this->assertFalse($this->form->has('0'));
+        $this->assertFalse($form->has('0'));
     }
 
     public function testPreSubmitDoesNothingIfNotAllowAddNorAllowDelete()
     {
-        $this->form->add($this->getForm('0'));
-        $this->form->add($this->getForm('1'));
+        $this->builder->add($this->getBuilder('0'));
+        $this->builder->add($this->getBuilder('1'));
 
         $data = [0 => 'string', 2 => 'string'];
-        $event = new FormEvent($this->form, $data);
-        $listener = new ResizeFormListener('text', [], false, false);
+        $form = $this->builder->getForm();
+        $event = new FormEvent($form, $data);
+        $listener = new ResizeFormListener(TextType::class, [], false, false);
         $listener->preSubmit($event);
 
-        $this->assertTrue($this->form->has('0'));
-        $this->assertTrue($this->form->has('1'));
-        $this->assertFalse($this->form->has('2'));
+        $this->assertTrue($form->has('0'));
+        $this->assertTrue($form->has('1'));
+        $this->assertFalse($form->has('2'));
     }
 
     public function testPreSubmitDealsWithNoArrayOrTraversable()
     {
         $data = 'no array or traversable';
-        $event = new FormEvent($this->form, $data);
-        $listener = new ResizeFormListener('text', [], false, false);
+        $form = $this->builder->getForm();
+        $event = new FormEvent($form, $data);
+        $listener = new ResizeFormListener(TextType::class, [], false, false);
         $listener->preSubmit($event);
 
-        $this->assertFalse($this->form->has('1'));
+        $this->assertFalse($form->has('1'));
     }
 
     public function testPreSubmitDealsWithNullData()
     {
-        $this->form->add($this->getForm('1'));
+        $this->builder->add($this->getBuilder('1'));
 
         $data = null;
-        $event = new FormEvent($this->form, $data);
-        $listener = new ResizeFormListener('text', [], false, true);
+        $form = $this->builder->getForm();
+        $event = new FormEvent($form, $data);
+        $listener = new ResizeFormListener(TextType::class, [], false, true);
         $listener->preSubmit($event);
 
-        $this->assertFalse($this->form->has('1'));
+        $this->assertFalse($form->has('1'));
     }
 
     // fixes https://github.com/symfony/symfony/pull/40
     public function testPreSubmitDealsWithEmptyData()
     {
-        $this->form->add($this->getForm('1'));
+        $this->builder->add($this->getBuilder('1'));
 
         $data = '';
-        $event = new FormEvent($this->form, $data);
-        $listener = new ResizeFormListener('text', [], false, true);
+        $form = $this->builder->getForm();
+        $event = new FormEvent($form, $data);
+        $listener = new ResizeFormListener(TextType::class, [], false, true);
         $listener->preSubmit($event);
 
-        $this->assertFalse($this->form->has('1'));
+        $this->assertFalse($form->has('1'));
     }
 
     public function testOnSubmitNormDataRemovesEntriesMissingInTheFormIfAllowDelete()
     {
-        $this->form->add($this->getForm('1'));
+        $this->builder->add($this->getBuilder('1'));
 
         $data = [0 => 'first', 1 => 'second', 2 => 'third'];
-        $event = new FormEvent($this->form, $data);
-        $listener = new ResizeFormListener('text', [], false, true);
+        $form = $this->builder->getForm();
+        $event = new FormEvent($form, $data);
+        $listener = new ResizeFormListener(TextType::class, [], false, true);
         $listener->onSubmit($event);
 
         $this->assertEquals([1 => 'second'], $event->getData());
@@ -186,11 +265,12 @@ public function testOnSubmitNormDataRemovesEntriesMissingInTheFormIfAllowDelete(
 
     public function testOnSubmitNormDataDoesNothingIfNotAllowDelete()
     {
-        $this->form->add($this->getForm('1'));
+        $this->builder->add($this->getBuilder('1'));
 
         $data = [0 => 'first', 1 => 'second', 2 => 'third'];
-        $event = new FormEvent($this->form, $data);
-        $listener = new ResizeFormListener('text', [], false, false);
+        $form = $this->builder->getForm();
+        $event = new FormEvent($form, $data);
+        $listener = new ResizeFormListener(TextType::class, [], false, false);
         $listener->onSubmit($event);
 
         $this->assertEquals($data, $event->getData());
@@ -200,18 +280,18 @@ public function testOnSubmitNormDataRequiresArrayOrTraversable()
     {
         $this->expectException(UnexpectedTypeException::class);
         $data = 'no array or traversable';
-        $event = new FormEvent($this->form, $data);
-        $listener = new ResizeFormListener('text', [], false, false);
+        $event = new FormEvent($this->builder->getForm(), $data);
+        $listener = new ResizeFormListener(TextType::class, [], false, false);
         $listener->onSubmit($event);
     }
 
     public function testOnSubmitNormDataDealsWithNullData()
     {
-        $this->form->add($this->getForm('1'));
+        $this->builder->add($this->getBuilder('1'));
 
         $data = null;
-        $event = new FormEvent($this->form, $data);
-        $listener = new ResizeFormListener('text', [], false, true);
+        $event = new FormEvent($this->builder->getForm(), $data);
+        $listener = new ResizeFormListener(TextType::class, [], false, true);
         $listener->onSubmit($event);
 
         $this->assertEquals([], $event->getData());
@@ -219,11 +299,11 @@ public function testOnSubmitNormDataDealsWithNullData()
 
     public function testOnSubmitDealsWithObjectBackedIteratorAggregate()
     {
-        $this->form->add($this->getForm('1'));
+        $this->builder->add($this->getBuilder('1'));
 
         $data = new \ArrayObject([0 => 'first', 1 => 'second', 2 => 'third']);
-        $event = new FormEvent($this->form, $data);
-        $listener = new ResizeFormListener('text', [], false, true);
+        $event = new FormEvent($this->builder->getForm(), $data);
+        $listener = new ResizeFormListener(TextType::class, [], false, true);
         $listener->onSubmit($event);
 
         $this->assertArrayNotHasKey(0, $event->getData());
@@ -232,11 +312,11 @@ public function testOnSubmitDealsWithObjectBackedIteratorAggregate()
 
     public function testOnSubmitDealsWithArrayBackedIteratorAggregate()
     {
-        $this->form->add($this->getForm('1'));
+        $this->builder->add($this->getBuilder('1'));
 
         $data = new ArrayCollection([0 => 'first', 1 => 'second', 2 => 'third']);
-        $event = new FormEvent($this->form, $data);
-        $listener = new ResizeFormListener('text', [], false, true);
+        $event = new FormEvent($this->builder->getForm(), $data);
+        $listener = new ResizeFormListener(TextType::class, [], false, true);
         $listener->onSubmit($event);
 
         $this->assertArrayNotHasKey(0, $event->getData());
@@ -245,46 +325,37 @@ public function testOnSubmitDealsWithArrayBackedIteratorAggregate()
 
     public function testOnSubmitDeleteEmptyNotCompoundEntriesIfAllowDelete()
     {
-        $this->form->setData(['0' => 'first', '1' => 'second']);
-        $this->form->add($this->getForm('0'));
-        $this->form->add($this->getForm('1'));
-
-        $data = [0 => 'first', 1 => ''];
-        foreach ($data as $child => $dat) {
-            $this->form->get($child)->submit($dat);
-        }
-        $event = new FormEvent($this->form, $data);
-        $listener = new ResizeFormListener('text', [], false, true, true);
-        $listener->onSubmit($event);
+        $this->builder->setData(['0' => 'first', '1' => 'second']);
+        $this->builder->add($this->getBuilder('0'));
+        $this->builder->add($this->getBuilder('1'));
+        $this->builder->addEventSubscriber(new ResizeFormListener(TextType::class, [], false, true, true));
 
-        $this->assertEquals([0 => 'first'], $event->getData());
+        $form = $this->builder->getForm();
+
+        $form->submit([0 => 'first', 1 => '']);
+
+        $this->assertEquals([0 => 'first'], $form->getData());
     }
 
     public function testOnSubmitDeleteEmptyCompoundEntriesIfAllowDelete()
     {
-        $this->form->setData(['0' => ['name' => 'John'], '1' => ['name' => 'Jane']]);
-        $form1 = $this->getBuilder('0')
-            ->setCompound(true)
-            ->setDataMapper(new DataMapper())
-            ->getForm();
-        $form1->add($this->getForm('name'));
-        $form2 = $this->getBuilder('1')
-            ->setCompound(true)
-            ->setDataMapper(new DataMapper())
-            ->getForm();
-        $form2->add($this->getForm('name'));
-        $this->form->add($form1);
-        $this->form->add($form2);
-
-        $data = ['0' => ['name' => 'John'], '1' => ['name' => '']];
-        foreach ($data as $child => $dat) {
-            $this->form->get($child)->submit($dat);
-        }
-        $event = new FormEvent($this->form, $data);
-        $callback = fn ($data) => null === $data['name'];
-        $listener = new ResizeFormListener('text', [], false, true, $callback);
-        $listener->onSubmit($event);
+        $this->builder->setData(['0' => ['name' => 'John'], '1' => ['name' => 'Jane']]);
+        $this->builder->add('0', NestedType::class);
+        $this->builder->add('1', NestedType::class);
+        $callback = fn ($data) => empty($data['name']);
+        $this->builder->addEventSubscriber(new ResizeFormListener(NestedType::class, [], false, true, $callback));
+
+        $form = $this->builder->getForm();
+        $form->submit(['0' => ['name' => 'John'], '1' => ['name' => '']]);
 
-        $this->assertEquals(['0' => ['name' => 'John']], $event->getData());
+        $this->assertEquals(['0' => ['name' => 'John']], $form->getData());
+    }
+}
+
+class NestedType extends AbstractType
+{
+    public function buildForm(FormBuilderInterface $builder, array $options): void
+    {
+        $builder->add('name');
     }
 }
diff --git a/Tests/Extension/Core/Type/BirthdayTypeTest.php b/Tests/Extension/Core/Type/BirthdayTypeTest.php
index 048457141..53e5c959c 100644
--- a/Tests/Extension/Core/Type/BirthdayTypeTest.php
+++ b/Tests/Extension/Core/Type/BirthdayTypeTest.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Form\Tests\Extension\Core\Type;
 
+use Symfony\Component\Form\Extension\Core\Type\BirthdayType;
 use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
 
 /**
@@ -18,7 +19,7 @@
  */
 class BirthdayTypeTest extends DateTypeTest
 {
-    public const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\BirthdayType';
+    public const TESTED_TYPE = BirthdayType::class;
 
     public function testSetInvalidYearsOption()
     {
diff --git a/Tests/Extension/Core/Type/ButtonTypeTest.php b/Tests/Extension/Core/Type/ButtonTypeTest.php
index 0125631c5..4825015d2 100644
--- a/Tests/Extension/Core/Type/ButtonTypeTest.php
+++ b/Tests/Extension/Core/Type/ButtonTypeTest.php
@@ -14,6 +14,7 @@
 use Symfony\Component\Form\Button;
 use Symfony\Component\Form\Exception\BadMethodCallException;
 use Symfony\Component\Form\Exception\LogicException;
+use Symfony\Component\Form\Extension\Core\Type\ButtonType;
 use Symfony\Component\Form\Extension\Core\Type\FormType;
 
 /**
@@ -21,7 +22,7 @@
  */
 class ButtonTypeTest extends BaseTypeTestCase
 {
-    public const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\ButtonType';
+    public const TESTED_TYPE = ButtonType::class;
 
     public function testCreateButtonInstances()
     {
diff --git a/Tests/Extension/Core/Type/CheckboxTypeTest.php b/Tests/Extension/Core/Type/CheckboxTypeTest.php
index 62312e28d..69fd0fd60 100644
--- a/Tests/Extension/Core/Type/CheckboxTypeTest.php
+++ b/Tests/Extension/Core/Type/CheckboxTypeTest.php
@@ -12,11 +12,12 @@
 namespace Symfony\Component\Form\Tests\Extension\Core\Type;
 
 use Symfony\Component\Form\CallbackTransformer;
+use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
 use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
 
 class CheckboxTypeTest extends BaseTypeTestCase
 {
-    public const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\CheckboxType';
+    public const TESTED_TYPE = CheckboxType::class;
 
     public function testDataIsFalseByDefault()
     {
diff --git a/Tests/Extension/Core/Type/ChoiceTypeTest.php b/Tests/Extension/Core/Type/ChoiceTypeTest.php
index 8e2372d7e..28810bbc7 100644
--- a/Tests/Extension/Core/Type/ChoiceTypeTest.php
+++ b/Tests/Extension/Core/Type/ChoiceTypeTest.php
@@ -14,13 +14,15 @@
 use Symfony\Component\Form\ChoiceList\Loader\CallbackChoiceLoader;
 use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
 use Symfony\Component\Form\ChoiceList\View\ChoiceView;
+use Symfony\Component\Form\Exception\LogicException;
 use Symfony\Component\Form\Exception\TransformationFailedException;
+use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
 use Symfony\Component\Form\FormInterface;
 use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
 
 class ChoiceTypeTest extends BaseTypeTestCase
 {
-    public const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\ChoiceType';
+    public const TESTED_TYPE = ChoiceType::class;
 
     private array $choices = [
         'Bernhard' => 'a',
@@ -1895,8 +1897,8 @@ public function testInitializeWithEmptyChoices()
     {
         $this->assertInstanceOf(
             FormInterface::class, $this->factory->createNamed('name', static::TESTED_TYPE, null, [
-            'choices' => [],
-        ]));
+                'choices' => [],
+            ]));
     }
 
     public function testInitializeWithDefaultObjectChoice()
@@ -2276,4 +2278,111 @@ public function testWithSameLoaderAndDifferentChoiceValueCallbacks()
         $this->assertSame('20', $view['choice_two']->vars['choices'][1]->value);
         $this->assertSame('30', $view['choice_two']->vars['choices'][2]->value);
     }
+
+    public function testChoiceLazyThrowsWhenChoiceLoaderIsNotSet()
+    {
+        $this->expectException(LogicException::class);
+        $this->expectExceptionMessage('The "choice_lazy" option can only be used if the "choice_loader" option is set.');
+
+        $this->factory->create(static::TESTED_TYPE, null, [
+            'choice_lazy' => true,
+        ]);
+    }
+
+    public function testChoiceLazyLoadsAndRendersNothingWhenNoDataSet()
+    {
+        $form = $this->factory->create(static::TESTED_TYPE, null, [
+            'choice_loader' => new CallbackChoiceLoader(fn () => ['a' => 'A', 'b' => 'B']),
+            'choice_lazy' => true,
+        ]);
+
+        $this->assertNull($form->getData());
+
+        $view = $form->createView();
+        $this->assertArrayHasKey('choices', $view->vars);
+        $this->assertSame([], $view->vars['choices']);
+    }
+
+    public function testChoiceLazyLoadsAndRendersOnlyDataSetViaDefault()
+    {
+        $form = $this->factory->create(static::TESTED_TYPE, 'A', [
+            'choice_loader' => new CallbackChoiceLoader(fn () => ['a' => 'A', 'b' => 'B']),
+            'choice_lazy' => true,
+        ]);
+
+        $this->assertSame('A', $form->getData());
+
+        $view = $form->createView();
+        $this->assertArrayHasKey('choices', $view->vars);
+        $this->assertCount(1, $view->vars['choices']);
+        $this->assertSame('A', $view->vars['choices'][0]->value);
+    }
+
+    public function testChoiceLazyLoadsAndRendersOnlyDataSetViaSubmit()
+    {
+        $form = $this->factory->create(static::TESTED_TYPE, null, [
+            'choice_loader' => new CallbackChoiceLoader(fn () => ['a' => 'A', 'b' => 'B']),
+            'choice_lazy' => true,
+        ]);
+
+        $form->submit('B');
+        $this->assertSame('B', $form->getData());
+
+        $view = $form->createView();
+        $this->assertArrayHasKey('choices', $view->vars);
+        $this->assertCount(1, $view->vars['choices']);
+        $this->assertSame('B', $view->vars['choices'][0]->value);
+    }
+
+    public function testChoiceLazyErrorWhenInvalidSubmitData()
+    {
+        $form = $this->factory->create(static::TESTED_TYPE, null, [
+            'choice_loader' => new CallbackChoiceLoader(fn () => ['a' => 'A', 'b' => 'B']),
+            'choice_lazy' => true,
+        ]);
+
+        $form->submit('invalid');
+        $this->assertNull($form->getData());
+
+        $view = $form->createView();
+        $this->assertArrayHasKey('choices', $view->vars);
+        $this->assertCount(0, $view->vars['choices']);
+        $this->assertCount(1, $form->getErrors());
+        $this->assertSame('ERROR: The selected choice is invalid.', trim((string) $form->getErrors()));
+    }
+
+    public function testChoiceLazyMultipleWithDefaultData()
+    {
+        $form = $this->factory->create(static::TESTED_TYPE, ['A', 'B'], [
+            'choice_loader' => new CallbackChoiceLoader(fn () => ['a' => 'A', 'b' => 'B', 'c' => 'C']),
+            'choice_lazy' => true,
+            'multiple' => true,
+        ]);
+
+        $this->assertSame(['A', 'B'], $form->getData());
+
+        $view = $form->createView();
+        $this->assertArrayHasKey('choices', $view->vars);
+        $this->assertCount(2, $view->vars['choices']);
+        $this->assertSame('A', $view->vars['choices'][0]->value);
+        $this->assertSame('B', $view->vars['choices'][1]->value);
+    }
+
+    public function testChoiceLazyMultipleWithSubmittedData()
+    {
+        $form = $this->factory->create(static::TESTED_TYPE, null, [
+            'choice_loader' => new CallbackChoiceLoader(fn () => ['a' => 'A', 'b' => 'B', 'c' => 'C']),
+            'choice_lazy' => true,
+            'multiple' => true,
+        ]);
+
+        $form->submit(['B', 'C']);
+        $this->assertSame(['B', 'C'], $form->getData());
+
+        $view = $form->createView();
+        $this->assertArrayHasKey('choices', $view->vars);
+        $this->assertCount(2, $view->vars['choices']);
+        $this->assertSame('B', $view->vars['choices'][0]->value);
+        $this->assertSame('C', $view->vars['choices'][1]->value);
+    }
 }
diff --git a/Tests/Extension/Core/Type/ChoiceTypeTranslationTest.php b/Tests/Extension/Core/Type/ChoiceTypeTranslationTest.php
index 26055c203..f60c6664c 100644
--- a/Tests/Extension/Core/Type/ChoiceTypeTranslationTest.php
+++ b/Tests/Extension/Core/Type/ChoiceTypeTranslationTest.php
@@ -12,12 +12,13 @@
 namespace Symfony\Component\Form\Tests\Extension\Core\Type;
 
 use Symfony\Component\Form\Extension\Core\CoreExtension;
+use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
 use Symfony\Component\Form\Test\TypeTestCase;
 use Symfony\Contracts\Translation\TranslatorInterface;
 
 class ChoiceTypeTranslationTest extends TypeTestCase
 {
-    public const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\ChoiceType';
+    public const TESTED_TYPE = ChoiceType::class;
 
     private array $choices = [
         'Bernhard' => 'a',
@@ -27,11 +28,11 @@ class ChoiceTypeTranslationTest extends TypeTestCase
         'Roman' => 'e',
     ];
 
-    protected function getExtensions()
+    protected function getExtensions(): array
     {
         $translator = $this->createMock(TranslatorInterface::class);
         $translator->expects($this->any())->method('trans')
-            ->willReturnCallback(fn ($key, $params) => strtr(sprintf('Translation of: %s', $key), $params)
+            ->willReturnCallback(fn ($key, $params) => strtr(\sprintf('Translation of: %s', $key), $params)
             );
 
         return array_merge(parent::getExtensions(), [new CoreExtension(null, null, $translator)]);
diff --git a/Tests/Extension/Core/Type/CollectionTypeTest.php b/Tests/Extension/Core/Type/CollectionTypeTest.php
index 79134db99..95e1d9ca7 100644
--- a/Tests/Extension/Core/Type/CollectionTypeTest.php
+++ b/Tests/Extension/Core/Type/CollectionTypeTest.php
@@ -13,6 +13,7 @@
 
 use Symfony\Component\Form\Exception\UnexpectedTypeException;
 use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
+use Symfony\Component\Form\Extension\Core\Type\CollectionType;
 use Symfony\Component\Form\Form;
 use Symfony\Component\Form\Tests\Fixtures\Author;
 use Symfony\Component\Form\Tests\Fixtures\AuthorType;
@@ -20,7 +21,7 @@
 
 class CollectionTypeTest extends BaseTypeTestCase
 {
-    public const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\CollectionType';
+    public const TESTED_TYPE = CollectionType::class;
 
     public function testContainsNoChildByDefault()
     {
diff --git a/Tests/Extension/Core/Type/CountryTypeTest.php b/Tests/Extension/Core/Type/CountryTypeTest.php
index 57146e1ec..44073ef7b 100644
--- a/Tests/Extension/Core/Type/CountryTypeTest.php
+++ b/Tests/Extension/Core/Type/CountryTypeTest.php
@@ -12,11 +12,12 @@
 namespace Symfony\Component\Form\Tests\Extension\Core\Type;
 
 use Symfony\Component\Form\ChoiceList\View\ChoiceView;
+use Symfony\Component\Form\Extension\Core\Type\CountryType;
 use Symfony\Component\Intl\Util\IntlTestHelper;
 
 class CountryTypeTest extends BaseTypeTestCase
 {
-    public const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\CountryType';
+    public const TESTED_TYPE = CountryType::class;
 
     protected function setUp(): void
     {
diff --git a/Tests/Extension/Core/Type/CurrencyTypeTest.php b/Tests/Extension/Core/Type/CurrencyTypeTest.php
index 51aaf6b37..3e8a53dfb 100644
--- a/Tests/Extension/Core/Type/CurrencyTypeTest.php
+++ b/Tests/Extension/Core/Type/CurrencyTypeTest.php
@@ -12,11 +12,12 @@
 namespace Symfony\Component\Form\Tests\Extension\Core\Type;
 
 use Symfony\Component\Form\ChoiceList\View\ChoiceView;
+use Symfony\Component\Form\Extension\Core\Type\CurrencyType;
 use Symfony\Component\Intl\Util\IntlTestHelper;
 
 class CurrencyTypeTest extends BaseTypeTestCase
 {
-    public const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\CurrencyType';
+    public const TESTED_TYPE = CurrencyType::class;
 
     protected function setUp(): void
     {
diff --git a/Tests/Extension/Core/Type/DateTimeTypeTest.php b/Tests/Extension/Core/Type/DateTimeTypeTest.php
index a402a70f9..5067bb05e 100644
--- a/Tests/Extension/Core/Type/DateTimeTypeTest.php
+++ b/Tests/Extension/Core/Type/DateTimeTypeTest.php
@@ -12,12 +12,13 @@
 namespace Symfony\Component\Form\Tests\Extension\Core\Type;
 
 use Symfony\Component\Form\Exception\LogicException;
+use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
 use Symfony\Component\Form\FormError;
 use Symfony\Component\Form\FormInterface;
 
 class DateTimeTypeTest extends BaseTypeTestCase
 {
-    public const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\DateTimeType';
+    public const TESTED_TYPE = DateTimeType::class;
 
     private string $defaultLocale;
 
diff --git a/Tests/Extension/Core/Type/DateTypeTest.php b/Tests/Extension/Core/Type/DateTypeTest.php
index dfbae9330..5f4f896b5 100644
--- a/Tests/Extension/Core/Type/DateTypeTest.php
+++ b/Tests/Extension/Core/Type/DateTypeTest.php
@@ -13,6 +13,7 @@
 
 use Symfony\Component\Form\ChoiceList\View\ChoiceView;
 use Symfony\Component\Form\Exception\LogicException;
+use Symfony\Component\Form\Extension\Core\Type\DateType;
 use Symfony\Component\Form\FormError;
 use Symfony\Component\Form\FormInterface;
 use Symfony\Component\Intl\Intl;
@@ -21,7 +22,7 @@
 
 class DateTypeTest extends BaseTypeTestCase
 {
-    public const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\DateType';
+    public const TESTED_TYPE = DateType::class;
 
     private string $defaultTimezone;
     private string $defaultLocale;
@@ -1156,6 +1157,44 @@ public function testDateTimeImmutableInputTimezoneNotMatchingModelTimezone()
         ]);
     }
 
+    public function testSubmitWithCustomCalendarOption()
+    {
+        IntlTestHelper::requireFullIntl($this);
+
+        // Creates a new form using the "roc" (Republic Of China) calendar. This calendar starts in 1912, the year 2024 in
+        // the Gregorian calendar is the year 113 in the "roc" calendar.
+        $form = $this->factory->create(static::TESTED_TYPE, options: [
+            'format' => 'y-MM-dd',
+            'html5' => false,
+            'input' => 'array',
+            'calendar' => \IntlCalendar::createInstance(locale: 'zh_TW@calendar=roc'),
+        ]);
+        $form->submit('113-03-31');
+
+        $this->assertSame('2024', $form->getData()['year'], 'The year should be converted to the default locale (en)');
+        $this->assertSame('31', $form->getData()['day']);
+        $this->assertSame('3', $form->getData()['month']);
+
+        $this->assertSame('113-03-31', $form->getViewData());
+    }
+
+    public function testSetDataWithCustomCalendarOption()
+    {
+        IntlTestHelper::requireFullIntl($this);
+
+        // Creates a new form using the "roc" (Republic Of China) calendar. This calendar starts in 1912, the year 2024 in
+        // the Gregorian calendar is the year 113 in the "roc" calendar.
+        $form = $this->factory->create(static::TESTED_TYPE, options: [
+            'format' => 'y-MM-dd',
+            'html5' => false,
+            'input' => 'array',
+            'calendar' => \IntlCalendar::createInstance(locale: 'zh_TW@calendar=roc'),
+        ]);
+        $form->setData(['year' => '2024', 'month' => '3', 'day' => '31']);
+
+        $this->assertSame('113-03-31', $form->getViewData());
+    }
+
     protected function getTestOptions(): array
     {
         return ['widget' => 'choice'];
diff --git a/Tests/Extension/Core/Type/FileTypeTest.php b/Tests/Extension/Core/Type/FileTypeTest.php
index b7f3332c1..85907b695 100644
--- a/Tests/Extension/Core/Type/FileTypeTest.php
+++ b/Tests/Extension/Core/Type/FileTypeTest.php
@@ -12,6 +12,7 @@
 namespace Symfony\Component\Form\Tests\Extension\Core\Type;
 
 use Symfony\Component\Form\Extension\Core\CoreExtension;
+use Symfony\Component\Form\Extension\Core\Type\FileType;
 use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationRequestHandler;
 use Symfony\Component\Form\NativeRequestHandler;
 use Symfony\Component\Form\RequestHandlerInterface;
@@ -21,9 +22,9 @@
 
 class FileTypeTest extends BaseTypeTestCase
 {
-    public const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\FileType';
+    public const TESTED_TYPE = FileType::class;
 
-    protected function getExtensions()
+    protected function getExtensions(): array
     {
         return array_merge(parent::getExtensions(), [new CoreExtension(null, null, new IdentityTranslator())]);
     }
diff --git a/Tests/Extension/Core/Type/FormTypeTest.php b/Tests/Extension/Core/Type/FormTypeTest.php
index be89c559f..fe19f3b12 100644
--- a/Tests/Extension/Core/Type/FormTypeTest.php
+++ b/Tests/Extension/Core/Type/FormTypeTest.php
@@ -64,7 +64,7 @@ public function setReferenceCopy($reference)
 
 class FormTypeTest extends BaseTypeTestCase
 {
-    public const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\FormType';
+    public const TESTED_TYPE = FormType::class;
 
     public function testCreateFormInstances()
     {
@@ -156,24 +156,24 @@ public function testDataClassMayBeNull()
     {
         $this->assertInstanceOf(
             FormBuilderInterface::class, $this->factory->createBuilder(static::TESTED_TYPE, null, [
-            'data_class' => null,
-        ]));
+                'data_class' => null,
+            ]));
     }
 
     public function testDataClassMayBeAbstractClass()
     {
         $this->assertInstanceOf(
             FormBuilderInterface::class, $this->factory->createBuilder(static::TESTED_TYPE, null, [
-            'data_class' => 'Symfony\Component\Form\Tests\Fixtures\AbstractAuthor',
-        ]));
+                'data_class' => 'Symfony\Component\Form\Tests\Fixtures\AbstractAuthor',
+            ]));
     }
 
     public function testDataClassMayBeInterface()
     {
         $this->assertInstanceOf(
             FormBuilderInterface::class, $this->factory->createBuilder(static::TESTED_TYPE, null, [
-            'data_class' => 'Symfony\Component\Form\Tests\Fixtures\AuthorInterface',
-        ]));
+                'data_class' => 'Symfony\Component\Form\Tests\Fixtures\AuthorInterface',
+            ]));
     }
 
     public function testDataClassMustBeValidClassOrInterface()
@@ -402,7 +402,7 @@ public function testSubformCallsSettersIfTheObjectChanged()
             // referenceCopy has a getter that returns a copy
             'referenceCopy' => [
                 'firstName' => 'Foo',
-        ],
+            ],
         ]);
 
         $this->assertEquals('Foo', $author->getReferenceCopy()->firstName);
@@ -680,8 +680,8 @@ public function testDataMapperTransformationFailedExceptionInvalidMessageIsUsed(
     public function testPassZeroLabelToView()
     {
         $view = $this->factory->create(static::TESTED_TYPE, null, [
-                'label' => '0',
-            ])
+            'label' => '0',
+        ])
             ->createView();
 
         $this->assertSame('0', $view->vars['label']);
diff --git a/Tests/Extension/Core/Type/IntegerTypeTest.php b/Tests/Extension/Core/Type/IntegerTypeTest.php
index 1e143b342..ff33c17c6 100644
--- a/Tests/Extension/Core/Type/IntegerTypeTest.php
+++ b/Tests/Extension/Core/Type/IntegerTypeTest.php
@@ -11,11 +11,12 @@
 
 namespace Symfony\Component\Form\Tests\Extension\Core\Type;
 
+use Symfony\Component\Form\Extension\Core\Type\IntegerType;
 use Symfony\Component\Intl\Util\IntlTestHelper;
 
 class IntegerTypeTest extends BaseTypeTestCase
 {
-    public const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\IntegerType';
+    public const TESTED_TYPE = IntegerType::class;
 
     private string $previousLocale;
 
diff --git a/Tests/Extension/Core/Type/LanguageTypeTest.php b/Tests/Extension/Core/Type/LanguageTypeTest.php
index e214e0afd..8eb085112 100644
--- a/Tests/Extension/Core/Type/LanguageTypeTest.php
+++ b/Tests/Extension/Core/Type/LanguageTypeTest.php
@@ -13,11 +13,12 @@
 
 use Symfony\Component\Form\ChoiceList\View\ChoiceView;
 use Symfony\Component\Form\Exception\LogicException;
+use Symfony\Component\Form\Extension\Core\Type\LanguageType;
 use Symfony\Component\Intl\Util\IntlTestHelper;
 
 class LanguageTypeTest extends BaseTypeTestCase
 {
-    public const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\LanguageType';
+    public const TESTED_TYPE = LanguageType::class;
 
     protected function setUp(): void
     {
diff --git a/Tests/Extension/Core/Type/LocaleTypeTest.php b/Tests/Extension/Core/Type/LocaleTypeTest.php
index 8486b6656..a2a820b39 100644
--- a/Tests/Extension/Core/Type/LocaleTypeTest.php
+++ b/Tests/Extension/Core/Type/LocaleTypeTest.php
@@ -12,11 +12,12 @@
 namespace Symfony\Component\Form\Tests\Extension\Core\Type;
 
 use Symfony\Component\Form\ChoiceList\View\ChoiceView;
+use Symfony\Component\Form\Extension\Core\Type\LocaleType;
 use Symfony\Component\Intl\Util\IntlTestHelper;
 
 class LocaleTypeTest extends BaseTypeTestCase
 {
-    public const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\LocaleType';
+    public const TESTED_TYPE = LocaleType::class;
 
     protected function setUp(): void
     {
diff --git a/Tests/Extension/Core/Type/MoneyTypeTest.php b/Tests/Extension/Core/Type/MoneyTypeTest.php
index 58e33af19..f9112ffca 100644
--- a/Tests/Extension/Core/Type/MoneyTypeTest.php
+++ b/Tests/Extension/Core/Type/MoneyTypeTest.php
@@ -11,11 +11,12 @@
 
 namespace Symfony\Component\Form\Tests\Extension\Core\Type;
 
+use Symfony\Component\Form\Extension\Core\Type\MoneyType;
 use Symfony\Component\Intl\Util\IntlTestHelper;
 
 class MoneyTypeTest extends BaseTypeTestCase
 {
-    public const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\MoneyType';
+    public const TESTED_TYPE = MoneyType::class;
 
     private string $defaultLocale;
 
@@ -32,8 +33,6 @@ protected function setUp(): void
 
     protected function tearDown(): void
     {
-        parent::tearDown();
-
         \Locale::setDefault($this->defaultLocale);
     }
 
diff --git a/Tests/Extension/Core/Type/NumberTypeTest.php b/Tests/Extension/Core/Type/NumberTypeTest.php
index 9efe05221..95ccdfea9 100644
--- a/Tests/Extension/Core/Type/NumberTypeTest.php
+++ b/Tests/Extension/Core/Type/NumberTypeTest.php
@@ -13,11 +13,12 @@
 
 use Symfony\Component\Form\Exception\LogicException;
 use Symfony\Component\Form\Exception\TransformationFailedException;
+use Symfony\Component\Form\Extension\Core\Type\NumberType;
 use Symfony\Component\Intl\Util\IntlTestHelper;
 
 class NumberTypeTest extends BaseTypeTestCase
 {
-    public const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\NumberType';
+    public const TESTED_TYPE = NumberType::class;
 
     private string $defaultLocale;
 
@@ -34,8 +35,6 @@ protected function setUp(): void
 
     protected function tearDown(): void
     {
-        parent::tearDown();
-
         \Locale::setDefault($this->defaultLocale);
     }
 
diff --git a/Tests/Extension/Core/Type/PasswordTypeTest.php b/Tests/Extension/Core/Type/PasswordTypeTest.php
index 8d428a26a..945437bcb 100644
--- a/Tests/Extension/Core/Type/PasswordTypeTest.php
+++ b/Tests/Extension/Core/Type/PasswordTypeTest.php
@@ -11,9 +11,11 @@
 
 namespace Symfony\Component\Form\Tests\Extension\Core\Type;
 
+use Symfony\Component\Form\Extension\Core\Type\PasswordType;
+
 class PasswordTypeTest extends BaseTypeTestCase
 {
-    public const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\PasswordType';
+    public const TESTED_TYPE = PasswordType::class;
 
     public function testEmptyIfNotSubmitted()
     {
diff --git a/Tests/Extension/Core/Type/PercentTypeTest.php b/Tests/Extension/Core/Type/PercentTypeTest.php
index 76595d79b..120aab2f3 100644
--- a/Tests/Extension/Core/Type/PercentTypeTest.php
+++ b/Tests/Extension/Core/Type/PercentTypeTest.php
@@ -34,8 +34,6 @@ protected function setUp(): void
 
     protected function tearDown(): void
     {
-        parent::tearDown();
-
         \Locale::setDefault($this->defaultLocale);
     }
 
diff --git a/Tests/Extension/Core/Type/RepeatedTypeTest.php b/Tests/Extension/Core/Type/RepeatedTypeTest.php
index e328d3724..2d19a0613 100644
--- a/Tests/Extension/Core/Type/RepeatedTypeTest.php
+++ b/Tests/Extension/Core/Type/RepeatedTypeTest.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Form\Tests\Extension\Core\Type;
 
+use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
 use Symfony\Component\Form\Extension\Core\Type\TextType;
 use Symfony\Component\Form\Form;
 use Symfony\Component\Form\Tests\Fixtures\NotMappedType;
@@ -18,7 +19,7 @@
 
 class RepeatedTypeTest extends BaseTypeTestCase
 {
-    public const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\RepeatedType';
+    public const TESTED_TYPE = RepeatedType::class;
 
     protected Form $form;
 
@@ -198,7 +199,7 @@ public function testSubmitNullForTextTypeWithEmptyDataOptionSetToEmptyString($em
             'type' => TextType::class,
             'options' => [
                 'empty_data' => $emptyData,
-            ]
+            ],
         ]);
         $form->submit($submittedData);
 
diff --git a/Tests/Extension/Core/Type/SubmitTypeTest.php b/Tests/Extension/Core/Type/SubmitTypeTest.php
index 8a16175d7..af5ab8400 100644
--- a/Tests/Extension/Core/Type/SubmitTypeTest.php
+++ b/Tests/Extension/Core/Type/SubmitTypeTest.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Form\Tests\Extension\Core\Type;
 
+use Symfony\Component\Form\Extension\Core\Type\SubmitType;
 use Symfony\Component\Form\SubmitButton;
 
 /**
@@ -18,7 +19,7 @@
  */
 class SubmitTypeTest extends ButtonTypeTest
 {
-    public const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\SubmitType';
+    public const TESTED_TYPE = SubmitType::class;
 
     public function testCreateSubmitButtonInstances()
     {
diff --git a/Tests/Extension/Core/Type/TextTypeTest.php b/Tests/Extension/Core/Type/TextTypeTest.php
index a28dfa9af..483215168 100644
--- a/Tests/Extension/Core/Type/TextTypeTest.php
+++ b/Tests/Extension/Core/Type/TextTypeTest.php
@@ -11,9 +11,11 @@
 
 namespace Symfony\Component\Form\Tests\Extension\Core\Type;
 
+use Symfony\Component\Form\Extension\Core\Type\TextType;
+
 class TextTypeTest extends BaseTypeTestCase
 {
-    public const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\TextType';
+    public const TESTED_TYPE = TextType::class;
 
     public function testSubmitNull($expected = null, $norm = null, $view = null)
     {
diff --git a/Tests/Extension/Core/Type/TimeTypeTest.php b/Tests/Extension/Core/Type/TimeTypeTest.php
index 155657038..8a2baf1b4 100644
--- a/Tests/Extension/Core/Type/TimeTypeTest.php
+++ b/Tests/Extension/Core/Type/TimeTypeTest.php
@@ -14,13 +14,14 @@
 use Symfony\Component\Form\ChoiceList\View\ChoiceView;
 use Symfony\Component\Form\Exception\InvalidConfigurationException;
 use Symfony\Component\Form\Exception\LogicException;
+use Symfony\Component\Form\Extension\Core\Type\TimeType;
 use Symfony\Component\Form\FormError;
 use Symfony\Component\Form\FormInterface;
 use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
 
 class TimeTypeTest extends BaseTypeTestCase
 {
-    public const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\TimeType';
+    public const TESTED_TYPE = TimeType::class;
 
     public function testSubmitDateTime()
     {
diff --git a/Tests/Extension/Core/Type/TimezoneTypeTest.php b/Tests/Extension/Core/Type/TimezoneTypeTest.php
index 9966b4043..4f2343974 100644
--- a/Tests/Extension/Core/Type/TimezoneTypeTest.php
+++ b/Tests/Extension/Core/Type/TimezoneTypeTest.php
@@ -13,11 +13,12 @@
 
 use Symfony\Component\Form\ChoiceList\View\ChoiceView;
 use Symfony\Component\Form\Exception\LogicException;
+use Symfony\Component\Form\Extension\Core\Type\TimezoneType;
 use Symfony\Component\Intl\Util\IntlTestHelper;
 
 class TimezoneTypeTest extends BaseTypeTestCase
 {
-    public const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\TimezoneType';
+    public const TESTED_TYPE = TimezoneType::class;
 
     public function testTimezonesAreSelectable()
     {
diff --git a/Tests/Extension/Core/Type/UrlTypeTest.php b/Tests/Extension/Core/Type/UrlTypeTest.php
index 28e8b9ac7..a0d335647 100644
--- a/Tests/Extension/Core/Type/UrlTypeTest.php
+++ b/Tests/Extension/Core/Type/UrlTypeTest.php
@@ -11,21 +11,22 @@
 
 namespace Symfony\Component\Form\Tests\Extension\Core\Type;
 
-use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
+use Symfony\Bridge\PhpUnit\ExpectUserDeprecationMessageTrait;
+use Symfony\Component\Form\Extension\Core\Type\UrlType;
 use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
 
 class UrlTypeTest extends TextTypeTest
 {
-    use ExpectDeprecationTrait;
+    use ExpectUserDeprecationMessageTrait;
 
-    public const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\UrlType';
+    public const TESTED_TYPE = UrlType::class;
 
     /**
      * @group legacy
      */
     public function testSubmitAddsDefaultProtocolIfNoneIsIncluded()
     {
-        $this->expectDeprecation('Since symfony/form 7.1: Not configuring the "default_protocol" option when using the UrlType is deprecated. It will default to "null" in 8.0.');
+        $this->expectUserDeprecationMessage('Since symfony/form 7.1: Not configuring the "default_protocol" option when using the UrlType is deprecated. It will default to "null" in 8.0.');
         $form = $this->factory->create(static::TESTED_TYPE, 'name');
 
         $form->submit('www.domain.com');
diff --git a/Tests/Extension/Core/Type/WeekTypeTest.php b/Tests/Extension/Core/Type/WeekTypeTest.php
index a69b96a38..b4d58fd95 100644
--- a/Tests/Extension/Core/Type/WeekTypeTest.php
+++ b/Tests/Extension/Core/Type/WeekTypeTest.php
@@ -11,11 +11,12 @@
 
 namespace Symfony\Component\Form\Tests\Extension\Core\Type;
 
+use Symfony\Component\Form\Extension\Core\Type\WeekType;
 use Symfony\Component\Form\FormError;
 
 class WeekTypeTest extends BaseTypeTestCase
 {
-    public const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\WeekType';
+    public const TESTED_TYPE = WeekType::class;
 
     public function testSubmitArray()
     {
diff --git a/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php b/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php
index bfa302555..d5bce6527 100644
--- a/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php
+++ b/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php
@@ -42,7 +42,7 @@ protected function setUp(): void
         parent::setUp();
     }
 
-    protected function getExtensions()
+    protected function getExtensions(): array
     {
         return array_merge(parent::getExtensions(), [
             new CsrfExtension($this->tokenManager, new IdentityTranslator()),
diff --git a/Tests/Extension/DataCollector/FormDataCollectorTest.php b/Tests/Extension/DataCollector/FormDataCollectorTest.php
index 798faa0c5..4090fc97b 100644
--- a/Tests/Extension/DataCollector/FormDataCollectorTest.php
+++ b/Tests/Extension/DataCollector/FormDataCollectorTest.php
@@ -18,7 +18,6 @@
 use Symfony\Component\Form\Extension\Core\Type\TextType;
 use Symfony\Component\Form\Extension\DataCollector\FormDataCollector;
 use Symfony\Component\Form\Extension\DataCollector\FormDataExtractor;
-use Symfony\Component\Form\Form;
 use Symfony\Component\Form\FormError;
 use Symfony\Component\Form\FormFactory;
 use Symfony\Component\Form\FormInterface;
@@ -71,7 +70,7 @@ public function testBuildPreliminaryFormTree()
             ],
             'errors' => [],
             'children' => [],
-         ];
+        ];
 
         $formData = [
             'id' => 'name',
@@ -87,11 +86,11 @@ public function testBuildPreliminaryFormTree()
                 'norm' => null,
             ],
             'errors' => [],
-             'has_children_error' => false,
-             'children' => [
-                 'child' => $childFormData,
-             ],
-         ];
+            'has_children_error' => false,
+            'children' => [
+                'child' => $childFormData,
+            ],
+        ];
 
         $this->assertEquals([
             'forms' => [
@@ -102,7 +101,7 @@ public function testBuildPreliminaryFormTree()
                 spl_object_hash($this->childForm) => $childFormData,
             ],
             'nb_errors' => 0,
-         ], $this->dataCollector->getData());
+        ], $this->dataCollector->getData());
     }
 
     public function testBuildMultiplePreliminaryFormTrees()
diff --git a/Tests/Extension/DataCollector/FormDataExtractorTest.php b/Tests/Extension/DataCollector/FormDataExtractorTest.php
index b8a1fee37..29f9359df 100644
--- a/Tests/Extension/DataCollector/FormDataExtractorTest.php
+++ b/Tests/Extension/DataCollector/FormDataExtractorTest.php
@@ -362,8 +362,7 @@ public function testExtractSubmittedDataStoresErrorCause()
                 ]
                 EODUMP;
         }
-        $this->assertDumpMatchesFormat($expectedFormat
-            ,
+        $this->assertDumpMatchesFormat($expectedFormat,
             $this->dataExtractor->extractSubmittedData($form)
         );
     }
diff --git a/Tests/Extension/DependencyInjection/DependencyInjectionExtensionTest.php b/Tests/Extension/DependencyInjection/DependencyInjectionExtensionTest.php
index b99240c8c..8f2cbdcd5 100644
--- a/Tests/Extension/DependencyInjection/DependencyInjectionExtensionTest.php
+++ b/Tests/Extension/DependencyInjection/DependencyInjectionExtensionTest.php
@@ -44,7 +44,7 @@ public function testGetTypeExtensions()
     public function testThrowExceptionForInvalidExtendedType()
     {
         $this->expectException(InvalidArgumentException::class);
-        $this->expectExceptionMessage(sprintf('The extended type "unmatched" specified for the type extension class "%s" does not match any of the actual extended types (["test"]).', TestTypeExtension::class));
+        $this->expectExceptionMessage(\sprintf('The extended type "unmatched" specified for the type extension class "%s" does not match any of the actual extended types (["test"]).', TestTypeExtension::class));
 
         $extensions = [
             'unmatched' => new \ArrayIterator([new TestTypeExtension()]),
diff --git a/Tests/Extension/HtmlSanitizer/Type/TextTypeHtmlSanitizerExtensionTest.php b/Tests/Extension/HtmlSanitizer/Type/TextTypeHtmlSanitizerExtensionTest.php
index 39b8d0332..6784576b0 100644
--- a/Tests/Extension/HtmlSanitizer/Type/TextTypeHtmlSanitizerExtensionTest.php
+++ b/Tests/Extension/HtmlSanitizer/Type/TextTypeHtmlSanitizerExtensionTest.php
@@ -20,7 +20,7 @@
 
 class TextTypeHtmlSanitizerExtensionTest extends TypeTestCase
 {
-    protected function getExtensions()
+    protected function getExtensions(): array
     {
         $fooSanitizer = $this->createMock(HtmlSanitizerInterface::class);
         $fooSanitizer->expects($this->once())
diff --git a/Tests/Extension/PasswordHasher/Type/PasswordTypePasswordHasherExtensionTest.php b/Tests/Extension/PasswordHasher/Type/PasswordTypePasswordHasherExtensionTest.php
index 4ec91c827..07d1292a3 100644
--- a/Tests/Extension/PasswordHasher/Type/PasswordTypePasswordHasherExtensionTest.php
+++ b/Tests/Extension/PasswordHasher/Type/PasswordTypePasswordHasherExtensionTest.php
@@ -40,7 +40,7 @@ protected function setUp(): void
         parent::setUp();
     }
 
-    protected function getExtensions()
+    protected function getExtensions(): array
     {
         return array_merge(parent::getExtensions(), [
             new PasswordHasherExtension(new PasswordHasherListener($this->passwordHasher)),
diff --git a/Tests/Extension/Validator/Constraints/FormValidatorFunctionalTest.php b/Tests/Extension/Validator/Constraints/FormValidatorFunctionalTest.php
index aa6056c13..14595e8cf 100644
--- a/Tests/Extension/Validator/Constraints/FormValidatorFunctionalTest.php
+++ b/Tests/Extension/Validator/Constraints/FormValidatorFunctionalTest.php
@@ -88,9 +88,9 @@ public function testFieldConstraintsInvalidateFormIfFieldIsSubmitted()
     public function testNonCompositeConstraintValidatedOnce()
     {
         $form = $this->formFactory->create(TextType::class, null, [
-                'constraints' => [new NotBlank(['groups' => ['foo', 'bar']])],
-                'validation_groups' => ['foo', 'bar'],
-            ]);
+            'constraints' => [new NotBlank(['groups' => ['foo', 'bar']])],
+            'validation_groups' => ['foo', 'bar'],
+        ]);
         $form->submit('');
 
         $violations = $this->validator->validate($form);
diff --git a/Tests/Extension/Validator/Constraints/FormValidatorPerformanceTest.php b/Tests/Extension/Validator/Constraints/FormValidatorPerformanceTest.php
index e8bfbc64a..b0c7d719a 100644
--- a/Tests/Extension/Validator/Constraints/FormValidatorPerformanceTest.php
+++ b/Tests/Extension/Validator/Constraints/FormValidatorPerformanceTest.php
@@ -20,7 +20,7 @@
  */
 class FormValidatorPerformanceTest extends FormPerformanceTestCase
 {
-    protected function getExtensions()
+    protected function getExtensions(): array
     {
         return [
             new ValidatorExtension(Validation::createValidator(), false),
diff --git a/Tests/Extension/Validator/Constraints/FormValidatorTest.php b/Tests/Extension/Validator/Constraints/FormValidatorTest.php
index 4e1588a9c..86b53ac3a 100644
--- a/Tests/Extension/Validator/Constraints/FormValidatorTest.php
+++ b/Tests/Extension/Validator/Constraints/FormValidatorTest.php
@@ -184,8 +184,8 @@ public function testDontValidateIfNoValidationGroups()
         $object = new \stdClass();
 
         $form = $this->getBuilder('name', '\stdClass', [
-                'validation_groups' => [],
-            ])
+            'validation_groups' => [],
+        ])
             ->setData($object)
             ->setCompound(true)
             ->setDataMapper(new DataMapper())
@@ -256,12 +256,12 @@ public function testDontValidateIfNotSynchronized()
         $object = new \stdClass();
 
         $form = $this->getBuilder('name', '\stdClass', [
-                'invalid_message' => 'invalid_message_key',
-                // Invalid message parameters must be supported, because the
-                // invalid message can be a translation key
-                // see https://github.com/symfony/symfony/issues/5144
-                'invalid_message_parameters' => ['{{ foo }}' => 'bar'],
-            ])
+            'invalid_message' => 'invalid_message_key',
+            // Invalid message parameters must be supported, because the
+            // invalid message can be a translation key
+            // see https://github.com/symfony/symfony/issues/5144
+            'invalid_message_parameters' => ['{{ foo }}' => 'bar'],
+        ])
             ->setData($object)
             ->addViewTransformer(new CallbackTransformer(
                 static fn ($data) => $data,
@@ -292,13 +292,13 @@ public function testAddInvalidErrorEvenIfNoValidationGroups()
         $object = new \stdClass();
 
         $form = $this->getBuilder('name', '\stdClass', [
-                'invalid_message' => 'invalid_message_key',
-                // Invalid message parameters must be supported, because the
-                // invalid message can be a translation key
-                // see https://github.com/symfony/symfony/issues/5144
-                'invalid_message_parameters' => ['{{ foo }}' => 'bar'],
-                'validation_groups' => [],
-            ])
+            'invalid_message' => 'invalid_message_key',
+            // Invalid message parameters must be supported, because the
+            // invalid message can be a translation key
+            // see https://github.com/symfony/symfony/issues/5144
+            'invalid_message_parameters' => ['{{ foo }}' => 'bar'],
+            'validation_groups' => [],
+        ])
             ->setData($object)
             ->addViewTransformer(new CallbackTransformer(
                 static fn ($data) => $data,
diff --git a/Tests/Fixtures/Descriptor/resolved_form_type_1.json b/Tests/Fixtures/Descriptor/resolved_form_type_1.json
index e071ec712..5590018c0 100644
--- a/Tests/Fixtures/Descriptor/resolved_form_type_1.json
+++ b/Tests/Fixtures/Descriptor/resolved_form_type_1.json
@@ -6,6 +6,7 @@
             "choice_attr",
             "choice_filter",
             "choice_label",
+            "choice_lazy",
             "choice_loader",
             "choice_name",
             "choice_translation_domain",
diff --git a/Tests/Fixtures/Descriptor/resolved_form_type_1.txt b/Tests/Fixtures/Descriptor/resolved_form_type_1.txt
index 005bfd3e9..93c6b66d9 100644
--- a/Tests/Fixtures/Descriptor/resolved_form_type_1.txt
+++ b/Tests/Fixtures/Descriptor/resolved_form_type_1.txt
@@ -8,22 +8,22 @@ Symfony\Component\Form\Extension\Core\Type\ChoiceType (Block prefix: "choice")
   choice_attr                     FormType             FormType                       FormTypeCsrfExtension  
   choice_filter                  -------------------- ------------------------------ ----------------------- 
   choice_label                    compound             action                         csrf_field_name        
-  choice_loader                   data_class           allow_file_upload              csrf_message           
-  choice_name                     empty_data           attr                           csrf_protection        
-  choice_translation_domain       error_bubbling       attr_translation_parameters    csrf_token_id          
-  choice_translation_parameters   invalid_message      auto_initialize                csrf_token_manager     
-  choice_value                    trim                 block_name                                            
-  choices                                              block_prefix                                          
-  duplicate_preferred_choices                          by_reference                                          
-  expanded                                             data                                                  
-  group_by                                             disabled                                              
-  multiple                                             form_attr                                             
-  placeholder                                          getter                                                
-  placeholder_attr                                     help                                                  
-  preferred_choices                                    help_attr                                             
-  separator                                            help_html                                             
-  separator_html                                       help_translation_parameters                           
-                                                       inherit_data                                          
+  choice_lazy                     data_class           allow_file_upload              csrf_message           
+  choice_loader                   empty_data           attr                           csrf_protection        
+  choice_name                     error_bubbling       attr_translation_parameters    csrf_token_id          
+  choice_translation_domain       invalid_message      auto_initialize                csrf_token_manager     
+  choice_translation_parameters   trim                 block_name                                            
+  choice_value                                         block_prefix                                          
+  choices                                              by_reference                                          
+  duplicate_preferred_choices                          data                                                  
+  expanded                                             disabled                                              
+  group_by                                             form_attr                                             
+  multiple                                             getter                                                
+  placeholder                                          help                                                  
+  placeholder_attr                                     help_attr                                             
+  preferred_choices                                    help_html                                             
+  separator                                            help_translation_parameters                           
+  separator_html                                       inherit_data                                          
                                                        invalid_message_parameters                            
                                                        is_empty_callback                                     
                                                        label                                                 
diff --git a/Tests/NativeRequestHandlerTest.php b/Tests/NativeRequestHandlerTest.php
index 679c3366d..6ff64bc65 100644
--- a/Tests/NativeRequestHandlerTest.php
+++ b/Tests/NativeRequestHandlerTest.php
@@ -41,8 +41,6 @@ protected function setUp(): void
 
     protected function tearDown(): void
     {
-        parent::tearDown();
-
         $_GET = [];
         $_POST = [];
         $_FILES = [];
@@ -172,8 +170,8 @@ public function testMethodOverrideHeaderIgnoredIfNotPost()
         $form = $this->createForm('param1', 'POST');
 
         $this->setRequestData('GET', [
-                'param1' => 'DATA',
-            ]);
+            'param1' => 'DATA',
+        ]);
 
         $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] = 'PUT';
 
diff --git a/Tests/ResolvedFormTypeTest.php b/Tests/ResolvedFormTypeTest.php
index ba0bf243d..fa28f7547 100644
--- a/Tests/ResolvedFormTypeTest.php
+++ b/Tests/ResolvedFormTypeTest.php
@@ -100,7 +100,7 @@ public function testCreateBuilderWithDataClassOption()
     public function testFailsCreateBuilderOnInvalidFormOptionsResolution()
     {
         $this->expectException(MissingOptionsException::class);
-        $this->expectExceptionMessage(sprintf('An error has occurred resolving the options of the form "%s": The required option "foo" is missing.', UsageTrackingFormType::class));
+        $this->expectExceptionMessage(\sprintf('An error has occurred resolving the options of the form "%s": The required option "foo" is missing.', UsageTrackingFormType::class));
 
         $this->resolvedType->createBuilder($this->formFactory, 'name');
     }
diff --git a/Tests/Resources/TranslationFilesTest.php b/Tests/Resources/TranslationFilesTest.php
index 3b2fe40f4..157335dc6 100644
--- a/Tests/Resources/TranslationFilesTest.php
+++ b/Tests/Resources/TranslationFilesTest.php
@@ -26,7 +26,7 @@ public function testTranslationFileIsValid($filePath)
 
         $errors = XliffUtils::validateSchema($document);
 
-        $this->assertCount(0, $errors, sprintf('"%s" is invalid:%s', $filePath, \PHP_EOL.implode(\PHP_EOL, array_column($errors, 'message'))));
+        $this->assertCount(0, $errors, \sprintf('"%s" is invalid:%s', $filePath, \PHP_EOL.implode(\PHP_EOL, array_column($errors, 'message'))));
     }
 
     /**
@@ -39,7 +39,7 @@ public function testTranslationFileIsValidWithoutEntityLoader($filePath)
 
         $errors = XliffUtils::validateSchema($document);
 
-        $this->assertCount(0, $errors, sprintf('"%s" is invalid:%s', $filePath, \PHP_EOL.implode(\PHP_EOL, array_column($errors, 'message'))));
+        $this->assertCount(0, $errors, \sprintf('"%s" is invalid:%s', $filePath, \PHP_EOL.implode(\PHP_EOL, array_column($errors, 'message'))));
     }
 
     public static function provideTranslationFiles()
diff --git a/Tests/SimpleFormTest.php b/Tests/SimpleFormTest.php
index 7ded9b853..d5d3549d2 100644
--- a/Tests/SimpleFormTest.php
+++ b/Tests/SimpleFormTest.php
@@ -503,9 +503,9 @@ public function testSetDataConvertsScalarToStringIfOnlyModelTransformer()
     {
         $form = $this->getBuilder()
             ->addModelTransformer(new FixedDataTransformer([
-            '' => '',
-            1 => 23,
-        ]))
+                '' => '',
+                1 => 23,
+            ]))
             ->getForm();
 
         $form->setData(1);
diff --git a/Tests/Util/StringUtilTest.php b/Tests/Util/StringUtilTest.php
index 8199d6843..d51481f6c 100644
--- a/Tests/Util/StringUtilTest.php
+++ b/Tests/Util/StringUtilTest.php
@@ -56,7 +56,7 @@ public static function spaceProvider(): array
             ['0020'],
             ['00A0'],
             ['1680'],
-//            ['180E'],
+            // ['180E'],
             ['2000'],
             ['2001'],
             ['2002'],
diff --git a/Tests/VersionAwareTest.php b/Tests/VersionAwareTest.php
deleted file mode 100644
index 1a35b72fc..000000000
--- a/Tests/VersionAwareTest.php
+++ /dev/null
@@ -1,24 +0,0 @@
-<?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\Form\Tests;
-
-trait VersionAwareTest
-{
-    protected static int $supportedFeatureSetVersion = 404;
-
-    protected function requiresFeatureSet(int $requiredFeatureSetVersion)
-    {
-        if ($requiredFeatureSetVersion > static::$supportedFeatureSetVersion) {
-            $this->markTestSkipped(sprintf('Test requires features from symfony/form %.2f but only version %.2f is supported.', $requiredFeatureSetVersion / 100, static::$supportedFeatureSetVersion / 100));
-        }
-    }
-}
diff --git a/Tests/VersionAwareTestTrait.php b/Tests/VersionAwareTestTrait.php
new file mode 100644
index 000000000..62e98934e
--- /dev/null
+++ b/Tests/VersionAwareTestTrait.php
@@ -0,0 +1,32 @@
+<?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\Form\Tests;
+
+/**
+ * @deprecated since Symfony 7.2, use feature detection instead.
+ */
+trait VersionAwareTestTrait
+{
+    protected static int $supportedFeatureSetVersion = 404;
+
+    /**
+     * @deprecated since Symfony 7.2, use feature detection instead.
+     */
+    protected function requiresFeatureSet(int $requiredFeatureSetVersion)
+    {
+        trigger_deprecation('symfony/form', '7.2', 'The "%s" trait is deprecated, use feature detection instead.', VersionAwareTestTrait::class);
+
+        if ($requiredFeatureSetVersion > static::$supportedFeatureSetVersion) {
+            $this->markTestSkipped(\sprintf('Test requires features from symfony/form %.2f but only version %.2f is supported.', $requiredFeatureSetVersion / 100, static::$supportedFeatureSetVersion / 100));
+        }
+    }
+}
diff --git a/Util/OrderedHashMap.php b/Util/OrderedHashMap.php
index 145bd2e25..7f81c0739 100644
--- a/Util/OrderedHashMap.php
+++ b/Util/OrderedHashMap.php
@@ -105,7 +105,7 @@ public function offsetExists(mixed $key): bool
     public function offsetGet(mixed $key): mixed
     {
         if (!isset($this->elements[$key])) {
-            throw new \OutOfBoundsException(sprintf('The offset "%s" does not exist.', $key));
+            throw new \OutOfBoundsException(\sprintf('The offset "%s" does not exist.', $key));
         }
 
         return $this->elements[$key];