vendor/symfony/form/Form.php line 983

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