bug #30879 [Form] Php doc fixes and cs + optimizations (Jules Pietri)

This PR was merged into the 3.4 branch.

Discussion
----------

[Form] Php doc fixes and cs + optimizations

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | no
| New feature?  | no <!-- don't forget to update src/**/CHANGELOG.md files -->
| BC breaks?    | no     <!-- see https://symfony.com/bc -->
| Deprecations? | no <!-- don't forget to update UPGRADE-*.md and src/**/CHANGELOG.md files -->
| Tests pass?   | yes    <!-- please add some, will be required by reviewers -->
| Fixed tickets | ~
| License       | MIT
| Doc PR        | ~

<!--
Write a short README entry for your feature/bugfix here (replace this comment block.)
This will help people understand your PR and can be used as a start of the Doc PR.
Additionally:
 - Bug fixes must be submitted against the lowest branch where they apply
   (lowest branches are regularly merged to upper ones so they get the fixes too).
 - Features and deprecations must be submitted against the master branch.
-->

Some micro optimizations may target master with some profiling but I would like some reviews first. Thanks!

Commits
-------

56429a6f08 [Form] various minor fixes
This commit is contained in:
Fabien Potencier 2019-04-06 15:47:14 +02:00
commit 967fa368b2
26 changed files with 370 additions and 318 deletions

View File

