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];