[Form] Adapted Form to create a deterministic property path by default

This commit is contained in:
Bernhard Schussek 2012-05-17 16:09:13 +02:00
parent 03f50588c2
commit 860dd1f7d8
23 changed files with 355 additions and 156 deletions

View File

@ -18,18 +18,6 @@ use Symfony\Component\Form\Exception\UnexpectedTypeException;
class PropertyPathMapper implements DataMapperInterface class PropertyPathMapper implements DataMapperInterface
{ {
/**
* Stores the class that the data of this form must be instances of.
*
* @var string
*/
private $dataClass;
public function __construct($dataClass = null)
{
$this->dataClass = $dataClass;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@ -40,10 +28,6 @@ class PropertyPathMapper implements DataMapperInterface
} }
if (!empty($data)) { if (!empty($data)) {
if (null !== $this->dataClass && !$data instanceof $this->dataClass) {
throw new UnexpectedTypeException($data, $this->dataClass);
}
$iterator = new VirtualFormAwareIterator($forms); $iterator = new VirtualFormAwareIterator($forms);
$iterator = new \RecursiveIteratorIterator($iterator); $iterator = new \RecursiveIteratorIterator($iterator);
@ -59,7 +43,7 @@ class PropertyPathMapper implements DataMapperInterface
public function mapDataToForm($data, FormInterface $form) public function mapDataToForm($data, FormInterface $form)
{ {
if (!empty($data)) { if (!empty($data)) {
$propertyPath = $form->getAttribute('property_path'); $propertyPath = $form->getPropertyPath();
if (null !== $propertyPath) { if (null !== $propertyPath) {
$propertyData = $propertyPath->getValue($data); $propertyData = $propertyPath->getValue($data);
@ -91,7 +75,7 @@ class PropertyPathMapper implements DataMapperInterface
*/ */
public function mapFormToData(FormInterface $form, &$data) public function mapFormToData(FormInterface $form, &$data)
{ {
$propertyPath = $form->getAttribute('property_path'); $propertyPath = $form->getPropertyPath();
if (null !== $propertyPath && $form->isSynchronized() && !$form->isDisabled()) { if (null !== $propertyPath && $form->isSynchronized() && !$form->isDisabled()) {
// If the data is identical to the value in $data, we are // If the data is identical to the value in $data, we are

View File

@ -31,16 +31,6 @@ class FormType extends AbstractType
*/ */
public function buildForm(FormBuilder $builder, array $options) public function buildForm(FormBuilder $builder, array $options)
{ {
if (null === $options['property_path']) {
$options['property_path'] = $builder->getName();
}
if (false === $options['property_path'] || '' === $options['property_path']) {
$options['property_path'] = null;
} else {
$options['property_path'] = new PropertyPath($options['property_path']);
}
if (!is_array($options['attr'])) { if (!is_array($options['attr'])) {
throw new FormException('The "attr" option must be an "array".'); throw new FormException('The "attr" option must be an "array".');
} }
@ -54,9 +44,9 @@ class FormType extends AbstractType
->setDisabled($options['disabled']) ->setDisabled($options['disabled'])
->setErrorBubbling($options['error_bubbling']) ->setErrorBubbling($options['error_bubbling'])
->setEmptyData($options['empty_data']) ->setEmptyData($options['empty_data'])
->setPropertyPath($options['property_path'])
->setAttribute('read_only', $options['read_only']) ->setAttribute('read_only', $options['read_only'])
->setAttribute('by_reference', $options['by_reference']) ->setAttribute('by_reference', $options['by_reference'])
->setAttribute('property_path', $options['property_path'])
->setAttribute('error_mapping', $options['error_mapping']) ->setAttribute('error_mapping', $options['error_mapping'])
->setAttribute('max_length', $options['max_length']) ->setAttribute('max_length', $options['max_length'])
->setAttribute('pattern', $options['pattern']) ->setAttribute('pattern', $options['pattern'])
@ -69,7 +59,7 @@ class FormType extends AbstractType
->setAttribute('virtual', $options['virtual']) ->setAttribute('virtual', $options['virtual'])
->setAttribute('single_control', $options['single_control']) ->setAttribute('single_control', $options['single_control'])
->setData($options['data']) ->setData($options['data'])
->setDataMapper(new PropertyPathMapper($options['data_class'])) ->setDataMapper(new PropertyPathMapper())
->addEventSubscriber(new ValidationListener()) ->addEventSubscriber(new ValidationListener())
; ;
@ -112,7 +102,7 @@ class FormType extends AbstractType
} }
$types = array(); $types = array();
foreach ($form->getTypes() as $type) { foreach ($form->getConfig()->getTypes() as $type) {
$types[] = $type->getName(); $types[] = $type->getName();
} }
@ -229,7 +219,7 @@ class FormType extends AbstractType
*/ */
public function createBuilder($name, FormFactoryInterface $factory, array $options) public function createBuilder($name, FormFactoryInterface $factory, array $options)
{ {
return new FormBuilder($name, $factory, new EventDispatcher(), $options['data_class']); return new FormBuilder($name, $options['data_class'], new EventDispatcher(), $factory);
} }
/** /**

View File

@ -17,6 +17,7 @@ use Symfony\Component\Form\Exception\FormException;
use Symfony\Component\Form\Exception\AlreadyBoundException; use Symfony\Component\Form\Exception\AlreadyBoundException;
use Symfony\Component\Form\Exception\UnexpectedTypeException; use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\Form\Exception\TransformationFailedException; use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\Form\Util\PropertyPath;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@ -162,10 +163,33 @@ class Form implements \IteratorAggregate, FormInterface
return $this->config->getName(); return $this->config->getName();
} }
/**
* {@inheritdoc}
*/
public function getPropertyPath()
{
if (!$this->hasParent() || null !== $this->config->getPropertyPath()) {
return $this->config->getPropertyPath();
}
if (null === $this->getName() || '' === $this->getName()) {
return null;
}
if (null === $this->getParent()->getConfig()->getDataClass()) {
return new PropertyPath('[' . $this->getName() . ']');
}
return new PropertyPath($this->getName());
}
/** /**
* Returns the types used by this form. * Returns the types used by this form.
* *
* @return array An array of FormTypeInterface * @return array An array of FormTypeInterface
*
* @deprecated Deprecated since version 2.1, to be removed in 2.3. Use
* {@link getConfig()} and {@link FormConfigInterface::getTypes()} instead.
*/ */
public function getTypes() public function getTypes()
{ {
@ -261,9 +285,9 @@ class Form implements \IteratorAggregate, FormInterface
/** /**
* Returns whether the form has an attribute with the given name. * Returns whether the form has an attribute with the given name.
* *
* @param string $name The name of the attribute * @param string $name The name of the attribute.
* *
* @return Boolean * @return Boolean Whether the attribute exists.
*/ */
public function hasAttribute($name) public function hasAttribute($name)
{ {
@ -273,7 +297,9 @@ class Form implements \IteratorAggregate, FormInterface
/** /**
* Returns the value of the attributes with the given name. * Returns the value of the attributes with the given name.
* *
* @param string $name The name of the attribute * @param string $name The name of the attribute
*
* @return mixed The attribute value.
*/ */
public function getAttribute($name) public function getAttribute($name)
{ {
@ -310,6 +336,25 @@ class Form implements \IteratorAggregate, FormInterface
$normData = $this->appToNorm($appData); $normData = $this->appToNorm($appData);
$clientData = $this->normToClient($normData); $clientData = $this->normToClient($normData);
// Validate if client data matches data class (unless empty)
if (!empty($clientData)) {
// TESTME
$dataClass = $this->config->getDataClass();
if (null === $dataClass && is_object($clientData)) {
// TODO clarify error message: should not be transformed to object if data class is null
// possible solutions: set data class or change client transformers
throw new UnexpectedTypeException($clientData, 'scalar or array');
}
// TESTME
if (null !== $dataClass && !$clientData instanceof $dataClass) {
// TODO clarify error message: should be transformed to object if data class is set
// possible solutions: change data class or client transformers
throw new UnexpectedTypeException($clientData, $dataClass);
}
}
$this->appData = $appData; $this->appData = $appData;
$this->normData = $normData; $this->normData = $normData;
$this->clientData = $clientData; $this->clientData = $clientData;
@ -568,6 +613,9 @@ class Form implements \IteratorAggregate, FormInterface
* Returns whether errors bubble up to the parent. * Returns whether errors bubble up to the parent.
* *
* @return Boolean * @return Boolean
*
* @deprecated Deprecated since version 2.1, to be removed in 2.3. Use
* {@link getConfig()} and {@link FormConfigInterface::getErrorBubbling()} instead.
*/ */
public function getErrorBubbling() public function getErrorBubbling()
{ {
@ -693,6 +741,9 @@ class Form implements \IteratorAggregate, FormInterface
* Returns the DataTransformers. * Returns the DataTransformers.
* *
* @return array An array of DataTransformerInterface * @return array An array of DataTransformerInterface
*
* @deprecated Deprecated since version 2.1, to be removed in 2.3. Use
* {@link getConfig()} and {@link FormConfigInterface::getNormTransformers()} instead.
*/ */
public function getNormTransformers() public function getNormTransformers()
{ {
@ -703,6 +754,9 @@ class Form implements \IteratorAggregate, FormInterface
* Returns the DataTransformers. * Returns the DataTransformers.
* *
* @return array An array of DataTransformerInterface * @return array An array of DataTransformerInterface
*
* @deprecated Deprecated since version 2.1, to be removed in 2.3. Use
* {@link getConfig()} and {@link FormConfigInterface::getClientTransformers()} instead.
*/ */
public function getClientTransformers() public function getClientTransformers()
{ {

View File

@ -31,11 +31,6 @@ class FormBuilder extends FormConfig
*/ */
private $factory; private $factory;
/**
* @var string
*/
private $dataClass;
/** /**
* The children of the form builder. * The children of the form builder.
* *
@ -59,19 +54,18 @@ class FormBuilder extends FormConfig
private $parent; private $parent;
/** /**
* Constructor. * Creates a new form builder.
* *
* @param string $name * @param string $name
* @param FormFactoryInterface $factory
* @param EventDispatcherInterface $dispatcher
* @param string $dataClass * @param string $dataClass
* @param EventDispatcherInterface $dispatcher
* @param FormFactoryInterface $factory
*/ */
public function __construct($name, FormFactoryInterface $factory, EventDispatcherInterface $dispatcher, $dataClass = null) public function __construct($name, $dataClass, EventDispatcherInterface $dispatcher, FormFactoryInterface $factory)
{ {
parent::__construct($name, $dispatcher); parent::__construct($name, $dataClass, $dispatcher);
$this->factory = $factory; $this->factory = $factory;
$this->dataClass = $dataClass;
} }
/** /**
@ -140,7 +134,7 @@ class FormBuilder extends FormConfig
*/ */
public function create($name, $type = null, array $options = array()) public function create($name, $type = null, array $options = array())
{ {
if (null === $type && !$this->dataClass) { if (null === $type && null === $this->getDataClass()) {
$type = 'text'; $type = 'text';
} }
@ -148,7 +142,7 @@ class FormBuilder extends FormConfig
return $this->getFormFactory()->createNamedBuilder($type, $name, null, $options, $this); return $this->getFormFactory()->createNamedBuilder($type, $name, null, $options, $this);
} }
return $this->getFormFactory()->createBuilderForProperty($this->dataClass, $name, null, $options, $this); return $this->getFormFactory()->createBuilderForProperty($this->getDataClass(), $name, null, $options, $this);
} }
/** /**

View File

@ -33,6 +33,16 @@ class FormConfig implements FormConfigInterface
*/ */
private $name; private $name;
/**
* @var PropertyPath
*/
private $propertyPath;
/**
* @var Boolean
*/
private $mapped;
/** /**
* @var array * @var array
*/ */
@ -88,19 +98,34 @@ class FormConfig implements FormConfigInterface
*/ */
private $data; private $data;
/**
* @var string
*/
private $dataClass;
/** /**
* Creates an empty form configuration. * Creates an empty form configuration.
* *
* @param string $name The form name. * @param string $name The form name.
* @param EventDispatcherInterface $dispatcher The event dispatcher. * @param string $dataClass The class of the form's data.
* @param EventDispatcherInterface $dispatcher The event dispatcher.
*
* @throws UnexpectedTypeException If the name is not a string.
* @throws \InvalidArgumentException If the data class is not a valid class or if
* the name contains invalid characters.
*/ */
public function __construct($name, EventDispatcherInterface $dispatcher) public function __construct($name, $dataClass, EventDispatcherInterface $dispatcher)
{ {
$name = (string) $name; $name = (string) $name;
self::validateName($name); self::validateName($name);
if (null !== $dataClass && !class_exists($dataClass)) {
throw new \InvalidArgumentException(sprintf('The data class "%s" is not a valid class.', $dataClass));
}
$this->name = $name; $this->name = $name;
$this->dataClass = $dataClass;
$this->dispatcher = $dispatcher; $this->dispatcher = $dispatcher;
} }
@ -248,6 +273,22 @@ class FormConfig implements FormConfigInterface
return $this->name; return $this->name;
} }
/**
* {@inheritdoc}
*/
public function getPropertyPath()
{
return $this->propertyPath;
}
/**
* {@inheritdoc}
*/
public function getMapped()
{
return $this->mapped;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@ -354,6 +395,14 @@ class FormConfig implements FormConfigInterface
return $this->data; return $this->data;
} }
/**
* {@inheritdoc}
*/
public function getDataClass()
{
return $this->dataClass;
}
/** /**
* Sets the value for an attribute. * Sets the value for an attribute.
* *
@ -453,6 +502,41 @@ class FormConfig implements FormConfigInterface
return $this; return $this;
} }
/**
* Sets the property path that the form should be mapped to.
*
* @param string|PropertyPath $propertyPath The property path or null if the path
* should be set automatically based on
* the form's name.
*
* @return self The configuration object.
*/
public function setPropertyPath($propertyPath)
{
if (null !== $propertyPath && !$propertyPath instanceof PropertyPath) {
$propertyPath = new PropertyPath($propertyPath);
}
$this->propertyPath = $propertyPath;
return $this;
}
/**
* Sets whether the form should be mapped to an element of its
* parent's data.
*
* @param Boolean $mapped Whether the form should be mapped.
*
* @return self The configuration object.
*/
public function setMapped($mapped)
{
$this->mapped = $mapped;
return $this;
}
/** /**
* Set the types. * Set the types.
* *
@ -486,7 +570,7 @@ class FormConfig implements FormConfigInterface
* *
* @param string $name The tested form name. * @param string $name The tested form name.
* *
* @throws UnexpectedTypeException If the name is not a string. * @throws UnexpectedTypeException If the name is not a string.
* @throws \InvalidArgumentException If the name contains invalid characters. * @throws \InvalidArgumentException If the name contains invalid characters.
*/ */
static public function validateName($name) static public function validateName($name)

View File

@ -32,6 +32,21 @@ interface FormConfigInterface
*/ */
function getName(); function getName();
/**
* Returns the property path that the form should be mapped to.
*
* @return Util\PropertyPath The property path.
*/
function getPropertyPath();
/**
* Returns whether the form should be mapped to an element of its
* parent's data.
*
* @return Boolean Whether the form is mapped.
*/
function getMapped();
/** /**
* Returns the form types used to construct the form. * Returns the form types used to construct the form.
* *
@ -128,4 +143,11 @@ interface FormConfigInterface
* @return mixed The initial form data. * @return mixed The initial form data.
*/ */
function getData(); function getData();
/**
* Returns the class of the form data or null if the data is scalar or an array.
*
* @return string The data class or null.
*/
function getDataClass();
} }

View File

@ -151,7 +151,6 @@ class FormFactory implements FormFactoryInterface
$builder = null; $builder = null;
$types = array(); $types = array();
$optionValues = array();
$knownOptions = array(); $knownOptions = array();
$optionsResolver = new OptionsResolver(); $optionsResolver = new OptionsResolver();

View File

@ -131,6 +131,13 @@ interface FormInterface extends \ArrayAccess, \Traversable, \Countable
*/ */
function getExtraData(); function getExtraData();
/**
* Returns the form's configuration.
*
* @return FormConfigInterface The configuration.
*/
function getConfig();
/** /**
* Returns whether the field is bound. * Returns whether the field is bound.
* *
@ -138,13 +145,6 @@ interface FormInterface extends \ArrayAccess, \Traversable, \Countable
*/ */
function isBound(); function isBound();
/**
* Returns the supported types.
*
* @return array An array of FormTypeInterface
*/
function getTypes();
/** /**
* Returns the name by which the form is identified in forms. * Returns the name by which the form is identified in forms.
* *
@ -152,6 +152,13 @@ interface FormInterface extends \ArrayAccess, \Traversable, \Countable
*/ */
function getName(); function getName();
/**
* Returns the property path that the form is mapped to.
*
* @return Util\PropertyPath The property path.
*/
function getPropertyPath();
/** /**
* Adds an error to this form. * Adds an error to this form.
* *

View File

@ -31,6 +31,16 @@ class ImmutableFormConfig implements FormConfigInterface
*/ */
private $name; private $name;
/**
* @var PropertyPath
*/
private $propertyPath;
/**
* @var Boolean
*/
private $mapped;
/** /**
* @var array * @var array
*/ */
@ -86,6 +96,11 @@ class ImmutableFormConfig implements FormConfigInterface
*/ */
private $data; private $data;
/**
* @var string
*/
private $dataClass;
/** /**
* Creates an immutable copy of a given configuration. * Creates an immutable copy of a given configuration.
* *
@ -95,6 +110,8 @@ class ImmutableFormConfig implements FormConfigInterface
{ {
$this->dispatcher = $config->getEventDispatcher(); $this->dispatcher = $config->getEventDispatcher();
$this->name = $config->getName(); $this->name = $config->getName();
$this->propertyPath = $config->getPropertyPath();
$this->mapped = $config->getMapped();
$this->types = $config->getTypes(); $this->types = $config->getTypes();
$this->clientTransformers = $config->getClientTransformers(); $this->clientTransformers = $config->getClientTransformers();
$this->normTransformers = $config->getNormTransformers(); $this->normTransformers = $config->getNormTransformers();
@ -104,8 +121,9 @@ class ImmutableFormConfig implements FormConfigInterface
$this->disabled = $config->getDisabled(); $this->disabled = $config->getDisabled();
$this->errorBubbling = $config->getErrorBubbling(); $this->errorBubbling = $config->getErrorBubbling();
$this->emptyData = $config->getEmptyData(); $this->emptyData = $config->getEmptyData();
$this->data = $config->getData();
$this->attributes = $config->getAttributes(); $this->attributes = $config->getAttributes();
$this->data = $config->getData();
$this->dataClass = $config->getDataClass();
} }
/** /**
@ -124,6 +142,22 @@ class ImmutableFormConfig implements FormConfigInterface
return $this->name; return $this->name;
} }
/**
* {@inheritdoc}
*/
public function getPropertyPath()
{
return $this->propertyPath;
}
/**
* {@inheritdoc}
*/
public function getMapped()
{
return $this->mapped;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@ -229,4 +263,12 @@ class ImmutableFormConfig implements FormConfigInterface
{ {
return $this->data; return $this->data;
} }
/**
* {@inheritdoc}
*/
public function getDataClass()
{
return $this->dataClass;
}
} }

View File

@ -74,9 +74,12 @@ class PropertyPathMapperTest extends \PHPUnit_Framework_TestCase
->setMethods(array('foo')) ->setMethods(array('foo'))
->getMockForAbstractClass(); ->getMockForAbstractClass();
$form->setAttribute('property_path', $propertyPath);
$form->setAttribute('by_reference', $byReference); $form->setAttribute('by_reference', $byReference);
$form->expects($this->any())
->method('getPropertyPath')
->will($this->returnValue($propertyPath));
$form->expects($this->any()) $form->expects($this->any())
->method('isSynchronized') ->method('isSynchronized')
->will($this->returnValue($synchronized)); ->will($this->returnValue($synchronized));

View File

@ -11,10 +11,17 @@
namespace Symfony\Component\Form\Tests\Extension\Core\EventListener; namespace Symfony\Component\Form\Tests\Extension\Core\EventListener;
use Symfony\Component\Form\FormBuilder;
class MergeCollectionListenerArrayObjectTest extends MergeCollectionListenerTest class MergeCollectionListenerArrayObjectTest extends MergeCollectionListenerTest
{ {
protected function getData(array $data) protected function getData(array $data)
{ {
return new \ArrayObject($data); return new \ArrayObject($data);
} }
protected function getBuilder($name = 'name')
{
return new FormBuilder($name, '\ArrayObject', $this->dispatcher, $this->factory);
}
} }

View File

@ -11,10 +11,17 @@
namespace Symfony\Component\Form\Tests\Extension\Core\EventListener; namespace Symfony\Component\Form\Tests\Extension\Core\EventListener;
use Symfony\Component\Form\FormBuilder;
class MergeCollectionListenerArrayTest extends MergeCollectionListenerTest class MergeCollectionListenerArrayTest extends MergeCollectionListenerTest
{ {
protected function getData(array $data) protected function getData(array $data)
{ {
return $data; return $data;
} }
protected function getBuilder($name = 'name')
{
return new FormBuilder($name, null, $this->dispatcher, $this->factory);
}
} }

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Form\Tests\Extension\Core\EventListener; namespace Symfony\Component\Form\Tests\Extension\Core\EventListener;
use Symfony\Component\Form\Tests\Fixtures\CustomArrayObject; use Symfony\Component\Form\Tests\Fixtures\CustomArrayObject;
use Symfony\Component\Form\FormBuilder;
class MergeCollectionListenerCustomArrayObjectTest extends MergeCollectionListenerTest class MergeCollectionListenerCustomArrayObjectTest extends MergeCollectionListenerTest
{ {
@ -19,4 +20,9 @@ class MergeCollectionListenerCustomArrayObjectTest extends MergeCollectionListen
{ {
return new CustomArrayObject($data); return new CustomArrayObject($data);
} }
protected function getBuilder($name = 'name')
{
return new FormBuilder($name, 'Symfony\Component\Form\Tests\Fixtures\CustomArrayObject', $this->dispatcher, $this->factory);
}
} }

View File

@ -17,9 +17,9 @@ use Symfony\Component\Form\FormBuilder;
abstract class MergeCollectionListenerTest extends \PHPUnit_Framework_TestCase abstract class MergeCollectionListenerTest extends \PHPUnit_Framework_TestCase
{ {
private $dispatcher; protected $dispatcher;
private $factory; protected $factory;
private $form; protected $form;
protected function setUp() protected function setUp()
{ {
@ -39,10 +39,7 @@ abstract class MergeCollectionListenerTest extends \PHPUnit_Framework_TestCase
$this->form = null; $this->form = null;
} }
protected function getBuilder($name = 'name') abstract protected function getBuilder($name = 'name');
{
return new FormBuilder($name, $this->factory, $this->dispatcher);
}
protected function getForm($name = 'name', $propertyPath = null) protected function getForm($name = 'name', $propertyPath = null)
{ {

View File

@ -42,7 +42,7 @@ class ResizeFormListenerTest extends \PHPUnit_Framework_TestCase
protected function getBuilder($name = 'name') protected function getBuilder($name = 'name')
{ {
return new FormBuilder($name, $this->factory, $this->dispatcher); return new FormBuilder($name, null, $this->dispatcher, $this->factory);
} }
protected function getForm($name = 'name') protected function getForm($name = 'name')

View File

@ -51,41 +51,6 @@ class FormTest_AuthorWithoutRefSetter
class FormTypeTest extends TypeTestCase class FormTypeTest extends TypeTestCase
{ {
public function testGetPropertyPathDefaultPath()
{
$form = $this->factory->createNamed('form', 'title');
$this->assertEquals(new PropertyPath('title'), $form->getAttribute('property_path'));
}
public function testGetPropertyPathPathIsZero()
{
$form = $this->factory->create('form', null, array('property_path' => '0'));
$this->assertEquals(new PropertyPath('0'), $form->getAttribute('property_path'));
}
public function testGetPropertyPathPathIsEmpty()
{
$form = $this->factory->create('form', null, array('property_path' => ''));
$this->assertNull($form->getAttribute('property_path'));
}
public function testGetPropertyPathPathIsFalse()
{
$form = $this->factory->create('form', null, array('property_path' => false));
$this->assertNull($form->getAttribute('property_path'));
}
public function testGetPropertyPathPathIsNull()
{
$form = $this->factory->createNamed('form', 'title', null, array('property_path' => null));
$this->assertEquals(new PropertyPath('title'), $form->getAttribute('property_path'));
}
public function testPassRequiredAsOption() public function testPassRequiredAsOption()
{ {
$form = $this->factory->create('form', null, array('required' => false)); $form = $this->factory->create('form', null, array('required' => false));
@ -285,10 +250,9 @@ class FormTypeTest extends TypeTestCase
$this->assertEquals($author, $form->getData()); $this->assertEquals($author, $form->getData());
} }
public function testBindWithEmptyDataDoesNotCreateObjectIfDataClassIsNull() public function testBindWithEmptyDataCreatesArrayIfDataClassIsNull()
{ {
$form = $this->factory->create('form', null, array( $form = $this->factory->create('form', null, array(
'data' => new Author(),
'data_class' => null, 'data_class' => null,
'required' => false, 'required' => false,
)); ));
@ -365,6 +329,7 @@ class FormTypeTest extends TypeTestCase
$author = new Author(); $author = new Author();
$form = $this->factory->create('form', null, array( $form = $this->factory->create('form', null, array(
'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author',
'empty_data' => $author, 'empty_data' => $author,
)); ));
$form->add($this->factory->createNamed('form', 'firstName')); $form->add($this->factory->createNamed('form', 'firstName'));
@ -430,10 +395,11 @@ class FormTypeTest extends TypeTestCase
{ {
$author = new FormTest_AuthorWithoutRefSetter(new Author()); $author = new FormTest_AuthorWithoutRefSetter(new Author());
$builder = $this->factory->createBuilder('form'); $builder = $this->factory->createBuilder('form', $author);
$builder->add('reference', 'form'); $builder->add('reference', 'form', array(
'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author',
));
$builder->get('reference')->add('firstName', 'form'); $builder->get('reference')->add('firstName', 'form');
$builder->setData($author);
$form = $builder->getForm(); $form = $builder->getForm();
$form->bind(array( $form->bind(array(
@ -452,10 +418,11 @@ class FormTypeTest extends TypeTestCase
$author = new FormTest_AuthorWithoutRefSetter(null); $author = new FormTest_AuthorWithoutRefSetter(null);
$newReference = new Author(); $newReference = new Author();
$builder = $this->factory->createBuilder('form'); $builder = $this->factory->createBuilder('form', $author);
$builder->add('referenceCopy', 'form'); $builder->add('referenceCopy', 'form', array(
'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author',
));
$builder->get('referenceCopy')->add('firstName', 'form'); $builder->get('referenceCopy')->add('firstName', 'form');
$builder->setData($author);
$form = $builder->getForm(); $form = $builder->getForm();
$form['referenceCopy']->setData($newReference); // new author object $form['referenceCopy']->setData($newReference); // new author object
@ -474,17 +441,19 @@ class FormTypeTest extends TypeTestCase
{ {
$author = new FormTest_AuthorWithoutRefSetter(new Author()); $author = new FormTest_AuthorWithoutRefSetter(new Author());
$builder = $this->factory->createBuilder('form'); $builder = $this->factory->createBuilder('form', $author);
$builder->add('referenceCopy', 'form', array('by_reference' => false)); $builder->add('referenceCopy', 'form', array(
'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author',
'by_reference' => false
));
$builder->get('referenceCopy')->add('firstName', 'form'); $builder->get('referenceCopy')->add('firstName', 'form');
$builder->setData($author);
$form = $builder->getForm(); $form = $builder->getForm();
$form->bind(array( $form->bind(array(
// referenceCopy has a getter that returns a copy // referenceCopy has a getter that returns a copy
'referenceCopy' => array( 'referenceCopy' => array(
'firstName' => 'Foo', 'firstName' => 'Foo',
) )
)); ));
// firstName can only be updated if setReferenceCopy() was called // firstName can only be updated if setReferenceCopy() was called
@ -495,16 +464,14 @@ class FormTypeTest extends TypeTestCase
{ {
$author = new FormTest_AuthorWithoutRefSetter('scalar'); $author = new FormTest_AuthorWithoutRefSetter('scalar');
$builder = $this->factory->createBuilder('form'); $builder = $this->factory->createBuilder('form', $author);
$builder->add('referenceCopy', 'form'); $builder->add('referenceCopy', 'form');
$builder->get('referenceCopy')->appendClientTransformer(new CallbackTransformer( $builder->get('referenceCopy')->appendClientTransformer(new CallbackTransformer(
function () {}, function () {},
function ($value) { // reverseTransform function ($value) { // reverseTransform
return 'foobar';
return 'foobar'; }
}
)); ));
$builder->setData($author);
$form = $builder->getForm(); $form = $builder->getForm();
$form->bind(array( $form->bind(array(
@ -525,11 +492,10 @@ class FormTypeTest extends TypeTestCase
$builder->setData($author); $builder->setData($author);
$builder->add('referenceCopy', 'form'); $builder->add('referenceCopy', 'form');
$builder->get('referenceCopy')->appendClientTransformer(new CallbackTransformer( $builder->get('referenceCopy')->appendClientTransformer(new CallbackTransformer(
function () {}, function () {},
function ($value) use ($ref2) { // reverseTransform function ($value) use ($ref2) { // reverseTransform
return $ref2;
return $ref2; }
}
)); ));
$form = $builder->getForm(); $form = $builder->getForm();

View File

@ -18,10 +18,19 @@ use Symfony\Component\EventDispatcher\EventDispatcher;
abstract class TypeTestCase extends \PHPUnit_Framework_TestCase abstract class TypeTestCase extends \PHPUnit_Framework_TestCase
{ {
/**
* @var FormFactory
*/
protected $factory; protected $factory;
/**
* @var FormBuilder
*/
protected $builder; protected $builder;
/**
* @var EventDispatcher
*/
protected $dispatcher; protected $dispatcher;
protected function setUp() protected function setUp()
@ -32,7 +41,7 @@ abstract class TypeTestCase extends \PHPUnit_Framework_TestCase
$this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); $this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
$this->factory = new FormFactory($this->getExtensions()); $this->factory = new FormFactory($this->getExtensions());
$this->builder = new FormBuilder(null, $this->factory, $this->dispatcher); $this->builder = new FormBuilder(null, null, $this->dispatcher, $this->factory);
} }
protected function tearDown() protected function tearDown()

View File

@ -88,9 +88,9 @@ class DelegatingValidationListenerTest extends \PHPUnit_Framework_TestCase
return new FormError($this->message, $this->params); return new FormError($this->message, $this->params);
} }
protected function getBuilder($name = 'name', $propertyPath = null) protected function getBuilder($name = 'name', $propertyPath = null, $dataClass = null)
{ {
$builder = new FormBuilder($name, $this->factory, $this->dispatcher); $builder = new FormBuilder($name, $dataClass, $this->dispatcher, $this->factory);
$builder->setAttribute('property_path', new PropertyPath($propertyPath ?: $name)); $builder->setAttribute('property_path', new PropertyPath($propertyPath ?: $name));
$builder->setAttribute('error_mapping', array()); $builder->setAttribute('error_mapping', array());
$builder->setErrorBubbling(false); $builder->setErrorBubbling(false);
@ -98,9 +98,9 @@ class DelegatingValidationListenerTest extends \PHPUnit_Framework_TestCase
return $builder; return $builder;
} }
protected function getForm($name = 'name', $propertyPath = null) protected function getForm($name = 'name', $propertyPath = null, $dataClass = null)
{ {
return $this->getBuilder($name, $propertyPath)->getForm(); return $this->getBuilder($name, $propertyPath, $dataClass)->getForm();
} }
protected function getMockForm() protected function getMockForm()
@ -608,7 +608,7 @@ class DelegatingValidationListenerTest extends \PHPUnit_Framework_TestCase
$context = $this->getExecutionContext(); $context = $this->getExecutionContext();
$graphWalker = $context->getGraphWalker(); $graphWalker = $context->getGraphWalker();
$object = $this->getMock('\stdClass'); $object = $this->getMock('\stdClass');
$form = $this->getBuilder() $form = $this->getBuilder('name', null, '\stdClass')
->setAttribute('validation_groups', array('group1', 'group2')) ->setAttribute('validation_groups', array('group1', 'group2'))
->getForm(); ->getForm();
@ -629,7 +629,7 @@ class DelegatingValidationListenerTest extends \PHPUnit_Framework_TestCase
$context = $this->getExecutionContext(); $context = $this->getExecutionContext();
$graphWalker = $context->getGraphWalker(); $graphWalker = $context->getGraphWalker();
$object = $this->getMock('\stdClass'); $object = $this->getMock('\stdClass');
$form = $this->getBuilder() $form = $this->getBuilder('name', null, '\stdClass')
->setAttribute('validation_groups', array($this, 'getValidationGroups')) ->setAttribute('validation_groups', array($this, 'getValidationGroups'))
->getForm(); ->getForm();
@ -650,7 +650,7 @@ class DelegatingValidationListenerTest extends \PHPUnit_Framework_TestCase
$context = $this->getExecutionContext(); $context = $this->getExecutionContext();
$graphWalker = $context->getGraphWalker(); $graphWalker = $context->getGraphWalker();
$object = $this->getMock('\stdClass'); $object = $this->getMock('\stdClass');
$form = $this->getBuilder() $form = $this->getBuilder('name', null, '\stdClass')
->setAttribute('validation_groups', function(FormInterface $form){ ->setAttribute('validation_groups', function(FormInterface $form){
return array('group1', 'group2'); return array('group1', 'group2');
}) })
@ -677,7 +677,7 @@ class DelegatingValidationListenerTest extends \PHPUnit_Framework_TestCase
$parent = $this->getBuilder() $parent = $this->getBuilder()
->setAttribute('validation_groups', 'group') ->setAttribute('validation_groups', 'group')
->getForm(); ->getForm();
$child = $this->getBuilder() $child = $this->getBuilder('name', null, '\stdClass')
->setAttribute('validation_groups', null) ->setAttribute('validation_groups', null)
->getForm(); ->getForm();
$parent->add($child); $parent->add($child);
@ -700,7 +700,7 @@ class DelegatingValidationListenerTest extends \PHPUnit_Framework_TestCase
$parent = $this->getBuilder() $parent = $this->getBuilder()
->setAttribute('validation_groups', array($this, 'getValidationGroups')) ->setAttribute('validation_groups', array($this, 'getValidationGroups'))
->getForm(); ->getForm();
$child = $this->getBuilder() $child = $this->getBuilder('name', null, '\stdClass')
->setAttribute('validation_groups', null) ->setAttribute('validation_groups', null)
->getForm(); ->getForm();
$parent->add($child); $parent->add($child);
@ -728,7 +728,7 @@ class DelegatingValidationListenerTest extends \PHPUnit_Framework_TestCase
return array('group1', 'group2'); return array('group1', 'group2');
}) })
->getForm(); ->getForm();
$child = $this->getBuilder() $child = $this->getBuilder('name', null, '\stdClass')
->setAttribute('validation_groups', null) ->setAttribute('validation_groups', null)
->getForm(); ->getForm();
$parent->add($child); $parent->add($child);
@ -750,7 +750,7 @@ class DelegatingValidationListenerTest extends \PHPUnit_Framework_TestCase
$context = $this->getExecutionContext('foo.bar'); $context = $this->getExecutionContext('foo.bar');
$graphWalker = $context->getGraphWalker(); $graphWalker = $context->getGraphWalker();
$object = $this->getMock('\stdClass'); $object = $this->getMock('\stdClass');
$form = $this->getForm(); $form = $this->getForm('name', null, '\stdClass');
$graphWalker->expects($this->once()) $graphWalker->expects($this->once())
->method('walkReference') ->method('walkReference')

View File

@ -31,7 +31,7 @@ class FooType extends AbstractType
public function createBuilder($name, FormFactoryInterface $factory, array $options) public function createBuilder($name, FormFactoryInterface $factory, array $options)
{ {
return new FormBuilder($name, $factory, new EventDispatcher()); return new FormBuilder($name, null, new EventDispatcher(), $factory);
} }
public function getDefaultOptions() public function getDefaultOptions()

View File

@ -25,7 +25,7 @@ class FormBuilderTest extends \PHPUnit_Framework_TestCase
{ {
$this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); $this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
$this->factory = $this->getMock('Symfony\Component\Form\FormFactoryInterface'); $this->factory = $this->getMock('Symfony\Component\Form\FormFactoryInterface');
$this->builder = new FormBuilder('name', $this->factory, $this->dispatcher); $this->builder = new FormBuilder('name', null, $this->dispatcher, $this->factory);
} }
protected function tearDown() protected function tearDown()
@ -60,7 +60,7 @@ class FormBuilderTest extends \PHPUnit_Framework_TestCase
public function testAddWithGuessFluent() public function testAddWithGuessFluent()
{ {
$this->builder = new FormBuilder('name', $this->factory, $this->dispatcher, 'stdClass'); $this->builder = new FormBuilder('name', 'stdClass', $this->dispatcher, $this->factory);
$builder = $this->builder->add('foo'); $builder = $this->builder->add('foo');
$this->assertSame($builder, $this->builder); $this->assertSame($builder, $this->builder);
} }
@ -83,7 +83,7 @@ class FormBuilderTest extends \PHPUnit_Framework_TestCase
$this->factory->expects($this->once()) $this->factory->expects($this->once())
->method('createNamedBuilder') ->method('createNamedBuilder')
->with('text', 'foo') ->with('text', 'foo')
->will($this->returnValue(new FormBuilder('foo', $this->factory, $this->dispatcher))); ->will($this->returnValue(new FormBuilder('foo', null, $this->dispatcher, $this->factory)));
$this->assertCount(0, $this->builder->all()); $this->assertCount(0, $this->builder->all());
$this->assertFalse($this->builder->has('foo')); $this->assertFalse($this->builder->has('foo'));
@ -159,7 +159,7 @@ class FormBuilderTest extends \PHPUnit_Framework_TestCase
->with('stdClass', $expectedName, null, $expectedOptions) ->with('stdClass', $expectedName, null, $expectedOptions)
->will($this->returnValue($this->getFormBuilder())); ->will($this->returnValue($this->getFormBuilder()));
$this->builder = new FormBuilder('name', $this->factory, $this->dispatcher, 'stdClass'); $this->builder = new FormBuilder('name', 'stdClass', $this->dispatcher, $this->factory);
$this->builder->add($expectedName, null, $expectedOptions); $this->builder->add($expectedName, null, $expectedOptions);
$builder = $this->builder->get($expectedName); $builder = $this->builder->get($expectedName);
@ -173,14 +173,14 @@ class FormBuilderTest extends \PHPUnit_Framework_TestCase
public function testGetParentForAddedBuilder() public function testGetParentForAddedBuilder()
{ {
$builder = new FormBuilder('name', $this->factory, $this->dispatcher); $builder = new FormBuilder('name', null, $this->dispatcher, $this->factory);
$this->builder->add($builder); $this->builder->add($builder);
$this->assertSame($this->builder, $builder->getParent()); $this->assertSame($this->builder, $builder->getParent());
} }
public function testGetParentForRemovedBuilder() public function testGetParentForRemovedBuilder()
{ {
$builder = new FormBuilder('name', $this->factory, $this->dispatcher); $builder = new FormBuilder('name', null, $this->dispatcher, $this->factory);
$this->builder->add($builder); $this->builder->add($builder);
$this->builder->remove('name'); $this->builder->remove('name');
$this->assertNull($builder->getParent()); $this->assertNull($builder->getParent());
@ -188,7 +188,7 @@ class FormBuilderTest extends \PHPUnit_Framework_TestCase
public function testGetParentForCreatedBuilder() public function testGetParentForCreatedBuilder()
{ {
$this->builder = new FormBuilder('name', $this->factory, $this->dispatcher, 'stdClass'); $this->builder = new FormBuilder('name', 'stdClass', $this->dispatcher, $this->factory);
$this->factory $this->factory
->expects($this->once()) ->expects($this->once())
->method('createNamedBuilder') ->method('createNamedBuilder')

View File

@ -64,7 +64,7 @@ class FormConfigTest extends \PHPUnit_Framework_TestCase
$dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); $dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
try { try {
new FormConfig($name, $dispatcher); new FormConfig($name, null, $dispatcher);
if (!$accepted) { if (!$accepted) {
$this->fail(sprintf('The value "%s" should not be accepted', $name)); $this->fail(sprintf('The value "%s" should not be accepted', $name));
} }

View File

@ -598,7 +598,7 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
$this->extension1->addType($type); $this->extension1->addType($type);
$parentBuilder = $this->getMockBuilder('Symfony\Component\Form\FormBuilder') $parentBuilder = $this->getMockBuilder('Symfony\Component\Form\FormBuilder')
->setConstructorArgs(array('name', $this->factory, $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'))) ->setConstructorArgs(array('name', null, $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'), $this->factory))
->getMock() ->getMock()
; ;

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Form\Tests; namespace Symfony\Component\Form\Tests;
use Symfony\Component\Form\Form; use Symfony\Component\Form\Form;
use Symfony\Component\Form\Util\PropertyPath;
use Symfony\Component\Form\FormConfig; use Symfony\Component\Form\FormConfig;
use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormBuilder; use Symfony\Component\Form\FormBuilder;
@ -61,7 +62,7 @@ class FormTest extends \PHPUnit_Framework_TestCase
'foo' => 'bar', 'foo' => 'bar',
)); ));
$config = new FormConfig('name', $this->dispatcher); $config = new FormConfig('name', null, $this->dispatcher);
$config->appendClientTransformer($client); $config->appendClientTransformer($client);
$config->appendNormTransformer($norm); $config->appendNormTransformer($norm);
$form = new Form($config); $form = new Form($config);
@ -1201,14 +1202,41 @@ class FormTest extends \PHPUnit_Framework_TestCase
*/ */
public function testFormCannotHaveEmptyNameNotInRootLevel() public function testFormCannotHaveEmptyNameNotInRootLevel()
{ {
$parent = $this->getBuilder() $this->getBuilder()
->add($this->getBuilder('')) ->add($this->getBuilder(''))
->getForm(); ->getForm();
} }
protected function getBuilder($name = 'name', EventDispatcherInterface $dispatcher = null) public function testGetPropertyPathReturnsConfiguredPath()
{ {
return new FormBuilder($name, $this->factory, $dispatcher ?: $this->dispatcher); $form = $this->getBuilder()->setPropertyPath('address.street')->getForm();
$this->assertEquals(new PropertyPath('address.street'), $form->getPropertyPath());
}
// see https://github.com/symfony/symfony/issues/3903
public function testGetPropertyPathDefaultsToNameIfParentHasDataClass()
{
$parent = $this->getBuilder(null, null, 'stdClass')->getForm();
$form = $this->getBuilder('name')->getForm();
$parent->add($form);
$this->assertEquals(new PropertyPath('name'), $form->getPropertyPath());
}
// see https://github.com/symfony/symfony/issues/3903
public function testGetPropertyPathDefaultsToIndexedNameIfParentDataClassIsNull()
{
$parent = $this->getBuilder()->getForm();
$form = $this->getBuilder('name')->getForm();
$parent->add($form);
$this->assertEquals(new PropertyPath('[name]'), $form->getPropertyPath());
}
protected function getBuilder($name = 'name', EventDispatcherInterface $dispatcher = null, $dataClass = null)
{
return new FormBuilder($name, $dataClass, $dispatcher ?: $this->dispatcher, $this->factory);
} }
protected function getMockForm($name = 'name') protected function getMockForm($name = 'name')