vendor/symfony/form/FormConfigBuilder.php line 73

  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\Form;
  11. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  12. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  13. use Symfony\Component\EventDispatcher\ImmutableEventDispatcher;
  14. use Symfony\Component\Form\Exception\BadMethodCallException;
  15. use Symfony\Component\Form\Exception\InvalidArgumentException;
  16. use Symfony\Component\PropertyAccess\PropertyPath;
  17. use Symfony\Component\PropertyAccess\PropertyPathInterface;
  18. /**
  19.  * A basic form configuration.
  20.  *
  21.  * @author Bernhard Schussek <bschussek@gmail.com>
  22.  */
  23. class FormConfigBuilder implements FormConfigBuilderInterface
  24. {
  25.     /**
  26.      * Caches a globally unique {@link NativeRequestHandler} instance.
  27.      */
  28.     private static NativeRequestHandler $nativeRequestHandler;
  29.     /** @var bool */
  30.     protected $locked false;
  31.     private EventDispatcherInterface $dispatcher;
  32.     private string $name;
  33.     private ?PropertyPathInterface $propertyPath null;
  34.     private bool $mapped true;
  35.     private bool $byReference true;
  36.     private bool $inheritData false;
  37.     private bool $compound false;
  38.     private ResolvedFormTypeInterface $type;
  39.     private array $viewTransformers = [];
  40.     private array $modelTransformers = [];
  41.     private ?DataMapperInterface $dataMapper null;
  42.     private bool $required true;
  43.     private bool $disabled false;
  44.     private bool $errorBubbling false;
  45.     private mixed $emptyData null;
  46.     private array $attributes = [];
  47.     private mixed $data null;
  48.     private ?string $dataClass;
  49.     private bool $dataLocked false;
  50.     private FormFactoryInterface $formFactory;
  51.     private string $action '';
  52.     private string $method 'POST';
  53.     private RequestHandlerInterface $requestHandler;
  54.     private bool $autoInitialize false;
  55.     private array $options;
  56.     private ?\Closure $isEmptyCallback null;
  57.     /**
  58.      * Creates an empty form configuration.
  59.      *
  60.      * @param string|null $name      The form name
  61.      * @param string|null $dataClass The class of the form's data
  62.      *
  63.      * @throws InvalidArgumentException if the data class is not a valid class or if
  64.      *                                  the name contains invalid characters
  65.      */
  66.     public function __construct(?string $name, ?string $dataClassEventDispatcherInterface $dispatcher, array $options = [])
  67.     {
  68.         self::validateName($name);
  69.         if (null !== $dataClass && !class_exists($dataClass) && !interface_exists($dataClassfalse)) {
  70.             throw new InvalidArgumentException(sprintf('Class "%s" not found. Is the "data_class" form option set correctly?'$dataClass));
  71.         }
  72.         $this->name = (string) $name;
  73.         $this->dataClass $dataClass;
  74.         $this->dispatcher $dispatcher;
  75.         $this->options $options;
  76.     }
  77.     public function addEventListener(string $eventName, callable $listenerint $priority 0): static
  78.     {
  79.         if ($this->locked) {
  80.             throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  81.         }
  82.         $this->dispatcher->addListener($eventName$listener$priority);
  83.         return $this;
  84.     }
  85.     public function addEventSubscriber(EventSubscriberInterface $subscriber): static
  86.     {
  87.         if ($this->locked) {
  88.             throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  89.         }
  90.         $this->dispatcher->addSubscriber($subscriber);
  91.         return $this;
  92.     }
  93.     public function addViewTransformer(DataTransformerInterface $viewTransformerbool $forcePrepend false): static
  94.     {
  95.         if ($this->locked) {
  96.             throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  97.         }
  98.         if ($forcePrepend) {
  99.             array_unshift($this->viewTransformers$viewTransformer);
  100.         } else {
  101.             $this->viewTransformers[] = $viewTransformer;
  102.         }
  103.         return $this;
  104.     }
  105.     public function resetViewTransformers(): static
  106.     {
  107.         if ($this->locked) {
  108.             throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  109.         }
  110.         $this->viewTransformers = [];
  111.         return $this;
  112.     }
  113.     public function addModelTransformer(DataTransformerInterface $modelTransformerbool $forceAppend false): static
  114.     {
  115.         if ($this->locked) {
  116.             throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  117.         }
  118.         if ($forceAppend) {
  119.             $this->modelTransformers[] = $modelTransformer;
  120.         } else {
  121.             array_unshift($this->modelTransformers$modelTransformer);
  122.         }
  123.         return $this;
  124.     }
  125.     public function resetModelTransformers(): static
  126.     {
  127.         if ($this->locked) {
  128.             throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  129.         }
  130.         $this->modelTransformers = [];
  131.         return $this;
  132.     }
  133.     public function getEventDispatcher(): EventDispatcherInterface
  134.     {
  135.         if ($this->locked && !$this->dispatcher instanceof ImmutableEventDispatcher) {
  136.             $this->dispatcher = new ImmutableEventDispatcher($this->dispatcher);
  137.         }
  138.         return $this->dispatcher;
  139.     }
  140.     public function getName(): string
  141.     {
  142.         return $this->name;
  143.     }
  144.     public function getPropertyPath(): ?PropertyPathInterface
  145.     {
  146.         return $this->propertyPath;
  147.     }
  148.     public function getMapped(): bool
  149.     {
  150.         return $this->mapped;
  151.     }
  152.     public function getByReference(): bool
  153.     {
  154.         return $this->byReference;
  155.     }
  156.     public function getInheritData(): bool
  157.     {
  158.         return $this->inheritData;
  159.     }
  160.     public function getCompound(): bool
  161.     {
  162.         return $this->compound;
  163.     }
  164.     public function getType(): ResolvedFormTypeInterface
  165.     {
  166.         return $this->type;
  167.     }
  168.     public function getViewTransformers(): array
  169.     {
  170.         return $this->viewTransformers;
  171.     }
  172.     public function getModelTransformers(): array
  173.     {
  174.         return $this->modelTransformers;
  175.     }
  176.     public function getDataMapper(): ?DataMapperInterface
  177.     {
  178.         return $this->dataMapper;
  179.     }
  180.     public function getRequired(): bool
  181.     {
  182.         return $this->required;
  183.     }
  184.     public function getDisabled(): bool
  185.     {
  186.         return $this->disabled;
  187.     }
  188.     public function getErrorBubbling(): bool
  189.     {
  190.         return $this->errorBubbling;
  191.     }
  192.     public function getEmptyData(): mixed
  193.     {
  194.         return $this->emptyData;
  195.     }
  196.     public function getAttributes(): array
  197.     {
  198.         return $this->attributes;
  199.     }
  200.     public function hasAttribute(string $name): bool
  201.     {
  202.         return \array_key_exists($name$this->attributes);
  203.     }
  204.     public function getAttribute(string $namemixed $default null): mixed
  205.     {
  206.         return \array_key_exists($name$this->attributes) ? $this->attributes[$name] : $default;
  207.     }
  208.     public function getData(): mixed
  209.     {
  210.         return $this->data;
  211.     }
  212.     public function getDataClass(): ?string
  213.     {
  214.         return $this->dataClass;
  215.     }
  216.     public function getDataLocked(): bool
  217.     {
  218.         return $this->dataLocked;
  219.     }
  220.     public function getFormFactory(): FormFactoryInterface
  221.     {
  222.         if (!isset($this->formFactory)) {
  223.             throw new BadMethodCallException('The form factory must be set before retrieving it.');
  224.         }
  225.         return $this->formFactory;
  226.     }
  227.     public function getAction(): string
  228.     {
  229.         return $this->action;
  230.     }
  231.     public function getMethod(): string
  232.     {
  233.         return $this->method;
  234.     }
  235.     public function getRequestHandler(): RequestHandlerInterface
  236.     {
  237.         return $this->requestHandler ??= self::$nativeRequestHandler ??= new NativeRequestHandler();
  238.     }
  239.     public function getAutoInitialize(): bool
  240.     {
  241.         return $this->autoInitialize;
  242.     }
  243.     public function getOptions(): array
  244.     {
  245.         return $this->options;
  246.     }
  247.     public function hasOption(string $name): bool
  248.     {
  249.         return \array_key_exists($name$this->options);
  250.     }
  251.     public function getOption(string $namemixed $default null): mixed
  252.     {
  253.         return \array_key_exists($name$this->options) ? $this->options[$name] : $default;
  254.     }
  255.     public function getIsEmptyCallback(): ?callable
  256.     {
  257.         return $this->isEmptyCallback;
  258.     }
  259.     public function setAttribute(string $namemixed $value): static
  260.     {
  261.         if ($this->locked) {
  262.             throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  263.         }
  264.         $this->attributes[$name] = $value;
  265.         return $this;
  266.     }
  267.     public function setAttributes(array $attributes): static
  268.     {
  269.         if ($this->locked) {
  270.             throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  271.         }
  272.         $this->attributes $attributes;
  273.         return $this;
  274.     }
  275.     public function setDataMapper(DataMapperInterface $dataMapper null): static
  276.     {
  277.         if (\func_num_args()) {
  278.             trigger_deprecation('symfony/form''6.2''Calling "%s()" without any arguments is deprecated, pass null explicitly instead.'__METHOD__);
  279.         }
  280.         if ($this->locked) {
  281.             throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  282.         }
  283.         $this->dataMapper $dataMapper;
  284.         return $this;
  285.     }
  286.     public function setDisabled(bool $disabled): static
  287.     {
  288.         if ($this->locked) {
  289.             throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  290.         }
  291.         $this->disabled $disabled;
  292.         return $this;
  293.     }
  294.     public function setEmptyData(mixed $emptyData): static
  295.     {
  296.         if ($this->locked) {
  297.             throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  298.         }
  299.         $this->emptyData $emptyData;
  300.         return $this;
  301.     }
  302.     public function setErrorBubbling(bool $errorBubbling): static
  303.     {
  304.         if ($this->locked) {
  305.             throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  306.         }
  307.         $this->errorBubbling $errorBubbling;
  308.         return $this;
  309.     }
  310.     public function setRequired(bool $required): static
  311.     {
  312.         if ($this->locked) {
  313.             throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  314.         }
  315.         $this->required $required;
  316.         return $this;
  317.     }
  318.     public function setPropertyPath(string|PropertyPathInterface|null $propertyPath): static
  319.     {
  320.         if ($this->locked) {
  321.             throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  322.         }
  323.         if (null !== $propertyPath && !$propertyPath instanceof PropertyPathInterface) {
  324.             $propertyPath = new PropertyPath($propertyPath);
  325.         }
  326.         $this->propertyPath $propertyPath;
  327.         return $this;
  328.     }
  329.     public function setMapped(bool $mapped): static
  330.     {
  331.         if ($this->locked) {
  332.             throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  333.         }
  334.         $this->mapped $mapped;
  335.         return $this;
  336.     }
  337.     public function setByReference(bool $byReference): static
  338.     {
  339.         if ($this->locked) {
  340.             throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  341.         }
  342.         $this->byReference $byReference;
  343.         return $this;
  344.     }
  345.     public function setInheritData(bool $inheritData): static
  346.     {
  347.         if ($this->locked) {
  348.             throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  349.         }
  350.         $this->inheritData $inheritData;
  351.         return $this;
  352.     }
  353.     public function setCompound(bool $compound): static
  354.     {
  355.         if ($this->locked) {
  356.             throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  357.         }
  358.         $this->compound $compound;
  359.         return $this;
  360.     }
  361.     public function setType(ResolvedFormTypeInterface $type): static
  362.     {
  363.         if ($this->locked) {
  364.             throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  365.         }
  366.         $this->type $type;
  367.         return $this;
  368.     }
  369.     public function setData(mixed $data): static
  370.     {
  371.         if ($this->locked) {
  372.             throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  373.         }
  374.         $this->data $data;
  375.         return $this;
  376.     }
  377.     public function setDataLocked(bool $locked): static
  378.     {
  379.         if ($this->locked) {
  380.             throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  381.         }
  382.         $this->dataLocked $locked;
  383.         return $this;
  384.     }
  385.     public function setFormFactory(FormFactoryInterface $formFactory)
  386.     {
  387.         if ($this->locked) {
  388.             throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  389.         }
  390.         $this->formFactory $formFactory;
  391.         return $this;
  392.     }
  393.     public function setAction(string $action): static
  394.     {
  395.         if ($this->locked) {
  396.             throw new BadMethodCallException('The config builder cannot be modified anymore.');
  397.         }
  398.         $this->action $action;
  399.         return $this;
  400.     }
  401.     public function setMethod(string $method): static
  402.     {
  403.         if ($this->locked) {
  404.             throw new BadMethodCallException('The config builder cannot be modified anymore.');
  405.         }
  406.         $this->method strtoupper($method);
  407.         return $this;
  408.     }
  409.     public function setRequestHandler(RequestHandlerInterface $requestHandler): static
  410.     {
  411.         if ($this->locked) {
  412.             throw new BadMethodCallException('The config builder cannot be modified anymore.');
  413.         }
  414.         $this->requestHandler $requestHandler;
  415.         return $this;
  416.     }
  417.     public function setAutoInitialize(bool $initialize): static
  418.     {
  419.         if ($this->locked) {
  420.             throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  421.         }
  422.         $this->autoInitialize $initialize;
  423.         return $this;
  424.     }
  425.     public function getFormConfig(): FormConfigInterface
  426.     {
  427.         if ($this->locked) {
  428.             throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  429.         }
  430.         // This method should be idempotent, so clone the builder
  431.         $config = clone $this;
  432.         $config->locked true;
  433.         return $config;
  434.     }
  435.     public function setIsEmptyCallback(?callable $isEmptyCallback): static
  436.     {
  437.         $this->isEmptyCallback null === $isEmptyCallback null $isEmptyCallback(...);
  438.         return $this;
  439.     }
  440.     /**
  441.      * Validates whether the given variable is a valid form name.
  442.      *
  443.      * @throws InvalidArgumentException if the name contains invalid characters
  444.      *
  445.      * @internal
  446.      */
  447.     final public static function validateName(?string $name)
  448.     {
  449.         if (!self::isValidName($name)) {
  450.             throw new InvalidArgumentException(sprintf('The name "%s" contains illegal characters. Names should start with a letter, digit or underscore and only contain letters, digits, numbers, underscores ("_"), hyphens ("-") and colons (":").'$name));
  451.         }
  452.     }
  453.     /**
  454.      * Returns whether the given variable contains a valid form name.
  455.      *
  456.      * A name is accepted if it
  457.      *
  458.      *   * is empty
  459.      *   * starts with a letter, digit or underscore
  460.      *   * contains only letters, digits, numbers, underscores ("_"),
  461.      *     hyphens ("-") and colons (":")
  462.      */
  463.     final public static function isValidName(?string $name): bool
  464.     {
  465.         return '' === $name || null === $name || preg_match('/^[a-zA-Z0-9_][a-zA-Z0-9_\-:]*$/D'$name);
  466.     }
  467. }