@ -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 @@ abstract class AbstractExtension implements FormExtensionInterface
/**
* Registers the type guesser.
*
* @return FormTypeGuesserInterface|null A type guesser
* @return FormTypeGuesserInterface|null
*/
protected function loadTypeGuesser()
{

View File

@ -133,7 +133,7 @@ abstract class AbstractRendererEngine implements FormRendererEngineInterface
* 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

View File

@ -22,7 +22,7 @@ use Symfony\Component\Form\Exception\BadMethodCallException;
class Button implements \IteratorAggregate, FormInterface
{
/**
* @var FormInterface|null
* @var FormInterface
*/
private $parent;
@ -111,6 +111,8 @@ class Button implements \IteratorAggregate, FormInterface
}
$this->parent = $parent;
return $this;
}
/**
@ -199,11 +201,13 @@ class Button implements \IteratorAggregate, FormInterface
* 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 @@ class Button implements \IteratorAggregate, FormInterface
*/
public function getData()
{
return null;
}
/**
@ -218,6 +223,7 @@ class Button implements \IteratorAggregate, FormInterface
*/
public function getNormData()
{
return null;
}
/**
@ -225,6 +231,7 @@ class Button implements \IteratorAggregate, FormInterface
*/
public function getViewData()
{
return null;
}
/**
@ -240,7 +247,7 @@ class Button implements \IteratorAggregate, FormInterface
/**
* Returns the button's configuration.
*
* @return FormConfigInterface The configuration
* @return FormConfigInterface The configuration instance
*/
public function getConfig()
{
@ -272,6 +279,7 @@ class Button implements \IteratorAggregate, FormInterface
*/
public function getPropertyPath()
{
return null;
}
/**
@ -309,11 +317,11 @@ class Button implements \IteratorAggregate, FormInterface
*/
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 @@ class Button implements \IteratorAggregate, FormInterface
*/
public function getTransformationFailure()
{
return null;
}
/**
@ -368,7 +377,7 @@ class Button implements \IteratorAggregate, FormInterface
/**
* Submits data to the button.
*
* @param string|null $submittedData The data
* @param string|null $submittedData Not used
* @param bool $clearMissing Not used
*
* @return $this

View File

@ -22,9 +22,6 @@ use Symfony\Component\Form\Exception\InvalidArgumentException;
*/
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 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface
*/
public function getEventDispatcher()
{
return null;
}
/**
@ -539,6 +535,7 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface
*/
public function getPropertyPath()
{
return null;
}
/**
@ -606,6 +603,7 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface
*/
public function getDataMapper()
{
return null;
}
/**
@ -643,6 +641,7 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface
*/
public function getEmptyData()
{
return null;
}
/**
@ -685,6 +684,7 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface
*/
public function getData()
{
return null;
}
/**
@ -692,6 +692,7 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface
*/
public function getDataClass()
{
return null;
}
/**
@ -709,6 +710,7 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface
*/
public function getFormFactory()
{
throw new BadMethodCallException('Buttons do not support adding children.');
}
/**
@ -716,6 +718,7 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface
*/
public function getAction()
{
return null;
}
/**
@ -723,6 +726,7 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface
*/
public function getMethod()
{
return null;
}
/**
@ -730,6 +734,7 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface
*/
public function getRequestHandler()
{
return null;
}
/**

View File

@ -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 @@ class CallbackTransformer implements DataTransformerInterface
}
/**
* 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 @@ class CallbackTransformer implements DataTransformerInterface
}
/**
* 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)
{

View File

@ -17,22 +17,46 @@ namespace Symfony\Component\Form;
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.
*
* @param FormInterface[]|\Traversable $forms A list of {@link FormInterface} instances
* @param mixed $data Structured data
* 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 $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);
}

View File

@ -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 @@ interface DataTransformerInterface
* 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

View File

@ -43,8 +43,6 @@ class ButtonType extends BaseType implements ButtonTypeInterface
{
parent::configureOptions($resolver);
$resolver->setDefaults([
'auto_initialize' => false,
]);
$resolver->setDefault('auto_initialize', false);
}
}

View File

@ -328,8 +328,8 @@ class ChoiceType extends AbstractType
'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,

View File

@ -21,6 +21,7 @@ use Symfony\Component\Form\Util\FormUtil;
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 @@ use Symfony\Component\PropertyAccess\PropertyPath;
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 @@ class Form implements \IteratorAggregate, FormInterface
// 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 @@ class Form implements \IteratorAggregate, FormInterface
*/
public function getName()
{
return $this->config->getName();
return $this->name;
}
/**
@ -214,11 +207,11 @@ class Form implements \IteratorAggregate, FormInterface
*/
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 @@ class Form implements \IteratorAggregate, FormInterface
}
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 @@ class Form implements \IteratorAggregate, FormInterface
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 @@ class Form implements \IteratorAggregate, FormInterface
// 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 @@ class Form implements \IteratorAggregate, FormInterface
$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 @@ class Form implements \IteratorAggregate, FormInterface
}
// 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 @@ class Form implements \IteratorAggregate, FormInterface
$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 @@ class Form implements \IteratorAggregate, FormInterface
*/
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 @@ class Form implements \IteratorAggregate, FormInterface
*/
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 @@ class Form implements \IteratorAggregate, FormInterface
*/
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 @@ class Form implements \IteratorAggregate, FormInterface
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 @@ class Form implements \IteratorAggregate, FormInterface
// 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 @@ class Form implements \IteratorAggregate, FormInterface
// 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 @@ class Form implements \IteratorAggregate, FormInterface
// 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 @@ class Form implements \IteratorAggregate, FormInterface
/**
* 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 @@ class Form implements \IteratorAggregate, FormInterface
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 @@ class Form implements \IteratorAggregate, FormInterface
$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 @@ class Form implements \IteratorAggregate, FormInterface
}
/**
* 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 @@ class Form implements \IteratorAggregate, FormInterface
$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 @@ class Form implements \IteratorAggregate, FormInterface
*
* @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 @@ class Form implements \IteratorAggregate, FormInterface
// 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 @@ class Form implements \IteratorAggregate, FormInterface
*
* @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;
}

View File

@ -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 @@ class FormBuilder extends FormConfigBuilder implements \IteratorAggregate, FormB
// 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 @@ class FormBuilder extends FormConfigBuilder implements \IteratorAggregate, FormB
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 @@ class FormBuilder extends FormConfigBuilder implements \IteratorAggregate, FormB
/**
* {@inheritdoc}
*
* @return FormBuilderInterface[]
* @return FormBuilderInterface[]|\Traversable
*/
public function getIterator()
{
@ -252,12 +239,11 @@ class FormBuilder extends FormConfigBuilder implements \IteratorAggregate, FormB
*/
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 @@ class FormBuilder extends FormConfigBuilder implements \IteratorAggregate, FormB
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 = [];

View File

@ -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 @@ class FormConfigBuilder implements FormConfigBuilderInterface
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 @@ class FormConfigBuilder implements FormConfigBuilderInterface
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 @@ class FormConfigBuilder implements FormConfigBuilderInterface
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 @@ class FormConfigBuilder implements FormConfigBuilderInterface
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 @@ class FormConfigBuilder implements FormConfigBuilderInterface
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 @@ class FormConfigBuilder implements FormConfigBuilderInterface
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 @@ class FormConfigBuilder implements FormConfigBuilderInterface
throw new BadMethodCallException('The config builder cannot be modified anymore.');
}
$this->action = $action;
$this->action = (string) $action;
return $this;
}
@ -790,7 +731,7 @@ class FormConfigBuilder implements FormConfigBuilderInterface
$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 @@ class FormConfigBuilder implements FormConfigBuilderInterface
/**
* 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 @@ class FormConfigBuilder implements FormConfigBuilderInterface
* * 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
*/

