vendor/symfony/form/Form.php line 1008

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