vendor/symfony/form/Form.php line 69

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