├── AbstractExtension.php ├── AbstractRendererEngine.php ├── AbstractType.php ├── AbstractTypeExtension.php ├── Button.php ├── ButtonBuilder.php ├── ButtonTypeInterface.php ├── CHANGELOG.md ├── CallbackTransformer.php ├── ChoiceList ├── ArrayChoiceList.php ├── ChoiceList.php ├── ChoiceListInterface.php ├── Factory │ ├── Cache │ │ ├── AbstractStaticOption.php │ │ ├── ChoiceAttr.php │ │ ├── ChoiceFieldName.php │ │ ├── ChoiceFilter.php │ │ ├── ChoiceLabel.php │ │ ├── ChoiceLoader.php │ │ ├── ChoiceTranslationParameters.php │ │ ├── ChoiceValue.php │ │ ├── GroupBy.php │ │ └── PreferredChoice.php │ ├── CachingFactoryDecorator.php │ ├── ChoiceListFactoryInterface.php │ ├── DefaultChoiceListFactory.php │ └── PropertyAccessDecorator.php ├── LazyChoiceList.php ├── Loader │ ├── AbstractChoiceLoader.php │ ├── CallbackChoiceLoader.php │ ├── ChoiceLoaderInterface.php │ ├── FilterChoiceLoaderDecorator.php │ ├── IntlCallbackChoiceLoader.php │ └── LazyChoiceLoader.php └── View │ ├── ChoiceGroupView.php │ ├── ChoiceListView.php │ └── ChoiceView.php ├── ClearableErrorsInterface.php ├── ClickableInterface.php ├── Command └── DebugCommand.php ├── Console ├── Descriptor │ ├── Descriptor.php │ ├── JsonDescriptor.php │ └── TextDescriptor.php └── Helper │ └── DescriptorHelper.php ├── DataAccessorInterface.php ├── DataMapperInterface.php ├── DataTransformerInterface.php ├── DependencyInjection └── FormPass.php ├── Event ├── PostSetDataEvent.php ├── PostSubmitEvent.php ├── PreSetDataEvent.php ├── PreSubmitEvent.php └── SubmitEvent.php ├── Exception ├── AccessException.php ├── AlreadySubmittedException.php ├── BadMethodCallException.php ├── ErrorMappingException.php ├── ExceptionInterface.php ├── InvalidArgumentException.php ├── InvalidConfigurationException.php ├── LogicException.php ├── OutOfBoundsException.php ├── RuntimeException.php ├── StringCastException.php ├── TransformationFailedException.php └── UnexpectedTypeException.php ├── Extension ├── Core │ ├── CoreExtension.php │ ├── DataAccessor │ │ ├── CallbackAccessor.php │ │ ├── ChainAccessor.php │ │ └── PropertyPathAccessor.php │ ├── DataMapper │ │ ├── CheckboxListMapper.php │ │ ├── DataMapper.php │ │ └── RadioListMapper.php │ ├── DataTransformer │ │ ├── ArrayToPartsTransformer.php │ │ ├── BaseDateTimeTransformer.php │ │ ├── BooleanToStringTransformer.php │ │ ├── ChoiceToValueTransformer.php │ │ ├── ChoicesToValuesTransformer.php │ │ ├── DataTransformerChain.php │ │ ├── DateIntervalToArrayTransformer.php │ │ ├── DateIntervalToStringTransformer.php │ │ ├── DateTimeImmutableToDateTimeTransformer.php │ │ ├── DateTimeToArrayTransformer.php │ │ ├── DateTimeToHtml5LocalDateTimeTransformer.php │ │ ├── DateTimeToLocalizedStringTransformer.php │ │ ├── DateTimeToRfc3339Transformer.php │ │ ├── DateTimeToStringTransformer.php │ │ ├── DateTimeToTimestampTransformer.php │ │ ├── DateTimeZoneToStringTransformer.php │ │ ├── IntegerToLocalizedStringTransformer.php │ │ ├── IntlTimeZoneToStringTransformer.php │ │ ├── MoneyToLocalizedStringTransformer.php │ │ ├── NumberToLocalizedStringTransformer.php │ │ ├── PercentToLocalizedStringTransformer.php │ │ ├── StringToFloatTransformer.php │ │ ├── UlidToStringTransformer.php │ │ ├── UuidToStringTransformer.php │ │ ├── ValueToDuplicatesTransformer.php │ │ └── WeekToArrayTransformer.php │ ├── EventListener │ │ ├── FixUrlProtocolListener.php │ │ ├── MergeCollectionListener.php │ │ ├── ResizeFormListener.php │ │ ├── TransformationFailureListener.php │ │ └── TrimListener.php │ └── Type │ │ ├── BaseType.php │ │ ├── BirthdayType.php │ │ ├── ButtonType.php │ │ ├── CheckboxType.php │ │ ├── ChoiceType.php │ │ ├── CollectionType.php │ │ ├── ColorType.php │ │ ├── CountryType.php │ │ ├── CurrencyType.php │ │ ├── DateIntervalType.php │ │ ├── DateTimeType.php │ │ ├── DateType.php │ │ ├── EmailType.php │ │ ├── EnumType.php │ │ ├── FileType.php │ │ ├── FormType.php │ │ ├── HiddenType.php │ │ ├── IntegerType.php │ │ ├── LanguageType.php │ │ ├── LocaleType.php │ │ ├── MoneyType.php │ │ ├── NumberType.php │ │ ├── PasswordType.php │ │ ├── PercentType.php │ │ ├── RadioType.php │ │ ├── RangeType.php │ │ ├── RepeatedType.php │ │ ├── ResetType.php │ │ ├── SearchType.php │ │ ├── SubmitType.php │ │ ├── TelType.php │ │ ├── TextType.php │ │ ├── TextareaType.php │ │ ├── TimeType.php │ │ ├── TimezoneType.php │ │ ├── TransformationFailureExtension.php │ │ ├── UlidType.php │ │ ├── UrlType.php │ │ ├── UuidType.php │ │ └── WeekType.php ├── Csrf │ ├── CsrfExtension.php │ ├── EventListener │ │ └── CsrfValidationListener.php │ └── Type │ │ └── FormTypeCsrfExtension.php ├── DataCollector │ ├── DataCollectorExtension.php │ ├── EventListener │ │ └── DataCollectorListener.php │ ├── FormDataCollector.php │ ├── FormDataCollectorInterface.php │ ├── FormDataExtractor.php │ ├── FormDataExtractorInterface.php │ ├── Proxy │ │ ├── ResolvedTypeDataCollectorProxy.php │ │ └── ResolvedTypeFactoryDataCollectorProxy.php │ └── Type │ │ └── DataCollectorTypeExtension.php ├── DependencyInjection │ └── DependencyInjectionExtension.php ├── HtmlSanitizer │ ├── HtmlSanitizerExtension.php │ └── Type │ │ └── TextTypeHtmlSanitizerExtension.php ├── HttpFoundation │ ├── HttpFoundationExtension.php │ ├── HttpFoundationRequestHandler.php │ └── Type │ │ └── FormTypeHttpFoundationExtension.php ├── PasswordHasher │ ├── EventListener │ │ └── PasswordHasherListener.php │ ├── PasswordHasherExtension.php │ └── Type │ │ ├── FormTypePasswordHasherExtension.php │ │ └── PasswordTypePasswordHasherExtension.php └── Validator │ ├── Constraints │ ├── Form.php │ └── FormValidator.php │ ├── EventListener │ └── ValidationListener.php │ ├── Type │ ├── BaseValidatorExtension.php │ ├── FormTypeValidatorExtension.php │ ├── RepeatedTypeValidatorExtension.php │ ├── SubmitTypeValidatorExtension.php │ └── UploadValidatorExtension.php │ ├── ValidatorExtension.php │ ├── ValidatorTypeGuesser.php │ └── ViolationMapper │ ├── MappingRule.php │ ├── RelativePath.php │ ├── ViolationMapper.php │ ├── ViolationMapperInterface.php │ ├── ViolationPath.php │ └── ViolationPathIterator.php ├── FileUploadError.php ├── Form.php ├── FormBuilder.php ├── FormBuilderInterface.php ├── FormConfigBuilder.php ├── FormConfigBuilderInterface.php ├── FormConfigInterface.php ├── FormError.php ├── FormErrorIterator.php ├── FormEvent.php ├── FormEvents.php ├── FormExtensionInterface.php ├── FormFactory.php ├── FormFactoryBuilder.php ├── FormFactoryBuilderInterface.php ├── FormFactoryInterface.php ├── FormInterface.php ├── FormRegistry.php ├── FormRegistryInterface.php ├── FormRenderer.php ├── FormRendererEngineInterface.php ├── FormRendererInterface.php ├── FormTypeExtensionInterface.php ├── FormTypeGuesserChain.php ├── FormTypeGuesserInterface.php ├── FormTypeInterface.php ├── FormView.php ├── Forms.php ├── Guess ├── Guess.php ├── TypeGuess.php └── ValueGuess.php ├── LICENSE ├── NativeRequestHandler.php ├── PreloadedExtension.php ├── README.md ├── RequestHandlerInterface.php ├── ResolvedFormType.php ├── ResolvedFormTypeFactory.php ├── ResolvedFormTypeFactoryInterface.php ├── ResolvedFormTypeInterface.php ├── Resources ├── config │ └── validation.xml └── translations │ ├── validators.af.xlf │ ├── validators.ar.xlf │ ├── validators.az.xlf │ ├── validators.be.xlf │ ├── validators.bg.xlf │ ├── validators.bs.xlf │ ├── validators.ca.xlf │ ├── validators.cs.xlf │ ├── validators.cy.xlf │ ├── validators.da.xlf │ ├── validators.de.xlf │ ├── validators.el.xlf │ ├── validators.en.xlf │ ├── validators.es.xlf │ ├── validators.et.xlf │ ├── validators.eu.xlf │ ├── validators.fa.xlf │ ├── validators.fi.xlf │ ├── validators.fr.xlf │ ├── validators.gl.xlf │ ├── validators.he.xlf │ ├── validators.hr.xlf │ ├── validators.hu.xlf │ ├── validators.hy.xlf │ ├── validators.id.xlf │ ├── validators.it.xlf │ ├── validators.ja.xlf │ ├── validators.lb.xlf │ ├── validators.lt.xlf │ ├── validators.lv.xlf │ ├── validators.mk.xlf │ ├── validators.mn.xlf │ ├── validators.my.xlf │ ├── validators.nb.xlf │ ├── validators.nl.xlf │ ├── validators.nn.xlf │ ├── validators.no.xlf │ ├── validators.pl.xlf │ ├── validators.pt.xlf │ ├── validators.pt_BR.xlf │ ├── validators.ro.xlf │ ├── validators.ru.xlf │ ├── validators.sk.xlf │ ├── validators.sl.xlf │ ├── validators.sq.xlf │ ├── validators.sr_Cyrl.xlf │ ├── validators.sr_Latn.xlf │ ├── validators.sv.xlf │ ├── validators.th.xlf │ ├── validators.tl.xlf │ ├── validators.tr.xlf │ ├── validators.uk.xlf │ ├── validators.ur.xlf │ ├── validators.uz.xlf │ ├── validators.vi.xlf │ ├── validators.zh_CN.xlf │ └── validators.zh_TW.xlf ├── ReversedTransformer.php ├── SubmitButton.php ├── SubmitButtonBuilder.php ├── SubmitButtonTypeInterface.php ├── Test ├── FormBuilderInterface.php ├── FormIntegrationTestCase.php ├── FormInterface.php ├── FormPerformanceTestCase.php ├── Traits │ └── ValidatorExtensionTrait.php └── TypeTestCase.php ├── Util ├── FormUtil.php ├── InheritDataAwareIterator.php ├── OptionsResolverWrapper.php ├── OrderedHashMap.php ├── OrderedHashMapIterator.php ├── ServerParams.php └── StringUtil.php └── composer.json /AbstractType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form; 13 | 14 | use Symfony\Component\Form\Extension\Core\Type\FormType; 15 | use Symfony\Component\Form\Util\StringUtil; 16 | use Symfony\Component\OptionsResolver\OptionsResolver; 17 | 18 | /** 19 | * @author Bernhard Schussek 20 | */ 21 | abstract class AbstractType implements FormTypeInterface 22 | { 23 | /** 24 | * @return string|null 25 | */ 26 | public function getParent() 27 | { 28 | return FormType::class; 29 | } 30 | 31 | /** 32 | * @return void 33 | */ 34 | public function configureOptions(OptionsResolver $resolver) 35 | { 36 | } 37 | 38 | /** 39 | * @return void 40 | */ 41 | public function buildForm(FormBuilderInterface $builder, array $options) 42 | { 43 | } 44 | 45 | /** 46 | * @return void 47 | */ 48 | public function buildView(FormView $view, FormInterface $form, array $options) 49 | { 50 | } 51 | 52 | /** 53 | * @return void 54 | */ 55 | public function finishView(FormView $view, FormInterface $form, array $options) 56 | { 57 | } 58 | 59 | /** 60 | * @return string 61 | */ 62 | public function getBlockPrefix() 63 | { 64 | return StringUtil::fqcnToBlockPrefix(static::class) ?: ''; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /AbstractTypeExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form; 13 | 14 | use Symfony\Component\OptionsResolver\OptionsResolver; 15 | 16 | /** 17 | * @author Bernhard Schussek 18 | */ 19 | abstract class AbstractTypeExtension implements FormTypeExtensionInterface 20 | { 21 | public function configureOptions(OptionsResolver $resolver): void 22 | { 23 | } 24 | 25 | public function buildForm(FormBuilderInterface $builder, array $options): void 26 | { 27 | } 28 | 29 | public function buildView(FormView $view, FormInterface $form, array $options): void 30 | { 31 | } 32 | 33 | public function finishView(FormView $view, FormInterface $form, array $options): void 34 | { 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ButtonTypeInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form; 13 | 14 | /** 15 | * A type that should be converted into a {@link Button} instance. 16 | * 17 | * @author Bernhard Schussek 18 | */ 19 | interface ButtonTypeInterface extends FormTypeInterface 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /CallbackTransformer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form; 13 | 14 | class CallbackTransformer implements DataTransformerInterface 15 | { 16 | private \Closure $transform; 17 | private \Closure $reverseTransform; 18 | 19 | public function __construct(callable $transform, callable $reverseTransform) 20 | { 21 | $this->transform = $transform(...); 22 | $this->reverseTransform = $reverseTransform(...); 23 | } 24 | 25 | public function transform(mixed $data): mixed 26 | { 27 | return ($this->transform)($data); 28 | } 29 | 30 | public function reverseTransform(mixed $data): mixed 31 | { 32 | return ($this->reverseTransform)($data); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ChoiceList/Factory/Cache/AbstractStaticOption.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\ChoiceList\Factory\Cache; 13 | 14 | use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator; 15 | use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface; 16 | use Symfony\Component\Form\Extension\Core\Type\ChoiceType; 17 | use Symfony\Component\Form\FormTypeExtensionInterface; 18 | use Symfony\Component\Form\FormTypeInterface; 19 | 20 | /** 21 | * A template decorator for static {@see ChoiceType} options. 22 | * 23 | * Used as fly weight for {@see CachingFactoryDecorator}. 24 | * 25 | * @internal 26 | * 27 | * @author Jules Pietri 28 | */ 29 | abstract class AbstractStaticOption 30 | { 31 | private static array $options = []; 32 | 33 | private bool|string|array|\Closure|ChoiceLoaderInterface $option; 34 | 35 | /** 36 | * @param mixed $option Any pseudo callable, array, string or bool to define a choice list option 37 | * @param mixed $vary Dynamic data used to compute a unique hash when caching the option 38 | */ 39 | final public function __construct(FormTypeInterface|FormTypeExtensionInterface $formType, mixed $option, mixed $vary = null) 40 | { 41 | $hash = CachingFactoryDecorator::generateHash([static::class, $formType, $vary]); 42 | 43 | $this->option = self::$options[$hash] ??= $option instanceof \Closure || \is_string($option) || \is_bool($option) || $option instanceof ChoiceLoaderInterface || !\is_callable($option) ? $option : $option(...); 44 | } 45 | 46 | final public function getOption(): mixed 47 | { 48 | return $this->option; 49 | } 50 | 51 | final public static function reset(): void 52 | { 53 | self::$options = []; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /ChoiceList/Factory/Cache/ChoiceAttr.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\ChoiceList\Factory\Cache; 13 | 14 | use Symfony\Component\Form\FormTypeExtensionInterface; 15 | use Symfony\Component\Form\FormTypeInterface; 16 | 17 | /** 18 | * A cacheable wrapper for any {@see FormTypeInterface} or {@see FormTypeExtensionInterface} 19 | * which configures a "choice_attr" option. 20 | * 21 | * @internal 22 | * 23 | * @author Jules Pietri 24 | */ 25 | final class ChoiceAttr extends AbstractStaticOption 26 | { 27 | } 28 | -------------------------------------------------------------------------------- /ChoiceList/Factory/Cache/ChoiceFieldName.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\ChoiceList\Factory\Cache; 13 | 14 | use Symfony\Component\Form\FormTypeExtensionInterface; 15 | use Symfony\Component\Form\FormTypeInterface; 16 | 17 | /** 18 | * A cacheable wrapper for any {@see FormTypeInterface} or {@see FormTypeExtensionInterface} 19 | * which configures a "choice_name" callback. 20 | * 21 | * @internal 22 | * 23 | * @author Jules Pietri 24 | */ 25 | final class ChoiceFieldName extends AbstractStaticOption 26 | { 27 | } 28 | -------------------------------------------------------------------------------- /ChoiceList/Factory/Cache/ChoiceFilter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\ChoiceList\Factory\Cache; 13 | 14 | use Symfony\Component\Form\FormTypeExtensionInterface; 15 | use Symfony\Component\Form\FormTypeInterface; 16 | 17 | /** 18 | * A cacheable wrapper for any {@see FormTypeInterface} or {@see FormTypeExtensionInterface} 19 | * which configures a "choice_filter" option. 20 | * 21 | * @internal 22 | * 23 | * @author Jules Pietri 24 | */ 25 | final class ChoiceFilter extends AbstractStaticOption 26 | { 27 | } 28 | -------------------------------------------------------------------------------- /ChoiceList/Factory/Cache/ChoiceLabel.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\ChoiceList\Factory\Cache; 13 | 14 | use Symfony\Component\Form\FormTypeExtensionInterface; 15 | use Symfony\Component\Form\FormTypeInterface; 16 | 17 | /** 18 | * A cacheable wrapper for any {@see FormTypeInterface} or {@see FormTypeExtensionInterface} 19 | * which configures a "choice_label" option. 20 | * 21 | * @internal 22 | * 23 | * @author Jules Pietri 24 | */ 25 | final class ChoiceLabel extends AbstractStaticOption 26 | { 27 | } 28 | -------------------------------------------------------------------------------- /ChoiceList/Factory/Cache/ChoiceLoader.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\ChoiceList\Factory\Cache; 13 | 14 | use Symfony\Component\Form\ChoiceList\ChoiceListInterface; 15 | use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface; 16 | use Symfony\Component\Form\FormTypeExtensionInterface; 17 | use Symfony\Component\Form\FormTypeInterface; 18 | 19 | /** 20 | * A cacheable wrapper for {@see FormTypeInterface} or {@see FormTypeExtensionInterface} 21 | * which configures a "choice_loader" option. 22 | * 23 | * @internal 24 | * 25 | * @author Jules Pietri 26 | */ 27 | final class ChoiceLoader extends AbstractStaticOption implements ChoiceLoaderInterface 28 | { 29 | public function loadChoiceList(?callable $value = null): ChoiceListInterface 30 | { 31 | return $this->getOption()->loadChoiceList($value); 32 | } 33 | 34 | public function loadChoicesForValues(array $values, ?callable $value = null): array 35 | { 36 | return $this->getOption()->loadChoicesForValues($values, $value); 37 | } 38 | 39 | public function loadValuesForChoices(array $choices, ?callable $value = null): array 40 | { 41 | return $this->getOption()->loadValuesForChoices($choices, $value); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ChoiceList/Factory/Cache/ChoiceTranslationParameters.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\ChoiceList\Factory\Cache; 13 | 14 | use Symfony\Component\Form\FormTypeExtensionInterface; 15 | use Symfony\Component\Form\FormTypeInterface; 16 | 17 | /** 18 | * A cacheable wrapper for any {@see FormTypeInterface} or {@see FormTypeExtensionInterface} 19 | * which configures a "choice_translation_parameters" option. 20 | * 21 | * @internal 22 | * 23 | * @author Vincent Langlet 24 | */ 25 | final class ChoiceTranslationParameters extends AbstractStaticOption 26 | { 27 | } 28 | -------------------------------------------------------------------------------- /ChoiceList/Factory/Cache/ChoiceValue.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\ChoiceList\Factory\Cache; 13 | 14 | use Symfony\Component\Form\FormTypeExtensionInterface; 15 | use Symfony\Component\Form\FormTypeInterface; 16 | 17 | /** 18 | * A cacheable wrapper for any {@see FormTypeInterface} or {@see FormTypeExtensionInterface} 19 | * which configures a "choice_value" callback. 20 | * 21 | * @internal 22 | * 23 | * @author Jules Pietri 24 | */ 25 | final class ChoiceValue extends AbstractStaticOption 26 | { 27 | } 28 | -------------------------------------------------------------------------------- /ChoiceList/Factory/Cache/GroupBy.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\ChoiceList\Factory\Cache; 13 | 14 | use Symfony\Component\Form\FormTypeExtensionInterface; 15 | use Symfony\Component\Form\FormTypeInterface; 16 | 17 | /** 18 | * A cacheable wrapper for any {@see FormTypeInterface} or {@see FormTypeExtensionInterface} 19 | * which configures a "group_by" callback. 20 | * 21 | * @internal 22 | * 23 | * @author Jules Pietri 24 | */ 25 | final class GroupBy extends AbstractStaticOption 26 | { 27 | } 28 | -------------------------------------------------------------------------------- /ChoiceList/Factory/Cache/PreferredChoice.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\ChoiceList\Factory\Cache; 13 | 14 | use Symfony\Component\Form\FormTypeExtensionInterface; 15 | use Symfony\Component\Form\FormTypeInterface; 16 | 17 | /** 18 | * A cacheable wrapper for any {@see FormTypeInterface} or {@see FormTypeExtensionInterface} 19 | * which configures a "preferred_choices" option. 20 | * 21 | * @internal 22 | * 23 | * @author Jules Pietri 24 | */ 25 | final class PreferredChoice extends AbstractStaticOption 26 | { 27 | } 28 | -------------------------------------------------------------------------------- /ChoiceList/Loader/AbstractChoiceLoader.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\ChoiceList\Loader; 13 | 14 | use Symfony\Component\Form\ChoiceList\ArrayChoiceList; 15 | use Symfony\Component\Form\ChoiceList\ChoiceListInterface; 16 | 17 | /** 18 | * @author Jules Pietri 19 | */ 20 | abstract class AbstractChoiceLoader implements ChoiceLoaderInterface 21 | { 22 | private ?iterable $choices; 23 | 24 | /** 25 | * @final 26 | */ 27 | public function loadChoiceList(?callable $value = null): ChoiceListInterface 28 | { 29 | return new ArrayChoiceList($this->choices ??= $this->loadChoices(), $value); 30 | } 31 | 32 | public function loadChoicesForValues(array $values, ?callable $value = null): array 33 | { 34 | if (!$values) { 35 | return []; 36 | } 37 | 38 | return $this->doLoadChoicesForValues($values, $value); 39 | } 40 | 41 | public function loadValuesForChoices(array $choices, ?callable $value = null): array 42 | { 43 | if (!$choices) { 44 | return []; 45 | } 46 | 47 | if ($value) { 48 | // if a value callback exists, use it 49 | return array_map(fn ($item) => (string) $value($item), $choices); 50 | } 51 | 52 | return $this->doLoadValuesForChoices($choices); 53 | } 54 | 55 | abstract protected function loadChoices(): iterable; 56 | 57 | protected function doLoadChoicesForValues(array $values, ?callable $value): array 58 | { 59 | return $this->loadChoiceList($value)->getChoicesForValues($values); 60 | } 61 | 62 | protected function doLoadValuesForChoices(array $choices): array 63 | { 64 | return $this->loadChoiceList()->getValuesForChoices($choices); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /ChoiceList/Loader/CallbackChoiceLoader.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\ChoiceList\Loader; 13 | 14 | /** 15 | * Loads an {@link ArrayChoiceList} instance from a callable returning iterable choices. 16 | * 17 | * @author Jules Pietri 18 | */ 19 | class CallbackChoiceLoader extends AbstractChoiceLoader 20 | { 21 | private \Closure $callback; 22 | 23 | /** 24 | * @param callable $callback The callable returning iterable choices 25 | */ 26 | public function __construct(callable $callback) 27 | { 28 | $this->callback = $callback(...); 29 | } 30 | 31 | protected function loadChoices(): iterable 32 | { 33 | return ($this->callback)(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ChoiceList/Loader/FilterChoiceLoaderDecorator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\ChoiceList\Loader; 13 | 14 | /** 15 | * A decorator to filter choices only when they are loaded or partially loaded. 16 | * 17 | * @author Jules Pietri 18 | */ 19 | class FilterChoiceLoaderDecorator extends AbstractChoiceLoader 20 | { 21 | private ChoiceLoaderInterface $decoratedLoader; 22 | private \Closure $filter; 23 | 24 | public function __construct(ChoiceLoaderInterface $loader, callable $filter) 25 | { 26 | $this->decoratedLoader = $loader; 27 | $this->filter = $filter(...); 28 | } 29 | 30 | protected function loadChoices(): iterable 31 | { 32 | $list = $this->decoratedLoader->loadChoiceList(); 33 | 34 | if (array_values($list->getValues()) === array_values($structuredValues = $list->getStructuredValues())) { 35 | return array_filter(array_combine($list->getOriginalKeys(), $list->getChoices()), $this->filter); 36 | } 37 | 38 | foreach ($structuredValues as $group => $values) { 39 | if (\is_array($values)) { 40 | if ($values && $filtered = array_filter($list->getChoicesForValues($values), $this->filter)) { 41 | $choices[$group] = $filtered; 42 | } 43 | continue; 44 | // filter empty groups 45 | } 46 | 47 | if ($filtered = array_filter($list->getChoicesForValues([$values]), $this->filter)) { 48 | $choices[$group] = $filtered[0]; 49 | } 50 | } 51 | 52 | return $choices ?? []; 53 | } 54 | 55 | public function loadChoicesForValues(array $values, ?callable $value = null): array 56 | { 57 | return array_filter($this->decoratedLoader->loadChoicesForValues($values, $value), $this->filter); 58 | } 59 | 60 | public function loadValuesForChoices(array $choices, ?callable $value = null): array 61 | { 62 | return $this->decoratedLoader->loadValuesForChoices(array_filter($choices, $this->filter), $value); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /ChoiceList/Loader/IntlCallbackChoiceLoader.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\ChoiceList\Loader; 13 | 14 | /** 15 | * Callback choice loader optimized for Intl choice types. 16 | * 17 | * @author Jules Pietri 18 | * @author Yonel Ceruto 19 | */ 20 | class IntlCallbackChoiceLoader extends CallbackChoiceLoader 21 | { 22 | public function loadChoicesForValues(array $values, ?callable $value = null): array 23 | { 24 | return parent::loadChoicesForValues(array_filter($values), $value); 25 | } 26 | 27 | public function loadValuesForChoices(array $choices, ?callable $value = null): array 28 | { 29 | $choices = array_filter($choices); 30 | 31 | // If no callable is set, choices are the same as values 32 | if (null === $value) { 33 | return $choices; 34 | } 35 | 36 | return parent::loadValuesForChoices($choices, $value); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ChoiceList/Loader/LazyChoiceLoader.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\ChoiceList\Loader; 13 | 14 | use Symfony\Component\Form\ChoiceList\ArrayChoiceList; 15 | use Symfony\Component\Form\ChoiceList\ChoiceListInterface; 16 | 17 | /** 18 | * A choice loader that loads its choices and values lazily, only when necessary. 19 | * 20 | * @author Yonel Ceruto 21 | */ 22 | class LazyChoiceLoader implements ChoiceLoaderInterface 23 | { 24 | private ?ChoiceListInterface $choiceList = null; 25 | 26 | public function __construct( 27 | private readonly ChoiceLoaderInterface $loader, 28 | ) { 29 | } 30 | 31 | public function loadChoiceList(?callable $value = null): ChoiceListInterface 32 | { 33 | return $this->choiceList ??= new ArrayChoiceList([], $value); 34 | } 35 | 36 | public function loadChoicesForValues(array $values, ?callable $value = null): array 37 | { 38 | $choices = $this->loader->loadChoicesForValues($values, $value); 39 | $this->choiceList = new ArrayChoiceList($choices, $value); 40 | 41 | return $choices; 42 | } 43 | 44 | public function loadValuesForChoices(array $choices, ?callable $value = null): array 45 | { 46 | $values = $this->loader->loadValuesForChoices($choices, $value); 47 | 48 | if ($this->choiceList?->getValuesForChoices($choices) !== $values) { 49 | $this->loadChoicesForValues($values, $value); 50 | } 51 | 52 | return $values; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ChoiceList/View/ChoiceGroupView.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\ChoiceList\View; 13 | 14 | /** 15 | * Represents a group of choices in templates. 16 | * 17 | * @author Bernhard Schussek 18 | * 19 | * @implements \IteratorAggregate 20 | */ 21 | class ChoiceGroupView implements \IteratorAggregate 22 | { 23 | /** 24 | * Creates a new choice group view. 25 | * 26 | * @param array $choices the choice views in the group 27 | */ 28 | public function __construct( 29 | public string $label, 30 | public array $choices = [], 31 | ) { 32 | } 33 | 34 | /** 35 | * @return \Traversable 36 | */ 37 | public function getIterator(): \Traversable 38 | { 39 | return new \ArrayIterator($this->choices); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /ChoiceList/View/ChoiceListView.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\ChoiceList\View; 13 | 14 | /** 15 | * Represents a choice list in templates. 16 | * 17 | * A choice list contains choices and optionally preferred choices which are 18 | * displayed in the very beginning of the list. Both choices and preferred 19 | * choices may be grouped in {@link ChoiceGroupView} instances. 20 | * 21 | * @author Bernhard Schussek 22 | */ 23 | class ChoiceListView 24 | { 25 | /** 26 | * Creates a new choice list view. 27 | * 28 | * @param array $choices The choice views 29 | * @param array $preferredChoices the preferred choice views 30 | */ 31 | public function __construct( 32 | public array $choices = [], 33 | public array $preferredChoices = [], 34 | ) { 35 | } 36 | 37 | /** 38 | * Returns whether a placeholder is in the choices. 39 | * 40 | * A placeholder must be the first child element, not be in a group and have an empty value. 41 | */ 42 | public function hasPlaceholder(): bool 43 | { 44 | if ($this->preferredChoices) { 45 | $firstChoice = reset($this->preferredChoices); 46 | 47 | return $firstChoice instanceof ChoiceView && '' === $firstChoice->value; 48 | } 49 | 50 | $firstChoice = reset($this->choices); 51 | 52 | return $firstChoice instanceof ChoiceView && '' === $firstChoice->value; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ChoiceList/View/ChoiceView.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\ChoiceList\View; 13 | 14 | use Symfony\Contracts\Translation\TranslatableInterface; 15 | 16 | /** 17 | * Represents a choice in templates. 18 | * 19 | * @author Bernhard Schussek 20 | */ 21 | class ChoiceView 22 | { 23 | /** 24 | * Creates a new choice view. 25 | * 26 | * @param mixed $data The original choice 27 | * @param string $value The view representation of the choice 28 | * @param string|TranslatableInterface|false $label The label displayed to humans; pass false to discard the label 29 | * @param array $attr Additional attributes for the HTML tag 30 | * @param array $labelTranslationParameters Additional parameters used to translate the label 31 | */ 32 | public function __construct( 33 | public mixed $data, 34 | public string $value, 35 | public string|TranslatableInterface|false $label, 36 | public array $attr = [], 37 | public array $labelTranslationParameters = [], 38 | ) { 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ClearableErrorsInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form; 13 | 14 | /** 15 | * A form element whose errors can be cleared. 16 | * 17 | * @author Colin O'Dell 18 | */ 19 | interface ClearableErrorsInterface 20 | { 21 | /** 22 | * Removes all the errors of this form. 23 | * 24 | * @param bool $deep Whether to remove errors from child forms as well 25 | * 26 | * @return $this 27 | */ 28 | public function clearErrors(bool $deep = false): static; 29 | } 30 | -------------------------------------------------------------------------------- /ClickableInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form; 13 | 14 | /** 15 | * A clickable form element. 16 | * 17 | * @author Bernhard Schussek 18 | */ 19 | interface ClickableInterface 20 | { 21 | /** 22 | * Returns whether this element was clicked. 23 | */ 24 | public function isClicked(): bool; 25 | } 26 | -------------------------------------------------------------------------------- /Console/Helper/DescriptorHelper.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Console\Helper; 13 | 14 | use Symfony\Component\Console\Helper\DescriptorHelper as BaseDescriptorHelper; 15 | use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; 16 | use Symfony\Component\Form\Console\Descriptor\JsonDescriptor; 17 | use Symfony\Component\Form\Console\Descriptor\TextDescriptor; 18 | 19 | /** 20 | * @author Yonel Ceruto 21 | * 22 | * @internal 23 | */ 24 | class DescriptorHelper extends BaseDescriptorHelper 25 | { 26 | public function __construct(?FileLinkFormatter $fileLinkFormatter = null) 27 | { 28 | $this 29 | ->register('txt', new TextDescriptor($fileLinkFormatter)) 30 | ->register('json', new JsonDescriptor()) 31 | ; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /DataAccessorInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form; 13 | 14 | /** 15 | * Writes and reads values to/from an object or array bound to a form. 16 | * 17 | * @author Yonel Ceruto 18 | */ 19 | interface DataAccessorInterface 20 | { 21 | /** 22 | * Returns the value at the end of the property of the object graph. 23 | * 24 | * @throws Exception\AccessException If unable to read from the given form data 25 | */ 26 | public function getValue(object|array $viewData, FormInterface $form): mixed; 27 | 28 | /** 29 | * Sets the value at the end of the property of the object graph. 30 | * 31 | * @throws Exception\AccessException If unable to write the given value 32 | */ 33 | public function setValue(object|array &$viewData, mixed $value, FormInterface $form): void; 34 | 35 | /** 36 | * Returns whether a value can be read from an object graph. 37 | * 38 | * Whenever this method returns true, {@link getValue()} is guaranteed not 39 | * to throw an exception when called with the same arguments. 40 | */ 41 | public function isReadable(object|array $viewData, FormInterface $form): bool; 42 | 43 | /** 44 | * Returns whether a value can be written at a given object graph. 45 | * 46 | * Whenever this method returns true, {@link setValue()} is guaranteed not 47 | * to throw an exception when called with the same arguments. 48 | */ 49 | public function isWritable(object|array $viewData, FormInterface $form): bool; 50 | } 51 | -------------------------------------------------------------------------------- /DataMapperInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form; 13 | 14 | /** 15 | * @author Bernhard Schussek 16 | */ 17 | interface DataMapperInterface 18 | { 19 | /** 20 | * Maps the view data of a compound form to its children. 21 | * 22 | * The method is responsible for calling {@link FormInterface::setData()} 23 | * on the children of compound forms, defining their underlying model data. 24 | * 25 | * @param mixed $viewData View data of the compound form being initialized 26 | * @param \Traversable $forms A list of {@link FormInterface} instances 27 | * 28 | * @throws Exception\UnexpectedTypeException if the type of the data parameter is not supported 29 | */ 30 | public function mapDataToForms(mixed $viewData, \Traversable $forms): void; 31 | 32 | /** 33 | * Maps the model data of a list of children forms into the view data of their parent. 34 | * 35 | * This is the internal cascade call of FormInterface::submit for compound forms, since they 36 | * cannot be bound to any input nor the request as scalar, but their children may: 37 | * 38 | * $compoundForm->submit($arrayOfChildrenViewData) 39 | * // inside: 40 | * $childForm->submit($childViewData); 41 | * // for each entry, do the same and/or reverse transform 42 | * $this->dataMapper->mapFormsToData($compoundForm, $compoundInitialViewData) 43 | * // then reverse transform 44 | * 45 | * When a simple form is submitted the following is happening: 46 | * 47 | * $simpleForm->submit($submittedViewData) 48 | * // inside: 49 | * $this->viewData = $submittedViewData 50 | * // then reverse transform 51 | * 52 | * The model data can be an array or an object, so this second argument is always passed 53 | * by reference. 54 | * 55 | * @param \Traversable $forms A list of {@link FormInterface} instances 56 | * @param mixed &$viewData The compound form's view data that get mapped 57 | * its children model data 58 | * 59 | * @throws Exception\UnexpectedTypeException if the type of the data parameter is not supported 60 | */ 61 | public function mapFormsToData(\Traversable $forms, mixed &$viewData): void; 62 | } 63 | -------------------------------------------------------------------------------- /Event/PostSetDataEvent.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Event; 13 | 14 | use Symfony\Component\Form\Exception\BadMethodCallException; 15 | use Symfony\Component\Form\FormEvent; 16 | 17 | /** 18 | * This event is dispatched at the end of the Form::setData() method. 19 | * 20 | * It can be used to modify a form depending on the populated data (adding or 21 | * removing fields dynamically). 22 | */ 23 | final class PostSetDataEvent extends FormEvent 24 | { 25 | public function setData(mixed $data): never 26 | { 27 | throw new BadMethodCallException('Form data cannot be changed during "form.post_set_data", you should use "form.pre_set_data" instead.'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Event/PostSubmitEvent.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Event; 13 | 14 | use Symfony\Component\Form\Exception\BadMethodCallException; 15 | use Symfony\Component\Form\FormEvent; 16 | 17 | /** 18 | * This event is dispatched after the Form::submit() 19 | * once the model and view data have been denormalized. 20 | * 21 | * It can be used to fetch data after denormalization. 22 | */ 23 | final class PostSubmitEvent extends FormEvent 24 | { 25 | public function setData(mixed $data): never 26 | { 27 | throw new BadMethodCallException('Form data cannot be changed during "form.post_submit", you should use "form.pre_submit" or "form.submit" instead.'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Event/PreSetDataEvent.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Event; 13 | 14 | use Symfony\Component\Form\FormEvent; 15 | 16 | /** 17 | * This event is dispatched at the beginning of the Form::setData() method. 18 | * 19 | * It can be used to modify the data given during pre-population. 20 | */ 21 | final class PreSetDataEvent extends FormEvent 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /Event/PreSubmitEvent.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Event; 13 | 14 | use Symfony\Component\Form\FormEvent; 15 | 16 | /** 17 | * This event is dispatched at the beginning of the Form::submit() method. 18 | * 19 | * It can be used to: 20 | * - Change data from the request, before submitting the data to the form. 21 | * - Add or remove form fields, before submitting the data to the form. 22 | */ 23 | final class PreSubmitEvent extends FormEvent 24 | { 25 | } 26 | -------------------------------------------------------------------------------- /Event/SubmitEvent.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Event; 13 | 14 | use Symfony\Component\Form\FormEvent; 15 | 16 | /** 17 | * This event is dispatched just before the Form::submit() method 18 | * transforms back the normalized data to the model and view data. 19 | * 20 | * It can be used to change data from the normalized representation of the data. 21 | */ 22 | final class SubmitEvent extends FormEvent 23 | { 24 | } 25 | -------------------------------------------------------------------------------- /Exception/AccessException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Exception; 13 | 14 | class AccessException extends RuntimeException 15 | { 16 | } 17 | -------------------------------------------------------------------------------- /Exception/AlreadySubmittedException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Exception; 13 | 14 | /** 15 | * Thrown when an operation is called that is not acceptable after submitting 16 | * a form. 17 | * 18 | * @author Bernhard Schussek 19 | */ 20 | class AlreadySubmittedException extends LogicException 21 | { 22 | } 23 | -------------------------------------------------------------------------------- /Exception/BadMethodCallException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Exception; 13 | 14 | /** 15 | * Base BadMethodCallException for the Form component. 16 | * 17 | * @author Bernhard Schussek 18 | */ 19 | class BadMethodCallException extends \BadMethodCallException implements ExceptionInterface 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /Exception/ErrorMappingException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Exception; 13 | 14 | class ErrorMappingException extends RuntimeException 15 | { 16 | } 17 | -------------------------------------------------------------------------------- /Exception/ExceptionInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Exception; 13 | 14 | /** 15 | * Base ExceptionInterface for the Form component. 16 | * 17 | * @author Bernhard Schussek 18 | */ 19 | interface ExceptionInterface extends \Throwable 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /Exception/InvalidArgumentException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Exception; 13 | 14 | /** 15 | * Base InvalidArgumentException for the Form component. 16 | * 17 | * @author Bernhard Schussek 18 | */ 19 | class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /Exception/InvalidConfigurationException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Exception; 13 | 14 | class InvalidConfigurationException extends InvalidArgumentException 15 | { 16 | } 17 | -------------------------------------------------------------------------------- /Exception/LogicException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Exception; 13 | 14 | /** 15 | * Base LogicException for Form component. 16 | * 17 | * @author Alexander Kotynia 18 | */ 19 | class LogicException extends \LogicException implements ExceptionInterface 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /Exception/OutOfBoundsException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Exception; 13 | 14 | /** 15 | * Base OutOfBoundsException for Form component. 16 | * 17 | * @author Alexander Kotynia 18 | */ 19 | class OutOfBoundsException extends \OutOfBoundsException implements ExceptionInterface 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /Exception/RuntimeException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Exception; 13 | 14 | /** 15 | * Base RuntimeException for the Form component. 16 | * 17 | * @author Bernhard Schussek 18 | */ 19 | class RuntimeException extends \RuntimeException implements ExceptionInterface 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /Exception/StringCastException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Exception; 13 | 14 | class StringCastException extends RuntimeException 15 | { 16 | } 17 | -------------------------------------------------------------------------------- /Exception/TransformationFailedException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Exception; 13 | 14 | /** 15 | * Indicates a value transformation error. 16 | * 17 | * @author Bernhard Schussek 18 | */ 19 | class TransformationFailedException extends RuntimeException 20 | { 21 | private ?string $invalidMessage; 22 | private array $invalidMessageParameters; 23 | 24 | public function __construct(string $message = '', int $code = 0, ?\Throwable $previous = null, ?string $invalidMessage = null, array $invalidMessageParameters = []) 25 | { 26 | parent::__construct($message, $code, $previous); 27 | 28 | $this->setInvalidMessage($invalidMessage, $invalidMessageParameters); 29 | } 30 | 31 | /** 32 | * Sets the message that will be shown to the user. 33 | * 34 | * @param string|null $invalidMessage The message or message key 35 | * @param array $invalidMessageParameters Data to be passed into the translator 36 | */ 37 | public function setInvalidMessage(?string $invalidMessage, array $invalidMessageParameters = []): void 38 | { 39 | $this->invalidMessage = $invalidMessage; 40 | $this->invalidMessageParameters = $invalidMessageParameters; 41 | } 42 | 43 | public function getInvalidMessage(): ?string 44 | { 45 | return $this->invalidMessage; 46 | } 47 | 48 | public function getInvalidMessageParameters(): array 49 | { 50 | return $this->invalidMessageParameters; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Exception/UnexpectedTypeException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Exception; 13 | 14 | class UnexpectedTypeException extends InvalidArgumentException 15 | { 16 | public function __construct(mixed $value, string $expectedType) 17 | { 18 | parent::__construct(\sprintf('Expected argument of type "%s", "%s" given', $expectedType, get_debug_type($value))); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Extension/Core/DataAccessor/CallbackAccessor.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\DataAccessor; 13 | 14 | use Symfony\Component\Form\DataAccessorInterface; 15 | use Symfony\Component\Form\Exception\AccessException; 16 | use Symfony\Component\Form\FormInterface; 17 | 18 | /** 19 | * Writes and reads values to/from an object or array using callback functions. 20 | * 21 | * @author Yonel Ceruto 22 | */ 23 | class CallbackAccessor implements DataAccessorInterface 24 | { 25 | public function getValue(object|array $data, FormInterface $form): mixed 26 | { 27 | if (null === $getter = $form->getConfig()->getOption('getter')) { 28 | throw new AccessException('Unable to read from the given form data as no getter is defined.'); 29 | } 30 | 31 | return ($getter)($data, $form); 32 | } 33 | 34 | public function setValue(object|array &$data, mixed $value, FormInterface $form): void 35 | { 36 | if (null === $setter = $form->getConfig()->getOption('setter')) { 37 | throw new AccessException('Unable to write the given value as no setter is defined.'); 38 | } 39 | 40 | ($setter)($data, $form->getData(), $form); 41 | } 42 | 43 | public function isReadable(object|array $data, FormInterface $form): bool 44 | { 45 | return null !== $form->getConfig()->getOption('getter'); 46 | } 47 | 48 | public function isWritable(object|array $data, FormInterface $form): bool 49 | { 50 | return null !== $form->getConfig()->getOption('setter'); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Extension/Core/DataAccessor/ChainAccessor.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\DataAccessor; 13 | 14 | use Symfony\Component\Form\DataAccessorInterface; 15 | use Symfony\Component\Form\Exception\AccessException; 16 | use Symfony\Component\Form\FormInterface; 17 | 18 | /** 19 | * @author Yonel Ceruto 20 | */ 21 | class ChainAccessor implements DataAccessorInterface 22 | { 23 | /** 24 | * @param DataAccessorInterface[]|iterable $accessors 25 | */ 26 | public function __construct( 27 | private iterable $accessors, 28 | ) { 29 | } 30 | 31 | public function getValue(object|array $data, FormInterface $form): mixed 32 | { 33 | foreach ($this->accessors as $accessor) { 34 | if ($accessor->isReadable($data, $form)) { 35 | return $accessor->getValue($data, $form); 36 | } 37 | } 38 | 39 | throw new AccessException('Unable to read from the given form data as no accessor in the chain is able to read the data.'); 40 | } 41 | 42 | public function setValue(object|array &$data, mixed $value, FormInterface $form): void 43 | { 44 | foreach ($this->accessors as $accessor) { 45 | if ($accessor->isWritable($data, $form)) { 46 | $accessor->setValue($data, $value, $form); 47 | 48 | return; 49 | } 50 | } 51 | 52 | throw new AccessException('Unable to write the given value as no accessor in the chain is able to set the data.'); 53 | } 54 | 55 | public function isReadable(object|array $data, FormInterface $form): bool 56 | { 57 | foreach ($this->accessors as $accessor) { 58 | if ($accessor->isReadable($data, $form)) { 59 | return true; 60 | } 61 | } 62 | 63 | return false; 64 | } 65 | 66 | public function isWritable(object|array $data, FormInterface $form): bool 67 | { 68 | foreach ($this->accessors as $accessor) { 69 | if ($accessor->isWritable($data, $form)) { 70 | return true; 71 | } 72 | } 73 | 74 | return false; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Extension/Core/DataMapper/CheckboxListMapper.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\DataMapper; 13 | 14 | use Symfony\Component\Form\DataMapperInterface; 15 | use Symfony\Component\Form\Exception\UnexpectedTypeException; 16 | 17 | /** 18 | * Maps choices to/from checkbox forms. 19 | * 20 | * A {@link ChoiceListInterface} implementation is used to find the 21 | * corresponding string values for the choices. Each checkbox form whose "value" 22 | * option corresponds to any of the selected values is marked as selected. 23 | * 24 | * @author Bernhard Schussek 25 | */ 26 | class CheckboxListMapper implements DataMapperInterface 27 | { 28 | public function mapDataToForms(mixed $choices, \Traversable $checkboxes): void 29 | { 30 | if (!\is_array($choices ??= [])) { 31 | throw new UnexpectedTypeException($choices, 'array'); 32 | } 33 | 34 | foreach ($checkboxes as $checkbox) { 35 | $value = $checkbox->getConfig()->getOption('value'); 36 | $checkbox->setData(\in_array($value, $choices, true)); 37 | } 38 | } 39 | 40 | public function mapFormsToData(\Traversable $checkboxes, mixed &$choices): void 41 | { 42 | if (!\is_array($choices)) { 43 | throw new UnexpectedTypeException($choices, 'array'); 44 | } 45 | 46 | $values = []; 47 | 48 | foreach ($checkboxes as $checkbox) { 49 | if ($checkbox->getData()) { 50 | // construct an array of choice values 51 | $values[] = $checkbox->getConfig()->getOption('value'); 52 | } 53 | } 54 | 55 | $choices = $values; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Extension/Core/DataMapper/RadioListMapper.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\DataMapper; 13 | 14 | use Symfony\Component\Form\DataMapperInterface; 15 | use Symfony\Component\Form\Exception\UnexpectedTypeException; 16 | 17 | /** 18 | * Maps choices to/from radio forms. 19 | * 20 | * A {@link ChoiceListInterface} implementation is used to find the 21 | * corresponding string values for the choices. The radio form whose "value" 22 | * option corresponds to the selected value is marked as selected. 23 | * 24 | * @author Bernhard Schussek 25 | */ 26 | class RadioListMapper implements DataMapperInterface 27 | { 28 | public function mapDataToForms(mixed $choice, \Traversable $radios): void 29 | { 30 | if (!\is_string($choice)) { 31 | throw new UnexpectedTypeException($choice, 'string'); 32 | } 33 | 34 | foreach ($radios as $radio) { 35 | $value = $radio->getConfig()->getOption('value'); 36 | $radio->setData($choice === $value); 37 | } 38 | } 39 | 40 | public function mapFormsToData(\Traversable $radios, mixed &$choice): void 41 | { 42 | if (null !== $choice && !\is_string($choice)) { 43 | throw new UnexpectedTypeException($choice, 'null or string'); 44 | } 45 | 46 | $choice = null; 47 | 48 | foreach ($radios as $radio) { 49 | if ($radio->getData()) { 50 | if ('placeholder' === $radio->getName()) { 51 | return; 52 | } 53 | 54 | $choice = $radio->getConfig()->getOption('value'); 55 | 56 | return; 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Extension/Core/DataTransformer/ArrayToPartsTransformer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\DataTransformer; 13 | 14 | use Symfony\Component\Form\DataTransformerInterface; 15 | use Symfony\Component\Form\Exception\TransformationFailedException; 16 | 17 | /** 18 | * @author Bernhard Schussek 19 | * 20 | * @implements DataTransformerInterface 21 | */ 22 | class ArrayToPartsTransformer implements DataTransformerInterface 23 | { 24 | public function __construct( 25 | private array $partMapping, 26 | ) { 27 | } 28 | 29 | public function transform(mixed $array): mixed 30 | { 31 | if (!\is_array($array ??= [])) { 32 | throw new TransformationFailedException('Expected an array.'); 33 | } 34 | 35 | $result = []; 36 | 37 | foreach ($this->partMapping as $partKey => $originalKeys) { 38 | if (!$array) { 39 | $result[$partKey] = null; 40 | } else { 41 | $result[$partKey] = array_intersect_key($array, array_flip($originalKeys)); 42 | } 43 | } 44 | 45 | return $result; 46 | } 47 | 48 | public function reverseTransform(mixed $array): mixed 49 | { 50 | if (!\is_array($array)) { 51 | throw new TransformationFailedException('Expected an array.'); 52 | } 53 | 54 | $result = []; 55 | $emptyKeys = []; 56 | 57 | foreach ($this->partMapping as $partKey => $originalKeys) { 58 | if (!empty($array[$partKey])) { 59 | foreach ($originalKeys as $originalKey) { 60 | if (isset($array[$partKey][$originalKey])) { 61 | $result[$originalKey] = $array[$partKey][$originalKey]; 62 | } 63 | } 64 | } else { 65 | $emptyKeys[] = $partKey; 66 | } 67 | } 68 | 69 | if (\count($emptyKeys) > 0) { 70 | if (\count($emptyKeys) === \count($this->partMapping)) { 71 | // All parts empty 72 | return null; 73 | } 74 | 75 | throw new TransformationFailedException(\sprintf('The keys "%s" should not be empty.', implode('", "', $emptyKeys))); 76 | } 77 | 78 | return $result; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Extension/Core/DataTransformer/BaseDateTimeTransformer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\DataTransformer; 13 | 14 | use Symfony\Component\Form\DataTransformerInterface; 15 | use Symfony\Component\Form\Exception\InvalidArgumentException; 16 | 17 | /** 18 | * @template TTransformedValue 19 | * 20 | * @implements DataTransformerInterface<\DateTimeInterface, TTransformedValue> 21 | */ 22 | abstract class BaseDateTimeTransformer implements DataTransformerInterface 23 | { 24 | protected static array $formats = [ 25 | \IntlDateFormatter::NONE, 26 | \IntlDateFormatter::FULL, 27 | \IntlDateFormatter::LONG, 28 | \IntlDateFormatter::MEDIUM, 29 | \IntlDateFormatter::SHORT, 30 | ]; 31 | 32 | protected string $inputTimezone; 33 | protected string $outputTimezone; 34 | 35 | /** 36 | * @param string|null $inputTimezone The name of the input timezone 37 | * @param string|null $outputTimezone The name of the output timezone 38 | * 39 | * @throws InvalidArgumentException if a timezone is not valid 40 | */ 41 | public function __construct(?string $inputTimezone = null, ?string $outputTimezone = null) 42 | { 43 | $this->inputTimezone = $inputTimezone ?: date_default_timezone_get(); 44 | $this->outputTimezone = $outputTimezone ?: date_default_timezone_get(); 45 | 46 | // Check if input and output timezones are valid 47 | try { 48 | new \DateTimeZone($this->inputTimezone); 49 | } catch (\Exception $e) { 50 | throw new InvalidArgumentException(\sprintf('Input timezone is invalid: "%s".', $this->inputTimezone), $e->getCode(), $e); 51 | } 52 | 53 | try { 54 | new \DateTimeZone($this->outputTimezone); 55 | } catch (\Exception $e) { 56 | throw new InvalidArgumentException(\sprintf('Output timezone is invalid: "%s".', $this->outputTimezone), $e->getCode(), $e); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Extension/Core/DataTransformer/BooleanToStringTransformer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\DataTransformer; 13 | 14 | use Symfony\Component\Form\DataTransformerInterface; 15 | use Symfony\Component\Form\Exception\InvalidArgumentException; 16 | use Symfony\Component\Form\Exception\TransformationFailedException; 17 | 18 | /** 19 | * Transforms between a Boolean and a string. 20 | * 21 | * @author Bernhard Schussek 22 | * @author Florian Eckerstorfer 23 | * 24 | * @implements DataTransformerInterface 25 | */ 26 | class BooleanToStringTransformer implements DataTransformerInterface 27 | { 28 | /** 29 | * @param string $trueValue The value emitted upon transform if the input is true 30 | */ 31 | public function __construct( 32 | private string $trueValue, 33 | private array $falseValues = [null], 34 | ) { 35 | if (\in_array($this->trueValue, $this->falseValues, true)) { 36 | throw new InvalidArgumentException('The specified "true" value is contained in the false-values.'); 37 | } 38 | } 39 | 40 | /** 41 | * Transforms a Boolean into a string. 42 | * 43 | * @param bool $value Boolean value 44 | * 45 | * @throws TransformationFailedException if the given value is not a Boolean 46 | */ 47 | public function transform(mixed $value): ?string 48 | { 49 | if (null === $value) { 50 | return null; 51 | } 52 | 53 | if (!\is_bool($value)) { 54 | throw new TransformationFailedException('Expected a Boolean.'); 55 | } 56 | 57 | return $value ? $this->trueValue : null; 58 | } 59 | 60 | /** 61 | * Transforms a string into a Boolean. 62 | * 63 | * @param string $value String value 64 | * 65 | * @throws TransformationFailedException if the given value is not a string 66 | */ 67 | public function reverseTransform(mixed $value): bool 68 | { 69 | if (\in_array($value, $this->falseValues, true)) { 70 | return false; 71 | } 72 | 73 | if (!\is_string($value)) { 74 | throw new TransformationFailedException('Expected a string.'); 75 | } 76 | 77 | return true; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Extension/Core/DataTransformer/ChoiceToValueTransformer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\DataTransformer; 13 | 14 | use Symfony\Component\Form\ChoiceList\ChoiceListInterface; 15 | use Symfony\Component\Form\DataTransformerInterface; 16 | use Symfony\Component\Form\Exception\TransformationFailedException; 17 | 18 | /** 19 | * @author Bernhard Schussek 20 | * 21 | * @implements DataTransformerInterface 22 | */ 23 | class ChoiceToValueTransformer implements DataTransformerInterface 24 | { 25 | public function __construct( 26 | private ChoiceListInterface $choiceList, 27 | ) { 28 | } 29 | 30 | public function transform(mixed $choice): mixed 31 | { 32 | return (string) current($this->choiceList->getValuesForChoices([$choice])); 33 | } 34 | 35 | public function reverseTransform(mixed $value): mixed 36 | { 37 | if (null !== $value && !\is_string($value)) { 38 | throw new TransformationFailedException('Expected a string or null.'); 39 | } 40 | 41 | $choices = $this->choiceList->getChoicesForValues([(string) $value]); 42 | 43 | if (1 !== \count($choices)) { 44 | if (null === $value || '' === $value) { 45 | return null; 46 | } 47 | 48 | throw new TransformationFailedException(\sprintf('The choice "%s" does not exist or is not unique.', $value)); 49 | } 50 | 51 | return current($choices); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Extension/Core/DataTransformer/ChoicesToValuesTransformer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\DataTransformer; 13 | 14 | use Symfony\Component\Form\ChoiceList\ChoiceListInterface; 15 | use Symfony\Component\Form\DataTransformerInterface; 16 | use Symfony\Component\Form\Exception\TransformationFailedException; 17 | 18 | /** 19 | * @author Bernhard Schussek 20 | * 21 | * @implements DataTransformerInterface 22 | */ 23 | class ChoicesToValuesTransformer implements DataTransformerInterface 24 | { 25 | public function __construct( 26 | private ChoiceListInterface $choiceList, 27 | ) { 28 | } 29 | 30 | /** 31 | * @throws TransformationFailedException if the given value is not an array 32 | */ 33 | public function transform(mixed $array): array 34 | { 35 | if (null === $array) { 36 | return []; 37 | } 38 | 39 | if (!\is_array($array)) { 40 | throw new TransformationFailedException('Expected an array.'); 41 | } 42 | 43 | return $this->choiceList->getValuesForChoices($array); 44 | } 45 | 46 | /** 47 | * @throws TransformationFailedException if the given value is not an array 48 | * or if no matching choice could be 49 | * found for some given value 50 | */ 51 | public function reverseTransform(mixed $array): array 52 | { 53 | if (null === $array) { 54 | return []; 55 | } 56 | 57 | if (!\is_array($array)) { 58 | throw new TransformationFailedException('Expected an array.'); 59 | } 60 | 61 | $choices = $this->choiceList->getChoicesForValues($array); 62 | 63 | if (\count($choices) !== \count($array)) { 64 | throw new TransformationFailedException('Could not find all matching choices for the given values.'); 65 | } 66 | 67 | return $choices; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Extension/Core/DataTransformer/DateTimeImmutableToDateTimeTransformer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\DataTransformer; 13 | 14 | use Symfony\Component\Form\DataTransformerInterface; 15 | use Symfony\Component\Form\Exception\TransformationFailedException; 16 | 17 | /** 18 | * Transforms between a DateTimeImmutable object and a DateTime object. 19 | * 20 | * @author Valentin Udaltsov 21 | * 22 | * @implements DataTransformerInterface<\DateTimeImmutable, \DateTime> 23 | */ 24 | final class DateTimeImmutableToDateTimeTransformer implements DataTransformerInterface 25 | { 26 | /** 27 | * Transforms a DateTimeImmutable into a DateTime object. 28 | * 29 | * @param \DateTimeImmutable|null $value A DateTimeImmutable object 30 | * 31 | * @throws TransformationFailedException If the given value is not a \DateTimeImmutable 32 | */ 33 | public function transform(mixed $value): ?\DateTime 34 | { 35 | if (null === $value) { 36 | return null; 37 | } 38 | 39 | if (!$value instanceof \DateTimeImmutable) { 40 | throw new TransformationFailedException('Expected a \DateTimeImmutable.'); 41 | } 42 | 43 | return \DateTime::createFromImmutable($value); 44 | } 45 | 46 | /** 47 | * Transforms a DateTime object into a DateTimeImmutable object. 48 | * 49 | * @param \DateTime|null $value A DateTime object 50 | * 51 | * @throws TransformationFailedException If the given value is not a \DateTime 52 | */ 53 | public function reverseTransform(mixed $value): ?\DateTimeImmutable 54 | { 55 | if (null === $value) { 56 | return null; 57 | } 58 | 59 | if (!$value instanceof \DateTime) { 60 | throw new TransformationFailedException('Expected a \DateTime.'); 61 | } 62 | 63 | return \DateTimeImmutable::createFromMutable($value); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Extension/Core/DataTransformer/DateTimeToTimestampTransformer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\DataTransformer; 13 | 14 | use Symfony\Component\Form\Exception\TransformationFailedException; 15 | 16 | /** 17 | * Transforms between a timestamp and a DateTime object. 18 | * 19 | * @author Bernhard Schussek 20 | * @author Florian Eckerstorfer 21 | * 22 | * @extends BaseDateTimeTransformer 23 | */ 24 | class DateTimeToTimestampTransformer extends BaseDateTimeTransformer 25 | { 26 | /** 27 | * Transforms a DateTime object into a timestamp in the configured timezone. 28 | * 29 | * @param \DateTimeInterface $dateTime A DateTimeInterface object 30 | * 31 | * @throws TransformationFailedException If the given value is not a \DateTimeInterface 32 | */ 33 | public function transform(mixed $dateTime): ?int 34 | { 35 | if (null === $dateTime) { 36 | return null; 37 | } 38 | 39 | if (!$dateTime instanceof \DateTimeInterface) { 40 | throw new TransformationFailedException('Expected a \DateTimeInterface.'); 41 | } 42 | 43 | return $dateTime->getTimestamp(); 44 | } 45 | 46 | /** 47 | * Transforms a timestamp in the configured timezone into a DateTime object. 48 | * 49 | * @param string $value A timestamp 50 | * 51 | * @throws TransformationFailedException If the given value is not a timestamp 52 | * or if the given timestamp is invalid 53 | */ 54 | public function reverseTransform(mixed $value): ?\DateTime 55 | { 56 | if (null === $value) { 57 | return null; 58 | } 59 | 60 | if (!is_numeric($value)) { 61 | throw new TransformationFailedException('Expected a numeric.'); 62 | } 63 | 64 | try { 65 | $dateTime = new \DateTime(); 66 | $dateTime->setTimezone(new \DateTimeZone($this->outputTimezone)); 67 | $dateTime->setTimestamp($value); 68 | 69 | if ($this->inputTimezone !== $this->outputTimezone) { 70 | $dateTime->setTimezone(new \DateTimeZone($this->inputTimezone)); 71 | } 72 | } catch (\Exception $e) { 73 | throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); 74 | } 75 | 76 | return $dateTime; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Extension/Core/DataTransformer/DateTimeZoneToStringTransformer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\DataTransformer; 13 | 14 | use Symfony\Component\Form\DataTransformerInterface; 15 | use Symfony\Component\Form\Exception\TransformationFailedException; 16 | 17 | /** 18 | * Transforms between a timezone identifier string and a DateTimeZone object. 19 | * 20 | * @author Roland Franssen 21 | * 22 | * @implements DataTransformerInterface<\DateTimeZone|array<\DateTimeZone>, string|array> 23 | */ 24 | class DateTimeZoneToStringTransformer implements DataTransformerInterface 25 | { 26 | public function __construct( 27 | private bool $multiple = false, 28 | ) { 29 | } 30 | 31 | public function transform(mixed $dateTimeZone): mixed 32 | { 33 | if (null === $dateTimeZone) { 34 | return null; 35 | } 36 | 37 | if ($this->multiple) { 38 | if (!\is_array($dateTimeZone)) { 39 | throw new TransformationFailedException('Expected an array of \DateTimeZone objects.'); 40 | } 41 | 42 | return array_map([new self(), 'transform'], $dateTimeZone); 43 | } 44 | 45 | if (!$dateTimeZone instanceof \DateTimeZone) { 46 | throw new TransformationFailedException('Expected a \DateTimeZone object.'); 47 | } 48 | 49 | return $dateTimeZone->getName(); 50 | } 51 | 52 | public function reverseTransform(mixed $value): mixed 53 | { 54 | if (null === $value) { 55 | return null; 56 | } 57 | 58 | if ($this->multiple) { 59 | if (!\is_array($value)) { 60 | throw new TransformationFailedException('Expected an array of timezone identifier strings.'); 61 | } 62 | 63 | return array_map([new self(), 'reverseTransform'], $value); 64 | } 65 | 66 | if (!\is_string($value)) { 67 | throw new TransformationFailedException('Expected a timezone identifier string.'); 68 | } 69 | 70 | try { 71 | return new \DateTimeZone($value); 72 | } catch (\Exception $e) { 73 | throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Extension/Core/DataTransformer/IntegerToLocalizedStringTransformer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\DataTransformer; 13 | 14 | use Symfony\Component\Form\Exception\TransformationFailedException; 15 | 16 | /** 17 | * Transforms between an integer and a localized number with grouping 18 | * (each thousand) and comma separators. 19 | * 20 | * @author Bernhard Schussek 21 | */ 22 | class IntegerToLocalizedStringTransformer extends NumberToLocalizedStringTransformer 23 | { 24 | /** 25 | * Constructs a transformer. 26 | * 27 | * @param bool $grouping Whether thousands should be grouped 28 | * @param int|null $roundingMode One of the ROUND_ constants in this class 29 | * @param string|null $locale locale used for transforming 30 | */ 31 | public function __construct(?bool $grouping = false, ?int $roundingMode = \NumberFormatter::ROUND_DOWN, ?string $locale = null) 32 | { 33 | parent::__construct(0, $grouping, $roundingMode, $locale); 34 | } 35 | 36 | public function reverseTransform(mixed $value): int|float|null 37 | { 38 | $decimalSeparator = $this->getNumberFormatter()->getSymbol(\NumberFormatter::DECIMAL_SEPARATOR_SYMBOL); 39 | 40 | if (\is_string($value) && str_contains($value, $decimalSeparator)) { 41 | throw new TransformationFailedException(\sprintf('The value "%s" is not a valid integer.', $value)); 42 | } 43 | 44 | $result = parent::reverseTransform($value); 45 | 46 | return null !== $result ? (int) $result : null; 47 | } 48 | 49 | /** 50 | * @internal 51 | */ 52 | protected function castParsedValue(int|float $value): int|float 53 | { 54 | return $value; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Extension/Core/DataTransformer/IntlTimeZoneToStringTransformer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\DataTransformer; 13 | 14 | use Symfony\Component\Form\DataTransformerInterface; 15 | use Symfony\Component\Form\Exception\TransformationFailedException; 16 | 17 | /** 18 | * Transforms between a timezone identifier string and a IntlTimeZone object. 19 | * 20 | * @author Roland Franssen 21 | * 22 | * @implements DataTransformerInterface<\IntlTimeZone|array<\IntlTimeZone>, string|array> 23 | */ 24 | class IntlTimeZoneToStringTransformer implements DataTransformerInterface 25 | { 26 | public function __construct( 27 | private bool $multiple = false, 28 | ) { 29 | } 30 | 31 | public function transform(mixed $intlTimeZone): mixed 32 | { 33 | if (null === $intlTimeZone) { 34 | return null; 35 | } 36 | 37 | if ($this->multiple) { 38 | if (!\is_array($intlTimeZone)) { 39 | throw new TransformationFailedException('Expected an array of \IntlTimeZone objects.'); 40 | } 41 | 42 | return array_map([new self(), 'transform'], $intlTimeZone); 43 | } 44 | 45 | if (!$intlTimeZone instanceof \IntlTimeZone) { 46 | throw new TransformationFailedException('Expected a \IntlTimeZone object.'); 47 | } 48 | 49 | return $intlTimeZone->getID(); 50 | } 51 | 52 | public function reverseTransform(mixed $value): mixed 53 | { 54 | if (null === $value) { 55 | return null; 56 | } 57 | 58 | if ($this->multiple) { 59 | if (!\is_array($value)) { 60 | throw new TransformationFailedException('Expected an array of timezone identifier strings.'); 61 | } 62 | 63 | return array_map([new self(), 'reverseTransform'], $value); 64 | } 65 | 66 | if (!\is_string($value)) { 67 | throw new TransformationFailedException('Expected a timezone identifier string.'); 68 | } 69 | 70 | $intlTimeZone = \IntlTimeZone::createTimeZone($value); 71 | 72 | if ('Etc/Unknown' === $intlTimeZone->getID()) { 73 | throw new TransformationFailedException(\sprintf('Unknown timezone identifier "%s".', $value)); 74 | } 75 | 76 | return $intlTimeZone; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Extension/Core/DataTransformer/StringToFloatTransformer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\DataTransformer; 13 | 14 | use Symfony\Component\Form\DataTransformerInterface; 15 | use Symfony\Component\Form\Exception\TransformationFailedException; 16 | 17 | /** 18 | * @implements DataTransformerInterface 19 | */ 20 | class StringToFloatTransformer implements DataTransformerInterface 21 | { 22 | public function __construct( 23 | private ?int $scale = null, 24 | ) { 25 | } 26 | 27 | public function transform(mixed $value): ?float 28 | { 29 | if (null === $value) { 30 | return null; 31 | } 32 | 33 | if (!\is_string($value) || !is_numeric($value)) { 34 | throw new TransformationFailedException('Expected a numeric string.'); 35 | } 36 | 37 | return (float) $value; 38 | } 39 | 40 | public function reverseTransform(mixed $value): ?string 41 | { 42 | if (null === $value) { 43 | return null; 44 | } 45 | 46 | if (!\is_int($value) && !\is_float($value)) { 47 | throw new TransformationFailedException('Expected a numeric.'); 48 | } 49 | 50 | if ($this->scale > 0) { 51 | return number_format((float) $value, $this->scale, '.', ''); 52 | } 53 | 54 | return (string) $value; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Extension/Core/DataTransformer/UlidToStringTransformer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\DataTransformer; 13 | 14 | use Symfony\Component\Form\DataTransformerInterface; 15 | use Symfony\Component\Form\Exception\TransformationFailedException; 16 | use Symfony\Component\Uid\Ulid; 17 | 18 | /** 19 | * Transforms between a ULID string and a Ulid object. 20 | * 21 | * @author Pavel Dyakonov 22 | * 23 | * @implements DataTransformerInterface 24 | */ 25 | class UlidToStringTransformer implements DataTransformerInterface 26 | { 27 | /** 28 | * Transforms a Ulid object into a string. 29 | * 30 | * @param Ulid $value A Ulid object 31 | * 32 | * @throws TransformationFailedException If the given value is not a Ulid object 33 | */ 34 | public function transform(mixed $value): ?string 35 | { 36 | if (null === $value) { 37 | return null; 38 | } 39 | 40 | if (!$value instanceof Ulid) { 41 | throw new TransformationFailedException('Expected a Ulid.'); 42 | } 43 | 44 | return (string) $value; 45 | } 46 | 47 | /** 48 | * Transforms a ULID string into a Ulid object. 49 | * 50 | * @param string $value A ULID string 51 | * 52 | * @throws TransformationFailedException If the given value is not a string, 53 | * or could not be transformed 54 | */ 55 | public function reverseTransform(mixed $value): ?Ulid 56 | { 57 | if (null === $value || '' === $value) { 58 | return null; 59 | } 60 | 61 | if (!\is_string($value)) { 62 | throw new TransformationFailedException('Expected a string.'); 63 | } 64 | 65 | try { 66 | $ulid = new Ulid($value); 67 | } catch (\InvalidArgumentException $e) { 68 | throw new TransformationFailedException(\sprintf('The value "%s" is not a valid ULID.', $value), $e->getCode(), $e); 69 | } 70 | 71 | return $ulid; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Extension/Core/DataTransformer/UuidToStringTransformer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\DataTransformer; 13 | 14 | use Symfony\Component\Form\DataTransformerInterface; 15 | use Symfony\Component\Form\Exception\TransformationFailedException; 16 | use Symfony\Component\Uid\Uuid; 17 | 18 | /** 19 | * Transforms between a UUID string and a Uuid object. 20 | * 21 | * @author Pavel Dyakonov 22 | * 23 | * @implements DataTransformerInterface 24 | */ 25 | class UuidToStringTransformer implements DataTransformerInterface 26 | { 27 | /** 28 | * Transforms a Uuid object into a string. 29 | * 30 | * @param Uuid $value A Uuid object 31 | * 32 | * @throws TransformationFailedException If the given value is not a Uuid object 33 | */ 34 | public function transform(mixed $value): ?string 35 | { 36 | if (null === $value) { 37 | return null; 38 | } 39 | 40 | if (!$value instanceof Uuid) { 41 | throw new TransformationFailedException('Expected a Uuid.'); 42 | } 43 | 44 | return (string) $value; 45 | } 46 | 47 | /** 48 | * Transforms a UUID string into a Uuid object. 49 | * 50 | * @param string $value A UUID string 51 | * 52 | * @throws TransformationFailedException If the given value is not a string, 53 | * or could not be transformed 54 | */ 55 | public function reverseTransform(mixed $value): ?Uuid 56 | { 57 | if (null === $value || '' === $value) { 58 | return null; 59 | } 60 | 61 | if (!\is_string($value)) { 62 | throw new TransformationFailedException('Expected a string.'); 63 | } 64 | 65 | if (!Uuid::isValid($value)) { 66 | throw new TransformationFailedException(\sprintf('The value "%s" is not a valid UUID.', $value)); 67 | } 68 | 69 | try { 70 | return Uuid::fromString($value); 71 | } catch (\InvalidArgumentException $e) { 72 | throw new TransformationFailedException(\sprintf('The value "%s" is not a valid UUID.', $value), $e->getCode(), $e); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Extension/Core/DataTransformer/ValueToDuplicatesTransformer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\DataTransformer; 13 | 14 | use Symfony\Component\Form\DataTransformerInterface; 15 | use Symfony\Component\Form\Exception\TransformationFailedException; 16 | 17 | /** 18 | * @author Bernhard Schussek 19 | * 20 | * @implements DataTransformerInterface 21 | */ 22 | class ValueToDuplicatesTransformer implements DataTransformerInterface 23 | { 24 | public function __construct( 25 | private array $keys, 26 | ) { 27 | } 28 | 29 | /** 30 | * Duplicates the given value through the array. 31 | */ 32 | public function transform(mixed $value): array 33 | { 34 | $result = []; 35 | 36 | foreach ($this->keys as $key) { 37 | $result[$key] = $value; 38 | } 39 | 40 | return $result; 41 | } 42 | 43 | /** 44 | * Extracts the duplicated value from an array. 45 | * 46 | * @throws TransformationFailedException if the given value is not an array or 47 | * if the given array cannot be transformed 48 | */ 49 | public function reverseTransform(mixed $array): mixed 50 | { 51 | if (!\is_array($array)) { 52 | throw new TransformationFailedException('Expected an array.'); 53 | } 54 | 55 | $result = current($array); 56 | $emptyKeys = []; 57 | 58 | foreach ($this->keys as $key) { 59 | if (isset($array[$key]) && false !== $array[$key] && [] !== $array[$key]) { 60 | if ($array[$key] !== $result) { 61 | throw new TransformationFailedException('All values in the array should be the same.'); 62 | } 63 | } else { 64 | $emptyKeys[] = $key; 65 | } 66 | } 67 | 68 | if (\count($emptyKeys) > 0) { 69 | if (\count($emptyKeys) == \count($this->keys)) { 70 | // All keys empty 71 | return null; 72 | } 73 | 74 | throw new TransformationFailedException(\sprintf('The keys "%s" should not be empty.', implode('", "', $emptyKeys))); 75 | } 76 | 77 | return $result; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Extension/Core/EventListener/FixUrlProtocolListener.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\EventListener; 13 | 14 | use Symfony\Component\EventDispatcher\EventSubscriberInterface; 15 | use Symfony\Component\Form\FormEvent; 16 | use Symfony\Component\Form\FormEvents; 17 | 18 | /** 19 | * Adds a protocol to a URL if it doesn't already have one. 20 | * 21 | * @author Bernhard Schussek 22 | */ 23 | class FixUrlProtocolListener implements EventSubscriberInterface 24 | { 25 | /** 26 | * @param string|null $defaultProtocol The URL scheme to add when there is none or null to not modify the data 27 | */ 28 | public function __construct( 29 | private ?string $defaultProtocol = 'http', 30 | ) { 31 | } 32 | 33 | public function onSubmit(FormEvent $event): void 34 | { 35 | $data = $event->getData(); 36 | 37 | if ($this->defaultProtocol && $data && \is_string($data) && !preg_match('~^(?:[/.]|[\w+.-]+://|[^:/?@#]++@)~', $data)) { 38 | $event->setData($this->defaultProtocol.'://'.$data); 39 | } 40 | } 41 | 42 | public static function getSubscribedEvents(): array 43 | { 44 | return [FormEvents::SUBMIT => 'onSubmit']; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Extension/Core/EventListener/TransformationFailureListener.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\EventListener; 13 | 14 | use Symfony\Component\EventDispatcher\EventSubscriberInterface; 15 | use Symfony\Component\Form\FormError; 16 | use Symfony\Component\Form\FormEvent; 17 | use Symfony\Component\Form\FormEvents; 18 | use Symfony\Contracts\Translation\TranslatorInterface; 19 | 20 | /** 21 | * @author Christian Flothmann 22 | */ 23 | class TransformationFailureListener implements EventSubscriberInterface 24 | { 25 | public function __construct( 26 | private ?TranslatorInterface $translator = null, 27 | ) { 28 | } 29 | 30 | public static function getSubscribedEvents(): array 31 | { 32 | return [ 33 | FormEvents::POST_SUBMIT => ['convertTransformationFailureToFormError', -1024], 34 | ]; 35 | } 36 | 37 | public function convertTransformationFailureToFormError(FormEvent $event): void 38 | { 39 | $form = $event->getForm(); 40 | 41 | if (null === $form->getTransformationFailure() || !$form->isValid()) { 42 | return; 43 | } 44 | 45 | foreach ($form as $child) { 46 | if (!$child->isSynchronized()) { 47 | return; 48 | } 49 | } 50 | 51 | $clientDataAsString = \is_scalar($form->getViewData()) ? (string) $form->getViewData() : get_debug_type($form->getViewData()); 52 | $messageTemplate = $form->getConfig()->getOption('invalid_message', 'The value {{ value }} is not valid.'); 53 | $messageParameters = array_replace(['{{ value }}' => $clientDataAsString], $form->getConfig()->getOption('invalid_message_parameters', [])); 54 | 55 | if (null !== $this->translator) { 56 | $message = $this->translator->trans($messageTemplate, $messageParameters); 57 | } else { 58 | $message = strtr($messageTemplate, $messageParameters); 59 | } 60 | 61 | $form->addError(new FormError($message, $messageTemplate, $messageParameters, null, $form->getTransformationFailure())); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Extension/Core/EventListener/TrimListener.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\EventListener; 13 | 14 | use Symfony\Component\EventDispatcher\EventSubscriberInterface; 15 | use Symfony\Component\Form\FormEvent; 16 | use Symfony\Component\Form\FormEvents; 17 | use Symfony\Component\Form\Util\StringUtil; 18 | 19 | /** 20 | * Trims string data. 21 | * 22 | * @author Bernhard Schussek 23 | */ 24 | class TrimListener implements EventSubscriberInterface 25 | { 26 | public function preSubmit(FormEvent $event): void 27 | { 28 | $data = $event->getData(); 29 | 30 | if (!\is_string($data)) { 31 | return; 32 | } 33 | 34 | $event->setData(StringUtil::trim($data)); 35 | } 36 | 37 | public static function getSubscribedEvents(): array 38 | { 39 | return [FormEvents::PRE_SUBMIT => 'preSubmit']; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Extension/Core/Type/BirthdayType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\Type; 13 | 14 | use Symfony\Component\Form\AbstractType; 15 | use Symfony\Component\OptionsResolver\OptionsResolver; 16 | 17 | class BirthdayType extends AbstractType 18 | { 19 | public function configureOptions(OptionsResolver $resolver): void 20 | { 21 | $resolver->setDefaults([ 22 | 'years' => range((int) date('Y') - 120, date('Y')), 23 | 'invalid_message' => 'Please enter a valid birthdate.', 24 | ]); 25 | 26 | $resolver->setAllowedTypes('years', 'array'); 27 | } 28 | 29 | public function getParent(): ?string 30 | { 31 | return DateType::class; 32 | } 33 | 34 | public function getBlockPrefix(): string 35 | { 36 | return 'birthday'; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Extension/Core/Type/ButtonType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\Type; 13 | 14 | use Symfony\Component\Form\ButtonTypeInterface; 15 | use Symfony\Component\OptionsResolver\OptionsResolver; 16 | 17 | /** 18 | * A form button. 19 | * 20 | * @author Bernhard Schussek 21 | */ 22 | class ButtonType extends BaseType implements ButtonTypeInterface 23 | { 24 | public function getParent(): ?string 25 | { 26 | return null; 27 | } 28 | 29 | public function getBlockPrefix(): string 30 | { 31 | return 'button'; 32 | } 33 | 34 | public function configureOptions(OptionsResolver $resolver): void 35 | { 36 | parent::configureOptions($resolver); 37 | 38 | $resolver->setDefault('auto_initialize', false); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Extension/Core/Type/CheckboxType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\Type; 13 | 14 | use Symfony\Component\Form\AbstractType; 15 | use Symfony\Component\Form\Extension\Core\DataTransformer\BooleanToStringTransformer; 16 | use Symfony\Component\Form\FormBuilderInterface; 17 | use Symfony\Component\Form\FormInterface; 18 | use Symfony\Component\Form\FormView; 19 | use Symfony\Component\OptionsResolver\OptionsResolver; 20 | 21 | class CheckboxType extends AbstractType 22 | { 23 | public function buildForm(FormBuilderInterface $builder, array $options): void 24 | { 25 | // Unlike in other types, where the data is NULL by default, it 26 | // needs to be a Boolean here. setData(null) is not acceptable 27 | // for checkboxes and radio buttons (unless a custom model 28 | // transformer handles this case). 29 | // We cannot solve this case via overriding the "data" option, because 30 | // doing so also calls setDataLocked(true). 31 | $builder->setData($options['data'] ?? false); 32 | $builder->addViewTransformer(new BooleanToStringTransformer($options['value'], $options['false_values'])); 33 | } 34 | 35 | public function buildView(FormView $view, FormInterface $form, array $options): void 36 | { 37 | $view->vars = array_replace($view->vars, [ 38 | 'value' => $options['value'], 39 | 'checked' => null !== $form->getViewData(), 40 | ]); 41 | } 42 | 43 | public function configureOptions(OptionsResolver $resolver): void 44 | { 45 | $emptyData = static fn (FormInterface $form, $viewData) => $viewData; 46 | 47 | $resolver->setDefaults([ 48 | 'value' => '1', 49 | 'empty_data' => $emptyData, 50 | 'compound' => false, 51 | 'false_values' => [null], 52 | 'invalid_message' => 'The checkbox has an invalid value.', 53 | 'is_empty_callback' => static fn ($modelData): bool => false === $modelData, 54 | ]); 55 | 56 | $resolver->setAllowedTypes('false_values', 'array'); 57 | } 58 | 59 | public function getBlockPrefix(): string 60 | { 61 | return 'checkbox'; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Extension/Core/Type/ColorType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\Type; 13 | 14 | use Symfony\Component\Form\AbstractType; 15 | use Symfony\Component\Form\FormBuilderInterface; 16 | use Symfony\Component\Form\FormError; 17 | use Symfony\Component\Form\FormEvent; 18 | use Symfony\Component\Form\FormEvents; 19 | use Symfony\Component\OptionsResolver\OptionsResolver; 20 | use Symfony\Contracts\Translation\TranslatorInterface; 21 | 22 | class ColorType extends AbstractType 23 | { 24 | /** 25 | * @see https://www.w3.org/TR/html52/sec-forms.html#color-state-typecolor 26 | */ 27 | private const HTML5_PATTERN = '/^#[0-9a-f]{6}$/i'; 28 | 29 | public function __construct( 30 | private ?TranslatorInterface $translator = null, 31 | ) { 32 | } 33 | 34 | public function buildForm(FormBuilderInterface $builder, array $options): void 35 | { 36 | if (!$options['html5']) { 37 | return; 38 | } 39 | 40 | $translator = $this->translator; 41 | $builder->addEventListener(FormEvents::PRE_SUBMIT, static function (FormEvent $event) use ($translator): void { 42 | $value = $event->getData(); 43 | if (null === $value || '' === $value) { 44 | return; 45 | } 46 | 47 | if (\is_string($value) && preg_match(self::HTML5_PATTERN, $value)) { 48 | return; 49 | } 50 | 51 | $messageTemplate = 'This value is not a valid HTML5 color.'; 52 | $messageParameters = [ 53 | '{{ value }}' => \is_scalar($value) ? (string) $value : \gettype($value), 54 | ]; 55 | $message = $translator?->trans($messageTemplate, $messageParameters, 'validators') ?? $messageTemplate; 56 | 57 | $event->getForm()->addError(new FormError($message, $messageTemplate, $messageParameters)); 58 | }); 59 | } 60 | 61 | public function configureOptions(OptionsResolver $resolver): void 62 | { 63 | $resolver->setDefaults([ 64 | 'html5' => false, 65 | 'invalid_message' => 'Please select a valid color.', 66 | ]); 67 | 68 | $resolver->setAllowedTypes('html5', 'bool'); 69 | } 70 | 71 | public function getParent(): ?string 72 | { 73 | return TextType::class; 74 | } 75 | 76 | public function getBlockPrefix(): string 77 | { 78 | return 'color'; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Extension/Core/Type/CountryType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\Type; 13 | 14 | use Symfony\Component\Form\AbstractType; 15 | use Symfony\Component\Form\ChoiceList\ChoiceList; 16 | use Symfony\Component\Form\ChoiceList\Loader\IntlCallbackChoiceLoader; 17 | use Symfony\Component\Form\Exception\LogicException; 18 | use Symfony\Component\Intl\Countries; 19 | use Symfony\Component\Intl\Intl; 20 | use Symfony\Component\OptionsResolver\Options; 21 | use Symfony\Component\OptionsResolver\OptionsResolver; 22 | 23 | class CountryType extends AbstractType 24 | { 25 | public function configureOptions(OptionsResolver $resolver): void 26 | { 27 | $resolver->setDefaults([ 28 | 'choice_loader' => function (Options $options) { 29 | if (!class_exists(Intl::class)) { 30 | throw new LogicException(\sprintf('The "symfony/intl" component is required to use "%s". Try running "composer require symfony/intl".', static::class)); 31 | } 32 | 33 | $choiceTranslationLocale = $options['choice_translation_locale']; 34 | $alpha3 = $options['alpha3']; 35 | 36 | return ChoiceList::loader($this, new IntlCallbackChoiceLoader(static fn () => array_flip($alpha3 ? Countries::getAlpha3Names($choiceTranslationLocale) : Countries::getNames($choiceTranslationLocale))), [$choiceTranslationLocale, $alpha3]); 37 | }, 38 | 'choice_translation_domain' => false, 39 | 'choice_translation_locale' => null, 40 | 'alpha3' => false, 41 | 'invalid_message' => 'Please select a valid country.', 42 | ]); 43 | 44 | $resolver->setAllowedTypes('choice_translation_locale', ['null', 'string']); 45 | $resolver->setAllowedTypes('alpha3', 'bool'); 46 | } 47 | 48 | public function getParent(): ?string 49 | { 50 | return ChoiceType::class; 51 | } 52 | 53 | public function getBlockPrefix(): string 54 | { 55 | return 'country'; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Extension/Core/Type/CurrencyType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\Type; 13 | 14 | use Symfony\Component\Form\AbstractType; 15 | use Symfony\Component\Form\ChoiceList\ChoiceList; 16 | use Symfony\Component\Form\ChoiceList\Loader\IntlCallbackChoiceLoader; 17 | use Symfony\Component\Form\Exception\LogicException; 18 | use Symfony\Component\Intl\Currencies; 19 | use Symfony\Component\Intl\Intl; 20 | use Symfony\Component\OptionsResolver\Options; 21 | use Symfony\Component\OptionsResolver\OptionsResolver; 22 | 23 | class CurrencyType extends AbstractType 24 | { 25 | public function configureOptions(OptionsResolver $resolver): void 26 | { 27 | $resolver->setDefaults([ 28 | 'choice_loader' => function (Options $options) { 29 | if (!class_exists(Intl::class)) { 30 | throw new LogicException(\sprintf('The "symfony/intl" component is required to use "%s". Try running "composer require symfony/intl".', static::class)); 31 | } 32 | 33 | $choiceTranslationLocale = $options['choice_translation_locale']; 34 | 35 | return ChoiceList::loader($this, new IntlCallbackChoiceLoader(static fn () => array_flip(Currencies::getNames($choiceTranslationLocale))), $choiceTranslationLocale); 36 | }, 37 | 'choice_translation_domain' => false, 38 | 'choice_translation_locale' => null, 39 | 'invalid_message' => 'Please select a valid currency.', 40 | ]); 41 | 42 | $resolver->setAllowedTypes('choice_translation_locale', ['null', 'string']); 43 | } 44 | 45 | public function getParent(): ?string 46 | { 47 | return ChoiceType::class; 48 | } 49 | 50 | public function getBlockPrefix(): string 51 | { 52 | return 'currency'; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Extension/Core/Type/EmailType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\Type; 13 | 14 | use Symfony\Component\Form\AbstractType; 15 | use Symfony\Component\OptionsResolver\OptionsResolver; 16 | 17 | class EmailType extends AbstractType 18 | { 19 | public function configureOptions(OptionsResolver $resolver): void 20 | { 21 | $resolver->setDefaults([ 22 | 'invalid_message' => 'Please enter a valid email address.', 23 | ]); 24 | } 25 | 26 | public function getParent(): ?string 27 | { 28 | return TextType::class; 29 | } 30 | 31 | public function getBlockPrefix(): string 32 | { 33 | return 'email'; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Extension/Core/Type/EnumType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\Type; 13 | 14 | use Symfony\Component\Form\AbstractType; 15 | use Symfony\Component\OptionsResolver\Options; 16 | use Symfony\Component\OptionsResolver\OptionsResolver; 17 | use Symfony\Contracts\Translation\TranslatableInterface; 18 | 19 | /** 20 | * A choice type for native PHP enums. 21 | * 22 | * @author Alexander M. Turek 23 | */ 24 | final class EnumType extends AbstractType 25 | { 26 | public function configureOptions(OptionsResolver $resolver): void 27 | { 28 | $resolver 29 | ->setRequired(['class']) 30 | ->setAllowedTypes('class', 'string') 31 | ->setAllowedValues('class', enum_exists(...)) 32 | ->setDefault('choices', static fn (Options $options): array => $options['class']::cases()) 33 | ->setDefault('choice_label', static fn (\UnitEnum $choice) => $choice instanceof TranslatableInterface ? $choice : $choice->name) 34 | ->setDefault('choice_value', static function (Options $options): ?\Closure { 35 | if (!is_a($options['class'], \BackedEnum::class, true)) { 36 | return null; 37 | } 38 | 39 | return static function (?\BackedEnum $choice): ?string { 40 | if (null === $choice) { 41 | return null; 42 | } 43 | 44 | return (string) $choice->value; 45 | }; 46 | }) 47 | ; 48 | } 49 | 50 | public function getParent(): string 51 | { 52 | return ChoiceType::class; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Extension/Core/Type/HiddenType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\Type; 13 | 14 | use Symfony\Component\Form\AbstractType; 15 | use Symfony\Component\OptionsResolver\OptionsResolver; 16 | 17 | class HiddenType extends AbstractType 18 | { 19 | public function configureOptions(OptionsResolver $resolver): void 20 | { 21 | $resolver->setDefaults([ 22 | // hidden fields cannot have a required attribute 23 | 'required' => false, 24 | // Pass errors to the parent 25 | 'error_bubbling' => true, 26 | 'compound' => false, 27 | 'invalid_message' => 'The hidden field is invalid.', 28 | ]); 29 | } 30 | 31 | public function getBlockPrefix(): string 32 | { 33 | return 'hidden'; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Extension/Core/Type/IntegerType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\Type; 13 | 14 | use Symfony\Component\Form\AbstractType; 15 | use Symfony\Component\Form\Extension\Core\DataTransformer\IntegerToLocalizedStringTransformer; 16 | use Symfony\Component\Form\FormBuilderInterface; 17 | use Symfony\Component\Form\FormInterface; 18 | use Symfony\Component\Form\FormView; 19 | use Symfony\Component\OptionsResolver\OptionsResolver; 20 | 21 | class IntegerType extends AbstractType 22 | { 23 | public function buildForm(FormBuilderInterface $builder, array $options): void 24 | { 25 | $builder->addViewTransformer(new IntegerToLocalizedStringTransformer($options['grouping'], $options['rounding_mode'], !$options['grouping'] ? 'en' : null)); 26 | } 27 | 28 | public function buildView(FormView $view, FormInterface $form, array $options): void 29 | { 30 | if ($options['grouping']) { 31 | $view->vars['type'] = 'text'; 32 | } 33 | } 34 | 35 | public function configureOptions(OptionsResolver $resolver): void 36 | { 37 | $resolver->setDefaults([ 38 | 'grouping' => false, 39 | // Integer cast rounds towards 0, so do the same when displaying fractions 40 | 'rounding_mode' => \NumberFormatter::ROUND_DOWN, 41 | 'compound' => false, 42 | 'invalid_message' => 'Please enter an integer.', 43 | ]); 44 | 45 | $resolver->setAllowedValues('rounding_mode', [ 46 | \NumberFormatter::ROUND_FLOOR, 47 | \NumberFormatter::ROUND_DOWN, 48 | \NumberFormatter::ROUND_HALFDOWN, 49 | \NumberFormatter::ROUND_HALFEVEN, 50 | \NumberFormatter::ROUND_HALFUP, 51 | \NumberFormatter::ROUND_UP, 52 | \NumberFormatter::ROUND_CEILING, 53 | ]); 54 | } 55 | 56 | public function getBlockPrefix(): string 57 | { 58 | return 'integer'; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Extension/Core/Type/LocaleType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\Type; 13 | 14 | use Symfony\Component\Form\AbstractType; 15 | use Symfony\Component\Form\ChoiceList\ChoiceList; 16 | use Symfony\Component\Form\ChoiceList\Loader\IntlCallbackChoiceLoader; 17 | use Symfony\Component\Form\Exception\LogicException; 18 | use Symfony\Component\Intl\Intl; 19 | use Symfony\Component\Intl\Locales; 20 | use Symfony\Component\OptionsResolver\Options; 21 | use Symfony\Component\OptionsResolver\OptionsResolver; 22 | 23 | class LocaleType extends AbstractType 24 | { 25 | public function configureOptions(OptionsResolver $resolver): void 26 | { 27 | $resolver->setDefaults([ 28 | 'choice_loader' => function (Options $options) { 29 | if (!class_exists(Intl::class)) { 30 | throw new LogicException(\sprintf('The "symfony/intl" component is required to use "%s". Try running "composer require symfony/intl".', static::class)); 31 | } 32 | 33 | $choiceTranslationLocale = $options['choice_translation_locale']; 34 | 35 | return ChoiceList::loader($this, new IntlCallbackChoiceLoader(static fn () => array_flip(Locales::getNames($choiceTranslationLocale))), $choiceTranslationLocale); 36 | }, 37 | 'choice_translation_domain' => false, 38 | 'choice_translation_locale' => null, 39 | 'invalid_message' => 'Please select a valid locale.', 40 | ]); 41 | 42 | $resolver->setAllowedTypes('choice_translation_locale', ['null', 'string']); 43 | } 44 | 45 | public function getParent(): ?string 46 | { 47 | return ChoiceType::class; 48 | } 49 | 50 | public function getBlockPrefix(): string 51 | { 52 | return 'locale'; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Extension/Core/Type/PasswordType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\Type; 13 | 14 | use Symfony\Component\Form\AbstractType; 15 | use Symfony\Component\Form\FormInterface; 16 | use Symfony\Component\Form\FormView; 17 | use Symfony\Component\OptionsResolver\OptionsResolver; 18 | 19 | class PasswordType extends AbstractType 20 | { 21 | public function buildView(FormView $view, FormInterface $form, array $options): void 22 | { 23 | if ($options['always_empty'] || !$form->isSubmitted()) { 24 | $view->vars['value'] = ''; 25 | } 26 | } 27 | 28 | public function configureOptions(OptionsResolver $resolver): void 29 | { 30 | $resolver->setDefaults([ 31 | 'always_empty' => true, 32 | 'trim' => false, 33 | 'invalid_message' => 'The password is invalid.', 34 | ]); 35 | } 36 | 37 | public function getParent(): ?string 38 | { 39 | return TextType::class; 40 | } 41 | 42 | public function getBlockPrefix(): string 43 | { 44 | return 'password'; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Extension/Core/Type/PercentType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\Type; 13 | 14 | use Symfony\Component\Form\AbstractType; 15 | use Symfony\Component\Form\Extension\Core\DataTransformer\PercentToLocalizedStringTransformer; 16 | use Symfony\Component\Form\FormBuilderInterface; 17 | use Symfony\Component\Form\FormInterface; 18 | use Symfony\Component\Form\FormView; 19 | use Symfony\Component\OptionsResolver\OptionsResolver; 20 | 21 | class PercentType extends AbstractType 22 | { 23 | public function buildForm(FormBuilderInterface $builder, array $options): void 24 | { 25 | $builder->addViewTransformer(new PercentToLocalizedStringTransformer( 26 | $options['scale'], 27 | $options['type'], 28 | $options['rounding_mode'], 29 | $options['html5'] 30 | )); 31 | } 32 | 33 | public function buildView(FormView $view, FormInterface $form, array $options): void 34 | { 35 | $view->vars['symbol'] = $options['symbol']; 36 | 37 | if ($options['html5']) { 38 | $view->vars['type'] = 'number'; 39 | } 40 | } 41 | 42 | public function configureOptions(OptionsResolver $resolver): void 43 | { 44 | $resolver->setDefaults([ 45 | 'scale' => 0, 46 | 'rounding_mode' => \NumberFormatter::ROUND_HALFUP, 47 | 'symbol' => '%', 48 | 'type' => 'fractional', 49 | 'compound' => false, 50 | 'html5' => false, 51 | 'invalid_message' => 'Please enter a percentage value.', 52 | ]); 53 | 54 | $resolver->setAllowedValues('type', [ 55 | 'fractional', 56 | 'integer', 57 | ]); 58 | $resolver->setAllowedValues('rounding_mode', [ 59 | \NumberFormatter::ROUND_FLOOR, 60 | \NumberFormatter::ROUND_DOWN, 61 | \NumberFormatter::ROUND_HALFDOWN, 62 | \NumberFormatter::ROUND_HALFEVEN, 63 | \NumberFormatter::ROUND_HALFUP, 64 | \NumberFormatter::ROUND_UP, 65 | \NumberFormatter::ROUND_CEILING, 66 | ]); 67 | $resolver->setAllowedTypes('scale', 'int'); 68 | $resolver->setAllowedTypes('symbol', ['bool', 'string']); 69 | $resolver->setAllowedTypes('html5', 'bool'); 70 | } 71 | 72 | public function getBlockPrefix(): string 73 | { 74 | return 'percent'; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Extension/Core/Type/RadioType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\Type; 13 | 14 | use Symfony\Component\Form\AbstractType; 15 | use Symfony\Component\OptionsResolver\OptionsResolver; 16 | 17 | class RadioType extends AbstractType 18 | { 19 | public function configureOptions(OptionsResolver $resolver): void 20 | { 21 | $resolver->setDefaults([ 22 | 'invalid_message' => 'Please select a valid option.', 23 | ]); 24 | } 25 | 26 | public function getParent(): ?string 27 | { 28 | return CheckboxType::class; 29 | } 30 | 31 | public function getBlockPrefix(): string 32 | { 33 | return 'radio'; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Extension/Core/Type/RangeType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\Type; 13 | 14 | use Symfony\Component\Form\AbstractType; 15 | use Symfony\Component\OptionsResolver\OptionsResolver; 16 | 17 | class RangeType extends AbstractType 18 | { 19 | public function configureOptions(OptionsResolver $resolver): void 20 | { 21 | $resolver->setDefaults([ 22 | 'invalid_message' => 'Please choose a valid range.', 23 | ]); 24 | } 25 | 26 | public function getParent(): ?string 27 | { 28 | return TextType::class; 29 | } 30 | 31 | public function getBlockPrefix(): string 32 | { 33 | return 'range'; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Extension/Core/Type/RepeatedType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\Type; 13 | 14 | use Symfony\Component\Form\AbstractType; 15 | use Symfony\Component\Form\Extension\Core\DataTransformer\ValueToDuplicatesTransformer; 16 | use Symfony\Component\Form\FormBuilderInterface; 17 | use Symfony\Component\OptionsResolver\OptionsResolver; 18 | 19 | class RepeatedType extends AbstractType 20 | { 21 | public function buildForm(FormBuilderInterface $builder, array $options): void 22 | { 23 | // Overwrite required option for child fields 24 | $options['first_options']['required'] = $options['required']; 25 | $options['second_options']['required'] = $options['required']; 26 | 27 | if (!isset($options['options']['error_bubbling'])) { 28 | $options['options']['error_bubbling'] = $options['error_bubbling']; 29 | } 30 | 31 | // children fields must always be mapped 32 | $defaultOptions = ['mapped' => true]; 33 | 34 | $builder 35 | ->addViewTransformer(new ValueToDuplicatesTransformer([ 36 | $options['first_name'], 37 | $options['second_name'], 38 | ])) 39 | ->add($options['first_name'], $options['type'], array_merge($options['options'], $options['first_options'], $defaultOptions)) 40 | ->add($options['second_name'], $options['type'], array_merge($options['options'], $options['second_options'], $defaultOptions)) 41 | ; 42 | } 43 | 44 | public function configureOptions(OptionsResolver $resolver): void 45 | { 46 | $resolver->setDefaults([ 47 | 'type' => TextType::class, 48 | 'options' => [], 49 | 'first_options' => [], 50 | 'second_options' => [], 51 | 'first_name' => 'first', 52 | 'second_name' => 'second', 53 | 'error_bubbling' => false, 54 | 'invalid_message' => 'The values do not match.', 55 | ]); 56 | 57 | $resolver->setAllowedTypes('options', 'array'); 58 | $resolver->setAllowedTypes('first_options', 'array'); 59 | $resolver->setAllowedTypes('second_options', 'array'); 60 | } 61 | 62 | public function getBlockPrefix(): string 63 | { 64 | return 'repeated'; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Extension/Core/Type/ResetType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\Type; 13 | 14 | use Symfony\Component\Form\AbstractType; 15 | use Symfony\Component\Form\ButtonTypeInterface; 16 | 17 | /** 18 | * A reset button. 19 | * 20 | * @author Bernhard Schussek 21 | */ 22 | class ResetType extends AbstractType implements ButtonTypeInterface 23 | { 24 | public function getParent(): ?string 25 | { 26 | return ButtonType::class; 27 | } 28 | 29 | public function getBlockPrefix(): string 30 | { 31 | return 'reset'; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Extension/Core/Type/SearchType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\Type; 13 | 14 | use Symfony\Component\Form\AbstractType; 15 | use Symfony\Component\OptionsResolver\OptionsResolver; 16 | 17 | class SearchType extends AbstractType 18 | { 19 | public function configureOptions(OptionsResolver $resolver): void 20 | { 21 | $resolver->setDefaults([ 22 | 'invalid_message' => 'Please enter a valid search term.', 23 | ]); 24 | } 25 | 26 | public function getParent(): ?string 27 | { 28 | return TextType::class; 29 | } 30 | 31 | public function getBlockPrefix(): string 32 | { 33 | return 'search'; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Extension/Core/Type/SubmitType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\Type; 13 | 14 | use Symfony\Component\Form\AbstractType; 15 | use Symfony\Component\Form\FormInterface; 16 | use Symfony\Component\Form\FormView; 17 | use Symfony\Component\Form\SubmitButtonTypeInterface; 18 | use Symfony\Component\OptionsResolver\OptionsResolver; 19 | 20 | /** 21 | * A submit button. 22 | * 23 | * @author Bernhard Schussek 24 | */ 25 | class SubmitType extends AbstractType implements SubmitButtonTypeInterface 26 | { 27 | public function buildView(FormView $view, FormInterface $form, array $options): void 28 | { 29 | $view->vars['clicked'] = $form->isClicked(); 30 | 31 | if (!$options['validate']) { 32 | $view->vars['attr']['formnovalidate'] = true; 33 | } 34 | } 35 | 36 | public function configureOptions(OptionsResolver $resolver): void 37 | { 38 | $resolver->setDefault('validate', true); 39 | $resolver->setAllowedTypes('validate', 'bool'); 40 | } 41 | 42 | public function getParent(): ?string 43 | { 44 | return ButtonType::class; 45 | } 46 | 47 | public function getBlockPrefix(): string 48 | { 49 | return 'submit'; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Extension/Core/Type/TelType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\Type; 13 | 14 | use Symfony\Component\Form\AbstractType; 15 | use Symfony\Component\OptionsResolver\OptionsResolver; 16 | 17 | class TelType extends AbstractType 18 | { 19 | public function configureOptions(OptionsResolver $resolver): void 20 | { 21 | $resolver->setDefaults([ 22 | 'invalid_message' => 'Please provide a valid phone number.', 23 | ]); 24 | } 25 | 26 | public function getParent(): ?string 27 | { 28 | return TextType::class; 29 | } 30 | 31 | public function getBlockPrefix(): string 32 | { 33 | return 'tel'; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Extension/Core/Type/TextType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\Type; 13 | 14 | use Symfony\Component\Form\AbstractType; 15 | use Symfony\Component\Form\DataTransformerInterface; 16 | use Symfony\Component\Form\FormBuilderInterface; 17 | use Symfony\Component\OptionsResolver\OptionsResolver; 18 | 19 | class TextType extends AbstractType implements DataTransformerInterface 20 | { 21 | public function buildForm(FormBuilderInterface $builder, array $options): void 22 | { 23 | // When empty_data is explicitly set to an empty string, 24 | // a string should always be returned when NULL is submitted 25 | // This gives more control and thus helps preventing some issues 26 | // with PHP 7 which allows type hinting strings in functions 27 | // See https://github.com/symfony/symfony/issues/5906#issuecomment-203189375 28 | if ('' === $options['empty_data']) { 29 | $builder->addViewTransformer($this); 30 | } 31 | } 32 | 33 | public function configureOptions(OptionsResolver $resolver): void 34 | { 35 | $resolver->setDefaults([ 36 | 'compound' => false, 37 | ]); 38 | } 39 | 40 | public function getBlockPrefix(): string 41 | { 42 | return 'text'; 43 | } 44 | 45 | public function transform(mixed $data): mixed 46 | { 47 | // Model data should not be transformed 48 | return $data; 49 | } 50 | 51 | public function reverseTransform(mixed $data): mixed 52 | { 53 | return $data ?? ''; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Extension/Core/Type/TextareaType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\Type; 13 | 14 | use Symfony\Component\Form\AbstractType; 15 | use Symfony\Component\Form\FormInterface; 16 | use Symfony\Component\Form\FormView; 17 | 18 | class TextareaType extends AbstractType 19 | { 20 | public function buildView(FormView $view, FormInterface $form, array $options): void 21 | { 22 | $view->vars['pattern'] = null; 23 | unset($view->vars['attr']['pattern']); 24 | } 25 | 26 | public function getParent(): ?string 27 | { 28 | return TextType::class; 29 | } 30 | 31 | public function getBlockPrefix(): string 32 | { 33 | return 'textarea'; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Extension/Core/Type/TransformationFailureExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\Type; 13 | 14 | use Symfony\Component\Form\AbstractTypeExtension; 15 | use Symfony\Component\Form\Extension\Core\EventListener\TransformationFailureListener; 16 | use Symfony\Component\Form\FormBuilderInterface; 17 | use Symfony\Contracts\Translation\TranslatorInterface; 18 | 19 | /** 20 | * @author Christian Flothmann 21 | */ 22 | class TransformationFailureExtension extends AbstractTypeExtension 23 | { 24 | public function __construct( 25 | private ?TranslatorInterface $translator = null, 26 | ) { 27 | } 28 | 29 | public function buildForm(FormBuilderInterface $builder, array $options): void 30 | { 31 | if (!isset($options['constraints'])) { 32 | $builder->addEventSubscriber(new TransformationFailureListener($this->translator)); 33 | } 34 | } 35 | 36 | public static function getExtendedTypes(): iterable 37 | { 38 | return [FormType::class]; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Extension/Core/Type/UlidType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\Type; 13 | 14 | use Symfony\Component\Form\AbstractType; 15 | use Symfony\Component\Form\Extension\Core\DataTransformer\UlidToStringTransformer; 16 | use Symfony\Component\Form\FormBuilderInterface; 17 | use Symfony\Component\OptionsResolver\OptionsResolver; 18 | 19 | /** 20 | * @author Pavel Dyakonov 21 | */ 22 | class UlidType extends AbstractType 23 | { 24 | public function buildForm(FormBuilderInterface $builder, array $options): void 25 | { 26 | $builder 27 | ->addViewTransformer(new UlidToStringTransformer()) 28 | ; 29 | } 30 | 31 | public function configureOptions(OptionsResolver $resolver): void 32 | { 33 | $resolver->setDefaults([ 34 | 'compound' => false, 35 | 'invalid_message' => 'Please enter a valid ULID.', 36 | ]); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Extension/Core/Type/UrlType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\Type; 13 | 14 | use Symfony\Component\Form\AbstractType; 15 | use Symfony\Component\Form\Extension\Core\EventListener\FixUrlProtocolListener; 16 | use Symfony\Component\Form\FormBuilderInterface; 17 | use Symfony\Component\Form\FormInterface; 18 | use Symfony\Component\Form\FormView; 19 | use Symfony\Component\OptionsResolver\Options; 20 | use Symfony\Component\OptionsResolver\OptionsResolver; 21 | 22 | class UrlType extends AbstractType 23 | { 24 | public function buildForm(FormBuilderInterface $builder, array $options): void 25 | { 26 | if (null !== $options['default_protocol']) { 27 | $builder->addEventSubscriber(new FixUrlProtocolListener($options['default_protocol'])); 28 | } 29 | } 30 | 31 | public function buildView(FormView $view, FormInterface $form, array $options): void 32 | { 33 | if ($options['default_protocol']) { 34 | $view->vars['attr']['inputmode'] = 'url'; 35 | $view->vars['type'] = 'text'; 36 | } 37 | } 38 | 39 | public function configureOptions(OptionsResolver $resolver): void 40 | { 41 | $resolver->setDefaults([ 42 | 'default_protocol' => static function (Options $options) { 43 | trigger_deprecation('symfony/form', '7.1', 'Not configuring the "default_protocol" option when using the UrlType is deprecated. It will default to "null" in 8.0.'); 44 | 45 | return 'http'; 46 | }, 47 | 'invalid_message' => 'Please enter a valid URL.', 48 | ]); 49 | 50 | $resolver->setAllowedTypes('default_protocol', ['null', 'string']); 51 | } 52 | 53 | public function getParent(): ?string 54 | { 55 | return TextType::class; 56 | } 57 | 58 | public function getBlockPrefix(): string 59 | { 60 | return 'url'; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Extension/Core/Type/UuidType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Core\Type; 13 | 14 | use Symfony\Component\Form\AbstractType; 15 | use Symfony\Component\Form\Extension\Core\DataTransformer\UuidToStringTransformer; 16 | use Symfony\Component\Form\FormBuilderInterface; 17 | use Symfony\Component\OptionsResolver\OptionsResolver; 18 | 19 | /** 20 | * @author Pavel Dyakonov 21 | */ 22 | class UuidType extends AbstractType 23 | { 24 | public function buildForm(FormBuilderInterface $builder, array $options): void 25 | { 26 | $builder 27 | ->addViewTransformer(new UuidToStringTransformer()) 28 | ; 29 | } 30 | 31 | public function configureOptions(OptionsResolver $resolver): void 32 | { 33 | $resolver->setDefaults([ 34 | 'compound' => false, 35 | 'invalid_message' => 'Please enter a valid UUID.', 36 | ]); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Extension/Csrf/CsrfExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Csrf; 13 | 14 | use Symfony\Component\Form\AbstractExtension; 15 | use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; 16 | use Symfony\Contracts\Translation\TranslatorInterface; 17 | 18 | /** 19 | * This extension protects forms by using a CSRF token. 20 | * 21 | * @author Bernhard Schussek 22 | */ 23 | class CsrfExtension extends AbstractExtension 24 | { 25 | public function __construct( 26 | private CsrfTokenManagerInterface $tokenManager, 27 | private ?TranslatorInterface $translator = null, 28 | private ?string $translationDomain = null, 29 | ) { 30 | } 31 | 32 | protected function loadTypeExtensions(): array 33 | { 34 | return [ 35 | new Type\FormTypeCsrfExtension($this->tokenManager, true, '_token', $this->translator, $this->translationDomain), 36 | ]; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Extension/DataCollector/DataCollectorExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\DataCollector; 13 | 14 | use Symfony\Component\Form\AbstractExtension; 15 | 16 | /** 17 | * Extension for collecting data of the forms on a page. 18 | * 19 | * @author Robert Schönthal 20 | * @author Bernhard Schussek 21 | */ 22 | class DataCollectorExtension extends AbstractExtension 23 | { 24 | public function __construct( 25 | private FormDataCollectorInterface $dataCollector, 26 | ) { 27 | } 28 | 29 | protected function loadTypeExtensions(): array 30 | { 31 | return [ 32 | new Type\DataCollectorTypeExtension($this->dataCollector), 33 | ]; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Extension/DataCollector/EventListener/DataCollectorListener.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\DataCollector\EventListener; 13 | 14 | use Symfony\Component\EventDispatcher\EventSubscriberInterface; 15 | use Symfony\Component\Form\Extension\DataCollector\FormDataCollectorInterface; 16 | use Symfony\Component\Form\FormEvent; 17 | use Symfony\Component\Form\FormEvents; 18 | 19 | /** 20 | * Listener that invokes a data collector for the {@link FormEvents::POST_SET_DATA} 21 | * and {@link FormEvents::POST_SUBMIT} events. 22 | * 23 | * @author Bernhard Schussek 24 | */ 25 | class DataCollectorListener implements EventSubscriberInterface 26 | { 27 | public function __construct( 28 | private FormDataCollectorInterface $dataCollector, 29 | ) { 30 | } 31 | 32 | public static function getSubscribedEvents(): array 33 | { 34 | return [ 35 | // Low priority in order to be called as late as possible 36 | FormEvents::POST_SET_DATA => ['postSetData', -255], 37 | // Low priority in order to be called as late as possible 38 | FormEvents::POST_SUBMIT => ['postSubmit', -255], 39 | ]; 40 | } 41 | 42 | /** 43 | * Listener for the {@link FormEvents::POST_SET_DATA} event. 44 | */ 45 | public function postSetData(FormEvent $event): void 46 | { 47 | if ($event->getForm()->isRoot()) { 48 | // Collect basic information about each form 49 | $this->dataCollector->collectConfiguration($event->getForm()); 50 | 51 | // Collect the default data 52 | $this->dataCollector->collectDefaultData($event->getForm()); 53 | } 54 | } 55 | 56 | /** 57 | * Listener for the {@link FormEvents::POST_SUBMIT} event. 58 | */ 59 | public function postSubmit(FormEvent $event): void 60 | { 61 | if ($event->getForm()->isRoot()) { 62 | // Collect the submitted data of each form 63 | $this->dataCollector->collectSubmittedData($event->getForm()); 64 | 65 | // Assemble a form tree 66 | // This is done again after the view is built, but we need it here as the view is not always created. 67 | $this->dataCollector->buildPreliminaryFormTree($event->getForm()); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Extension/DataCollector/FormDataExtractorInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\DataCollector; 13 | 14 | use Symfony\Component\Form\FormInterface; 15 | use Symfony\Component\Form\FormView; 16 | 17 | /** 18 | * Extracts arrays of information out of forms. 19 | * 20 | * @author Bernhard Schussek 21 | */ 22 | interface FormDataExtractorInterface 23 | { 24 | /** 25 | * Extracts the configuration data of a form. 26 | */ 27 | public function extractConfiguration(FormInterface $form): array; 28 | 29 | /** 30 | * Extracts the default data of a form. 31 | */ 32 | public function extractDefaultData(FormInterface $form): array; 33 | 34 | /** 35 | * Extracts the submitted data of a form. 36 | */ 37 | public function extractSubmittedData(FormInterface $form): array; 38 | 39 | /** 40 | * Extracts the view variables of a form. 41 | */ 42 | public function extractViewVariables(FormView $view): array; 43 | } 44 | -------------------------------------------------------------------------------- /Extension/DataCollector/Proxy/ResolvedTypeFactoryDataCollectorProxy.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\DataCollector\Proxy; 13 | 14 | use Symfony\Component\Form\Extension\DataCollector\FormDataCollectorInterface; 15 | use Symfony\Component\Form\FormTypeInterface; 16 | use Symfony\Component\Form\ResolvedFormTypeFactoryInterface; 17 | use Symfony\Component\Form\ResolvedFormTypeInterface; 18 | 19 | /** 20 | * Proxy that wraps resolved types into {@link ResolvedTypeDataCollectorProxy} 21 | * instances. 22 | * 23 | * @author Bernhard Schussek 24 | */ 25 | class ResolvedTypeFactoryDataCollectorProxy implements ResolvedFormTypeFactoryInterface 26 | { 27 | public function __construct( 28 | private ResolvedFormTypeFactoryInterface $proxiedFactory, 29 | private FormDataCollectorInterface $dataCollector, 30 | ) { 31 | } 32 | 33 | public function createResolvedType(FormTypeInterface $type, array $typeExtensions, ?ResolvedFormTypeInterface $parent = null): ResolvedFormTypeInterface 34 | { 35 | return new ResolvedTypeDataCollectorProxy( 36 | $this->proxiedFactory->createResolvedType($type, $typeExtensions, $parent), 37 | $this->dataCollector 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Extension/DataCollector/Type/DataCollectorTypeExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\DataCollector\Type; 13 | 14 | use Symfony\Component\Form\AbstractTypeExtension; 15 | use Symfony\Component\Form\Extension\Core\Type\FormType; 16 | use Symfony\Component\Form\Extension\DataCollector\EventListener\DataCollectorListener; 17 | use Symfony\Component\Form\Extension\DataCollector\FormDataCollectorInterface; 18 | use Symfony\Component\Form\FormBuilderInterface; 19 | 20 | /** 21 | * Type extension for collecting data of a form with this type. 22 | * 23 | * @author Robert Schönthal 24 | * @author Bernhard Schussek 25 | */ 26 | class DataCollectorTypeExtension extends AbstractTypeExtension 27 | { 28 | private DataCollectorListener $listener; 29 | 30 | public function __construct(FormDataCollectorInterface $dataCollector) 31 | { 32 | $this->listener = new DataCollectorListener($dataCollector); 33 | } 34 | 35 | public function buildForm(FormBuilderInterface $builder, array $options): void 36 | { 37 | $builder->addEventSubscriber($this->listener); 38 | } 39 | 40 | public static function getExtendedTypes(): iterable 41 | { 42 | return [FormType::class]; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Extension/HtmlSanitizer/HtmlSanitizerExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\HtmlSanitizer; 13 | 14 | use Psr\Container\ContainerInterface; 15 | use Symfony\Component\Form\AbstractExtension; 16 | 17 | /** 18 | * Integrates the HtmlSanitizer component with the Form library. 19 | * 20 | * @author Nicolas Grekas 21 | */ 22 | class HtmlSanitizerExtension extends AbstractExtension 23 | { 24 | public function __construct( 25 | private ContainerInterface $sanitizers, 26 | private string $defaultSanitizer = 'default', 27 | ) { 28 | } 29 | 30 | protected function loadTypeExtensions(): array 31 | { 32 | return [ 33 | new Type\TextTypeHtmlSanitizerExtension($this->sanitizers, $this->defaultSanitizer), 34 | ]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Extension/HtmlSanitizer/Type/TextTypeHtmlSanitizerExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\HtmlSanitizer\Type; 13 | 14 | use Psr\Container\ContainerInterface; 15 | use Symfony\Component\Form\AbstractTypeExtension; 16 | use Symfony\Component\Form\Extension\Core\Type\TextType; 17 | use Symfony\Component\Form\FormBuilderInterface; 18 | use Symfony\Component\Form\FormEvent; 19 | use Symfony\Component\Form\FormEvents; 20 | use Symfony\Component\OptionsResolver\OptionsResolver; 21 | 22 | /** 23 | * @author Titouan Galopin 24 | */ 25 | class TextTypeHtmlSanitizerExtension extends AbstractTypeExtension 26 | { 27 | public function __construct( 28 | private ContainerInterface $sanitizers, 29 | private string $defaultSanitizer = 'default', 30 | ) { 31 | } 32 | 33 | public static function getExtendedTypes(): iterable 34 | { 35 | return [TextType::class]; 36 | } 37 | 38 | public function configureOptions(OptionsResolver $resolver): void 39 | { 40 | $resolver 41 | ->setDefaults(['sanitize_html' => false, 'sanitizer' => null]) 42 | ->setAllowedTypes('sanitize_html', 'bool') 43 | ->setAllowedTypes('sanitizer', ['string', 'null']) 44 | ; 45 | } 46 | 47 | public function buildForm(FormBuilderInterface $builder, array $options): void 48 | { 49 | if (!$options['sanitize_html']) { 50 | return; 51 | } 52 | 53 | $sanitizers = $this->sanitizers; 54 | $sanitizer = $options['sanitizer'] ?? $this->defaultSanitizer; 55 | 56 | $builder->addEventListener( 57 | FormEvents::PRE_SUBMIT, 58 | static function (FormEvent $event) use ($sanitizers, $sanitizer) { 59 | if (\is_scalar($data = $event->getData()) && '' !== trim($data)) { 60 | $event->setData($sanitizers->get($sanitizer)->sanitize($data)); 61 | } 62 | }, 63 | 10000 /* as soon as possible */ 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Extension/HttpFoundation/HttpFoundationExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\HttpFoundation; 13 | 14 | use Symfony\Component\Form\AbstractExtension; 15 | 16 | /** 17 | * Integrates the HttpFoundation component with the Form library. 18 | * 19 | * @author Bernhard Schussek 20 | */ 21 | class HttpFoundationExtension extends AbstractExtension 22 | { 23 | protected function loadTypeExtensions(): array 24 | { 25 | return [ 26 | new Type\FormTypeHttpFoundationExtension(), 27 | ]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Extension/HttpFoundation/Type/FormTypeHttpFoundationExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\HttpFoundation\Type; 13 | 14 | use Symfony\Component\Form\AbstractTypeExtension; 15 | use Symfony\Component\Form\Extension\Core\Type\FormType; 16 | use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationRequestHandler; 17 | use Symfony\Component\Form\FormBuilderInterface; 18 | use Symfony\Component\Form\RequestHandlerInterface; 19 | 20 | /** 21 | * @author Bernhard Schussek 22 | */ 23 | class FormTypeHttpFoundationExtension extends AbstractTypeExtension 24 | { 25 | private RequestHandlerInterface $requestHandler; 26 | 27 | public function __construct(?RequestHandlerInterface $requestHandler = null) 28 | { 29 | $this->requestHandler = $requestHandler ?? new HttpFoundationRequestHandler(); 30 | } 31 | 32 | public function buildForm(FormBuilderInterface $builder, array $options): void 33 | { 34 | $builder->setRequestHandler($this->requestHandler); 35 | } 36 | 37 | public static function getExtendedTypes(): iterable 38 | { 39 | return [FormType::class]; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Extension/PasswordHasher/PasswordHasherExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\PasswordHasher; 13 | 14 | use Symfony\Component\Form\AbstractExtension; 15 | use Symfony\Component\Form\Extension\PasswordHasher\EventListener\PasswordHasherListener; 16 | 17 | /** 18 | * Integrates the PasswordHasher component with the Form library. 19 | * 20 | * @author Sébastien Alfaiate 21 | */ 22 | class PasswordHasherExtension extends AbstractExtension 23 | { 24 | public function __construct( 25 | private PasswordHasherListener $passwordHasherListener, 26 | ) { 27 | } 28 | 29 | protected function loadTypeExtensions(): array 30 | { 31 | return [ 32 | new Type\FormTypePasswordHasherExtension($this->passwordHasherListener), 33 | new Type\PasswordTypePasswordHasherExtension($this->passwordHasherListener), 34 | ]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Extension/PasswordHasher/Type/FormTypePasswordHasherExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\PasswordHasher\Type; 13 | 14 | use Symfony\Component\Form\AbstractTypeExtension; 15 | use Symfony\Component\Form\Extension\Core\Type\FormType; 16 | use Symfony\Component\Form\Extension\PasswordHasher\EventListener\PasswordHasherListener; 17 | use Symfony\Component\Form\FormBuilderInterface; 18 | use Symfony\Component\Form\FormEvents; 19 | 20 | /** 21 | * @author Sébastien Alfaiate 22 | */ 23 | class FormTypePasswordHasherExtension extends AbstractTypeExtension 24 | { 25 | public function __construct( 26 | private PasswordHasherListener $passwordHasherListener, 27 | ) { 28 | } 29 | 30 | public function buildForm(FormBuilderInterface $builder, array $options): void 31 | { 32 | $builder->addEventListener(FormEvents::POST_SUBMIT, [$this->passwordHasherListener, 'hashPasswords']); 33 | } 34 | 35 | public static function getExtendedTypes(): iterable 36 | { 37 | return [FormType::class]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Extension/PasswordHasher/Type/PasswordTypePasswordHasherExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\PasswordHasher\Type; 13 | 14 | use Symfony\Component\Form\AbstractTypeExtension; 15 | use Symfony\Component\Form\Extension\Core\Type\PasswordType; 16 | use Symfony\Component\Form\Extension\PasswordHasher\EventListener\PasswordHasherListener; 17 | use Symfony\Component\Form\FormBuilderInterface; 18 | use Symfony\Component\Form\FormEvents; 19 | use Symfony\Component\OptionsResolver\OptionsResolver; 20 | use Symfony\Component\PropertyAccess\PropertyPath; 21 | 22 | /** 23 | * @author Sébastien Alfaiate 24 | */ 25 | class PasswordTypePasswordHasherExtension extends AbstractTypeExtension 26 | { 27 | public function __construct( 28 | private PasswordHasherListener $passwordHasherListener, 29 | ) { 30 | } 31 | 32 | public function buildForm(FormBuilderInterface $builder, array $options): void 33 | { 34 | if ($options['hash_property_path']) { 35 | $builder->addEventListener(FormEvents::POST_SUBMIT, [$this->passwordHasherListener, 'registerPassword']); 36 | } 37 | } 38 | 39 | public function configureOptions(OptionsResolver $resolver): void 40 | { 41 | $resolver->setDefaults([ 42 | 'hash_property_path' => null, 43 | ]); 44 | 45 | $resolver->setAllowedTypes('hash_property_path', ['null', 'string', PropertyPath::class]); 46 | 47 | $resolver->setInfo('hash_property_path', 'A valid PropertyAccess syntax where the hashed password will be set.'); 48 | } 49 | 50 | public static function getExtendedTypes(): iterable 51 | { 52 | return [PasswordType::class]; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Extension/Validator/Constraints/Form.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Validator\Constraints; 13 | 14 | use Symfony\Component\Validator\Constraint; 15 | 16 | /** 17 | * @author Bernhard Schussek 18 | */ 19 | class Form extends Constraint 20 | { 21 | public const NOT_SYNCHRONIZED_ERROR = '1dafa156-89e1-4736-b832-419c2e501fca'; 22 | public const NO_SUCH_FIELD_ERROR = '6e5212ed-a197-4339-99aa-5654798a4854'; 23 | 24 | protected const ERROR_NAMES = [ 25 | self::NOT_SYNCHRONIZED_ERROR => 'NOT_SYNCHRONIZED_ERROR', 26 | self::NO_SUCH_FIELD_ERROR => 'NO_SUCH_FIELD_ERROR', 27 | ]; 28 | 29 | public function getTargets(): string|array 30 | { 31 | return self::CLASS_CONSTRAINT; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Extension/Validator/EventListener/ValidationListener.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Validator\EventListener; 13 | 14 | use Symfony\Component\EventDispatcher\EventSubscriberInterface; 15 | use Symfony\Component\Form\Extension\Validator\Constraints\Form; 16 | use Symfony\Component\Form\Extension\Validator\ViolationMapper\ViolationMapperInterface; 17 | use Symfony\Component\Form\FormEvent; 18 | use Symfony\Component\Form\FormEvents; 19 | use Symfony\Component\Validator\Validator\ValidatorInterface; 20 | 21 | /** 22 | * @author Bernhard Schussek 23 | */ 24 | class ValidationListener implements EventSubscriberInterface 25 | { 26 | public static function getSubscribedEvents(): array 27 | { 28 | return [FormEvents::POST_SUBMIT => 'validateForm']; 29 | } 30 | 31 | public function __construct( 32 | private ValidatorInterface $validator, 33 | private ViolationMapperInterface $violationMapper, 34 | ) { 35 | } 36 | 37 | public function validateForm(FormEvent $event): void 38 | { 39 | $form = $event->getForm(); 40 | 41 | if ($form->isRoot()) { 42 | // Form groups are validated internally (FormValidator). Here we don't set groups as they are retrieved into the validator. 43 | foreach ($this->validator->validate($form) as $violation) { 44 | // Allow the "invalid" constraint to be put onto 45 | // non-synchronized forms 46 | $allowNonSynchronized = $violation->getConstraint() instanceof Form && Form::NOT_SYNCHRONIZED_ERROR === $violation->getCode(); 47 | 48 | $this->violationMapper->mapViolation($violation, $form, $allowNonSynchronized); 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Extension/Validator/Type/BaseValidatorExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Validator\Type; 13 | 14 | use Symfony\Component\Form\AbstractTypeExtension; 15 | use Symfony\Component\OptionsResolver\Options; 16 | use Symfony\Component\OptionsResolver\OptionsResolver; 17 | use Symfony\Component\Validator\Constraints\GroupSequence; 18 | 19 | /** 20 | * Encapsulates common logic of {@link FormTypeValidatorExtension} and 21 | * {@link SubmitTypeValidatorExtension}. 22 | * 23 | * @author Bernhard Schussek 24 | */ 25 | abstract class BaseValidatorExtension extends AbstractTypeExtension 26 | { 27 | public function configureOptions(OptionsResolver $resolver): void 28 | { 29 | // Make sure that validation groups end up as null, closure or array 30 | $validationGroupsNormalizer = static function (Options $options, $groups) { 31 | if (false === $groups) { 32 | return []; 33 | } 34 | 35 | if (!$groups) { 36 | return null; 37 | } 38 | 39 | if (\is_callable($groups)) { 40 | return $groups; 41 | } 42 | 43 | if ($groups instanceof GroupSequence) { 44 | return $groups; 45 | } 46 | 47 | return (array) $groups; 48 | }; 49 | 50 | $resolver->setDefaults([ 51 | 'validation_groups' => null, 52 | ]); 53 | 54 | $resolver->setNormalizer('validation_groups', $validationGroupsNormalizer); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Extension/Validator/Type/RepeatedTypeValidatorExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Validator\Type; 13 | 14 | use Symfony\Component\Form\AbstractTypeExtension; 15 | use Symfony\Component\Form\Extension\Core\Type\RepeatedType; 16 | use Symfony\Component\OptionsResolver\Options; 17 | use Symfony\Component\OptionsResolver\OptionsResolver; 18 | 19 | /** 20 | * @author Bernhard Schussek 21 | */ 22 | class RepeatedTypeValidatorExtension extends AbstractTypeExtension 23 | { 24 | public function configureOptions(OptionsResolver $resolver): void 25 | { 26 | // Map errors to the first field 27 | $errorMapping = static fn (Options $options) => ['.' => $options['first_name']]; 28 | 29 | $resolver->setDefaults([ 30 | 'error_mapping' => $errorMapping, 31 | ]); 32 | } 33 | 34 | public static function getExtendedTypes(): iterable 35 | { 36 | return [RepeatedType::class]; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Extension/Validator/Type/SubmitTypeValidatorExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Validator\Type; 13 | 14 | use Symfony\Component\Form\Extension\Core\Type\SubmitType; 15 | 16 | /** 17 | * @author Bernhard Schussek 18 | */ 19 | class SubmitTypeValidatorExtension extends BaseValidatorExtension 20 | { 21 | public static function getExtendedTypes(): iterable 22 | { 23 | return [SubmitType::class]; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Extension/Validator/Type/UploadValidatorExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Validator\Type; 13 | 14 | use Symfony\Component\Form\AbstractTypeExtension; 15 | use Symfony\Component\Form\Extension\Core\Type\FormType; 16 | use Symfony\Component\OptionsResolver\Options; 17 | use Symfony\Component\OptionsResolver\OptionsResolver; 18 | use Symfony\Contracts\Translation\TranslatorInterface; 19 | 20 | /** 21 | * @author Abdellatif Ait boudad 22 | * @author David Badura 23 | */ 24 | class UploadValidatorExtension extends AbstractTypeExtension 25 | { 26 | public function __construct( 27 | private TranslatorInterface $translator, 28 | private ?string $translationDomain = null, 29 | ) { 30 | } 31 | 32 | public function configureOptions(OptionsResolver $resolver): void 33 | { 34 | $translator = $this->translator; 35 | $translationDomain = $this->translationDomain; 36 | $resolver->setNormalizer('upload_max_size_message', static fn (Options $options, $message) => static fn () => $translator->trans($message(), [], $translationDomain)); 37 | } 38 | 39 | public static function getExtendedTypes(): iterable 40 | { 41 | return [FormType::class]; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Extension/Validator/ValidatorExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Validator; 13 | 14 | use Symfony\Component\Form\AbstractExtension; 15 | use Symfony\Component\Form\Extension\Validator\Constraints\Form; 16 | use Symfony\Component\Form\FormRendererInterface; 17 | use Symfony\Component\Form\FormTypeGuesserInterface; 18 | use Symfony\Component\Validator\Constraints\Traverse; 19 | use Symfony\Component\Validator\Mapping\ClassMetadata; 20 | use Symfony\Component\Validator\Validator\ValidatorInterface; 21 | use Symfony\Contracts\Translation\TranslatorInterface; 22 | 23 | /** 24 | * Extension supporting the Symfony Validator component in forms. 25 | * 26 | * @author Bernhard Schussek 27 | */ 28 | class ValidatorExtension extends AbstractExtension 29 | { 30 | public function __construct( 31 | private ValidatorInterface $validator, 32 | private bool $legacyErrorMessages = true, 33 | private ?FormRendererInterface $formRenderer = null, 34 | private ?TranslatorInterface $translator = null, 35 | ) { 36 | $metadata = $validator->getMetadataFor(\Symfony\Component\Form\Form::class); 37 | 38 | // Register the form constraints in the validator programmatically. 39 | // This functionality is required when using the Form component without 40 | // the DIC, where the XML file is loaded automatically. Thus the following 41 | // code must be kept synchronized with validation.xml 42 | 43 | /* @var ClassMetadata $metadata */ 44 | $metadata->addConstraint(new Form()); 45 | $metadata->addConstraint(new Traverse(false)); 46 | } 47 | 48 | public function loadTypeGuesser(): ?FormTypeGuesserInterface 49 | { 50 | return new ValidatorTypeGuesser($this->validator); 51 | } 52 | 53 | protected function loadTypeExtensions(): array 54 | { 55 | return [ 56 | new Type\FormTypeValidatorExtension($this->validator, $this->legacyErrorMessages, $this->formRenderer, $this->translator), 57 | new Type\RepeatedTypeValidatorExtension(), 58 | new Type\SubmitTypeValidatorExtension(), 59 | ]; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Extension/Validator/ViolationMapper/MappingRule.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Validator\ViolationMapper; 13 | 14 | use Symfony\Component\Form\Exception\ErrorMappingException; 15 | use Symfony\Component\Form\FormInterface; 16 | 17 | /** 18 | * @author Bernhard Schussek 19 | */ 20 | class MappingRule 21 | { 22 | public function __construct( 23 | private FormInterface $origin, 24 | private string $propertyPath, 25 | private string $targetPath, 26 | ) { 27 | } 28 | 29 | public function getOrigin(): FormInterface 30 | { 31 | return $this->origin; 32 | } 33 | 34 | /** 35 | * Matches a property path against the rule path. 36 | * 37 | * If the rule matches, the form mapped by the rule is returned. 38 | * Otherwise this method returns false. 39 | */ 40 | public function match(string $propertyPath): ?FormInterface 41 | { 42 | return $propertyPath === $this->propertyPath ? $this->getTarget() : null; 43 | } 44 | 45 | /** 46 | * Matches a property path against a prefix of the rule path. 47 | */ 48 | public function isPrefix(string $propertyPath): bool 49 | { 50 | $length = \strlen($propertyPath); 51 | $prefix = substr($this->propertyPath, 0, $length); 52 | $next = $this->propertyPath[$length] ?? null; 53 | 54 | return $prefix === $propertyPath && ('[' === $next || '.' === $next); 55 | } 56 | 57 | /** 58 | * @throws ErrorMappingException 59 | */ 60 | public function getTarget(): FormInterface 61 | { 62 | $childNames = explode('.', $this->targetPath); 63 | $target = $this->origin; 64 | 65 | foreach ($childNames as $childName) { 66 | if (!$target->has($childName)) { 67 | 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())); 68 | } 69 | $target = $target->get($childName); 70 | } 71 | 72 | return $target; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Extension/Validator/ViolationMapper/RelativePath.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Validator\ViolationMapper; 13 | 14 | use Symfony\Component\Form\FormInterface; 15 | use Symfony\Component\PropertyAccess\PropertyPath; 16 | 17 | /** 18 | * @author Bernhard Schussek 19 | */ 20 | class RelativePath extends PropertyPath 21 | { 22 | public function __construct( 23 | private FormInterface $root, 24 | string $propertyPath, 25 | ) { 26 | parent::__construct($propertyPath); 27 | } 28 | 29 | public function getRoot(): FormInterface 30 | { 31 | return $this->root; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Extension/Validator/ViolationMapper/ViolationMapperInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Validator\ViolationMapper; 13 | 14 | use Symfony\Component\Form\FormInterface; 15 | use Symfony\Component\Validator\ConstraintViolation; 16 | 17 | /** 18 | * @author Bernhard Schussek 19 | */ 20 | interface ViolationMapperInterface 21 | { 22 | /** 23 | * Maps a constraint violation to a form in the form tree under 24 | * the given form. 25 | * 26 | * @param bool $allowNonSynchronized Whether to allow mapping to non-synchronized forms 27 | */ 28 | public function mapViolation(ConstraintViolation $violation, FormInterface $form, bool $allowNonSynchronized = false): void; 29 | } 30 | -------------------------------------------------------------------------------- /Extension/Validator/ViolationMapper/ViolationPathIterator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Extension\Validator\ViolationMapper; 13 | 14 | use Symfony\Component\PropertyAccess\PropertyPathIterator; 15 | 16 | /** 17 | * @author Bernhard Schussek 18 | */ 19 | class ViolationPathIterator extends PropertyPathIterator 20 | { 21 | public function __construct(ViolationPath $violationPath) 22 | { 23 | parent::__construct($violationPath); 24 | } 25 | 26 | public function mapsForm(): bool 27 | { 28 | return $this->path->mapsForm($this->key()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /FileUploadError.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form; 13 | 14 | /** 15 | * @internal 16 | */ 17 | class FileUploadError extends FormError 18 | { 19 | } 20 | -------------------------------------------------------------------------------- /FormBuilderInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form; 13 | 14 | /** 15 | * @author Bernhard Schussek 16 | * 17 | * @extends \Traversable 18 | */ 19 | interface FormBuilderInterface extends \Traversable, \Countable, FormConfigBuilderInterface 20 | { 21 | /** 22 | * Adds a new field to this group. A field must have a unique name within 23 | * the group. Otherwise the existing field is overwritten. 24 | * 25 | * If you add a nested group, this group should also be represented in the 26 | * object hierarchy. 27 | * 28 | * @param array $options 29 | */ 30 | public function add(string|self $child, ?string $type = null, array $options = []): static; 31 | 32 | /** 33 | * Creates a form builder. 34 | * 35 | * @param string $name The name of the form or the name of the property 36 | * @param string|null $type The type of the form or null if name is a property 37 | * @param array $options 38 | */ 39 | public function create(string $name, ?string $type = null, array $options = []): self; 40 | 41 | /** 42 | * Returns a child by name. 43 | * 44 | * @throws Exception\InvalidArgumentException if the given child does not exist 45 | */ 46 | public function get(string $name): self; 47 | 48 | /** 49 | * Removes the field with the given name. 50 | */ 51 | public function remove(string $name): static; 52 | 53 | /** 54 | * Returns whether a field with the given name exists. 55 | */ 56 | public function has(string $name): bool; 57 | 58 | /** 59 | * Returns the children. 60 | * 61 | * @return array 62 | */ 63 | public function all(): array; 64 | 65 | /** 66 | * Creates the form. 67 | */ 68 | public function getForm(): FormInterface; 69 | } 70 | -------------------------------------------------------------------------------- /FormEvent.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form; 13 | 14 | use Symfony\Contracts\EventDispatcher\Event; 15 | 16 | /** 17 | * @author Bernhard Schussek 18 | */ 19 | class FormEvent extends Event 20 | { 21 | public function __construct( 22 | private FormInterface $form, 23 | protected mixed $data, 24 | ) { 25 | } 26 | 27 | /** 28 | * Returns the form at the source of the event. 29 | */ 30 | public function getForm(): FormInterface 31 | { 32 | return $this->form; 33 | } 34 | 35 | /** 36 | * Returns the data associated with this event. 37 | */ 38 | public function getData(): mixed 39 | { 40 | return $this->data; 41 | } 42 | 43 | /** 44 | * Allows updating with some filtered data. 45 | */ 46 | public function setData(mixed $data): void 47 | { 48 | $this->data = $data; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /FormExtensionInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form; 13 | 14 | /** 15 | * Interface for extensions which provide types, type extensions and a guesser. 16 | */ 17 | interface FormExtensionInterface 18 | { 19 | /** 20 | * Returns a type by name. 21 | * 22 | * @param string $name The name of the type 23 | * 24 | * @throws Exception\InvalidArgumentException if the given type is not supported by this extension 25 | */ 26 | public function getType(string $name): FormTypeInterface; 27 | 28 | /** 29 | * Returns whether the given type is supported. 30 | * 31 | * @param string $name The name of the type 32 | */ 33 | public function hasType(string $name): bool; 34 | 35 | /** 36 | * Returns the extensions for the given type. 37 | * 38 | * @param string $name The name of the type 39 | * 40 | * @return FormTypeExtensionInterface[] 41 | */ 42 | public function getTypeExtensions(string $name): array; 43 | 44 | /** 45 | * Returns whether this extension provides type extensions for the given type. 46 | * 47 | * @param string $name The name of the type 48 | */ 49 | public function hasTypeExtensions(string $name): bool; 50 | 51 | /** 52 | * Returns the type guesser provided by this extension. 53 | */ 54 | public function getTypeGuesser(): ?FormTypeGuesserInterface; 55 | } 56 | -------------------------------------------------------------------------------- /FormRegistryInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form; 13 | 14 | /** 15 | * The central registry of the Form component. 16 | * 17 | * @author Bernhard Schussek 18 | */ 19 | interface FormRegistryInterface 20 | { 21 | /** 22 | * Returns a form type by name. 23 | * 24 | * This method registers the type extensions from the form extensions. 25 | * 26 | * @throws Exception\InvalidArgumentException if the type cannot be retrieved from any extension 27 | */ 28 | public function getType(string $name): ResolvedFormTypeInterface; 29 | 30 | /** 31 | * Returns whether the given form type is supported. 32 | */ 33 | public function hasType(string $name): bool; 34 | 35 | /** 36 | * Returns the guesser responsible for guessing types. 37 | */ 38 | public function getTypeGuesser(): ?FormTypeGuesserInterface; 39 | 40 | /** 41 | * Returns the extensions loaded by the framework. 42 | * 43 | * @return FormExtensionInterface[] 44 | */ 45 | public function getExtensions(): array; 46 | } 47 | -------------------------------------------------------------------------------- /FormTypeExtensionInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form; 13 | 14 | use Symfony\Component\OptionsResolver\OptionsResolver; 15 | 16 | /** 17 | * @author Bernhard Schussek 18 | */ 19 | interface FormTypeExtensionInterface 20 | { 21 | /** 22 | * Gets the extended types. 23 | * 24 | * @return string[] 25 | */ 26 | public static function getExtendedTypes(): iterable; 27 | 28 | public function configureOptions(OptionsResolver $resolver): void; 29 | 30 | /** 31 | * Builds the form. 32 | * 33 | * This method is called after the extended type has built the form to 34 | * further modify it. 35 | * 36 | * @param array $options 37 | * 38 | * @see FormTypeInterface::buildForm() 39 | */ 40 | public function buildForm(FormBuilderInterface $builder, array $options): void; 41 | 42 | /** 43 | * Builds the view. 44 | * 45 | * This method is called after the extended type has built the view to 46 | * further modify it. 47 | * 48 | * @param array $options 49 | * 50 | * @see FormTypeInterface::buildView() 51 | */ 52 | public function buildView(FormView $view, FormInterface $form, array $options): void; 53 | 54 | /** 55 | * Finishes the view. 56 | * 57 | * This method is called after the extended type has finished the view to 58 | * further modify it. 59 | * 60 | * @param array $options 61 | * 62 | * @see FormTypeInterface::finishView() 63 | */ 64 | public function finishView(FormView $view, FormInterface $form, array $options): void; 65 | } 66 | -------------------------------------------------------------------------------- /FormTypeGuesserInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form; 13 | 14 | /** 15 | * @author Bernhard Schussek 16 | */ 17 | interface FormTypeGuesserInterface 18 | { 19 | /** 20 | * Returns a field guess for a property name of a class. 21 | */ 22 | public function guessType(string $class, string $property): ?Guess\TypeGuess; 23 | 24 | /** 25 | * Returns a guess whether a property of a class is required. 26 | */ 27 | public function guessRequired(string $class, string $property): ?Guess\ValueGuess; 28 | 29 | /** 30 | * Returns a guess about the field's maximum length. 31 | */ 32 | public function guessMaxLength(string $class, string $property): ?Guess\ValueGuess; 33 | 34 | /** 35 | * Returns a guess about the field's pattern. 36 | */ 37 | public function guessPattern(string $class, string $property): ?Guess\ValueGuess; 38 | } 39 | -------------------------------------------------------------------------------- /Guess/TypeGuess.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Guess; 13 | 14 | /** 15 | * Contains a guessed class name and a list of options for creating an instance 16 | * of that class. 17 | * 18 | * @author Bernhard Schussek 19 | */ 20 | class TypeGuess extends Guess 21 | { 22 | /** 23 | * @param string $type The guessed field type 24 | * @param array $options The options for creating instances of the 25 | * guessed class 26 | * @param int $confidence The confidence that the guessed class name 27 | * is correct 28 | */ 29 | public function __construct( 30 | private string $type, 31 | private array $options, 32 | int $confidence, 33 | ) { 34 | parent::__construct($confidence); 35 | } 36 | 37 | /** 38 | * Returns the guessed field type. 39 | */ 40 | public function getType(): string 41 | { 42 | return $this->type; 43 | } 44 | 45 | /** 46 | * Returns the guessed options for creating instances of the guessed type. 47 | */ 48 | public function getOptions(): array 49 | { 50 | return $this->options; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Guess/ValueGuess.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Guess; 13 | 14 | /** 15 | * Contains a guessed value. 16 | * 17 | * @author Bernhard Schussek 18 | */ 19 | class ValueGuess extends Guess 20 | { 21 | /** 22 | * @param int $confidence The confidence that the guessed class name is correct 23 | */ 24 | public function __construct( 25 | private string|int|bool|null $value, 26 | int $confidence, 27 | ) { 28 | parent::__construct($confidence); 29 | } 30 | 31 | /** 32 | * Returns the guessed value. 33 | */ 34 | public function getValue(): string|int|bool|null 35 | { 36 | return $this->value; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2004-present Fabien Potencier 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /PreloadedExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form; 13 | 14 | use Symfony\Component\Form\Exception\InvalidArgumentException; 15 | 16 | /** 17 | * A form extension with preloaded types, type extensions and type guessers. 18 | * 19 | * @author Bernhard Schussek 20 | */ 21 | class PreloadedExtension implements FormExtensionInterface 22 | { 23 | private array $types = []; 24 | 25 | /** 26 | * Creates a new preloaded extension. 27 | * 28 | * @param FormTypeInterface[] $types The types that the extension should support 29 | * @param FormTypeExtensionInterface[][] $typeExtensions The type extensions that the extension should support 30 | */ 31 | public function __construct( 32 | array $types, 33 | private array $typeExtensions, 34 | private ?FormTypeGuesserInterface $typeGuesser = null, 35 | ) { 36 | foreach ($types as $type) { 37 | $this->types[$type::class] = $type; 38 | } 39 | } 40 | 41 | public function getType(string $name): FormTypeInterface 42 | { 43 | if (!isset($this->types[$name])) { 44 | throw new InvalidArgumentException(\sprintf('The type "%s" cannot be loaded by this extension.', $name)); 45 | } 46 | 47 | return $this->types[$name]; 48 | } 49 | 50 | public function hasType(string $name): bool 51 | { 52 | return isset($this->types[$name]); 53 | } 54 | 55 | public function getTypeExtensions(string $name): array 56 | { 57 | return $this->typeExtensions[$name] 58 | ?? []; 59 | } 60 | 61 | public function hasTypeExtensions(string $name): bool 62 | { 63 | return !empty($this->typeExtensions[$name]); 64 | } 65 | 66 | public function getTypeGuesser(): ?FormTypeGuesserInterface 67 | { 68 | return $this->typeGuesser; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Form Component 2 | ============== 3 | 4 | The Form component allows you to easily create, process and reuse HTML forms. 5 | 6 | Resources 7 | --------- 8 | 9 | * [Documentation](https://symfony.com/doc/current/components/form.html) 10 | * [Contributing](https://symfony.com/doc/current/contributing/index.html) 11 | * [Report issues](https://github.com/symfony/symfony/issues) and 12 | [send Pull Requests](https://github.com/symfony/symfony/pulls) 13 | in the [main Symfony repository](https://github.com/symfony/symfony) 14 | -------------------------------------------------------------------------------- /RequestHandlerInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form; 13 | 14 | /** 15 | * Submits forms if they were submitted. 16 | * 17 | * @author Bernhard Schussek 18 | */ 19 | interface RequestHandlerInterface 20 | { 21 | /** 22 | * Submits a form if it was submitted. 23 | */ 24 | public function handleRequest(FormInterface $form, mixed $request = null): void; 25 | 26 | /** 27 | * Returns true if the given data is a file upload. 28 | */ 29 | public function isFileUpload(mixed $data): bool; 30 | } 31 | -------------------------------------------------------------------------------- /ResolvedFormTypeFactory.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form; 13 | 14 | /** 15 | * @author Bernhard Schussek 16 | */ 17 | class ResolvedFormTypeFactory implements ResolvedFormTypeFactoryInterface 18 | { 19 | public function createResolvedType(FormTypeInterface $type, array $typeExtensions, ?ResolvedFormTypeInterface $parent = null): ResolvedFormTypeInterface 20 | { 21 | return new ResolvedFormType($type, $typeExtensions, $parent); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ResolvedFormTypeFactoryInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form; 13 | 14 | /** 15 | * Creates ResolvedFormTypeInterface instances. 16 | * 17 | * This interface allows you to use your custom ResolvedFormTypeInterface 18 | * implementation, within which you can customize the concrete FormBuilderInterface 19 | * implementations or FormView subclasses that are used by the framework. 20 | * 21 | * @author Bernhard Schussek 22 | */ 23 | interface ResolvedFormTypeFactoryInterface 24 | { 25 | /** 26 | * Resolves a form type. 27 | * 28 | * @param FormTypeExtensionInterface[] $typeExtensions 29 | * 30 | * @throws Exception\UnexpectedTypeException if the types parent {@link FormTypeInterface::getParent()} is not a string 31 | * @throws Exception\InvalidArgumentException if the types parent cannot be retrieved from any extension 32 | */ 33 | public function createResolvedType(FormTypeInterface $type, array $typeExtensions, ?ResolvedFormTypeInterface $parent = null): ResolvedFormTypeInterface; 34 | } 35 | -------------------------------------------------------------------------------- /ResolvedFormTypeInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form; 13 | 14 | use Symfony\Component\OptionsResolver\OptionsResolver; 15 | 16 | /** 17 | * A wrapper for a form type and its extensions. 18 | * 19 | * @author Bernhard Schussek 20 | */ 21 | interface ResolvedFormTypeInterface 22 | { 23 | /** 24 | * Returns the prefix of the template block name for this type. 25 | */ 26 | public function getBlockPrefix(): string; 27 | 28 | /** 29 | * Returns the parent type. 30 | */ 31 | public function getParent(): ?self; 32 | 33 | /** 34 | * Returns the wrapped form type. 35 | */ 36 | public function getInnerType(): FormTypeInterface; 37 | 38 | /** 39 | * Returns the extensions of the wrapped form type. 40 | * 41 | * @return FormTypeExtensionInterface[] 42 | */ 43 | public function getTypeExtensions(): array; 44 | 45 | /** 46 | * Creates a new form builder for this type. 47 | * 48 | * @param string $name The name for the builder 49 | */ 50 | public function createBuilder(FormFactoryInterface $factory, string $name, array $options = []): FormBuilderInterface; 51 | 52 | /** 53 | * Creates a new form view for a form of this type. 54 | */ 55 | public function createView(FormInterface $form, ?FormView $parent = null): FormView; 56 | 57 | /** 58 | * Configures a form builder for the type hierarchy. 59 | */ 60 | public function buildForm(FormBuilderInterface $builder, array $options): void; 61 | 62 | /** 63 | * Configures a form view for the type hierarchy. 64 | * 65 | * It is called before the children of the view are built. 66 | */ 67 | public function buildView(FormView $view, FormInterface $form, array $options): void; 68 | 69 | /** 70 | * Finishes a form view for the type hierarchy. 71 | * 72 | * It is called after the children of the view have been built. 73 | */ 74 | public function finishView(FormView $view, FormInterface $form, array $options): void; 75 | 76 | /** 77 | * Returns the configured options resolver used for this type. 78 | */ 79 | public function getOptionsResolver(): OptionsResolver; 80 | } 81 | -------------------------------------------------------------------------------- /Resources/config/validation.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ReversedTransformer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form; 13 | 14 | /** 15 | * Reverses a transformer. 16 | * 17 | * When the transform() method is called, the reversed transformer's 18 | * reverseTransform() method is called and vice versa. 19 | * 20 | * @author Bernhard Schussek 21 | */ 22 | class ReversedTransformer implements DataTransformerInterface 23 | { 24 | public function __construct( 25 | protected DataTransformerInterface $reversedTransformer, 26 | ) { 27 | } 28 | 29 | public function transform(mixed $value): mixed 30 | { 31 | return $this->reversedTransformer->reverseTransform($value); 32 | } 33 | 34 | public function reverseTransform(mixed $value): mixed 35 | { 36 | return $this->reversedTransformer->transform($value); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /SubmitButton.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form; 13 | 14 | /** 15 | * A button that submits the form. 16 | * 17 | * @author Bernhard Schussek 18 | */ 19 | class SubmitButton extends Button implements ClickableInterface 20 | { 21 | private bool $clicked = false; 22 | 23 | public function isClicked(): bool 24 | { 25 | return $this->clicked; 26 | } 27 | 28 | /** 29 | * Submits data to the button. 30 | * 31 | * @return $this 32 | * 33 | * @throws Exception\AlreadySubmittedException if the form has already been submitted 34 | */ 35 | public function submit(array|string|null $submittedData, bool $clearMissing = true): static 36 | { 37 | if ($this->getConfig()->getDisabled()) { 38 | $this->clicked = false; 39 | 40 | return $this; 41 | } 42 | 43 | parent::submit($submittedData, $clearMissing); 44 | 45 | $this->clicked = null !== $submittedData; 46 | 47 | return $this; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /SubmitButtonBuilder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form; 13 | 14 | /** 15 | * A builder for {@link SubmitButton} instances. 16 | * 17 | * @author Bernhard Schussek 18 | */ 19 | class SubmitButtonBuilder extends ButtonBuilder 20 | { 21 | /** 22 | * Creates the button. 23 | */ 24 | public function getForm(): SubmitButton 25 | { 26 | return new SubmitButton($this->getFormConfig()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /SubmitButtonTypeInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form; 13 | 14 | /** 15 | * A type that should be converted into a {@link SubmitButton} instance. 16 | * 17 | * @author Bernhard Schussek 18 | */ 19 | interface SubmitButtonTypeInterface extends FormTypeInterface 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /Test/FormBuilderInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Test; 13 | 14 | use Symfony\Component\Form\FormBuilderInterface as BaseFormBuilderInterface; 15 | 16 | interface FormBuilderInterface extends \Iterator, BaseFormBuilderInterface 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /Test/FormIntegrationTestCase.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Test; 13 | 14 | use PHPUnit\Framework\TestCase; 15 | use Symfony\Component\Form\FormExtensionInterface; 16 | use Symfony\Component\Form\FormFactoryInterface; 17 | use Symfony\Component\Form\Forms; 18 | use Symfony\Component\Form\FormTypeExtensionInterface; 19 | use Symfony\Component\Form\FormTypeGuesserInterface; 20 | use Symfony\Component\Form\FormTypeInterface; 21 | 22 | /** 23 | * @author Bernhard Schussek 24 | */ 25 | abstract class FormIntegrationTestCase extends TestCase 26 | { 27 | protected FormFactoryInterface $factory; 28 | 29 | protected function setUp(): void 30 | { 31 | $this->factory = Forms::createFormFactoryBuilder() 32 | ->addExtensions($this->getExtensions()) 33 | ->addTypeExtensions($this->getTypeExtensions()) 34 | ->addTypes($this->getTypes()) 35 | ->addTypeGuessers($this->getTypeGuessers()) 36 | ->getFormFactory(); 37 | } 38 | 39 | /** 40 | * @return FormExtensionInterface[] 41 | */ 42 | protected function getExtensions() 43 | { 44 | return []; 45 | } 46 | 47 | /** 48 | * @return FormTypeExtensionInterface[] 49 | */ 50 | protected function getTypeExtensions() 51 | { 52 | return []; 53 | } 54 | 55 | /** 56 | * @return FormTypeInterface[] 57 | */ 58 | protected function getTypes() 59 | { 60 | return []; 61 | } 62 | 63 | /** 64 | * @return FormTypeGuesserInterface[] 65 | */ 66 | protected function getTypeGuessers() 67 | { 68 | return []; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Test/FormInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Test; 13 | 14 | use Symfony\Component\Form\FormInterface as BaseFormInterface; 15 | 16 | interface FormInterface extends \Iterator, BaseFormInterface 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /Test/FormPerformanceTestCase.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Test; 13 | 14 | /** 15 | * Base class for performance tests. 16 | * 17 | * Copied from Doctrine 2's OrmPerformanceTestCase. 18 | * 19 | * @author robo 20 | * @author Bernhard Schussek 21 | */ 22 | abstract class FormPerformanceTestCase extends FormIntegrationTestCase 23 | { 24 | private float $startTime; 25 | protected int $maxRunningTime = 0; 26 | 27 | protected function setUp(): void 28 | { 29 | parent::setUp(); 30 | 31 | $this->startTime = microtime(true); 32 | } 33 | 34 | protected function assertPostConditions(): void 35 | { 36 | parent::assertPostConditions(); 37 | 38 | $time = microtime(true) - $this->startTime; 39 | 40 | if (0 != $this->maxRunningTime && $time > $this->maxRunningTime) { 41 | $this->fail(\sprintf('expected running time: <= %s but was: %s', $this->maxRunningTime, $time)); 42 | } 43 | 44 | $this->expectNotToPerformAssertions(); 45 | } 46 | 47 | /** 48 | * @throws \InvalidArgumentException 49 | */ 50 | public function setMaxRunningTime(int $maxRunningTime): void 51 | { 52 | if ($maxRunningTime < 0) { 53 | throw new \InvalidArgumentException(); 54 | } 55 | 56 | $this->maxRunningTime = $maxRunningTime; 57 | } 58 | 59 | public function getMaxRunningTime(): int 60 | { 61 | return $this->maxRunningTime; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Test/Traits/ValidatorExtensionTrait.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Test\Traits; 13 | 14 | use Symfony\Component\Form\Extension\Validator\ValidatorExtension; 15 | use Symfony\Component\Form\Test\TypeTestCase; 16 | use Symfony\Component\Validator\ConstraintViolationList; 17 | use Symfony\Component\Validator\Mapping\ClassMetadata; 18 | use Symfony\Component\Validator\Validator\ValidatorInterface; 19 | 20 | trait ValidatorExtensionTrait 21 | { 22 | protected ValidatorInterface $validator; 23 | 24 | protected function getValidatorExtension(): ValidatorExtension 25 | { 26 | if (!interface_exists(ValidatorInterface::class)) { 27 | throw new \Exception('In order to use the "ValidatorExtensionTrait", the symfony/validator component must be installed.'); 28 | } 29 | 30 | if (!$this instanceof TypeTestCase) { 31 | throw new \Exception(\sprintf('The trait "ValidatorExtensionTrait" can only be added to a class that extends "%s".', TypeTestCase::class)); 32 | } 33 | 34 | $this->validator = $this->createMock(ValidatorInterface::class); 35 | $metadata = $this->getMockBuilder(ClassMetadata::class)->setConstructorArgs([''])->onlyMethods(['addPropertyConstraint'])->getMock(); 36 | $this->validator->expects($this->any())->method('getMetadataFor')->willReturn($metadata); 37 | $this->validator->expects($this->any())->method('validate')->willReturn(new ConstraintViolationList()); 38 | 39 | return new ValidatorExtension($this->validator, false); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Test/TypeTestCase.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Test; 13 | 14 | use Symfony\Component\EventDispatcher\EventDispatcherInterface; 15 | use Symfony\Component\Form\FormBuilder; 16 | use Symfony\Component\Form\FormExtensionInterface; 17 | use Symfony\Component\Form\Test\Traits\ValidatorExtensionTrait; 18 | 19 | abstract class TypeTestCase extends FormIntegrationTestCase 20 | { 21 | protected FormBuilder $builder; 22 | protected EventDispatcherInterface $dispatcher; 23 | 24 | protected function setUp(): void 25 | { 26 | parent::setUp(); 27 | 28 | $this->dispatcher = $this->createMock(EventDispatcherInterface::class); 29 | $this->builder = new FormBuilder('', null, $this->dispatcher, $this->factory); 30 | } 31 | 32 | /** 33 | * @return FormExtensionInterface[] 34 | */ 35 | protected function getExtensions() 36 | { 37 | $extensions = []; 38 | 39 | if (\in_array(ValidatorExtensionTrait::class, class_uses($this), true)) { 40 | $extensions[] = $this->getValidatorExtension(); 41 | } 42 | 43 | return $extensions; 44 | } 45 | 46 | /** 47 | * @return void 48 | */ 49 | public static function assertDateTimeEquals(\DateTime $expected, \DateTime $actual) 50 | { 51 | self::assertEquals($expected->format('c'), $actual->format('c')); 52 | } 53 | 54 | /** 55 | * @return void 56 | */ 57 | public static function assertDateIntervalEquals(\DateInterval $expected, \DateInterval $actual) 58 | { 59 | self::assertEquals($expected->format('%RP%yY%mM%dDT%hH%iM%sS'), $actual->format('%RP%yY%mM%dDT%hH%iM%sS')); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Util/FormUtil.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Util; 13 | 14 | /** 15 | * @author Bernhard Schussek 16 | */ 17 | class FormUtil 18 | { 19 | /** 20 | * This class should not be instantiated. 21 | */ 22 | private function __construct() 23 | { 24 | } 25 | 26 | /** 27 | * Returns whether the given data is empty. 28 | * 29 | * This logic is reused multiple times throughout the processing of 30 | * a form and needs to be consistent. PHP keyword `empty` cannot 31 | * be used as it also considers 0 and "0" to be empty. 32 | */ 33 | public static function isEmpty(mixed $data): bool 34 | { 35 | // Should not do a check for [] === $data!!! 36 | // This method is used in occurrences where arrays are 37 | // not considered to be empty, ever. 38 | return null === $data || '' === $data; 39 | } 40 | 41 | /** 42 | * Recursively replaces or appends elements of the first array with elements 43 | * of second array. If the key is an integer, the values will be appended to 44 | * the new array; otherwise, the value from the second array will replace 45 | * the one from the first array. 46 | */ 47 | public static function mergeParamsAndFiles(array $params, array $files): array 48 | { 49 | $isFilesList = array_is_list($files); 50 | 51 | foreach ($params as $key => $value) { 52 | if (\is_array($value) && \is_array($files[$key] ?? null)) { 53 | $params[$key] = self::mergeParamsAndFiles($value, $files[$key]); 54 | unset($files[$key]); 55 | } 56 | } 57 | 58 | if (!$isFilesList) { 59 | return array_replace($params, $files); 60 | } 61 | 62 | foreach ($files as $value) { 63 | $params[] = $value; 64 | } 65 | 66 | return $params; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Util/InheritDataAwareIterator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Util; 13 | 14 | /** 15 | * Iterator that traverses an array of forms. 16 | * 17 | * Contrary to \ArrayIterator, this iterator recognizes changes in the original 18 | * array during iteration. 19 | * 20 | * You can wrap the iterator into a {@link \RecursiveIteratorIterator} in order to 21 | * enter any child form that inherits its parent's data and iterate the children 22 | * of that form as well. 23 | * 24 | * @author Bernhard Schussek 25 | */ 26 | class InheritDataAwareIterator extends \IteratorIterator implements \RecursiveIterator 27 | { 28 | public function getChildren(): static 29 | { 30 | return new static($this->current()); 31 | } 32 | 33 | public function hasChildren(): bool 34 | { 35 | return (bool) $this->current()->getConfig()->getInheritData(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Util/ServerParams.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Util; 13 | 14 | use Symfony\Component\HttpFoundation\RequestStack; 15 | 16 | /** 17 | * @author Bernhard Schussek 18 | */ 19 | class ServerParams 20 | { 21 | public function __construct( 22 | private ?RequestStack $requestStack = null, 23 | ) { 24 | } 25 | 26 | /** 27 | * Returns true if the POST max size has been exceeded in the request. 28 | */ 29 | public function hasPostMaxSizeBeenExceeded(): bool 30 | { 31 | $contentLength = $this->getContentLength(); 32 | $maxContentLength = $this->getPostMaxSize(); 33 | 34 | return $maxContentLength && $contentLength > $maxContentLength; 35 | } 36 | 37 | /** 38 | * Returns maximum post size in bytes. 39 | */ 40 | public function getPostMaxSize(): int|float|null 41 | { 42 | $iniMax = strtolower($this->getNormalizedIniPostMaxSize()); 43 | 44 | if ('' === $iniMax) { 45 | return null; 46 | } 47 | 48 | $max = ltrim($iniMax, '+'); 49 | if (str_starts_with($max, '0x')) { 50 | $max = \intval($max, 16); 51 | } elseif (str_starts_with($max, '0')) { 52 | $max = \intval($max, 8); 53 | } else { 54 | $max = (int) $max; 55 | } 56 | 57 | switch (substr($iniMax, -1)) { 58 | case 't': $max *= 1024; 59 | // no break 60 | case 'g': $max *= 1024; 61 | // no break 62 | case 'm': $max *= 1024; 63 | // no break 64 | case 'k': $max *= 1024; 65 | } 66 | 67 | return $max; 68 | } 69 | 70 | /** 71 | * Returns the normalized "post_max_size" ini setting. 72 | */ 73 | public function getNormalizedIniPostMaxSize(): string 74 | { 75 | return strtoupper(trim(\ini_get('post_max_size'))); 76 | } 77 | 78 | /** 79 | * Returns the content length of the request. 80 | */ 81 | public function getContentLength(): mixed 82 | { 83 | if (null !== $this->requestStack && null !== $request = $this->requestStack->getCurrentRequest()) { 84 | return $request->server->get('CONTENT_LENGTH'); 85 | } 86 | 87 | return isset($_SERVER['CONTENT_LENGTH']) 88 | ? (int) $_SERVER['CONTENT_LENGTH'] 89 | : null; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Util/StringUtil.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Form\Util; 13 | 14 | /** 15 | * @author Issei Murasawa 16 | * @author Bernhard Schussek 17 | */ 18 | class StringUtil 19 | { 20 | /** 21 | * This class should not be instantiated. 22 | */ 23 | private function __construct() 24 | { 25 | } 26 | 27 | /** 28 | * Returns the trimmed data. 29 | */ 30 | public static function trim(string $string): string 31 | { 32 | if (null !== $result = @preg_replace('/^[\pZ\p{Cc}\p{Cf}]+|[\pZ\p{Cc}\p{Cf}]+$/u', '', $string)) { 33 | return $result; 34 | } 35 | 36 | return trim($string); 37 | } 38 | 39 | /** 40 | * Converts a fully-qualified class name to a block prefix. 41 | * 42 | * @param string $fqcn The fully-qualified class name 43 | */ 44 | public static function fqcnToBlockPrefix(string $fqcn): ?string 45 | { 46 | // Non-greedy ("+?") to match "type" suffix, if present 47 | if (preg_match('~([^\\\\]+?)(type)?$~i', $fqcn, $matches)) { 48 | return strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], ['\\1_\\2', '\\1_\\2'], $matches[1])); 49 | } 50 | 51 | return null; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "symfony/form", 3 | "type": "library", 4 | "description": "Allows to easily create, process and reuse HTML forms", 5 | "keywords": [], 6 | "homepage": "https://symfony.com", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Fabien Potencier", 11 | "email": "fabien@symfony.com" 12 | }, 13 | { 14 | "name": "Symfony Community", 15 | "homepage": "https://symfony.com/contributors" 16 | } 17 | ], 18 | "require": { 19 | "php": ">=8.2", 20 | "symfony/deprecation-contracts": "^2.5|^3", 21 | "symfony/event-dispatcher": "^6.4|^7.0", 22 | "symfony/options-resolver": "^7.3", 23 | "symfony/polyfill-ctype": "~1.8", 24 | "symfony/polyfill-intl-icu": "^1.21", 25 | "symfony/polyfill-mbstring": "~1.0", 26 | "symfony/property-access": "^6.4|^7.0", 27 | "symfony/service-contracts": "^2.5|^3" 28 | }, 29 | "require-dev": { 30 | "doctrine/collections": "^1.0|^2.0", 31 | "symfony/validator": "^6.4|^7.0", 32 | "symfony/dependency-injection": "^6.4|^7.0", 33 | "symfony/expression-language": "^6.4|^7.0", 34 | "symfony/config": "^6.4|^7.0", 35 | "symfony/console": "^6.4|^7.0", 36 | "symfony/html-sanitizer": "^6.4|^7.0", 37 | "symfony/http-foundation": "^6.4|^7.0", 38 | "symfony/http-kernel": "^6.4|^7.0", 39 | "symfony/intl": "^6.4|^7.0", 40 | "symfony/security-core": "^6.4|^7.0", 41 | "symfony/security-csrf": "^6.4|^7.0", 42 | "symfony/translation": "^6.4.3|^7.0.3", 43 | "symfony/var-dumper": "^6.4|^7.0", 44 | "symfony/uid": "^6.4|^7.0" 45 | }, 46 | "conflict": { 47 | "symfony/console": "<6.4", 48 | "symfony/dependency-injection": "<6.4", 49 | "symfony/doctrine-bridge": "<6.4", 50 | "symfony/error-handler": "<6.4", 51 | "symfony/framework-bundle": "<6.4", 52 | "symfony/http-kernel": "<6.4", 53 | "symfony/translation": "<6.4.3|>=7.0,<7.0.3", 54 | "symfony/translation-contracts": "<2.5", 55 | "symfony/twig-bridge": "<6.4" 56 | }, 57 | "autoload": { 58 | "psr-4": { "Symfony\\Component\\Form\\": "" }, 59 | "exclude-from-classmap": [ 60 | "/Tests/" 61 | ] 62 | }, 63 | "minimum-stability": "dev" 64 | } 65 | --------------------------------------------------------------------------------