View File

@ -108,7 +108,7 @@ interface FormConfigBuilderInterface extends FormConfigInterface
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 @@ interface FormConfigBuilderInterface extends FormConfigInterface
/**
* 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 @@ interface FormConfigBuilderInterface extends FormConfigInterface
public function setCompound($compound);
/**
* Set the types.
* Sets the resolved type.
*
* @return $this The configuration object
*/
@ -203,7 +202,7 @@ interface FormConfigBuilderInterface extends FormConfigInterface
/**
* 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 @@ interface FormConfigBuilderInterface extends FormConfigInterface
*
* 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
*/

View File

@ -70,15 +70,17 @@ interface FormConfigInterface
* 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 @@ interface FormConfigInterface
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 @@ interface FormConfigInterface
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 @@ interface FormConfigInterface
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
*/

View File

@ -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;

View File

@ -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 @@ class FormErrorIterator implements \RecursiveIterator, \SeekableIterator, \Array
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 @@ class FormErrorIterator implements \RecursiveIterator, \SeekableIterator, \Array
/**
* 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()
{

View File

@ -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;

View File

@ -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")
*/

View File

@ -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 @@ interface FormInterface extends \ArrayAccess, \Traversable, \Countable
* @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 @@ interface FormInterface extends \ArrayAccess, \Traversable, \Countable
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 @@ interface FormInterface extends \ArrayAccess, \Traversable, \Countable
/**
* Returns the form's configuration.
*
* @return FormConfigInterface The configuration
* @return FormConfigInterface The configuration instance
*/
public function getConfig();
@ -164,6 +192,8 @@ interface FormInterface extends \ArrayAccess, \Traversable, \Countable
/**
* 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 @@ interface FormInterface extends \ArrayAccess, \Traversable, \Countable
/**
* 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 @@ interface FormInterface extends \ArrayAccess, \Traversable, \Countable
* 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 @@ interface FormInterface extends \ArrayAccess, \Traversable, \Countable
* 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 @@ interface FormInterface extends \ArrayAccess, \Traversable, \Countable
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 @@ interface FormInterface extends \ArrayAccess, \Traversable, \Countable
/**
* 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 @@ interface FormInterface extends \ArrayAccess, \Traversable, \Countable
public function isRoot();
/**
* Creates a view.
*
* @return FormView The view
*/
public function createView(FormView $parent = null);

View File

@ -26,7 +26,7 @@ class FormRegistry implements FormRegistryInterface
/**
* Extensions.
*
* @var FormExtensionInterface[] An array of FormExtensionInterface
* @var FormExtensionInterface[]
*/
private $extensions = [];

View File

@ -74,7 +74,7 @@ interface FormRendererEngineInterface
* 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 @@ interface FormRendererEngineInterface
* 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.

View File

@ -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
*/

View File

@ -49,8 +49,8 @@ interface FormTypeGuesserInterface
/**
* 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)
*

View File

@ -41,6 +41,8 @@ class NativeRequestHandler implements RequestHandlerInterface
/**
* {@inheritdoc}
*
* @throws Exception\UnexpectedTypeException If the $request is not null
*/
public function handleRequest(FormInterface $form, $request = null)
{

View File

@ -450,6 +450,31 @@ class CompoundFormTest extends AbstractFormTest
$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;

View File

@ -54,6 +54,26 @@ class SimpleFormTest_Traversable implements \IteratorAggregate
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 @@ class SimpleFormTest extends AbstractFormTest
/**
* @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()
{