vendor/symfony/form/Form.php line 71

Open in your IDE?
  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\LegacyEventDispatcherProxy;
  12. use Symfony\Component\Form\Event\PostSetDataEvent;
  13. use Symfony\Component\Form\Event\PostSubmitEvent;
  14. use Symfony\Component\Form\Event\PreSetDataEvent;
  15. use Symfony\Component\Form\Event\PreSubmitEvent;
  16. use Symfony\Component\Form\Event\SubmitEvent;
  17. use Symfony\Component\Form\Exception\AlreadySubmittedException;
  18. use Symfony\Component\Form\Exception\LogicException;
  19. use Symfony\Component\Form\Exception\OutOfBoundsException;
  20. use Symfony\Component\Form\Exception\RuntimeException;
  21. use Symfony\Component\Form\Exception\TransformationFailedException;
  22. use Symfony\Component\Form\Exception\UnexpectedTypeException;
  23. use Symfony\Component\Form\Extension\Core\Type\TextType;
  24. use Symfony\Component\Form\Util\FormUtil;
  25. use Symfony\Component\Form\Util\InheritDataAwareIterator;
  26. use Symfony\Component\Form\Util\OrderedHashMap;
  27. use Symfony\Component\PropertyAccess\PropertyPath;
  28. use Symfony\Component\PropertyAccess\PropertyPathInterface;
  29. /**
  30.  * Form represents a form.
  31.  *
  32.  * To implement your own form fields, you need to have a thorough understanding
  33.  * of the data flow within a form. A form stores its data in three different
  34.  * representations:
  35.  *
  36.  *   (1) the "model" format required by the form's object
  37.  *   (2) the "normalized" format for internal processing
  38.  *   (3) the "view" format used for display simple fields
  39.  *       or map children model data for compound fields
  40.  *
  41.  * A date field, for example, may store a date as "Y-m-d" string (1) in the
  42.  * object. To facilitate processing in the field, this value is normalized
  43.  * to a DateTime object (2). In the HTML representation of your form, a
  44.  * localized string (3) may be presented to and modified by the user, or it could be an array of values
  45.  * to be mapped to choices fields.
  46.  *
  47.  * In most cases, format (1) and format (2) will be the same. For example,
  48.  * a checkbox field uses a Boolean value for both internal processing and
  49.  * storage in the object. In these cases you need to set a view transformer
  50.  * to convert between formats (2) and (3). You can do this by calling
  51.  * addViewTransformer().
  52.  *
  53.  * In some cases though it makes sense to make format (1) configurable. To
  54.  * demonstrate this, let's extend our above date field to store the value
  55.  * either as "Y-m-d" string or as timestamp. Internally we still want to
  56.  * use a DateTime object for processing. To convert the data from string/integer
  57.  * to DateTime you can set a model transformer by calling
  58.  * addModelTransformer(). The normalized data is then converted to the displayed
  59.  * data as described before.
  60.  *
  61.  * The conversions (1) -> (2) -> (3) use the transform methods of the transformers.
  62.  * The conversions (3) -> (2) -> (1) use the reverseTransform methods of the transformers.
  63.  *
  64.  * @author Fabien Potencier <fabien@symfony.com>
  65.  * @author Bernhard Schussek <bschussek@gmail.com>
  66.  */
  67. class Form implements \IteratorAggregateFormInterfaceClearableErrorsInterface
  68. {
  69.     /**
  70.      * @var FormConfigInterface
  71.      */
  72.     private $config;
  73.     /**
  74.      * @var FormInterface|null
  75.      */
  76.     private $parent;
  77.     /**
  78.      * A map of FormInterface instances.
  79.      *
  80.      * @var FormInterface[]|OrderedHashMap
  81.      */
  82.     private $children;
  83.     /**
  84.      * @var FormError[]
  85.      */
  86.     private $errors = [];
  87.     /**
  88.      * @var bool
  89.      */
  90.     private $submitted false;
  91.     /**
  92.      * The button that was used to submit the form.
  93.      *
  94.      * @var FormInterface|ClickableInterface|null
  95.      */
  96.     private $clickedButton;
  97.     /**
  98.      * @var mixed
  99.      */
  100.     private $modelData;
  101.     /**
  102.      * @var mixed
  103.      */
  104.     private $normData;
  105.     /**
  106.      * @var mixed
  107.      */
  108.     private $viewData;
  109.     /**
  110.      * The submitted values that don't belong to any children.
  111.      *
  112.      * @var array
  113.      */
  114.     private $extraData = [];
  115.     /**
  116.      * The transformation failure generated during submission, if any.
  117.      *
  118.      * @var TransformationFailedException|null
  119.      */
  120.     private $transformationFailure;
  121.     /**
  122.      * Whether the form's data has been initialized.
  123.      *
  124.      * When the data is initialized with its default value, that default value
  125.      * is passed through the transformer chain in order to synchronize the
  126.      * model, normalized and view format for the first time. This is done
  127.      * lazily in order to save performance when {@link setData()} is called
  128.      * manually, making the initialization with the configured default value
  129.      * superfluous.
  130.      *
  131.      * @var bool
  132.      */
  133.     private $defaultDataSet false;
  134.     /**
  135.      * Whether setData() is currently being called.
  136.      *
  137.      * @var bool
  138.      */
  139.     private $lockSetData false;
  140.     /**
  141.      * @var string
  142.      */
  143.     private $name '';
  144.     /**
  145.      * Whether the form inherits its underlying data from its parent.
  146.      *
  147.      * @var bool
  148.      */
  149.     private $inheritData;
  150.     /**
  151.      * @var PropertyPathInterface|null
  152.      */
  153.     private $propertyPath;
  154.     /**
  155.      * @throws LogicException if a data mapper is not provided for a compound form
  156.      */
  157.     public function __construct(FormConfigInterface $config)
  158.     {
  159.         // Compound forms always need a data mapper, otherwise calls to
  160.         // `setData` and `add` will not lead to the correct population of
  161.         // the child forms.
  162.         if ($config->getCompound() && !$config->getDataMapper()) {
  163.             throw new LogicException('Compound forms need a data mapper.');
  164.         }
  165.         // If the form inherits the data from its parent, it is not necessary
  166.         // to call setData() with the default data.
  167.         if ($this->inheritData $config->getInheritData()) {
  168.             $this->defaultDataSet true;
  169.         }
  170.         $this->config $config;
  171.         $this->children = new OrderedHashMap();
  172.         $this->name $config->getName();
  173.     }
  174.     public function __clone()
  175.     {
  176.         $this->children = clone $this->children;
  177.         foreach ($this->children as $key => $child) {
  178.             $this->children[$key] = clone $child;
  179.         }
  180.     }
  181.     /**
  182.      * {@inheritdoc}
  183.      */
  184.     public function getConfig()
  185.     {
  186.         return $this->config;
  187.     }
  188.     /**
  189.      * {@inheritdoc}
  190.      */
  191.     public function getName()
  192.     {
  193.         return $this->name;
  194.     }
  195.     /**
  196.      * {@inheritdoc}
  197.      */
  198.     public function getPropertyPath()
  199.     {
  200.         if ($this->propertyPath || $this->propertyPath $this->config->getPropertyPath()) {
  201.             return $this->propertyPath;
  202.         }
  203.         if ('' === $this->name) {
  204.             return null;
  205.         }
  206.         $parent $this->parent;
  207.         while ($parent && $parent->getConfig()->getInheritData()) {
  208.             $parent $parent->getParent();
  209.         }
  210.         if ($parent && null === $parent->getConfig()->getDataClass()) {
  211.             $this->propertyPath = new PropertyPath('['.$this->name.']');
  212.         } else {
  213.             $this->propertyPath = new PropertyPath($this->name);
  214.         }
  215.         return $this->propertyPath;
  216.     }
  217.     /**
  218.      * {@inheritdoc}
  219.      */
  220.     public function isRequired()
  221.     {
  222.         if (null === $this->parent || $this->parent->isRequired()) {
  223.             return $this->config->getRequired();
  224.         }
  225.         return false;
  226.     }
  227.     /**
  228.      * {@inheritdoc}
  229.      */
  230.     public function isDisabled()
  231.     {
  232.         if (null === $this->parent || !$this->parent->isDisabled()) {
  233.             return $this->config->getDisabled();
  234.         }
  235.         return true;
  236.     }
  237.     /**
  238.      * {@inheritdoc}
  239.      */
  240.     public function setParent(FormInterface $parent null)
  241.     {
  242.         if ($this->submitted) {
  243.             throw new AlreadySubmittedException('You cannot set the parent of a submitted form.');
  244.         }
  245.         if (null !== $parent && '' === $this->name) {
  246.             throw new LogicException('A form with an empty name cannot have a parent form.');
  247.         }
  248.         $this->parent $parent;
  249.         return $this;
  250.     }
  251.     /**
  252.      * {@inheritdoc}
  253.      */
  254.     public function getParent()
  255.     {
  256.         return $this->parent;
  257.     }
  258.     /**
  259.      * {@inheritdoc}
  260.      */
  261.     public function getRoot()
  262.     {
  263.         return $this->parent $this->parent->getRoot() : $this;
  264.     }
  265.     /**
  266.      * {@inheritdoc}
  267.      */
  268.     public function isRoot()
  269.     {
  270.         return null === $this->parent;
  271.     }
  272.     /**
  273.      * {@inheritdoc}
  274.      */
  275.     public function setData($modelData)
  276.     {
  277.         // If the form is submitted while disabled, it is set to submitted, but the data is not
  278.         // changed. In such cases (i.e. when the form is not initialized yet) don't
  279.         // abort this method.
  280.         if ($this->submitted && $this->defaultDataSet) {
  281.             throw new AlreadySubmittedException('You cannot change the data of a submitted form.');
  282.         }
  283.         // If the form inherits its parent's data, disallow data setting to
  284.         // prevent merge conflicts
  285.         if ($this->inheritData) {
  286.             throw new RuntimeException('You cannot change the data of a form inheriting its parent data.');
  287.         }
  288.         // Don't allow modifications of the configured data if the data is locked
  289.         if ($this->config->getDataLocked() && $modelData !== $this->config->getData()) {
  290.             return $this;
  291.         }
  292.         if (\is_object($modelData) && !$this->config->getByReference()) {
  293.             $modelData = clone $modelData;
  294.         }
  295.         if ($this->lockSetData) {
  296.             throw new RuntimeException('A cycle was detected. Listeners to the PRE_SET_DATA event must not call setData(). You should call setData() on the FormEvent object instead.');
  297.         }
  298.         $this->lockSetData true;
  299.         $dispatcher LegacyEventDispatcherProxy::decorate($this->config->getEventDispatcher());
  300.         // Hook to change content of the model data before transformation and mapping children
  301.         if ($dispatcher->hasListeners(FormEvents::PRE_SET_DATA)) {
  302.             $event = new PreSetDataEvent($this$modelData);
  303.             $dispatcher->dispatch($eventFormEvents::PRE_SET_DATA);
  304.             $modelData $event->getData();
  305.         }
  306.         // Treat data as strings unless a transformer exists
  307.         if (is_scalar($modelData) && !$this->config->getViewTransformers() && !$this->config->getModelTransformers()) {
  308.             $modelData = (string) $modelData;
  309.         }
  310.         // Synchronize representations - must not change the content!
  311.         // Transformation exceptions are not caught on initialization
  312.         $normData $this->modelToNorm($modelData);
  313.         $viewData $this->normToView($normData);
  314.         // Validate if view data matches data class (unless empty)
  315.         if (!FormUtil::isEmpty($viewData)) {
  316.             $dataClass $this->config->getDataClass();
  317.             if (null !== $dataClass && !$viewData instanceof $dataClass) {
  318.                 $actualType = \is_object($viewData)
  319.                     ? 'an instance of class '.\get_class($viewData)
  320.                     : 'a(n) '.\gettype($viewData);
  321.                 throw new LogicException('The form\'s view data is expected to be an instance of class '.$dataClass.', but is '.$actualType.'. You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms '.$actualType.' to an instance of '.$dataClass.'.');
  322.             }
  323.         }
  324.         $this->modelData $modelData;
  325.         $this->normData $normData;
  326.         $this->viewData $viewData;
  327.         $this->defaultDataSet true;
  328.         $this->lockSetData false;
  329.         // Compound forms don't need to invoke this method if they don't have children
  330.         if (\count($this->children) > 0) {
  331.             // Update child forms from the data (unless their config data is locked)
  332.             $this->config->getDataMapper()->mapDataToForms($viewData, new \RecursiveIteratorIterator(new InheritDataAwareIterator($this->children)));
  333.         }
  334.         if ($dispatcher->hasListeners(FormEvents::POST_SET_DATA)) {
  335.             $event = new PostSetDataEvent($this$modelData);
  336.             $dispatcher->dispatch($eventFormEvents::POST_SET_DATA);
  337.         }
  338.         return $this;
  339.     }
  340.     /**
  341.      * {@inheritdoc}
  342.      */
  343.     public function getData()
  344.     {
  345.         if ($this->inheritData) {
  346.             if (!$this->parent) {
  347.                 throw new RuntimeException('The form is configured to inherit its parent\'s data, but does not have a parent.');
  348.             }
  349.             return $this->parent->getData();
  350.         }
  351.         if (!$this->defaultDataSet) {
  352.             if ($this->lockSetData) {
  353.                 throw new RuntimeException('A cycle was detected. Listeners to the PRE_SET_DATA event must not call getData() if the form data has not already been set. You should call getData() on the FormEvent object instead.');
  354.             }
  355.             $this->setData($this->config->getData());
  356.         }
  357.         return $this->modelData;
  358.     }
  359.     /**
  360.      * {@inheritdoc}
  361.      */
  362.     public function getNormData()
  363.     {
  364.         if ($this->inheritData) {
  365.             if (!$this->parent) {
  366.                 throw new RuntimeException('The form is configured to inherit its parent\'s data, but does not have a parent.');
  367.             }
  368.             return $this->parent->getNormData();
  369.         }
  370.         if (!$this->defaultDataSet) {
  371.             if ($this->lockSetData) {
  372.                 throw new RuntimeException('A cycle was detected. Listeners to the PRE_SET_DATA event must not call getNormData() if the form data has not already been set.');
  373.             }
  374.             $this->setData($this->config->getData());
  375.         }
  376.         return $this->normData;
  377.     }
  378.     /**
  379.      * {@inheritdoc}
  380.      */
  381.     public function getViewData()
  382.     {
  383.         if ($this->inheritData) {
  384.             if (!$this->parent) {
  385.                 throw new RuntimeException('The form is configured to inherit its parent\'s data, but does not have a parent.');
  386.             }
  387.             return $this->parent->getViewData();
  388.         }
  389.         if (!$this->defaultDataSet) {
  390.             if ($this->lockSetData) {
  391.                 throw new RuntimeException('A cycle was detected. Listeners to the PRE_SET_DATA event must not call getViewData() if the form data has not already been set.');
  392.             }
  393.             $this->setData($this->config->getData());
  394.         }
  395.         return $this->viewData;
  396.     }
  397.     /**
  398.      * {@inheritdoc}
  399.      */
  400.     public function getExtraData()
  401.     {
  402.         return $this->extraData;
  403.     }
  404.     /**
  405.      * {@inheritdoc}
  406.      */
  407.     public function initialize()
  408.     {
  409.         if (null !== $this->parent) {
  410.             throw new RuntimeException('Only root forms should be initialized.');
  411.         }
  412.         // Guarantee that the *_SET_DATA events have been triggered once the
  413.         // form is initialized. This makes sure that dynamically added or
  414.         // removed fields are already visible after initialization.
  415.         if (!$this->defaultDataSet) {
  416.             $this->setData($this->config->getData());
  417.         }
  418.         return $this;
  419.     }
  420.     /**
  421.      * {@inheritdoc}
  422.      */
  423.     public function handleRequest($request null)
  424.     {
  425.         $this->config->getRequestHandler()->handleRequest($this$request);
  426.         return $this;
  427.     }
  428.     /**
  429.      * {@inheritdoc}
  430.      */
  431.     public function submit($submittedData$clearMissing true)
  432.     {
  433.         if ($this->submitted) {
  434.             throw new AlreadySubmittedException('A form can only be submitted once.');
  435.         }
  436.         // Initialize errors in the very beginning so we're sure
  437.         // they are collectable during submission only
  438.         $this->errors = [];
  439.         // Obviously, a disabled form should not change its data upon submission.
  440.         if ($this->isDisabled()) {
  441.             $this->submitted true;
  442.             return $this;
  443.         }
  444.         // The data must be initialized if it was not initialized yet.
  445.         // This is necessary to guarantee that the *_SET_DATA listeners
  446.         // are always invoked before submit() takes place.
  447.         if (!$this->defaultDataSet) {
  448.             $this->setData($this->config->getData());
  449.         }
  450.         // Treat false as NULL to support binding false to checkboxes.
  451.         // Don't convert NULL to a string here in order to determine later
  452.         // whether an empty value has been submitted or whether no value has
  453.         // been submitted at all. This is important for processing checkboxes
  454.         // and radio buttons with empty values.
  455.         if (false === $submittedData) {
  456.             $submittedData null;
  457.         } elseif (is_scalar($submittedData)) {
  458.             $submittedData = (string) $submittedData;
  459.         } elseif ($this->config->getRequestHandler()->isFileUpload($submittedData)) {
  460.             if (!$this->config->getOption('allow_file_upload')) {
  461.                 $submittedData null;
  462.                 $this->transformationFailure = new TransformationFailedException('Submitted data was expected to be text or number, file upload given.');
  463.             }
  464.         } elseif (\is_array($submittedData) && !$this->config->getCompound() && !$this->config->hasOption('multiple')) {
  465.             $submittedData null;
  466.             $this->transformationFailure = new TransformationFailedException('Submitted data was expected to be text or number, array given.');
  467.         }
  468.         $dispatcher LegacyEventDispatcherProxy::decorate($this->config->getEventDispatcher());
  469.         $modelData null;
  470.         $normData null;
  471.         $viewData null;
  472.         try {
  473.             if (null !== $this->transformationFailure) {
  474.                 throw $this->transformationFailure;
  475.             }
  476.             // Hook to change content of the data submitted by the browser
  477.             if ($dispatcher->hasListeners(FormEvents::PRE_SUBMIT)) {
  478.                 $event = new PreSubmitEvent($this$submittedData);
  479.                 $dispatcher->dispatch($eventFormEvents::PRE_SUBMIT);
  480.                 $submittedData $event->getData();
  481.             }
  482.             // Check whether the form is compound.
  483.             // This check is preferable over checking the number of children,
  484.             // since forms without children may also be compound.
  485.             // (think of empty collection forms)
  486.             if ($this->config->getCompound()) {
  487.                 if (null === $submittedData) {
  488.                     $submittedData = [];
  489.                 }
  490.                 if (!\is_array($submittedData)) {
  491.                     throw new TransformationFailedException('Compound forms expect an array or NULL on submission.');
  492.                 }
  493.                 foreach ($this->children as $name => $child) {
  494.                     $isSubmitted = \array_key_exists($name$submittedData);
  495.                     if ($isSubmitted || $clearMissing) {
  496.                         $child->submit($isSubmitted $submittedData[$name] : null$clearMissing);
  497.                         unset($submittedData[$name]);
  498.                         if (null !== $this->clickedButton) {
  499.                             continue;
  500.                         }
  501.                         if ($child instanceof ClickableInterface && $child->isClicked()) {
  502.                             $this->clickedButton $child;
  503.                             continue;
  504.                         }
  505.                         if (method_exists($child'getClickedButton') && null !== $child->getClickedButton()) {
  506.                             $this->clickedButton $child->getClickedButton();
  507.                         }
  508.                     }
  509.                 }
  510.                 $this->extraData $submittedData;
  511.             }
  512.             // Forms that inherit their parents' data also are not processed,
  513.             // because then it would be too difficult to merge the changes in
  514.             // the child and the parent form. Instead, the parent form also takes
  515.             // changes in the grandchildren (i.e. children of the form that inherits
  516.             // its parent's data) into account.
  517.             // (see InheritDataAwareIterator below)
  518.             if (!$this->inheritData) {
  519.                 // If the form is compound, the view data is merged with the data
  520.                 // of the children using the data mapper.
  521.                 // If the form is not compound, the view data is assigned to the submitted data.
  522.                 $viewData $this->config->getCompound() ? $this->viewData $submittedData;
  523.                 if (FormUtil::isEmpty($viewData)) {
  524.                     $emptyData $this->config->getEmptyData();
  525.                     if ($emptyData instanceof \Closure) {
  526.                         $emptyData $emptyData($this$viewData);
  527.                     }
  528.                     $viewData $emptyData;
  529.                 }
  530.                 // Merge form data from children into existing view data
  531.                 // It is not necessary to invoke this method if the form has no children,
  532.                 // even if it is compound.
  533.                 if (\count($this->children) > 0) {
  534.                     // Use InheritDataAwareIterator to process children of
  535.                     // descendants that inherit this form's data.
  536.                     // These descendants will not be submitted normally (see the check
  537.                     // for $this->config->getInheritData() above)
  538.                     $this->config->getDataMapper()->mapFormsToData(
  539.                         new \RecursiveIteratorIterator(new InheritDataAwareIterator($this->children)),
  540.                         $viewData
  541.                     );
  542.                 }
  543.                 // Normalize data to unified representation
  544.                 $normData $this->viewToNorm($viewData);
  545.                 // Hook to change content of the data in the normalized
  546.                 // representation
  547.                 if ($dispatcher->hasListeners(FormEvents::SUBMIT)) {
  548.                     $event = new SubmitEvent($this$normData);
  549.                     $dispatcher->dispatch($eventFormEvents::SUBMIT);
  550.                     $normData $event->getData();
  551.                 }
  552.                 // Synchronize representations - must not change the content!
  553.                 $modelData $this->normToModel($normData);
  554.                 $viewData $this->normToView($normData);
  555.             }
  556.         } catch (TransformationFailedException $e) {
  557.             $this->transformationFailure $e;
  558.             // If $viewData was not yet set, set it to $submittedData so that
  559.             // the erroneous data is accessible on the form.
  560.             // Forms that inherit data never set any data, because the getters
  561.             // forward to the parent form's getters anyway.
  562.             if (null === $viewData && !$this->inheritData) {
  563.                 $viewData $submittedData;
  564.             }
  565.         }
  566.         $this->submitted true;
  567.         $this->modelData $modelData;
  568.         $this->normData $normData;
  569.         $this->viewData $viewData;
  570.         if ($dispatcher->hasListeners(FormEvents::POST_SUBMIT)) {
  571.             $event = new PostSubmitEvent($this$viewData);
  572.             $dispatcher->dispatch($eventFormEvents::POST_SUBMIT);
  573.         }
  574.         return $this;
  575.     }
  576.     /**
  577.      * {@inheritdoc}
  578.      */
  579.     public function addError(FormError $error)
  580.     {
  581.         if (null === $error->getOrigin()) {
  582.             $error->setOrigin($this);
  583.         }
  584.         if ($this->parent && $this->config->getErrorBubbling()) {
  585.             $this->parent->addError($error);
  586.         } else {
  587.             $this->errors[] = $error;
  588.         }
  589.         return $this;
  590.     }
  591.     /**
  592.      * {@inheritdoc}
  593.      */
  594.     public function isSubmitted()
  595.     {
  596.         return $this->submitted;
  597.     }
  598.     /**
  599.      * {@inheritdoc}
  600.      */
  601.     public function isSynchronized()
  602.     {
  603.         return null === $this->transformationFailure;
  604.     }
  605.     /**
  606.      * {@inheritdoc}
  607.      */
  608.     public function getTransformationFailure()
  609.     {
  610.         return $this->transformationFailure;
  611.     }
  612.     /**
  613.      * {@inheritdoc}
  614.      */
  615.     public function isEmpty()
  616.     {
  617.         foreach ($this->children as $child) {
  618.             if (!$child->isEmpty()) {
  619.                 return false;
  620.             }
  621.         }
  622.         return FormUtil::isEmpty($this->modelData) ||
  623.             // arrays, countables
  624.             ((\is_array($this->modelData) || $this->modelData instanceof \Countable) && === \count($this->modelData)) ||
  625.             // traversables that are not countable
  626.             ($this->modelData instanceof \Traversable && === iterator_count($this->modelData)) ||
  627.             // @internal - Do not rely on it, it will be removed in Symfony 5.1.
  628.             (false === $this->modelData && $this->config->getAttribute('_false_is_empty'));
  629.     }
  630.     /**
  631.      * {@inheritdoc}
  632.      */
  633.     public function isValid()
  634.     {
  635.         if (!$this->submitted) {
  636.             throw new LogicException('Cannot check if an unsubmitted form is valid. Call Form::isSubmitted() before Form::isValid().');
  637.         }
  638.         if ($this->isDisabled()) {
  639.             return true;
  640.         }
  641.         return === \count($this->getErrors(true));
  642.     }
  643.     /**
  644.      * Returns the button that was used to submit the form.
  645.      *
  646.      * @return FormInterface|ClickableInterface|null
  647.      */
  648.     public function getClickedButton()
  649.     {
  650.         if ($this->clickedButton) {
  651.             return $this->clickedButton;
  652.         }
  653.         return $this->parent && method_exists($this->parent'getClickedButton') ? $this->parent->getClickedButton() : null;
  654.     }
  655.     /**
  656.      * {@inheritdoc}
  657.      */
  658.     public function getErrors($deep false$flatten true)
  659.     {
  660.         $errors $this->errors;
  661.         // Copy the errors of nested forms to the $errors array
  662.         if ($deep) {
  663.             foreach ($this as $child) {
  664.                 /** @var FormInterface $child */
  665.                 if ($child->isSubmitted() && $child->isValid()) {
  666.                     continue;
  667.                 }
  668.                 $iterator $child->getErrors(true$flatten);
  669.                 if (=== \count($iterator)) {
  670.                     continue;
  671.                 }
  672.                 if ($flatten) {
  673.                     foreach ($iterator as $error) {
  674.                         $errors[] = $error;
  675.                     }
  676.                 } else {
  677.                     $errors[] = $iterator;
  678.                 }
  679.             }
  680.         }
  681.         return new FormErrorIterator($this$errors);
  682.     }
  683.     /**
  684.      * {@inheritdoc}
  685.      *
  686.      * @return $this
  687.      */
  688.     public function clearErrors(bool $deep false): self
  689.     {
  690.         $this->errors = [];
  691.         if ($deep) {
  692.             // Clear errors from children
  693.             foreach ($this as $child) {
  694.                 if ($child instanceof ClearableErrorsInterface) {
  695.                     $child->clearErrors(true);
  696.                 }
  697.             }
  698.         }
  699.         return $this;
  700.     }
  701.     /**
  702.      * {@inheritdoc}
  703.      */
  704.     public function all()
  705.     {
  706.         return iterator_to_array($this->children);
  707.     }
  708.     /**
  709.      * {@inheritdoc}
  710.      */
  711.     public function add($child$type null, array $options = [])
  712.     {
  713.         if ($this->submitted) {
  714.             throw new AlreadySubmittedException('You cannot add children to a submitted form.');
  715.         }
  716.         if (!$this->config->getCompound()) {
  717.             throw new LogicException('You cannot add children to a simple form. Maybe you should set the option "compound" to true?');
  718.         }
  719.         if (!$child instanceof FormInterface) {
  720.             if (!\is_string($child) && !\is_int($child)) {
  721.                 throw new UnexpectedTypeException($child'string or Symfony\Component\Form\FormInterface');
  722.             }
  723.             $child = (string) $child;
  724.             if (null !== $type && !\is_string($type)) {
  725.                 throw new UnexpectedTypeException($type'string or null');
  726.             }
  727.             // Never initialize child forms automatically
  728.             $options['auto_initialize'] = false;
  729.             if (null === $type && null === $this->config->getDataClass()) {
  730.                 $type TextType::class;
  731.             }
  732.             if (null === $type) {
  733.                 $child $this->config->getFormFactory()->createForProperty($this->config->getDataClass(), $childnull$options);
  734.             } else {
  735.                 $child $this->config->getFormFactory()->createNamed($child$typenull$options);
  736.             }
  737.         } elseif ($child->getConfig()->getAutoInitialize()) {
  738.             throw new RuntimeException(sprintf('Automatic initialization is only supported on root forms. You should set the "auto_initialize" option to false on the field "%s".'$child->getName()));
  739.         }
  740.         $this->children[$child->getName()] = $child;
  741.         $child->setParent($this);
  742.         // If setData() is currently being called, there is no need to call
  743.         // mapDataToForms() here, as mapDataToForms() is called at the end
  744.         // of setData() anyway. Not doing this check leads to an endless
  745.         // recursion when initializing the form lazily and an event listener
  746.         // (such as ResizeFormListener) adds fields depending on the data:
  747.         //
  748.         //  * setData() is called, the form is not initialized yet
  749.         //  * add() is called by the listener (setData() is not complete, so
  750.         //    the form is still not initialized)
  751.         //  * getViewData() is called
  752.         //  * setData() is called since the form is not initialized yet
  753.         //  * ... endless recursion ...
  754.         //
  755.         // Also skip data mapping if setData() has not been called yet.
  756.         // setData() will be called upon form initialization and data mapping
  757.         // will take place by then.
  758.         if (!$this->lockSetData && $this->defaultDataSet && !$this->inheritData) {
  759.             $viewData $this->getViewData();
  760.             $this->config->getDataMapper()->mapDataToForms(
  761.                 $viewData,
  762.                 new \RecursiveIteratorIterator(new InheritDataAwareIterator(new \ArrayIterator([$child->getName() => $child])))
  763.             );
  764.         }
  765.         return $this;
  766.     }
  767.     /**
  768.      * {@inheritdoc}
  769.      */
  770.     public function remove($name)
  771.     {
  772.         if ($this->submitted) {
  773.             throw new AlreadySubmittedException('You cannot remove children from a submitted form.');
  774.         }
  775.         if (isset($this->children[$name])) {
  776.             if (!$this->children[$name]->isSubmitted()) {
  777.                 $this->children[$name]->setParent(null);
  778.             }
  779.             unset($this->children[$name]);
  780.         }
  781.         return $this;
  782.     }
  783.     /**
  784.      * {@inheritdoc}
  785.      */
  786.     public function has($name)
  787.     {
  788.         return isset($this->children[$name]);
  789.     }
  790.     /**
  791.      * {@inheritdoc}
  792.      */
  793.     public function get($name)
  794.     {
  795.         if (isset($this->children[$name])) {
  796.             return $this->children[$name];
  797.         }
  798.         throw new OutOfBoundsException(sprintf('Child "%s" does not exist.'$name));
  799.     }
  800.     /**
  801.      * Returns whether a child with the given name exists (implements the \ArrayAccess interface).
  802.      *
  803.      * @param string $name The name of the child
  804.      *
  805.      * @return bool
  806.      */
  807.     #[\ReturnTypeWillChange]
  808.     public function offsetExists($name)
  809.     {
  810.         return $this->has($name);
  811.     }
  812.     /**
  813.      * Returns the child with the given name (implements the \ArrayAccess interface).
  814.      *
  815.      * @param string $name The name of the child
  816.      *
  817.      * @return FormInterface The child form
  818.      *
  819.      * @throws OutOfBoundsException if the named child does not exist
  820.      */
  821.     #[\ReturnTypeWillChange]
  822.     public function offsetGet($name)
  823.     {
  824.         return $this->get($name);
  825.     }
  826.     /**
  827.      * Adds a child to the form (implements the \ArrayAccess interface).
  828.      *
  829.      * @param string        $name  Ignored. The name of the child is used
  830.      * @param FormInterface $child The child to be added
  831.      *
  832.      * @return void
  833.      *
  834.      * @throws AlreadySubmittedException if the form has already been submitted
  835.      * @throws LogicException            when trying to add a child to a non-compound form
  836.      *
  837.      * @see self::add()
  838.      */
  839.     #[\ReturnTypeWillChange]
  840.     public function offsetSet($name$child)
  841.     {
  842.         $this->add($child);
  843.     }
  844.     /**
  845.      * Removes the child with the given name from the form (implements the \ArrayAccess interface).
  846.      *
  847.      * @param string $name The name of the child to remove
  848.      *
  849.      * @return void
  850.      *
  851.      * @throws AlreadySubmittedException if the form has already been submitted
  852.      */
  853.     #[\ReturnTypeWillChange]
  854.     public function offsetUnset($name)
  855.     {
  856.         $this->remove($name);
  857.     }
  858.     /**
  859.      * Returns the iterator for this group.
  860.      *
  861.      * @return \Traversable<FormInterface>
  862.      */
  863.     #[\ReturnTypeWillChange]
  864.     public function getIterator()
  865.     {
  866.         return $this->children;
  867.     }
  868.     /**
  869.      * Returns the number of form children (implements the \Countable interface).
  870.      *
  871.      * @return int The number of embedded form children
  872.      */
  873.     #[\ReturnTypeWillChange]
  874.     public function count()
  875.     {
  876.         return \count($this->children);
  877.     }
  878.     /**
  879.      * {@inheritdoc}
  880.      */
  881.     public function createView(FormView $parent null)
  882.     {
  883.         if (null === $parent && $this->parent) {
  884.             $parent $this->parent->createView();
  885.         }
  886.         $type $this->config->getType();
  887.         $options $this->config->getOptions();
  888.         // The methods createView(), buildView() and finishView() are called
  889.         // explicitly here in order to be able to override either of them
  890.         // in a custom resolved form type.
  891.         $view $type->createView($this$parent);
  892.         $type->buildView($view$this$options);
  893.         foreach ($this->children as $name => $child) {
  894.             $view->children[$name] = $child->createView($view);
  895.         }
  896.         $type->finishView($view$this$options);
  897.         return $view;
  898.     }
  899.     /**
  900.      * Normalizes the underlying data if a model transformer is set.
  901.      *
  902.      * @return mixed
  903.      *
  904.      * @throws TransformationFailedException If the underlying data cannot be transformed to "normalized" format
  905.      */
  906.     private function modelToNorm($value)
  907.     {
  908.         try {
  909.             foreach ($this->config->getModelTransformers() as $transformer) {
  910.                 $value $transformer->transform($value);
  911.             }
  912.         } catch (TransformationFailedException $exception) {
  913.             throw new TransformationFailedException(sprintf('Unable to transform data for property path "%s": '$this->getPropertyPath()).$exception->getMessage(), $exception->getCode(), $exception$exception->getInvalidMessage(), $exception->getInvalidMessageParameters());
  914.         }
  915.         return $value;
  916.     }
  917.     /**
  918.      * Reverse transforms a value if a model transformer is set.
  919.      *
  920.      * @return mixed
  921.      *
  922.      * @throws TransformationFailedException If the value cannot be transformed to "model" format
  923.      */
  924.     private function normToModel($value)
  925.     {
  926.         try {
  927.             $transformers $this->config->getModelTransformers();
  928.             for ($i = \count($transformers) - 1$i >= 0; --$i) {
  929.                 $value $transformers[$i]->reverseTransform($value);
  930.             }
  931.         } catch (TransformationFailedException $exception) {
  932.             throw new TransformationFailedException(sprintf('Unable to reverse value for property path "%s": '$this->getPropertyPath()).$exception->getMessage(), $exception->getCode(), $exception$exception->getInvalidMessage(), $exception->getInvalidMessageParameters());
  933.         }
  934.         return $value;
  935.     }
  936.     /**
  937.      * Transforms the value if a view transformer is set.
  938.      *
  939.      * @return mixed
  940.      *
  941.      * @throws TransformationFailedException If the normalized value cannot be transformed to "view" format
  942.      */
  943.     private function normToView($value)
  944.     {
  945.         // Scalar values should  be converted to strings to
  946.         // facilitate differentiation between empty ("") and zero (0).
  947.         // Only do this for simple forms, as the resulting value in
  948.         // compound forms is passed to the data mapper and thus should
  949.         // not be converted to a string before.
  950.         if (!($transformers $this->config->getViewTransformers()) && !$this->config->getCompound()) {
  951.             return null === $value || is_scalar($value) ? (string) $value $value;
  952.         }
  953.         try {
  954.             foreach ($transformers as $transformer) {
  955.                 $value $transformer->transform($value);
  956.             }
  957.         } catch (TransformationFailedException $exception) {
  958.             throw new TransformationFailedException(sprintf('Unable to transform value for property path "%s": '$this->getPropertyPath()).$exception->getMessage(), $exception->getCode(), $exception$exception->getInvalidMessage(), $exception->getInvalidMessageParameters());
  959.         }
  960.         return $value;
  961.     }
  962.     /**
  963.      * Reverse transforms a value if a view transformer is set.
  964.      *
  965.      * @return mixed
  966.      *
  967.      * @throws TransformationFailedException If the submitted value cannot be transformed to "normalized" format
  968.      */
  969.     private function viewToNorm($value)
  970.     {
  971.         if (!$transformers $this->config->getViewTransformers()) {
  972.             return '' === $value null $value;
  973.         }
  974.         try {
  975.             for ($i = \count($transformers) - 1$i >= 0; --$i) {
  976.                 $value $transformers[$i]->reverseTransform($value);
  977.             }
  978.         } catch (TransformationFailedException $exception) {
  979.             throw new TransformationFailedException(sprintf('Unable to reverse value for property path "%s": '$this->getPropertyPath()).$exception->getMessage(), $exception->getCode(), $exception$exception->getInvalidMessage(), $exception->getInvalidMessageParameters());
  980.         }
  981.         return $value;
  982.     }
  983. }