From 56429a6f082c057880e88ca665910b04d26e76d8 Mon Sep 17 00:00:00 2001 From: Jules Pietri Date: Tue, 12 Mar 2019 21:16:42 +0100 Subject: [PATCH] [Form] various minor fixes --- .../Component/Form/AbstractExtension.php | 4 +- .../Component/Form/AbstractRendererEngine.php | 2 +- src/Symfony/Component/Form/Button.php | 25 ++- src/Symfony/Component/Form/ButtonBuilder.php | 15 +- .../Component/Form/CallbackTransformer.php | 22 +-- .../Component/Form/DataMapperInterface.php | 40 +++- .../Form/DataTransformerInterface.php | 41 ++-- .../Form/Extension/Core/Type/ButtonType.php | 4 +- .../Form/Extension/Core/Type/ChoiceType.php | 4 +- src/Symfony/Component/Form/Form.php | 182 ++++++++---------- src/Symfony/Component/Form/FormBuilder.php | 28 +-- .../Component/Form/FormConfigBuilder.php | 91 ++------- .../Form/FormConfigBuilderInterface.php | 16 +- .../Component/Form/FormConfigInterface.php | 20 +- src/Symfony/Component/Form/FormError.php | 2 +- .../Component/Form/FormErrorIterator.php | 12 +- src/Symfony/Component/Form/FormEvent.php | 4 + src/Symfony/Component/Form/FormEvents.php | 29 ++- src/Symfony/Component/Form/FormInterface.php | 86 ++++++--- src/Symfony/Component/Form/FormRegistry.php | 2 +- .../Form/FormRendererEngineInterface.php | 4 +- .../Component/Form/FormTypeGuesserChain.php | 2 +- .../Form/FormTypeGuesserInterface.php | 4 +- .../Component/Form/NativeRequestHandler.php | 2 + .../Component/Form/Tests/CompoundFormTest.php | 25 +++ .../Component/Form/Tests/SimpleFormTest.php | 22 ++- 26 files changed, 370 insertions(+), 318 deletions(-) diff --git a/src/Symfony/Component/Form/AbstractExtension.php b/src/Symfony/Component/Form/AbstractExtension.php index f4ac2e5ce2b0f..d5ab6bd48b883 100644 --- a/src/Symfony/Component/Form/AbstractExtension.php +++ b/src/Symfony/Component/Form/AbstractExtension.php @@ -36,7 +36,7 @@ abstract class AbstractExtension implements FormExtensionInterface /** * The type guesser provided by this extension. * - * @var FormTypeGuesserInterface + * @var FormTypeGuesserInterface|null */ private $typeGuesser; @@ -136,7 +136,7 @@ protected function loadTypeExtensions() /** * Registers the type guesser. * - * @return FormTypeGuesserInterface|null A type guesser + * @return FormTypeGuesserInterface|null */ protected function loadTypeGuesser() { diff --git a/src/Symfony/Component/Form/AbstractRendererEngine.php b/src/Symfony/Component/Form/AbstractRendererEngine.php index 649b600ddcf23..00bc254daefd3 100644 --- a/src/Symfony/Component/Form/AbstractRendererEngine.php +++ b/src/Symfony/Component/Form/AbstractRendererEngine.php @@ -133,7 +133,7 @@ abstract protected function loadResourceForBlockName($cacheKey, FormView $view, * resource * @param FormView $view The form view for finding the applying * themes - * @param array $blockNameHierarchy The block hierarchy, with the most + * @param string[] $blockNameHierarchy The block hierarchy, with the most * specific block name at the end * @param int $hierarchyLevel The level in the block hierarchy that * should be loaded diff --git a/src/Symfony/Component/Form/Button.php b/src/Symfony/Component/Form/Button.php index 8ae4946c565e0..ed1106b467a21 100644 --- a/src/Symfony/Component/Form/Button.php +++ b/src/Symfony/Component/Form/Button.php @@ -22,7 +22,7 @@ class Button implements \IteratorAggregate, FormInterface { /** - * @var FormInterface|null + * @var FormInterface */ private $parent; @@ -111,6 +111,8 @@ public function setParent(FormInterface $parent = null) } $this->parent = $parent; + + return $this; } /** @@ -199,11 +201,13 @@ public function getErrors($deep = false, $flatten = true) * This method should not be invoked. * * @param mixed $modelData + * + * @return $this */ public function setData($modelData) { - // called during initialization of the form tree - // noop + // no-op, called during initialization of the form tree + return $this; } /** @@ -211,6 +215,7 @@ public function setData($modelData) */ public function getData() { + return null; } /** @@ -218,6 +223,7 @@ public function getData() */ public function getNormData() { + return null; } /** @@ -225,6 +231,7 @@ public function getNormData() */ public function getViewData() { + return null; } /** @@ -240,7 +247,7 @@ public function getExtraData() /** * Returns the button's configuration. * - * @return FormConfigInterface The configuration + * @return FormConfigInterface The configuration instance */ public function getConfig() { @@ -272,6 +279,7 @@ public function getName() */ public function getPropertyPath() { + return null; } /** @@ -309,11 +317,11 @@ public function isRequired() */ public function isDisabled() { - if (null === $this->parent || !$this->parent->isDisabled()) { - return $this->config->getDisabled(); + if ($this->parent && $this->parent->isDisabled()) { + return true; } - return true; + return $this->config->getDisabled(); } /** @@ -341,6 +349,7 @@ public function isSynchronized() */ public function getTransformationFailure() { + return null; } /** @@ -368,7 +377,7 @@ public function handleRequest($request = null) /** * Submits data to the button. * - * @param string|null $submittedData The data + * @param string|null $submittedData Not used * @param bool $clearMissing Not used * * @return $this diff --git a/src/Symfony/Component/Form/ButtonBuilder.php b/src/Symfony/Component/Form/ButtonBuilder.php index 903e842b9e8b8..598749eeac27d 100644 --- a/src/Symfony/Component/Form/ButtonBuilder.php +++ b/src/Symfony/Component/Form/ButtonBuilder.php @@ -22,9 +22,6 @@ */ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface { - /** - * @var bool - */ protected $locked = false; /** @@ -53,8 +50,6 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface private $options; /** - * Creates a new button builder. - * * @param string $name The name of the button * @param array $options The button's options * @@ -524,6 +519,7 @@ public function getFormConfig() */ public function getEventDispatcher() { + return null; } /** @@ -539,6 +535,7 @@ public function getName() */ public function getPropertyPath() { + return null; } /** @@ -606,6 +603,7 @@ public function getModelTransformers() */ public function getDataMapper() { + return null; } /** @@ -643,6 +641,7 @@ public function getErrorBubbling() */ public function getEmptyData() { + return null; } /** @@ -685,6 +684,7 @@ public function getAttribute($name, $default = null) */ public function getData() { + return null; } /** @@ -692,6 +692,7 @@ public function getData() */ public function getDataClass() { + return null; } /** @@ -709,6 +710,7 @@ public function getDataLocked() */ public function getFormFactory() { + throw new BadMethodCallException('Buttons do not support adding children.'); } /** @@ -716,6 +718,7 @@ public function getFormFactory() */ public function getAction() { + return null; } /** @@ -723,6 +726,7 @@ public function getAction() */ public function getMethod() { + return null; } /** @@ -730,6 +734,7 @@ public function getMethod() */ public function getRequestHandler() { + return null; } /** diff --git a/src/Symfony/Component/Form/CallbackTransformer.php b/src/Symfony/Component/Form/CallbackTransformer.php index 8155e4dca8ed1..6db5bfb183351 100644 --- a/src/Symfony/Component/Form/CallbackTransformer.php +++ b/src/Symfony/Component/Form/CallbackTransformer.php @@ -11,9 +11,6 @@ namespace Symfony\Component\Form; -use Symfony\Component\Form\Exception\TransformationFailedException; -use Symfony\Component\Form\Exception\UnexpectedTypeException; - class CallbackTransformer implements DataTransformerInterface { private $transform; @@ -30,14 +27,7 @@ public function __construct(callable $transform, callable $reverseTransform) } /** - * Transforms a value from the original representation to a transformed representation. - * - * @param mixed $data The value in the original representation - * - * @return mixed The value in the transformed representation - * - * @throws UnexpectedTypeException when the argument is not of the expected type - * @throws TransformationFailedException when the transformation fails + * {@inheritdoc} */ public function transform($data) { @@ -45,15 +35,7 @@ public function transform($data) } /** - * Transforms a value from the transformed representation to its original - * representation. - * - * @param mixed $data The value in the transformed representation - * - * @return mixed The value in the original representation - * - * @throws UnexpectedTypeException when the argument is not of the expected type - * @throws TransformationFailedException when the transformation fails + * {@inheritdoc} */ public function reverseTransform($data) { diff --git a/src/Symfony/Component/Form/DataMapperInterface.php b/src/Symfony/Component/Form/DataMapperInterface.php index bb262e7b8e6b8..dee8f784910ea 100644 --- a/src/Symfony/Component/Form/DataMapperInterface.php +++ b/src/Symfony/Component/Form/DataMapperInterface.php @@ -17,22 +17,46 @@ interface DataMapperInterface { /** - * Maps properties of some data to a list of forms. + * Maps the view data of a compound form to its children. * - * @param mixed $data Structured data - * @param FormInterface[]|\Traversable $forms A list of {@link FormInterface} instances + * The method is responsible for calling {@link FormInterface::setData()} + * on the children of compound forms, defining their underlying model data. + * + * @param mixed $viewData View data of the compound form being initialized + * @param FormInterface[]|\Traversable $forms A list of {@link FormInterface} instances * * @throws Exception\UnexpectedTypeException if the type of the data parameter is not supported */ - public function mapDataToForms($data, $forms); + public function mapDataToForms($viewData, $forms); /** - * Maps the data of a list of forms into the properties of some data. + * Maps the model data of a list of children forms into the view data of their parent. + * + * This is the internal cascade call of FormInterface::submit for compound forms, since they + * cannot be bound to any input nor the request as scalar, but their children may: + * + * $compoundForm->submit($arrayOfChildrenViewData) + * // inside: + * $childForm->submit($childViewData); + * // for each entry, do the same and/or reverse transform + * $this->dataMapper->mapFormsToData($compoundForm, $compoundInitialViewData) + * // then reverse transform + * + * When a simple form is submitted the following is happening: + * + * $simpleForm->submit($submittedViewData) + * // inside: + * $this->viewData = $submittedViewData + * // then reverse transform + * + * The model data can be an array or an object, so this second argument is always passed + * by reference. * - * @param FormInterface[]|\Traversable $forms A list of {@link FormInterface} instances - * @param mixed $data Structured data + * @param FormInterface[]|\Traversable $forms A list of {@link FormInterface} instances + * @param mixed $viewData The compound form's view data that get mapped + * its children model data * * @throws Exception\UnexpectedTypeException if the type of the data parameter is not supported */ - public function mapFormsToData($forms, &$data); + public function mapFormsToData($forms, &$viewData); } diff --git a/src/Symfony/Component/Form/DataTransformerInterface.php b/src/Symfony/Component/Form/DataTransformerInterface.php index deb073c8128fe..e5ac5992944e5 100644 --- a/src/Symfony/Component/Form/DataTransformerInterface.php +++ b/src/Symfony/Component/Form/DataTransformerInterface.php @@ -23,23 +23,35 @@ interface DataTransformerInterface /** * Transforms a value from the original representation to a transformed representation. * - * This method is called on two occasions inside a form field: + * This method is called when the form field is initialized with its default data, on + * two occasions for two types of transformers: * - * 1. When the form field is initialized with the data attached from the datasource (object or array). - * 2. When data from a request is submitted using {@link Form::submit()} to transform the new input data - * back into the renderable format. For example if you have a date field and submit '2009-10-10' - * you might accept this value because its easily parsed, but the transformer still writes back - * "2009/10/10" onto the form field (for further displaying or other purposes). + * 1. Model transformers which normalize the model data. + * This is mainly useful when the same form type (the same configuration) + * has to handle different kind of underlying data, e.g The DateType can + * deal with strings or \DateTime objects as input. + * + * 2. View transformers which adapt the normalized data to the view format. + * a/ When the form is simple, the value returned by convention is used + * directly in the view and thus can only be a string or an array. In + * this case the data class should be null. + * + * b/ When the form is compound the returned value should be an array or + * an object to be mapped to the children. Each property of the compound + * data will be used as model data by each child and will be transformed + * too. In this case data class should be the class of the object, or null + * when it is an array. + * + * All transformers are called in a configured order from model data to view value. + * At the end of this chain the view data will be validated against the data class + * setting. * * This method must be able to deal with empty values. Usually this will * be NULL, but depending on your implementation other empty values are * possible as well (such as empty strings). The reasoning behind this is - * that value transformers must be chainable. If the transform() method - * of the first value transformer outputs NULL, the second value transformer - * must be able to process that value. - * - * By convention, transform() should return an empty string if NULL is - * passed. + * that data transformers must be chainable. If the transform() method + * of the first data transformer outputs NULL, the second must be able to + * process that value. * * @param mixed $value The value in the original representation * @@ -54,7 +66,10 @@ public function transform($value); * representation. * * This method is called when {@link Form::submit()} is called to transform the requests tainted data - * into an acceptable format for your data processing/model layer. + * into an acceptable format. + * + * The same transformers are called in the reverse order so the responsibility is to + * return one of the types that would be expected as input of transform(). * * This method must be able to deal with empty values. Usually this will * be an empty string, but depending on your implementation other empty diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ButtonType.php b/src/Symfony/Component/Form/Extension/Core/Type/ButtonType.php index 2b60f4f31e1e5..b05dcc018dc36 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ButtonType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ButtonType.php @@ -43,8 +43,6 @@ public function configureOptions(OptionsResolver $resolver) { parent::configureOptions($resolver); - $resolver->setDefaults([ - 'auto_initialize' => false, - ]); + $resolver->setDefault('auto_initialize', false); } } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php index 28320ca38942e..2ad0859e880fa 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php @@ -328,8 +328,8 @@ public function configureOptions(OptionsResolver $resolver) 'placeholder' => $placeholderDefault, 'error_bubbling' => false, 'compound' => $compound, - // The view data is always a string, even if the "data" option - // is manually set to an object. + // The view data is always a string or an array of strings, + // even if the "data" option is manually set to an object. // See https://github.com/symfony/symfony/pull/5582 'data_class' => null, 'choice_translation_domain' => true, diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index cfa7eb5b6a6d8..5f417d6d90901 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -21,6 +21,7 @@ use Symfony\Component\Form\Util\InheritDataAwareIterator; use Symfony\Component\Form\Util\OrderedHashMap; use Symfony\Component\PropertyAccess\PropertyPath; +use Symfony\Component\PropertyAccess\PropertyPathInterface; /** * Form represents a form. @@ -63,79 +64,57 @@ class Form implements \IteratorAggregate, FormInterface { /** - * The form's configuration. - * * @var FormConfigInterface */ private $config; /** - * The parent of this form. - * - * @var FormInterface + * @var FormInterface|null */ private $parent; /** - * The children of this form. - * - * @var FormInterface[] A map of FormInterface instances + * @var FormInterface[]|OrderedHashMap A map of FormInterface instances */ private $children; /** - * The errors of this form. - * * @var FormError[] An array of FormError instances */ private $errors = []; /** - * Whether this form was submitted. - * * @var bool */ private $submitted = false; /** - * The button that was used to submit the form. - * - * @var Button + * @var ClickableInterface|null The button that was used to submit the form */ private $clickedButton; /** - * The form data in model format. - * * @var mixed */ private $modelData; /** - * The form data in normalized format. - * * @var mixed */ private $normData; /** - * The form data in view format. - * * @var mixed */ private $viewData; /** - * The submitted values that don't belong to any children. - * - * @var array + * @var array The submitted values that don't belong to any children */ private $extraData = []; /** - * Returns the transformation failure generated during submission, if any. - * - * @var TransformationFailedException|null + * @var TransformationFailedException|null The transformation failure generated during submission, if any */ private $transformationFailure; @@ -161,8 +140,21 @@ class Form implements \IteratorAggregate, FormInterface private $lockSetData = false; /** - * Creates a new form based on the given configuration. - * + * @var string|int|null + */ + private $name; + + /** + * @var bool Whether the form inherits its underlying data from its parent + */ + private $inheritData; + + /** + * @var PropertyPathInterface|null + */ + private $propertyPath; + + /** * @throws LogicException if a data mapper is not provided for a compound form */ public function __construct(FormConfigInterface $config) @@ -176,12 +168,13 @@ public function __construct(FormConfigInterface $config) // If the form inherits the data from its parent, it is not necessary // to call setData() with the default data. - if ($config->getInheritData()) { + if ($this->inheritData = $config->getInheritData()) { $this->defaultDataSet = true; } $this->config = $config; $this->children = new OrderedHashMap(); + $this->name = $config->getName(); } public function __clone() @@ -206,7 +199,7 @@ public function getConfig() */ public function getName() { - return $this->config->getName(); + return $this->name; } /** @@ -214,11 +207,11 @@ public function getName() */ public function getPropertyPath() { - if (null !== $this->config->getPropertyPath()) { - return $this->config->getPropertyPath(); + if ($this->propertyPath || $this->propertyPath = $this->config->getPropertyPath()) { + return $this->propertyPath; } - if (null === $this->getName() || '' === $this->getName()) { + if (null === $this->name || '' === $this->name) { return null; } @@ -229,10 +222,12 @@ public function getPropertyPath() } if ($parent && null === $parent->getConfig()->getDataClass()) { - return new PropertyPath('['.$this->getName().']'); + $this->propertyPath = new PropertyPath('['.$this->name.']'); + } else { + $this->propertyPath = new PropertyPath($this->name); } - return new PropertyPath($this->getName()); + return $this->propertyPath; } /** @@ -268,7 +263,7 @@ public function setParent(FormInterface $parent = null) throw new AlreadySubmittedException('You cannot set the parent of a submitted form'); } - if (null !== $parent && '' === $this->config->getName()) { + if (null !== $parent && '' === $this->name) { throw new LogicException('A form with an empty name cannot have a parent form.'); } @@ -315,7 +310,7 @@ public function setData($modelData) // If the form inherits its parent's data, disallow data setting to // prevent merge conflicts - if ($this->config->getInheritData()) { + if ($this->inheritData) { throw new RuntimeException('You cannot change the data of a form inheriting its parent data.'); } @@ -335,7 +330,7 @@ public function setData($modelData) $this->lockSetData = true; $dispatcher = $this->config->getEventDispatcher(); - // Hook to change content of the data + // Hook to change content of the model data before transformation and mapping children if ($dispatcher->hasListeners(FormEvents::PRE_SET_DATA)) { $event = new FormEvent($this, $modelData); $dispatcher->dispatch(FormEvents::PRE_SET_DATA, $event); @@ -348,6 +343,7 @@ public function setData($modelData) } // Synchronize representations - must not change the content! + // Transformation exceptions are not caught on initialization $normData = $this->modelToNorm($modelData); $viewData = $this->normToView($normData); @@ -370,13 +366,10 @@ public function setData($modelData) $this->defaultDataSet = true; $this->lockSetData = false; - // It is not necessary to invoke this method if the form doesn't have children, - // even if the form is compound. + // Compound forms don't need to invoke this method if they don't have children if (\count($this->children) > 0) { - // Update child forms from the data - $iterator = new InheritDataAwareIterator($this->children); - $iterator = new \RecursiveIteratorIterator($iterator); - $this->config->getDataMapper()->mapDataToForms($viewData, $iterator); + // Update child forms from the data (unless their config data is locked) + $this->config->getDataMapper()->mapDataToForms($viewData, new \RecursiveIteratorIterator(new InheritDataAwareIterator($this->children))); } if ($dispatcher->hasListeners(FormEvents::POST_SET_DATA)) { @@ -392,7 +385,7 @@ public function setData($modelData) */ public function getData() { - if ($this->config->getInheritData()) { + if ($this->inheritData) { if (!$this->parent) { throw new RuntimeException('The form is configured to inherit its parent\'s data, but does not have a parent.'); } @@ -416,7 +409,7 @@ public function getData() */ public function getNormData() { - if ($this->config->getInheritData()) { + if ($this->inheritData) { if (!$this->parent) { throw new RuntimeException('The form is configured to inherit its parent\'s data, but does not have a parent.'); } @@ -440,7 +433,7 @@ public function getNormData() */ public function getViewData() { - if ($this->config->getInheritData()) { + if ($this->inheritData) { if (!$this->parent) { throw new RuntimeException('The form is configured to inherit its parent\'s data, but does not have a parent.'); } @@ -505,8 +498,8 @@ public function submit($submittedData, $clearMissing = true) throw new AlreadySubmittedException('A form can only be submitted once'); } - // Initialize errors in the very beginning so that we don't lose any - // errors added during listeners + // Initialize errors in the very beginning so we're sure + // they are collectable during submission only $this->errors = []; // Obviously, a disabled form should not change its data upon submission. @@ -605,18 +598,16 @@ public function submit($submittedData, $clearMissing = true) // changes in the grandchildren (i.e. children of the form that inherits // its parent's data) into account. // (see InheritDataAwareIterator below) - if (!$this->config->getInheritData()) { - // If the form is compound, the default data in view format - // is reused. The data of the children is merged into this - // default data using the data mapper. - // If the form is not compound, the submitted data is also the data in view format. + if (!$this->inheritData) { + // If the form is compound, the view data is merged with the data + // of the children using the data mapper. + // If the form is not compound, the view data is assigned to the submitted data. $viewData = $this->config->getCompound() ? $this->viewData : $submittedData; if (FormUtil::isEmpty($viewData)) { $emptyData = $this->config->getEmptyData(); if ($emptyData instanceof \Closure) { - /* @var \Closure $emptyData */ $emptyData = $emptyData($this, $viewData); } @@ -631,9 +622,10 @@ public function submit($submittedData, $clearMissing = true) // descendants that inherit this form's data. // These descendants will not be submitted normally (see the check // for $this->config->getInheritData() above) - $childrenIterator = new InheritDataAwareIterator($this->children); - $childrenIterator = new \RecursiveIteratorIterator($childrenIterator); - $this->config->getDataMapper()->mapFormsToData($childrenIterator, $viewData); + $this->config->getDataMapper()->mapFormsToData( + new \RecursiveIteratorIterator(new InheritDataAwareIterator($this->children)), + $viewData + ); } // Normalize data to unified representation @@ -658,7 +650,7 @@ public function submit($submittedData, $clearMissing = true) // the erroneous data is accessible on the form. // Forms that inherit data never set any data, because the getters // forward to the parent form's getters anyway. - if (null === $viewData && !$this->config->getInheritData()) { + if (null === $viewData && !$this->inheritData) { $viewData = $submittedData; } } @@ -757,8 +749,7 @@ public function isValid() /** * Returns the button that was used to submit the form. * - * @return Button|null The clicked button or NULL if the form was not - * submitted + * @return ClickableInterface|null */ public function getClickedButton() { @@ -826,29 +817,6 @@ public function add($child, $type = null, array $options = []) throw new LogicException('You cannot add children to a simple form. Maybe you should set the option "compound" to true?'); } - // Obtain the view data - $viewData = null; - - // If setData() is currently being called, there is no need to call - // mapDataToForms() here, as mapDataToForms() is called at the end - // of setData() anyway. Not doing this check leads to an endless - // recursion when initializing the form lazily and an event listener - // (such as ResizeFormListener) adds fields depending on the data: - // - // * setData() is called, the form is not initialized yet - // * add() is called by the listener (setData() is not complete, so - // the form is still not initialized) - // * getViewData() is called - // * setData() is called since the form is not initialized yet - // * ... endless recursion ... - // - // Also skip data mapping if setData() has not been called yet. - // setData() will be called upon form initialization and data mapping - // will take place by then. - if (!$this->lockSetData && $this->defaultDataSet && !$this->config->getInheritData()) { - $viewData = $this->getViewData(); - } - if (!$child instanceof FormInterface) { if (!\is_string($child) && !\is_int($child)) { throw new UnexpectedTypeException($child, 'string, integer or Symfony\Component\Form\FormInterface'); @@ -878,10 +846,28 @@ public function add($child, $type = null, array $options = []) $child->setParent($this); - if (!$this->lockSetData && $this->defaultDataSet && !$this->config->getInheritData()) { - $iterator = new InheritDataAwareIterator(new \ArrayIterator([$child->getName() => $child])); - $iterator = new \RecursiveIteratorIterator($iterator); - $this->config->getDataMapper()->mapDataToForms($viewData, $iterator); + // If setData() is currently being called, there is no need to call + // mapDataToForms() here, as mapDataToForms() is called at the end + // of setData() anyway. Not doing this check leads to an endless + // recursion when initializing the form lazily and an event listener + // (such as ResizeFormListener) adds fields depending on the data: + // + // * setData() is called, the form is not initialized yet + // * add() is called by the listener (setData() is not complete, so + // the form is still not initialized) + // * getViewData() is called + // * setData() is called since the form is not initialized yet + // * ... endless recursion ... + // + // Also skip data mapping if setData() has not been called yet. + // setData() will be called upon form initialization and data mapping + // will take place by then. + if (!$this->lockSetData && $this->defaultDataSet && !$this->inheritData) { + $viewData = $this->getViewData(); + $this->config->getDataMapper()->mapDataToForms( + $viewData, + new \RecursiveIteratorIterator(new InheritDataAwareIterator(new \ArrayIterator([$child->getName() => $child]))) + ); } return $this; @@ -1030,13 +1016,13 @@ public function createView(FormView $parent = null) } /** - * Normalizes the value if a model transformer is set. + * Normalizes the underlying data if a model transformer is set. * * @param mixed $value The value to transform * * @return mixed * - * @throws TransformationFailedException If the value cannot be transformed to "normalized" format + * @throws TransformationFailedException If the underlying data cannot be transformed to "normalized" format */ private function modelToNorm($value) { @@ -1045,7 +1031,7 @@ private function modelToNorm($value) $value = $transformer->transform($value); } } catch (TransformationFailedException $exception) { - throw new TransformationFailedException('Unable to transform value for property path "'.$this->getPropertyPath().'": '.$exception->getMessage(), $exception->getCode(), $exception); + throw new TransformationFailedException('Unable to transform data for property path "'.$this->getPropertyPath().'": '.$exception->getMessage(), $exception->getCode(), $exception); } return $value; @@ -1082,7 +1068,7 @@ private function normToModel($value) * * @return mixed * - * @throws TransformationFailedException If the value cannot be transformed to "view" format + * @throws TransformationFailedException If the normalized value cannot be transformed to "view" format */ private function normToView($value) { @@ -1091,12 +1077,12 @@ private function normToView($value) // Only do this for simple forms, as the resulting value in // compound forms is passed to the data mapper and thus should // not be converted to a string before. - if (!$this->config->getViewTransformers() && !$this->config->getCompound()) { + if (!($transformers = $this->config->getViewTransformers()) && !$this->config->getCompound()) { return null === $value || is_scalar($value) ? (string) $value : $value; } try { - foreach ($this->config->getViewTransformers() as $transformer) { + foreach ($transformers as $transformer) { $value = $transformer->transform($value); } } catch (TransformationFailedException $exception) { @@ -1113,13 +1099,11 @@ private function normToView($value) * * @return mixed * - * @throws TransformationFailedException If the value cannot be transformed to "normalized" format + * @throws TransformationFailedException If the submitted value cannot be transformed to "normalized" format */ private function viewToNorm($value) { - $transformers = $this->config->getViewTransformers(); - - if (!$transformers) { + if (!$transformers = $this->config->getViewTransformers()) { return '' === $value ? null : $value; } diff --git a/src/Symfony/Component/Form/FormBuilder.php b/src/Symfony/Component/Form/FormBuilder.php index 00affbf6d8c3d..13b1ea3b36420 100644 --- a/src/Symfony/Component/Form/FormBuilder.php +++ b/src/Symfony/Component/Form/FormBuilder.php @@ -38,8 +38,6 @@ class FormBuilder extends FormConfigBuilder implements \IteratorAggregate, FormB private $unresolvedChildren = []; /** - * Creates a new form builder. - * * @param string $name * @param string|null $dataClass * @param EventDispatcherInterface $dispatcher @@ -81,10 +79,7 @@ public function add($child, $type = null, array $options = []) // Add to "children" to maintain order $this->children[$child] = null; - $this->unresolvedChildren[$child] = [ - 'type' => $type, - 'options' => $options, - ]; + $this->unresolvedChildren[$child] = [$type, $options]; return $this; } @@ -152,15 +147,7 @@ public function has($name) throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); } - if (isset($this->unresolvedChildren[$name])) { - return true; - } - - if (isset($this->children[$name])) { - return true; - } - - return false; + return isset($this->unresolvedChildren[$name]) || isset($this->children[$name]); } /** @@ -232,7 +219,7 @@ public function getForm() /** * {@inheritdoc} * - * @return FormBuilderInterface[] + * @return FormBuilderInterface[]|\Traversable */ public function getIterator() { @@ -252,12 +239,11 @@ public function getIterator() */ private function resolveChild($name) { - $info = $this->unresolvedChildren[$name]; - $child = $this->create($name, $info['type'], $info['options']); - $this->children[$name] = $child; + list($type, $options) = $this->unresolvedChildren[$name]; + unset($this->unresolvedChildren[$name]); - return $child; + return $this->children[$name] = $this->create($name, $type, $options); } /** @@ -266,7 +252,7 @@ private function resolveChild($name) private function resolveChildren() { foreach ($this->unresolvedChildren as $name => $info) { - $this->children[$name] = $this->create($name, $info['type'], $info['options']); + $this->children[$name] = $this->create($name, $info[0], $info[1]); } $this->unresolvedChildren = []; diff --git a/src/Symfony/Component/Form/FormConfigBuilder.php b/src/Symfony/Component/Form/FormConfigBuilder.php index fc864fc617173..fea1d70cd8ee4 100644 --- a/src/Symfony/Component/Form/FormConfigBuilder.php +++ b/src/Symfony/Component/Form/FormConfigBuilder.php @@ -47,44 +47,19 @@ class FormConfigBuilder implements FormConfigBuilderInterface 'PATCH', ]; - /** - * @var bool - */ protected $locked = false; - /** - * @var EventDispatcherInterface - */ private $dispatcher; - - /** - * @var string - */ private $name; /** - * @var PropertyPathInterface + * @var PropertyPathInterface|string|null */ private $propertyPath; - /** - * @var bool - */ private $mapped = true; - - /** - * @var bool - */ private $byReference = true; - - /** - * @var bool - */ private $inheritData = false; - - /** - * @var bool - */ private $compound = false; /** @@ -92,34 +67,16 @@ class FormConfigBuilder implements FormConfigBuilderInterface */ private $type; - /** - * @var array - */ private $viewTransformers = []; - - /** - * @var array - */ private $modelTransformers = []; /** - * @var DataMapperInterface + * @var DataMapperInterface|null */ private $dataMapper; - /** - * @var bool - */ private $required = true; - - /** - * @var bool - */ private $disabled = false; - - /** - * @var bool - */ private $errorBubbling = false; /** @@ -127,9 +84,6 @@ class FormConfigBuilder implements FormConfigBuilderInterface */ private $emptyData; - /** - * @var array - */ private $attributes = []; /** @@ -142,39 +96,26 @@ class FormConfigBuilder implements FormConfigBuilderInterface */ private $dataClass; - /** - * @var bool - */ - private $dataLocked; + private $dataLocked = false; /** - * @var FormFactoryInterface + * @var FormFactoryInterface|null */ private $formFactory; /** - * @var string + * @var string|null */ private $action; - /** - * @var string - */ private $method = 'POST'; /** - * @var RequestHandlerInterface + * @var RequestHandlerInterface|null */ private $requestHandler; - /** - * @var bool - */ private $autoInitialize = false; - - /** - * @var array - */ private $options; /** @@ -616,7 +557,7 @@ public function setErrorBubbling($errorBubbling) throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); } - $this->errorBubbling = null === $errorBubbling ? null : (bool) $errorBubbling; + $this->errorBubbling = (bool) $errorBubbling; return $this; } @@ -662,7 +603,7 @@ public function setMapped($mapped) throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); } - $this->mapped = $mapped; + $this->mapped = (bool) $mapped; return $this; } @@ -676,7 +617,7 @@ public function setByReference($byReference) throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); } - $this->byReference = $byReference; + $this->byReference = (bool) $byReference; return $this; } @@ -690,7 +631,7 @@ public function setInheritData($inheritData) throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); } - $this->inheritData = $inheritData; + $this->inheritData = (bool) $inheritData; return $this; } @@ -704,7 +645,7 @@ public function setCompound($compound) throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); } - $this->compound = $compound; + $this->compound = (bool) $compound; return $this; } @@ -746,7 +687,7 @@ public function setDataLocked($locked) throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); } - $this->dataLocked = $locked; + $this->dataLocked = (bool) $locked; return $this; } @@ -774,7 +715,7 @@ public function setAction($action) throw new BadMethodCallException('The config builder cannot be modified anymore.'); } - $this->action = $action; + $this->action = (string) $action; return $this; } @@ -790,7 +731,7 @@ public function setMethod($method) $upperCaseMethod = strtoupper($method); - if (!\in_array($upperCaseMethod, self::$allowedMethods)) { + if (!\in_array($upperCaseMethod, self::$allowedMethods, true)) { throw new InvalidArgumentException(sprintf('The form method is "%s", but should be one of "%s".', $method, implode('", "', self::$allowedMethods))); } @@ -846,7 +787,7 @@ public function getFormConfig() /** * Validates whether the given variable is a valid form name. * - * @param string|int $name The tested form name + * @param string|int|null $name The tested form name * * @throws UnexpectedTypeException if the name is not a string or an integer * @throws InvalidArgumentException if the name contains invalid characters @@ -872,7 +813,7 @@ public static function validateName($name) * * contains only letters, digits, numbers, underscores ("_"), * hyphens ("-") and colons (":") * - * @param string $name The tested form name + * @param string|null $name The tested form name * * @return bool Whether the name is valid */ diff --git a/src/Symfony/Component/Form/FormConfigBuilderInterface.php b/src/Symfony/Component/Form/FormConfigBuilderInterface.php index f422840a82c45..d516e41056ecc 100644 --- a/src/Symfony/Component/Form/FormConfigBuilderInterface.php +++ b/src/Symfony/Component/Form/FormConfigBuilderInterface.php @@ -108,7 +108,7 @@ public function setAttributes(array $attributes); public function setDataMapper(DataMapperInterface $dataMapper = null); /** - * Set whether the form is disabled. + * Sets whether the form is disabled. * * @param bool $disabled Whether the form is disabled * @@ -166,8 +166,7 @@ public function setMapped($mapped); /** * Sets whether the form's data should be modified by reference. * - * @param bool $byReference Whether the data should be - * modified by reference + * @param bool $byReference Whether the data should be modified by reference * * @return $this The configuration object */ @@ -194,7 +193,7 @@ public function setInheritData($inheritData); public function setCompound($compound); /** - * Set the types. + * Sets the resolved type. * * @return $this The configuration object */ @@ -203,7 +202,7 @@ public function setType(ResolvedFormTypeInterface $type); /** * Sets the initial data of the form. * - * @param mixed $data The data of the form in application format + * @param mixed $data The data of the form in model format * * @return $this The configuration object */ @@ -214,9 +213,12 @@ public function setData($data); * * A form with locked data is restricted to the data passed in * this configuration. The data can only be modified then by - * submitting the form. + * submitting the form or using PRE_SET_DATA event. * - * @param bool $locked Whether to lock the default data + * It means data passed to a factory method or mapped from the + * parent will be ignored. + * + * @param bool $locked Whether to lock the default configured data * * @return $this The configuration object */ diff --git a/src/Symfony/Component/Form/FormConfigInterface.php b/src/Symfony/Component/Form/FormConfigInterface.php index ce9171f3d496b..7dbda33033b55 100644 --- a/src/Symfony/Component/Form/FormConfigInterface.php +++ b/src/Symfony/Component/Form/FormConfigInterface.php @@ -70,15 +70,17 @@ public function getInheritData(); * This property is independent of whether the form actually has * children. A form can be compound and have no children at all, like * for example an empty collection form. + * The contrary is not possible, a form which is not compound + * cannot have any children. * * @return bool Whether the form is compound */ public function getCompound(); /** - * Returns the form types used to construct the form. + * Returns the resolved form type used to construct the form. * - * @return ResolvedFormTypeInterface The form's type + * @return ResolvedFormTypeInterface The form's resolved type */ public function getType(); @@ -97,7 +99,7 @@ public function getViewTransformers(); public function getModelTransformers(); /** - * Returns the data mapper of the form. + * Returns the data mapper of the compound form or null for a simple form. * * @return DataMapperInterface|null The data mapper */ @@ -125,9 +127,15 @@ public function getDisabled(); public function getErrorBubbling(); /** - * Returns the data that should be returned when the form is empty. + * Used when the view data is empty on submission. * - * @return mixed The data returned if the form is empty + * When the form is compound it will also be used to map the + * children data. + * + * The empty data must match the view format as it will passed to the first view transformer's + * "reverseTransform" method. + * + * @return mixed The data used when the submitted form is initially empty */ public function getEmptyData(); @@ -165,7 +173,7 @@ public function getAttribute($name, $default = null); public function getData(); /** - * Returns the class of the form data or null if the data is scalar or an array. + * Returns the class of the view data or null if the data is scalar or an array. * * @return string|null The data class or null */ diff --git a/src/Symfony/Component/Form/FormError.php b/src/Symfony/Component/Form/FormError.php index 7717c19019a3c..98a1e29a83a82 100644 --- a/src/Symfony/Component/Form/FormError.php +++ b/src/Symfony/Component/Form/FormError.php @@ -49,7 +49,7 @@ class FormError implements \Serializable */ public function __construct($message, $messageTemplate = null, array $messageParameters = [], $messagePluralization = null, $cause = null) { - $this->message = $message; + $this->message = (string) $message; $this->messageTemplate = $messageTemplate ?: $message; $this->messageParameters = $messageParameters; $this->messagePluralization = $messagePluralization; diff --git a/src/Symfony/Component/Form/FormErrorIterator.php b/src/Symfony/Component/Form/FormErrorIterator.php index 2cc53c0923f94..db1d311a30d7a 100644 --- a/src/Symfony/Component/Form/FormErrorIterator.php +++ b/src/Symfony/Component/Form/FormErrorIterator.php @@ -39,10 +39,9 @@ class FormErrorIterator implements \RecursiveIterator, \SeekableIterator, \Array private $errors; /** - * Creates a new iterator. - * - * @param FormInterface $form The erroneous form - * @param FormError[]|FormErrorIterator[] $errors The form errors + * @param FormInterface $form The erroneous form + * @param FormError[]|self[] $errors An array of form errors and instances + * of FormErrorIterator * * @throws InvalidArgumentException If the errors are invalid */ @@ -71,7 +70,7 @@ public function __toString() if ($error instanceof FormError) { $string .= 'ERROR: '.$error->getMessage()."\n"; } else { - /* @var $error FormErrorIterator */ + /** @var self $error */ $string .= $error->form->getName().":\n"; $string .= self::indent((string) $error); } @@ -93,8 +92,7 @@ public function getForm() /** * Returns the current element of the iterator. * - * @return FormError|FormErrorIterator an error or an iterator containing - * nested errors + * @return FormError|self An error or an iterator containing nested errors */ public function current() { diff --git a/src/Symfony/Component/Form/FormEvent.php b/src/Symfony/Component/Form/FormEvent.php index c688a19566b76..3b6d484e75803 100644 --- a/src/Symfony/Component/Form/FormEvent.php +++ b/src/Symfony/Component/Form/FormEvent.php @@ -21,6 +21,10 @@ class FormEvent extends Event private $form; protected $data; + /** + * @param FormInterface $form The associated form + * @param mixed $data The data + */ public function __construct(FormInterface $form, $data) { $this->form = $form; diff --git a/src/Symfony/Component/Form/FormEvents.php b/src/Symfony/Component/Form/FormEvents.php index b795f95dcfafc..c4c613f567f19 100644 --- a/src/Symfony/Component/Form/FormEvents.php +++ b/src/Symfony/Component/Form/FormEvents.php @@ -34,21 +34,35 @@ final class FormEvents const PRE_SUBMIT = 'form.pre_bind'; /** - * The SUBMIT event is dispatched just before the Form::submit() method - * transforms back the normalized data to the model and view data. + * The SUBMIT event is dispatched after the Form::submit() method + * has changed the view data by the request data, or submitted and mapped + * the children if the form is compound, and after reverse transformation + * to normalized representation. * - * It can be used to change data from the normalized representation of the data. + * It's also dispatched just before the Form::submit() method transforms back + * the normalized data to the model and view data. + * + * So at this stage children of compound forms are submitted and synchronized, unless + * their transformation failed, but a parent would still be at the PRE_SUBMIT level. + * + * Since the current form is not synchronized yet, it is still possible to add and + * remove fields. * * @Event("Symfony\Component\Form\FormEvent") */ const SUBMIT = 'form.bind'; /** - * The FormEvents::POST_SUBMIT event is dispatched after the Form::submit() - * once the model and view data have been denormalized. + * The FormEvents::POST_SUBMIT event is dispatched at the very end of the Form::submit(). + * + * It this stage the model and view data may have been denormalized. Otherwise the form + * is desynchronized because transformation failed during submission. * * It can be used to fetch data after denormalization. * + * The event attaches the current view data. To know whether this is the renormalized data + * or the invalid request data, call Form::isSynchronized() first. + * * @Event("Symfony\Component\Form\FormEvent") */ const POST_SUBMIT = 'form.post_bind'; @@ -58,7 +72,7 @@ final class FormEvents * * It can be used to: * - Modify the data given during pre-population; - * - Modify a form depending on the pre-populated data (adding or removing fields dynamically). + * - Keep synchronized the form depending on the data (adding or removing fields dynamically). * * @Event("Symfony\Component\Form\FormEvent") */ @@ -67,7 +81,8 @@ final class FormEvents /** * The FormEvents::POST_SET_DATA event is dispatched at the end of the Form::setData() method. * - * This event is mostly here for reading data after having pre-populated the form. + * This event can be used to modify the form depending on the final state of the underlying data + * accessible in every representation: model, normalized and view. * * @Event("Symfony\Component\Form\FormEvent") */ diff --git a/src/Symfony/Component/Form/FormInterface.php b/src/Symfony/Component/Form/FormInterface.php index 2add7938443aa..022b60aa1368d 100644 --- a/src/Symfony/Component/Form/FormInterface.php +++ b/src/Symfony/Component/Form/FormInterface.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Form; -use Symfony\Component\Form\Exception\TransformationFailedException; +use Symfony\Component\PropertyAccess\PropertyPathInterface; /** * A form group bundling multiple forms in a hierarchical structure. @@ -23,7 +23,9 @@ interface FormInterface extends \ArrayAccess, \Traversable, \Countable /** * Sets the parent form. * - * @return self + * @param FormInterface|null $parent The parent form or null if it's the root + * + * @return $this * * @throws Exception\AlreadySubmittedException if the form has already been submitted * @throws Exception\LogicException when trying to set a parent for a form with @@ -45,7 +47,7 @@ public function getParent(); * @param string|null $type The child's type, if a name was passed * @param array $options The child's options, if a name was passed * - * @return self + * @return $this * * @throws Exception\AlreadySubmittedException if the form has already been submitted * @throws Exception\LogicException when trying to add a child to a non-compound form @@ -104,44 +106,70 @@ public function all(); public function getErrors($deep = false, $flatten = true); /** - * Updates the form with default data. + * Updates the form with default model data. * * @param mixed $modelData The data formatted as expected for the underlying object * * @return $this * - * @throws Exception\AlreadySubmittedException if the form has already been submitted - * @throws Exception\LogicException If listeners try to call setData in a cycle. Or if - * the view data does not match the expected type - * according to {@link FormConfigInterface::getDataClass}. + * @throws Exception\AlreadySubmittedException If the form has already been submitted + * @throws Exception\LogicException If the view data does not match the expected type + * according to {@link FormConfigInterface::getDataClass}. + * @throws Exception\RuntimeException If listeners try to call setData in a cycle or if + * the form inherits data from its parent + * @throws Exception\TransformationFailedException If the synchronization failed. */ public function setData($modelData); /** - * Returns the data in the format needed for the underlying object. + * Returns the model data in the format needed for the underlying object. * - * @return mixed + * @return mixed When the field is not submitted, the default data is returned. + * When the field is submitted, the default data has been bound + * to the submitted view data. + * + * @throws Exception\RuntimeException If the form inherits data but has no parent */ public function getData(); /** - * Returns the normalized data of the field. + * Returns the normalized data of the field, used as internal bridge + * between model data and view data. * * @return mixed When the field is not submitted, the default data is returned. - * When the field is submitted, the normalized submitted data is - * returned if the field is valid, null otherwise. + * When the field is submitted, the normalized submitted data + * is returned if the field is synchronized with the view data, + * null otherwise. + * + * @throws Exception\RuntimeException If the form inherits data but has no parent */ public function getNormData(); /** - * Returns the data transformed by the value transformer. + * Returns the view data of the field. + * + * It may be defined by {@link FormConfigInterface::getDataClass}. + * + * There are two cases: + * + * - When the form is compound the view data is mapped to the children. + * Each child will use its mapped data as model data. + * It can be an array, an object or null. + * + * - When the form is simple its view data is used to be bound + * to the submitted data. + * It can be a string or an array. + * + * In both cases the view data is the actual altered data on submission. * * @return mixed + * + * @throws Exception\RuntimeException If the form inherits data but has no parent */ public function getViewData(); /** - * Returns the extra data. + * Returns the extra submitted data. * * @return array The submitted data which do not belong to a child */ @@ -150,7 +178,7 @@ public function getExtraData(); /** * Returns the form's configuration. * - * @return FormConfigInterface The configuration + * @return FormConfigInterface The configuration instance */ public function getConfig(); @@ -164,6 +192,8 @@ public function isSubmitted(); /** * Returns the name by which the form is identified in forms. * + * Only root forms are allowed to have an empty name. + * * @return string The name of the form */ public function getName(); @@ -171,7 +201,7 @@ public function getName(); /** * Returns the property path that the form is mapped to. * - * @return \Symfony\Component\PropertyAccess\PropertyPathInterface|null The property path + * @return PropertyPathInterface|null The property path instance */ public function getPropertyPath(); @@ -230,14 +260,16 @@ public function isEmpty(); * If the data is not synchronized, you can get the transformation failure * by calling {@link getTransformationFailure()}. * + * If the form is not submitted, this method always returns true. + * * @return bool */ public function isSynchronized(); /** - * Returns the data transformation failure, if any. + * Returns the data transformation failure, if any, during submission. * - * @return TransformationFailedException|null The transformation failure + * @return Exception\TransformationFailedException|null The transformation failure or null */ public function getTransformationFailure(); @@ -247,6 +279,8 @@ public function getTransformationFailure(); * Should be called on the root form after constructing the tree. * * @return $this + * + * @throws Exception\RuntimeException If the form is not the root */ public function initialize(); @@ -265,11 +299,13 @@ public function initialize(); public function handleRequest($request = null); /** - * Submits data to the form, transforms and validates it. + * Submits data to the form. * - * @param mixed $submittedData The submitted data - * @param bool $clearMissing Whether to set fields to NULL when they - * are missing in the submitted data + * @param string|array|null $submittedData The submitted data + * @param bool $clearMissing Whether to set fields to NULL + * when they are missing in the + * submitted data. This argument + * is only used in compound form * * @return $this * @@ -280,7 +316,7 @@ public function submit($submittedData, $clearMissing = true); /** * Returns the root of the form tree. * - * @return self The root of the tree + * @return self The root of the tree, may be the instance itself */ public function getRoot(); @@ -292,8 +328,6 @@ public function getRoot(); public function isRoot(); /** - * Creates a view. - * * @return FormView The view */ public function createView(FormView $parent = null); diff --git a/src/Symfony/Component/Form/FormRegistry.php b/src/Symfony/Component/Form/FormRegistry.php index 1446976191c6d..cbb1d7a4174c7 100644 --- a/src/Symfony/Component/Form/FormRegistry.php +++ b/src/Symfony/Component/Form/FormRegistry.php @@ -26,7 +26,7 @@ class FormRegistry implements FormRegistryInterface /** * Extensions. * - * @var FormExtensionInterface[] An array of FormExtensionInterface + * @var FormExtensionInterface[] */ private $extensions = []; diff --git a/src/Symfony/Component/Form/FormRendererEngineInterface.php b/src/Symfony/Component/Form/FormRendererEngineInterface.php index 9ae5233f46273..2f0b868dd0098 100644 --- a/src/Symfony/Component/Form/FormRendererEngineInterface.php +++ b/src/Symfony/Component/Form/FormRendererEngineInterface.php @@ -74,7 +74,7 @@ public function getResourceForBlockName(FormView $view, $blockName); * First the themes attached directly to * the view with {@link setTheme()} are * considered, then the ones of its parent etc. - * @param array $blockNameHierarchy The block name hierarchy, with the root block + * @param string[] $blockNameHierarchy The block name hierarchy, with the root block * at the beginning * @param int $hierarchyLevel The level in the hierarchy at which to start * looking. Level 0 indicates the root block, i.e. @@ -112,7 +112,7 @@ public function getResourceForBlockNameHierarchy(FormView $view, array $blockNam * First the themes attached directly to * the view with {@link setTheme()} are * considered, then the ones of its parent etc. - * @param array $blockNameHierarchy The block name hierarchy, with the root block + * @param string[] $blockNameHierarchy The block name hierarchy, with the root block * at the beginning * @param int $hierarchyLevel The level in the hierarchy at which to start * looking. Level 0 indicates the root block, i.e. diff --git a/src/Symfony/Component/Form/FormTypeGuesserChain.php b/src/Symfony/Component/Form/FormTypeGuesserChain.php index f38ba8e5d431a..5ce8794248801 100644 --- a/src/Symfony/Component/Form/FormTypeGuesserChain.php +++ b/src/Symfony/Component/Form/FormTypeGuesserChain.php @@ -19,7 +19,7 @@ class FormTypeGuesserChain implements FormTypeGuesserInterface private $guessers = []; /** - * @param FormTypeGuesserInterface[] $guessers Guessers as instances of FormTypeGuesserInterface + * @param FormTypeGuesserInterface[] $guessers * * @throws UnexpectedTypeException if any guesser does not implement FormTypeGuesserInterface */ diff --git a/src/Symfony/Component/Form/FormTypeGuesserInterface.php b/src/Symfony/Component/Form/FormTypeGuesserInterface.php index 6521ea47ca767..3be9a0c9f8570 100644 --- a/src/Symfony/Component/Form/FormTypeGuesserInterface.php +++ b/src/Symfony/Component/Form/FormTypeGuesserInterface.php @@ -49,8 +49,8 @@ public function guessMaxLength($class, $property); /** * Returns a guess about the field's pattern. * - * - When you have a min value, you guess a min length of this min (LOW_CONFIDENCE) , lines below - * - If this value is a float type, this is wrong so you guess null with MEDIUM_CONFIDENCE to override the previous guess. + * - When you have a min value, you guess a min length of this min (LOW_CONFIDENCE) + * - Then line below, if this value is a float type, this is wrong so you guess null with MEDIUM_CONFIDENCE to override the previous guess. * Example: * You want a float greater than 5, 4.512313 is not valid but length(4.512314) > length(5) * diff --git a/src/Symfony/Component/Form/NativeRequestHandler.php b/src/Symfony/Component/Form/NativeRequestHandler.php index 246ea92ccee50..f3c0d839fec66 100644 --- a/src/Symfony/Component/Form/NativeRequestHandler.php +++ b/src/Symfony/Component/Form/NativeRequestHandler.php @@ -41,6 +41,8 @@ public function __construct(ServerParams $params = null) /** * {@inheritdoc} + * + * @throws Exception\UnexpectedTypeException If the $request is not null */ public function handleRequest(FormInterface $form, $request = null) { diff --git a/src/Symfony/Component/Form/Tests/CompoundFormTest.php b/src/Symfony/Component/Form/Tests/CompoundFormTest.php index ba4f26ecc9d8c..edc3aeda14b2c 100644 --- a/src/Symfony/Component/Form/Tests/CompoundFormTest.php +++ b/src/Symfony/Component/Form/Tests/CompoundFormTest.php @@ -450,6 +450,31 @@ public function testSetDataMapsViewDataToChildren() $form->setData('foo'); } + public function testSetDataDoesNotMapViewDataToChildrenWithLockedSetData() + { + $mapper = new PropertyPathMapper(); + $viewData = [ + 'firstName' => 'Fabien', + 'lastName' => 'Pot', + ]; + $form = $this->getBuilder() + ->setCompound(true) + ->setDataMapper($mapper) + ->addViewTransformer(new FixedDataTransformer([ + '' => '', + 'foo' => $viewData, + ])) + ->getForm(); + + $form->add($child1 = $this->getBuilder('firstName')->getForm()); + $form->add($child2 = $this->getBuilder('lastName')->setData('Potencier')->setDataLocked(true)->getForm()); + + $form->setData('foo'); + + $this->assertSame('Fabien', $form->get('firstName')->getData()); + $this->assertSame('Potencier', $form->get('lastName')->getData()); + } + public function testSubmitSupportsDynamicAdditionAndRemovalOfChildren() { $form = $this->form; diff --git a/src/Symfony/Component/Form/Tests/SimpleFormTest.php b/src/Symfony/Component/Form/Tests/SimpleFormTest.php index 214ff3beab241..5233a8f1aae18 100644 --- a/src/Symfony/Component/Form/Tests/SimpleFormTest.php +++ b/src/Symfony/Component/Form/Tests/SimpleFormTest.php @@ -54,6 +54,26 @@ public function getIterator() class SimpleFormTest extends AbstractFormTest { + /** + * @dataProvider provideFormNames + */ + public function testGetPropertyPath($name, $propertyPath) + { + $config = new FormConfigBuilder($name, null, $this->dispatcher); + $form = new Form($config); + + $this->assertEquals(new PropertyPath($propertyPath), $form->getPropertyPath()); + } + + public function provideFormNames() + { + yield [null, null]; + yield ['', null]; + yield ['0', '0']; + yield [0, '0']; + yield ['name', 'name']; + } + public function testDataIsInitializedToConfiguredValue() { $model = new FixedDataTransformer([ @@ -76,7 +96,7 @@ public function testDataIsInitializedToConfiguredValue() /** * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException - * @expectedExceptionMessage Unable to transform value for property path "name": No mapping for value "arg" + * @expectedExceptionMessage Unable to transform data for property path "name": No mapping for value "arg" */ public function testDataTransformationFailure() { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy