vendor/symfony/form/Form.php line 1024

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