From c6d39ed61f1951a3bb3ca083331a61df6ae9281d Mon Sep 17 00:00:00 2001 From: Francis Besset Date: Tue, 1 Mar 2011 23:35:57 +0100 Subject: [PATCH 1/8] [Validator] Date: check if the value is a DateTime instance --- src/Symfony/Component/Validator/Constraints/DateValidator.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Symfony/Component/Validator/Constraints/DateValidator.php b/src/Symfony/Component/Validator/Constraints/DateValidator.php index da437dbfdf..43a4422e32 100644 --- a/src/Symfony/Component/Validator/Constraints/DateValidator.php +++ b/src/Symfony/Component/Validator/Constraints/DateValidator.php @@ -25,6 +25,10 @@ class DateValidator extends ConstraintValidator return true; } + if ($value instanceof \DateTime) { + return true; + } + if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString()'))) { throw new UnexpectedTypeException($value, 'string'); } From 886149fa0028189b0ac09c401dc6ff0ac054ecb3 Mon Sep 17 00:00:00 2001 From: Francis Besset Date: Tue, 1 Mar 2011 23:37:10 +0100 Subject: [PATCH 2/8] [Validator] Refactoring DateTimeValidator and DateValidator --- .../Constraints/DateTimeValidator.php | 31 +------------------ .../Validator/Constraints/DateValidator.php | 2 +- 2 files changed, 2 insertions(+), 31 deletions(-) diff --git a/src/Symfony/Component/Validator/Constraints/DateTimeValidator.php b/src/Symfony/Component/Validator/Constraints/DateTimeValidator.php index 8d86d40215..5959040ecb 100644 --- a/src/Symfony/Component/Validator/Constraints/DateTimeValidator.php +++ b/src/Symfony/Component/Validator/Constraints/DateTimeValidator.php @@ -11,36 +11,7 @@ namespace Symfony\Component\Validator\Constraints; -use Symfony\Component\Validator\Constraint; -use Symfony\Component\Validator\ConstraintValidator; -use Symfony\Component\Validator\Exception\UnexpectedTypeException; - -class DateTimeValidator extends ConstraintValidator +class DateTimeValidator extends DateValidator { const PATTERN = '/^(\d{4})-(\d{2})-(\d{2}) (0[0-9]|1[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$/'; - - public function isValid($value, Constraint $constraint) - { - if (null === $value || '' === $value) { - return true; - } - - if ($value instanceof \DateTime) { - return true; - } - - if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString()'))) { - throw new UnexpectedTypeException($value, 'string'); - } - - $value = (string) $value; - - if (!preg_match(self::PATTERN, $value, $matches)) { - $this->setMessage($constraint->message, array('{{ value }}' => $value)); - - return false; - } - - return checkdate($matches[2], $matches[3], $matches[1]); - } } \ No newline at end of file diff --git a/src/Symfony/Component/Validator/Constraints/DateValidator.php b/src/Symfony/Component/Validator/Constraints/DateValidator.php index 43a4422e32..42518bb94b 100644 --- a/src/Symfony/Component/Validator/Constraints/DateValidator.php +++ b/src/Symfony/Component/Validator/Constraints/DateValidator.php @@ -35,7 +35,7 @@ class DateValidator extends ConstraintValidator $value = (string) $value; - if (!preg_match(self::PATTERN, $value, $matches)) { + if (!preg_match(static::PATTERN, $value, $matches)) { $this->setMessage($constraint->message, array('{{ value }}' => $value)); return false; From fdd18250b3dfa636d1844f31833edd39fe779316 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Fri, 13 May 2011 17:16:36 +0200 Subject: [PATCH 3/8] [Form] Added getType() to FormFactoryInterface --- src/Symfony/Component/Form/FormFactoryInterface.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Symfony/Component/Form/FormFactoryInterface.php b/src/Symfony/Component/Form/FormFactoryInterface.php index 7818bf2f61..0418de5145 100644 --- a/src/Symfony/Component/Form/FormFactoryInterface.php +++ b/src/Symfony/Component/Form/FormFactoryInterface.php @@ -24,4 +24,6 @@ interface FormFactoryInterface function createNamedBuilder($type, $name, $data = null, array $options = array()); function createBuilderForProperty($class, $property, $data = null, array $options = array()); + + function getType($name); } From 21013b930c04ce5bb903664541c9ab3856e3802a Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Fri, 13 May 2011 18:29:18 +0200 Subject: [PATCH 4/8] [Form] Improved test coverage of FormFactory and improved error handling --- .../Form/Exception/CreationException.php | 21 ++ .../Exception/TypeDefinitionException.php | 21 ++ src/Symfony/Component/Form/FormFactory.php | 122 ++++++--- .../Component/Form/FormFactoryInterface.php | 4 + .../Component/Form/AbstractExtensionTest.php | 36 +-- .../Tests/Component/Form/Fixtures/FooType.php | 41 +++ .../Form/Fixtures/FooTypeBarExtension.php | 19 ++ .../Form/Fixtures/FooTypeBazExtension.php | 19 ++ .../Component/Form/Fixtures/TestExtension.php | 63 +++++ .../Tests/Component/Form/FormFactoryTest.php | 257 +++++++++++++++++- 10 files changed, 531 insertions(+), 72 deletions(-) create mode 100644 src/Symfony/Component/Form/Exception/CreationException.php create mode 100644 src/Symfony/Component/Form/Exception/TypeDefinitionException.php create mode 100644 tests/Symfony/Tests/Component/Form/Fixtures/FooType.php create mode 100644 tests/Symfony/Tests/Component/Form/Fixtures/FooTypeBarExtension.php create mode 100644 tests/Symfony/Tests/Component/Form/Fixtures/FooTypeBazExtension.php create mode 100644 tests/Symfony/Tests/Component/Form/Fixtures/TestExtension.php diff --git a/src/Symfony/Component/Form/Exception/CreationException.php b/src/Symfony/Component/Form/Exception/CreationException.php new file mode 100644 index 0000000000..9742103d62 --- /dev/null +++ b/src/Symfony/Component/Form/Exception/CreationException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Exception; + +/** + * Thrown when a form could not be constructed by a FormFactory + * + * @author Bernhard Schussek + */ +class CreationException extends FormException +{ +} \ No newline at end of file diff --git a/src/Symfony/Component/Form/Exception/TypeDefinitionException.php b/src/Symfony/Component/Form/Exception/TypeDefinitionException.php new file mode 100644 index 0000000000..9c88d4a6e7 --- /dev/null +++ b/src/Symfony/Component/Form/Exception/TypeDefinitionException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Exception; + +/** + * Thrown when a form type is configured incorrectly + * + * @author Bernhard Schussek + */ +class TypeDefinitionException extends FormException +{ +} \ No newline at end of file diff --git a/src/Symfony/Component/Form/FormFactory.php b/src/Symfony/Component/Form/FormFactory.php index bd103ff5df..365482941d 100644 --- a/src/Symfony/Component/Form/FormFactory.php +++ b/src/Symfony/Component/Form/FormFactory.php @@ -13,9 +13,17 @@ namespace Symfony\Component\Form; use Symfony\Component\Form\Exception\FormException; use Symfony\Component\Form\Exception\UnexpectedTypeException; +use Symfony\Component\Form\Exception\TypeDefinitionException; +use Symfony\Component\Form\Exception\CreationException; class FormFactory implements FormFactoryInterface { + private static $requiredOptions = array( + 'data', + 'required', + 'max_length', + ); + private $extensions = array(); private $types = array(); @@ -40,41 +48,36 @@ class FormFactory implements FormFactoryInterface $this->extensions = $extensions; } + public function hasType($name) + { + if (isset($this->types[$name])) { + return true; + } + + try { + $this->loadType($name); + } catch (FormException $e) { + return false; + } + + return true; + } + + public function addType(FormTypeInterface $type) + { + $this->loadTypeExtensions($type); + + $this->types[$type->getName()] = $type; + } + public function getType($name) { - $type = null; - - if ($name instanceof FormTypeInterface) { - $type = $name; - $name = $type->getName(); + if (!is_string($name)) { + throw new UnexpectedTypeException($name, 'string'); } if (!isset($this->types[$name])) { - if (!$type) { - foreach ($this->extensions as $extension) { - if ($extension->hasType($name)) { - $type = $extension->getType($name); - break; - } - } - - if (!$type) { - throw new FormException(sprintf('Could not load type "%s"', $name)); - } - } - - $typeExtensions = array(); - - foreach ($this->extensions as $extension) { - $typeExtensions = array_merge( - $typeExtensions, - $extension->getTypeExtensions($name) - ); - } - - $type->setExtensions($typeExtensions); - - $this->types[$name] = $type; + $this->loadType($name); } return $this->types[$name]; @@ -117,7 +120,11 @@ class FormFactory implements FormFactoryInterface } while (null !== $type) { - $type = $this->getType($type); + if ($type instanceof FormTypeInterface) { + $this->addType($type); + } else { + $type = $this->getType($type); + } $defaultOptions = $type->getDefaultOptions($options); @@ -131,17 +138,30 @@ class FormFactory implements FormFactoryInterface $type = $type->getParent($options); } - $diff = array_diff($passedOptions, $knownOptions); + $type = end($types); + $diff = array_diff(self::$requiredOptions, $knownOptions); if (count($diff) > 0) { - throw new FormException(sprintf('The options "%s" do not exist', implode('", "', $diff))); + throw new TypeDefinitionException(sprintf('Type "%s" should support the option(s) "%s"', $type->getName(), implode('", "', $diff))); + } + + $diff = array_diff($passedOptions, $knownOptions); + + if (count($diff) > 1) { + throw new CreationException(sprintf('The options "%s" do not exist', implode('", "', $diff))); + } + + if (count($diff) > 0) { + throw new CreationException(sprintf('The option "%s" does not exist', $diff[0])); } for ($i = 0, $l = count($types); $i < $l && !$builder; ++$i) { $builder = $types[$i]->createBuilder($name, $this, $options); } - // TODO check if instance exists + if (!$builder) { + throw new TypeDefinitionException(sprintf('Type "%s" or any of its parents should return a FormBuilder instance from createBuilder()', $type->getName())); + } $builder->setTypes($types); @@ -198,4 +218,38 @@ class FormFactory implements FormFactoryInterface $this->guesser = new FormTypeGuesserChain($guessers); } + + private function loadType($name) + { + $type = null; + + foreach ($this->extensions as $extension) { + if ($extension->hasType($name)) { + $type = $extension->getType($name); + break; + } + } + + if (!$type) { + throw new FormException(sprintf('Could not load type "%s"', $name)); + } + + $this->loadTypeExtensions($type); + + $this->types[$name] = $type; + } + + private function loadTypeExtensions(FormTypeInterface $type) + { + $typeExtensions = array(); + + foreach ($this->extensions as $extension) { + $typeExtensions = array_merge( + $typeExtensions, + $extension->getTypeExtensions($type->getName()) + ); + } + + $type->setExtensions($typeExtensions); + } } diff --git a/src/Symfony/Component/Form/FormFactoryInterface.php b/src/Symfony/Component/Form/FormFactoryInterface.php index 0418de5145..6206737cc0 100644 --- a/src/Symfony/Component/Form/FormFactoryInterface.php +++ b/src/Symfony/Component/Form/FormFactoryInterface.php @@ -26,4 +26,8 @@ interface FormFactoryInterface function createBuilderForProperty($class, $property, $data = null, array $options = array()); function getType($name); + + function hasType($name); + + function addType(FormTypeInterface $type); } diff --git a/tests/Symfony/Tests/Component/Form/AbstractExtensionTest.php b/tests/Symfony/Tests/Component/Form/AbstractExtensionTest.php index 4b7d67141e..6e00c734c7 100644 --- a/tests/Symfony/Tests/Component/Form/AbstractExtensionTest.php +++ b/tests/Symfony/Tests/Component/Form/AbstractExtensionTest.php @@ -17,53 +17,29 @@ use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\Form\FormBuilder; +use Symfony\Tests\Component\Form\Fixtures\FooType; class AbstractExtensionTest extends \PHPUnit_Framework_TestCase { public function testHasType() { - $loader = new TestExtension(); + $loader = new ConcreteExtension(); $this->assertTrue($loader->hasType('foo')); $this->assertFalse($loader->hasType('bar')); } public function testGetType() { - $loader = new TestExtension(array($type)); - $this->assertInstanceOf(__NAMESPACE__.'\TestType', $loader->getType('foo')); - $this->assertSame($loader->getType('foo'), $loader->getType('foo')); + $loader = new ConcreteExtension(); + $this->assertTrue($loader->getType('foo') instanceof FooType); } } -class TestType implements FormTypeInterface -{ - public function getName() - { - return 'foo'; - } - - function buildForm(FormBuilder $builder, array $options) {} - - function buildView(FormView $view, FormInterface $form) {} - - function buildViewBottomUp(FormView $view, FormInterface $form) {} - - function createBuilder($name, FormFactoryInterface $factory, array $options) {} - - function getDefaultOptions(array $options) {} - - function getParent(array $options) {} - - function setExtensions(array $extensions) {} - - function getExtensions() {} -} - -class TestExtension extends AbstractExtension +class ConcreteExtension extends AbstractExtension { protected function loadTypes() { - return array(new TestType()); + return array(new FooType()); } protected function loadTypeGuesser() diff --git a/tests/Symfony/Tests/Component/Form/Fixtures/FooType.php b/tests/Symfony/Tests/Component/Form/Fixtures/FooType.php new file mode 100644 index 0000000000..93f3cc8f4a --- /dev/null +++ b/tests/Symfony/Tests/Component/Form/Fixtures/FooType.php @@ -0,0 +1,41 @@ +setAttribute('foo', 'x'); + $builder->setAttribute('data_option', $options['data']); + } + + public function getName() + { + return 'foo'; + } + + public function createBuilder($name, FormFactoryInterface $factory, array $options) + { + return new FormBuilder($name, $factory, new EventDispatcher()); + } + + public function getDefaultOptions(array $options) + { + return array( + 'data' => null, + 'required' => false, + 'max_length' => null, + ); + } + + public function getParent(array $options) + { + return null; + } +} \ No newline at end of file diff --git a/tests/Symfony/Tests/Component/Form/Fixtures/FooTypeBarExtension.php b/tests/Symfony/Tests/Component/Form/Fixtures/FooTypeBarExtension.php new file mode 100644 index 0000000000..8da2c44063 --- /dev/null +++ b/tests/Symfony/Tests/Component/Form/Fixtures/FooTypeBarExtension.php @@ -0,0 +1,19 @@ +setAttribute('bar', 'x'); + } + + public function getExtendedType() + { + return 'foo'; + } +} \ No newline at end of file diff --git a/tests/Symfony/Tests/Component/Form/Fixtures/FooTypeBazExtension.php b/tests/Symfony/Tests/Component/Form/Fixtures/FooTypeBazExtension.php new file mode 100644 index 0000000000..c256c39c7f --- /dev/null +++ b/tests/Symfony/Tests/Component/Form/Fixtures/FooTypeBazExtension.php @@ -0,0 +1,19 @@ +setAttribute('baz', 'x'); + } + + public function getExtendedType() + { + return 'foo'; + } +} \ No newline at end of file diff --git a/tests/Symfony/Tests/Component/Form/Fixtures/TestExtension.php b/tests/Symfony/Tests/Component/Form/Fixtures/TestExtension.php new file mode 100644 index 0000000000..e3071b8342 --- /dev/null +++ b/tests/Symfony/Tests/Component/Form/Fixtures/TestExtension.php @@ -0,0 +1,63 @@ +guesser = $guesser; + } + + public function addType(FormTypeInterface $type) + { + $this->types[$type->getName()] = $type; + } + + public function getType($name) + { + return isset($this->types[$name]) ? $this->types[$name] : null; + } + + public function hasType($name) + { + return isset($this->types[$name]); + } + + public function addTypeExtension(FormTypeExtensionInterface $extension) + { + $type = $extension->getExtendedType(); + + if (!isset($this->extensions[$type])) { + $this->extensions[$type] = array(); + } + + $this->extensions[$type][] = $extension; + } + + public function getTypeExtensions($name) + { + return isset($this->extensions[$name]) ? $this->extensions[$name] : array(); + } + + public function hasTypeExtensions($name) + { + return isset($this->extensions[$name]); + } + + public function getTypeGuesser() + { + return $this->guesser; + } +} \ No newline at end of file diff --git a/tests/Symfony/Tests/Component/Form/FormFactoryTest.php b/tests/Symfony/Tests/Component/Form/FormFactoryTest.php index dac741f880..8baff1a11c 100644 --- a/tests/Symfony/Tests/Component/Form/FormFactoryTest.php +++ b/tests/Symfony/Tests/Component/Form/FormFactoryTest.php @@ -11,10 +11,15 @@ namespace Symfony\Tests\Component\Form; +use Symfony\Component\Form\FormBuilder; use Symfony\Component\Form\FormFactory; use Symfony\Component\Form\Guess\Guess; use Symfony\Component\Form\Guess\ValueGuess; use Symfony\Component\Form\Guess\TypeGuess; +use Symfony\Tests\Component\Form\Fixtures\TestExtension; +use Symfony\Tests\Component\Form\Fixtures\FooType; +use Symfony\Tests\Component\Form\Fixtures\FooTypeBarExtension; +use Symfony\Tests\Component\Form\Fixtures\FooTypeBazExtension; class FormFactoryTest extends \PHPUnit_Framework_TestCase { @@ -32,17 +37,253 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase { $this->guesser1 = $this->getMock('Symfony\Component\Form\FormTypeGuesserInterface'); $this->guesser2 = $this->getMock('Symfony\Component\Form\FormTypeGuesserInterface'); - $this->extension1 = $this->getMock('Symfony\Component\Form\FormExtensionInterface'); - $this->extension1->expects($this->any()) - ->method('getTypeGuesser') - ->will($this->returnValue($this->guesser1)); - $this->extension2 = $this->getMock('Symfony\Component\Form\FormExtensionInterface'); - $this->extension2->expects($this->any()) - ->method('getTypeGuesser') - ->will($this->returnValue($this->guesser2)); + $this->extension1 = new TestExtension($this->guesser1); + $this->extension2 = new TestExtension($this->guesser2); $this->factory = new FormFactory(array($this->extension1, $this->extension2)); } + public function testAddType() + { + $this->assertFalse($this->factory->hasType('foo')); + + $type = new FooType(); + $this->factory->addType($type); + + $this->assertTrue($this->factory->hasType('foo')); + $this->assertSame($type, $this->factory->getType('foo')); + } + + public function testAddTypeAddsExtensions() + { + $type = new FooType(); + $ext1 = new FooTypeBarExtension(); + $ext2 = new FooTypeBazExtension(); + + $this->extension1->addTypeExtension($ext1); + $this->extension2->addTypeExtension($ext2); + + $this->factory->addType($type); + + $this->assertEquals(array($ext1, $ext2), $type->getExtensions()); + } + + public function testGetTypeFromExtension() + { + $type = new FooType(); + $this->extension2->addType($type); + + $this->assertSame($type, $this->factory->getType('foo')); + } + + public function testGetTypeAddsExtensions() + { + $type = new FooType(); + $ext1 = new FooTypeBarExtension(); + $ext2 = new FooTypeBazExtension(); + + $this->extension1->addTypeExtension($ext1); + $this->extension2->addTypeExtension($ext2); + $this->extension2->addType($type); + + $type = $this->factory->getType('foo'); + + $this->assertEquals(array($ext1, $ext2), $type->getExtensions()); + } + + /** + * @expectedException Symfony\Component\Form\Exception\FormException + */ + public function testGetTypeExpectsExistingType() + { + $this->factory->getType('bar'); + } + + public function testCreateNamedBuilder() + { + $type = new FooType(); + $this->extension1->addType($type); + + $builder = $this->factory->createNamedBuilder('foo', 'bar'); + + $this->assertTrue($builder instanceof FormBuilder); + $this->assertEquals('bar', $builder->getName()); + } + + public function testCreateNamedBuilderCallsBuildFormMethods() + { + $type = new FooType(); + $ext1 = new FooTypeBarExtension(); + $ext2 = new FooTypeBazExtension(); + + $this->extension1->addTypeExtension($ext1); + $this->extension2->addTypeExtension($ext2); + $this->extension2->addType($type); + + $builder = $this->factory->createNamedBuilder('foo', 'bar'); + + $this->assertTrue($builder->hasAttribute('foo')); + $this->assertTrue($builder->hasAttribute('bar')); + $this->assertTrue($builder->hasAttribute('baz')); + } + + public function testCreateNamedBuilderFillsDataOption() + { + $type = new FooType(); + $this->extension1->addType($type); + + $builder = $this->factory->createNamedBuilder('foo', 'bar', 'xyz'); + + // see FooType::buildForm() + $this->assertEquals('xyz', $builder->getAttribute('data_option')); + } + + public function testCreateNamedBuilderDoesNotOverrideExistingDataOption() + { + $type = new FooType(); + $this->extension1->addType($type); + + $builder = $this->factory->createNamedBuilder('foo', 'bar', 'xyz', array( + 'data' => 'abc', + )); + + // see FooType::buildForm() + $this->assertEquals('abc', $builder->getAttribute('data_option')); + } + + /** + * @expectedException Symfony\Component\Form\Exception\TypeDefinitionException + */ + public function testCreateNamedBuilderExpectsDataOptionToBeSupported() + { + $type = $this->getMock('Symfony\Component\Form\FormTypeInterface'); + $type->expects($this->any()) + ->method('getName') + ->will($this->returnValue('foo')); + $type->expects($this->any()) + ->method('getExtensions') + ->will($this->returnValue(array())); + $type->expects($this->any()) + ->method('getDefaultOptions') + ->will($this->returnValue(array( + 'required' => false, + 'max_length' => null, + ))); + + $this->extension1->addType($type); + + $this->factory->createNamedBuilder('foo', 'bar'); + } + + /** + * @expectedException Symfony\Component\Form\Exception\TypeDefinitionException + */ + public function testCreateNamedBuilderExpectsRequiredOptionToBeSupported() + { + $type = $this->getMock('Symfony\Component\Form\FormTypeInterface'); + $type->expects($this->any()) + ->method('getName') + ->will($this->returnValue('foo')); + $type->expects($this->any()) + ->method('getExtensions') + ->will($this->returnValue(array())); + $type->expects($this->any()) + ->method('getDefaultOptions') + ->will($this->returnValue(array( + 'data' => null, + 'max_length' => null, + ))); + + $this->extension1->addType($type); + + $this->factory->createNamedBuilder('foo', 'bar'); + } + + /** + * @expectedException Symfony\Component\Form\Exception\TypeDefinitionException + */ + public function testCreateNamedBuilderExpectsMaxLengthOptionToBeSupported() + { + $type = $this->getMock('Symfony\Component\Form\FormTypeInterface'); + $type->expects($this->any()) + ->method('getName') + ->will($this->returnValue('foo')); + $type->expects($this->any()) + ->method('getExtensions') + ->will($this->returnValue(array())); + $type->expects($this->any()) + ->method('getDefaultOptions') + ->will($this->returnValue(array( + 'data' => null, + 'required' => false, + ))); + + $this->extension1->addType($type); + + $this->factory->createNamedBuilder('foo', 'bar'); + } + + /** + * @expectedException Symfony\Component\Form\Exception\TypeDefinitionException + */ + public function testCreateNamedBuilderExpectsBuilderToBeReturned() + { + $type = $this->getMock('Symfony\Component\Form\FormTypeInterface'); + $type->expects($this->any()) + ->method('getName') + ->will($this->returnValue('foo')); + $type->expects($this->any()) + ->method('getExtensions') + ->will($this->returnValue(array())); + $type->expects($this->any()) + ->method('getDefaultOptions') + ->will($this->returnValue(array( + 'data' => null, + 'required' => false, + 'max_length' => null, + ))); + $type->expects($this->any()) + ->method('createBuilder') + ->will($this->returnValue(null)); + + $this->extension1->addType($type); + + $this->factory->createNamedBuilder('foo', 'bar'); + } + + /** + * @expectedException Symfony\Component\Form\Exception\CreationException + */ + public function testCreateNamedBuilderExpectsOptionsToExist() + { + $type = new FooType(); + $this->extension1->addType($type); + + $this->factory->createNamedBuilder('foo', 'bar', null, array( + 'invalid' => 'xyz', + )); + } + + public function testCreateNamedBuilderAddsTypeInstances() + { + $type = new FooType(); + $this->assertFalse($this->factory->hasType('foo')); + + $builder = $this->factory->createNamedBuilder($type, 'bar'); + + $this->assertTrue($builder instanceof FormBuilder); + $this->assertTrue($this->factory->hasType('foo')); + } + + public function testCreateUsesTypeNameAsName() + { + $type = new FooType(); + $this->extension1->addType($type); + + $builder = $this->factory->createBuilder('foo'); + + $this->assertEquals('foo', $builder->getName()); + } + public function testCreateBuilderForPropertyCreatesFieldWithHighestConfidence() { $this->guesser1->expects($this->once()) From da28f8e3b3ea52380c8197fd56fe0fd0300b7760 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Fri, 13 May 2011 18:41:23 +0200 Subject: [PATCH 5/8] [Form] Added FormTypeInterface::getAllowedOptionValues() to better validate passed options --- src/Symfony/Component/Form/AbstractType.php | 12 ++++++ .../Component/Form/AbstractTypeExtension.php | 12 ++++++ src/Symfony/Component/Form/FormFactory.php | 13 ++++++- .../Form/FormTypeExtensionInterface.php | 9 +++++ .../Component/Form/FormTypeInterface.php | 9 +++++ .../Tests/Component/Form/Fixtures/FooType.php | 8 ++++ .../Form/Fixtures/FooTypeBarExtension.php | 7 ++++ .../Tests/Component/Form/FormFactoryTest.php | 37 +++++++++++++++++++ 8 files changed, 105 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Form/AbstractType.php b/src/Symfony/Component/Form/AbstractType.php index acb581c902..dd66a03773 100644 --- a/src/Symfony/Component/Form/AbstractType.php +++ b/src/Symfony/Component/Form/AbstractType.php @@ -46,6 +46,18 @@ abstract class AbstractType implements FormTypeInterface return array(); } + /** + * Returns the allowed option values for each option (if any). + * + * @param array $options + * + * @return array The allowed option values + */ + public function getAllowedOptionValues(array $options) + { + return array(); + } + /** * Returns the name of the parent type. * diff --git a/src/Symfony/Component/Form/AbstractTypeExtension.php b/src/Symfony/Component/Form/AbstractTypeExtension.php index 56dbdc4920..9b0a5caa2b 100644 --- a/src/Symfony/Component/Form/AbstractTypeExtension.php +++ b/src/Symfony/Component/Form/AbstractTypeExtension.php @@ -29,4 +29,16 @@ abstract class AbstractTypeExtension implements FormTypeExtensionInterface { return array(); } + + /** + * Returns the allowed option values for each option (if any). + * + * @param array $options + * + * @return array The allowed option values + */ + public function getAllowedOptionValues(array $options) + { + return array(); + } } diff --git a/src/Symfony/Component/Form/FormFactory.php b/src/Symfony/Component/Form/FormFactory.php index 365482941d..e24c7aaebb 100644 --- a/src/Symfony/Component/Form/FormFactory.php +++ b/src/Symfony/Component/Form/FormFactory.php @@ -114,6 +114,7 @@ class FormFactory implements FormFactoryInterface $types = array(); $knownOptions = array(); $passedOptions = array_keys($options); + $optionValues = array(); if (!array_key_exists('data', $options)) { $options['data'] = $data; @@ -127,12 +128,14 @@ class FormFactory implements FormFactoryInterface } $defaultOptions = $type->getDefaultOptions($options); + $optionValues = array_merge_recursive($optionValues, $type->getAllowedOptionValues($options)); foreach ($type->getExtensions() as $typeExtension) { - $defaultOptions = array_merge($defaultOptions, $typeExtension->getDefaultOptions($options)); + $defaultOptions = array_replace($defaultOptions, $typeExtension->getDefaultOptions($options)); + $optionValues = array_merge_recursive($optionValues, $typeExtension->getAllowedOptionValues($options)); } - $options = array_merge($defaultOptions, $options); + $options = array_replace($defaultOptions, $options); $knownOptions = array_merge($knownOptions, array_keys($defaultOptions)); array_unshift($types, $type); $type = $type->getParent($options); @@ -155,6 +158,12 @@ class FormFactory implements FormFactoryInterface throw new CreationException(sprintf('The option "%s" does not exist', $diff[0])); } + foreach ($optionValues as $option => $allowedValues) { + if (!in_array($options[$option], $allowedValues, true)) { + throw new CreationException(sprintf('The option "%s" has the value "%s", but is expected to be one of "%s"', $option, $options[$option], implode('", "', $allowedValues))); + } + } + for ($i = 0, $l = count($types); $i < $l && !$builder; ++$i) { $builder = $types[$i]->createBuilder($name, $this, $options); } diff --git a/src/Symfony/Component/Form/FormTypeExtensionInterface.php b/src/Symfony/Component/Form/FormTypeExtensionInterface.php index f9ec5b4dc7..8837b5f858 100644 --- a/src/Symfony/Component/Form/FormTypeExtensionInterface.php +++ b/src/Symfony/Component/Form/FormTypeExtensionInterface.php @@ -21,5 +21,14 @@ interface FormTypeExtensionInterface function getDefaultOptions(array $options); + /** + * Returns the allowed option values for each option (if any). + * + * @param array $options + * + * @return array The allowed option values + */ + function getAllowedOptionValues(array $options); + function getExtendedType(); } \ No newline at end of file diff --git a/src/Symfony/Component/Form/FormTypeInterface.php b/src/Symfony/Component/Form/FormTypeInterface.php index 5248217c45..a4d7d5f3f5 100644 --- a/src/Symfony/Component/Form/FormTypeInterface.php +++ b/src/Symfony/Component/Form/FormTypeInterface.php @@ -30,6 +30,15 @@ interface FormTypeInterface */ function getDefaultOptions(array $options); + /** + * Returns the allowed option values for each option (if any). + * + * @param array $options + * + * @return array The allowed option values + */ + function getAllowedOptionValues(array $options); + /** * Returns the name of the parent type. * diff --git a/tests/Symfony/Tests/Component/Form/Fixtures/FooType.php b/tests/Symfony/Tests/Component/Form/Fixtures/FooType.php index 93f3cc8f4a..a5393f75ae 100644 --- a/tests/Symfony/Tests/Component/Form/Fixtures/FooType.php +++ b/tests/Symfony/Tests/Component/Form/Fixtures/FooType.php @@ -31,6 +31,14 @@ class FooType extends AbstractType 'data' => null, 'required' => false, 'max_length' => null, + 'a_or_b' => 'a', + ); + } + + public function getAllowedOptionValues(array $options) + { + return array( + 'a_or_b' => array('a', 'b'), ); } diff --git a/tests/Symfony/Tests/Component/Form/Fixtures/FooTypeBarExtension.php b/tests/Symfony/Tests/Component/Form/Fixtures/FooTypeBarExtension.php index 8da2c44063..3d8bc5a4a8 100644 --- a/tests/Symfony/Tests/Component/Form/Fixtures/FooTypeBarExtension.php +++ b/tests/Symfony/Tests/Component/Form/Fixtures/FooTypeBarExtension.php @@ -12,6 +12,13 @@ class FooTypeBarExtension extends AbstractTypeExtension $builder->setAttribute('bar', 'x'); } + public function getAllowedOptionValues(array $options) + { + return array( + 'a_or_b' => array('c'), + ); + } + public function getExtendedType() { return 'foo'; diff --git a/tests/Symfony/Tests/Component/Form/FormFactoryTest.php b/tests/Symfony/Tests/Component/Form/FormFactoryTest.php index 8baff1a11c..7870fb7b44 100644 --- a/tests/Symfony/Tests/Component/Form/FormFactoryTest.php +++ b/tests/Symfony/Tests/Component/Form/FormFactoryTest.php @@ -162,6 +162,9 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase $type->expects($this->any()) ->method('getExtensions') ->will($this->returnValue(array())); + $type->expects($this->any()) + ->method('getAllowedOptionValues') + ->will($this->returnValue(array())); $type->expects($this->any()) ->method('getDefaultOptions') ->will($this->returnValue(array( @@ -186,6 +189,9 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase $type->expects($this->any()) ->method('getExtensions') ->will($this->returnValue(array())); + $type->expects($this->any()) + ->method('getAllowedOptionValues') + ->will($this->returnValue(array())); $type->expects($this->any()) ->method('getDefaultOptions') ->will($this->returnValue(array( @@ -210,6 +216,9 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase $type->expects($this->any()) ->method('getExtensions') ->will($this->returnValue(array())); + $type->expects($this->any()) + ->method('getAllowedOptionValues') + ->will($this->returnValue(array())); $type->expects($this->any()) ->method('getDefaultOptions') ->will($this->returnValue(array( @@ -234,6 +243,9 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase $type->expects($this->any()) ->method('getExtensions') ->will($this->returnValue(array())); + $type->expects($this->any()) + ->method('getAllowedOptionValues') + ->will($this->returnValue(array())); $type->expects($this->any()) ->method('getDefaultOptions') ->will($this->returnValue(array( @@ -263,6 +275,31 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase )); } + /** + * @expectedException Symfony\Component\Form\Exception\CreationException + */ + public function testCreateNamedBuilderExpectsOptionsToBeInValidRange() + { + $type = new FooType(); + $this->extension1->addType($type); + + $this->factory->createNamedBuilder('foo', 'bar', null, array( + 'a_or_b' => 'c', + )); + } + + public function testCreateNamedBuilderAllowsExtensionsToExtendAllowedOptionValues() + { + $type = new FooType(); + $this->extension1->addType($type); + $this->extension1->addTypeExtension(new FooTypeBarExtension()); + + // no exception this time + $this->factory->createNamedBuilder('foo', 'bar', null, array( + 'a_or_b' => 'c', + )); + } + public function testCreateNamedBuilderAddsTypeInstances() { $type = new FooType(); From 486a01bfe2270b8fe6eb1e5592901e687572b35c Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Fri, 13 May 2011 18:56:09 +0200 Subject: [PATCH 6/8] [Form] Removed unused option --- src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php index ccd9270c27..21de4cd41b 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php @@ -42,7 +42,6 @@ class EntityType extends AbstractType public function getDefaultOptions(array $options) { $defaultOptions = array( - 'template' => 'choice', 'multiple' => false, 'expanded' => false, 'em' => $this->em, From 7570e04589f60861aeb592f284f4d2a7dc7e13e6 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Fri, 13 May 2011 18:58:19 +0200 Subject: [PATCH 7/8] [Form] Implemented getAllowedOptionValues() for core types --- .../Form/Extension/Core/Type/DateTimeType.php | 33 ++++++++++++++++--- .../Form/Extension/Core/Type/DateType.php | 26 +++++++++++++-- .../Form/Extension/Core/Type/FileType.php | 10 ++++++ .../Form/Extension/Core/Type/IntegerType.php | 17 +++++++++- .../Form/Extension/Core/Type/NumberType.php | 17 +++++++++- .../Form/Extension/Core/Type/PercentType.php | 10 ++++++ .../Form/Extension/Core/Type/TimeType.php | 16 +++++++++ 7 files changed, 120 insertions(+), 9 deletions(-) diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php index 286f0758d7..a859151bee 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php @@ -55,9 +55,6 @@ class DateTimeType extends AbstractType if (isset($options['time_widget'])) { $timeOptions['widget'] = $options['time_widget']; } - if (isset($options['time_format'])) { - $timeOptions['format'] = $options['time_format']; - } $timeOptions['input'] = 'array'; @@ -109,7 +106,6 @@ class DateTimeType extends AbstractType 'date_format' => null, 'time_pattern' => null, 'time_widget' => null, - 'time_format' => null, /* Defaults for date field */ 'years' => range(date('Y') - 5, date('Y') + 5), 'months' => range(1, 12), @@ -122,6 +118,35 @@ class DateTimeType extends AbstractType ); } + public function getAllowedOptionValues(array $options) + { + return array( + 'input' => array( + 'datetime', + 'string', + 'timestamp', + 'array', + ), + 'date_widget' => array( + null, // inherit default from DateType + 'text', + 'choice', + ), + 'date_format' => array( + null, // inherit default from DateType + \IntlDateFormatter::FULL, + \IntlDateFormatter::LONG, + \IntlDateFormatter::MEDIUM, + \IntlDateFormatter::SHORT, + ), + 'time_widget' => array( + null, // inherit default from TimeType + 'text', + 'choice', + ), + ); + } + public function getName() { return 'datetime'; diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php index 7bd7355e3f..5c19151350 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php @@ -37,7 +37,7 @@ class DateType extends AbstractType if ($options['widget'] === 'text') { $builder->appendClientTransformer(new DateTimeToLocalizedStringTransformer($options['data_timezone'], $options['user_timezone'], $options['format'], \IntlDateFormatter::NONE)); - } elseif ($options['widget'] == 'choice') { + } else { // Only pass a subset of the options to children $yearOptions = array( 'choice_list' => new PaddedChoiceList( @@ -59,8 +59,6 @@ class DateType extends AbstractType ->add('month', 'choice', $monthOptions) ->add('day', 'choice', $dayOptions) ->appendClientTransformer(new DateTimeToArrayTransformer($options['data_timezone'], $options['user_timezone'], array('year', 'month', 'day'))); - } else { - throw new FormException('The "widget" option must be set to either "text" or "choice".'); } if ($options['input'] === 'string') { @@ -124,6 +122,28 @@ class DateType extends AbstractType ); } + public function getAllowedOptionValues(array $options) + { + return array( + 'input' => array( + 'datetime', + 'string', + 'timestamp', + 'array', + ), + 'widget' => array( + 'text', + 'choice', + ), + 'format' => array( + \IntlDateFormatter::FULL, + \IntlDateFormatter::LONG, + \IntlDateFormatter::MEDIUM, + \IntlDateFormatter::SHORT, + ), + ); + } + public function getParent(array $options) { return $options['widget'] === 'text' ? 'field' : 'form'; diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php index 6595072ecc..bbe0f059a3 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php @@ -61,6 +61,16 @@ class FileType extends AbstractType ); } + public function getAllowedOptionValues(array $options) + { + return array( + 'type' => array( + 'string', + 'file', + ), + ); + } + public function getName() { return 'file'; diff --git a/src/Symfony/Component/Form/Extension/Core/Type/IntegerType.php b/src/Symfony/Component/Form/Extension/Core/Type/IntegerType.php index b4e3b22ac2..fe67662653 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/IntegerType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/IntegerType.php @@ -29,7 +29,22 @@ class IntegerType extends AbstractType 'precision' => null, 'grouping' => false, // Integer cast rounds towards 0, so do the same when displaying fractions - 'rounding_mode' => IntegerToLocalizedStringTransformer::ROUND_DOWN, + 'rounding_mode' => \NumberFormatter::ROUND_DOWN, + ); + } + + public function getAllowedOptionValues(array $options) + { + return array( + 'rounding_mode' => array( + \NumberFormatter::ROUND_FLOOR, + \NumberFormatter::ROUND_DOWN, + \NumberFormatter::ROUND_HALFDOWN, + \NumberFormatter::ROUND_HALFEVEN, + \NumberFormatter::ROUND_HALFUP, + \NumberFormatter::ROUND_UP, + \NumberFormatter::ROUND_CEILING, + ), ); } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/NumberType.php b/src/Symfony/Component/Form/Extension/Core/Type/NumberType.php index 7846cbca94..8f13b56446 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/NumberType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/NumberType.php @@ -28,7 +28,22 @@ class NumberType extends AbstractType // default precision is locale specific (usually around 3) 'precision' => null, 'grouping' => false, - 'rounding_mode' => NumberToLocalizedStringTransformer::ROUND_HALFUP, + 'rounding_mode' => \NumberFormatter::ROUND_HALFUP, + ); + } + + public function getAllowedOptionValues(array $options) + { + return array( + 'rounding_mode' => array( + \NumberFormatter::ROUND_FLOOR, + \NumberFormatter::ROUND_DOWN, + \NumberFormatter::ROUND_HALFDOWN, + \NumberFormatter::ROUND_HALFEVEN, + \NumberFormatter::ROUND_HALFUP, + \NumberFormatter::ROUND_UP, + \NumberFormatter::ROUND_CEILING, + ), ); } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/PercentType.php b/src/Symfony/Component/Form/Extension/Core/Type/PercentType.php index 3718bdadda..23a07ae2f3 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/PercentType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/PercentType.php @@ -30,6 +30,16 @@ class PercentType extends AbstractType ); } + public function getAllowedOptionValues(array $options) + { + return array( + 'type' => array( + 'fractional', + 'integer', + ), + ); + } + public function getParent(array $options) { return 'field'; diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php index 2ef4804daa..310f1c4254 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php @@ -96,6 +96,22 @@ class TimeType extends AbstractType ); } + public function getAllowedOptionValues(array $options) + { + return array( + 'input' => array( + 'datetime', + 'string', + 'timestamp', + 'array', + ), + 'widget' => array( + 'text', + 'choice', + ), + ); + } + public function getName() { return 'time'; From e0ff61949e99b98fe0bb16eac25286ac3f281f07 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Fri, 13 May 2011 19:17:28 +0200 Subject: [PATCH 8/8] [Form] Renamed the value "text" of the "widget" option of the "date" type to "single-text" --- UPDATE.md | 4 ++ .../Resources/views/Form/date_widget.html.php | 2 +- .../Resources/views/Form/div_layout.html.twig | 2 +- .../Form/Extension/Core/Type/DateType.php | 48 +++++++------ .../Component/Form/AbstractLayoutTest.php | 29 ++++++++ .../Form/Extension/Core/Type/DateTypeTest.php | 68 ++++++++++++------- 6 files changed, 107 insertions(+), 46 deletions(-) diff --git a/UPDATE.md b/UPDATE.md index 2f96877632..844d44cfc5 100644 --- a/UPDATE.md +++ b/UPDATE.md @@ -139,6 +139,10 @@ beta1 to beta2 * Serializer: The `$properties` argument has been dropped from all interfaces. +* Form: Renamed option value "text" of "widget" option of the "date" type was + renamed to "single-text". "text" indicates to use separate text boxes now + (like for the "time" type). + PR12 to beta1 ------------- diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/date_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/date_widget.html.php index a71e9b4059..583c881e3f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/date_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/date_widget.html.php @@ -1,4 +1,4 @@ - + attributes() ?> name="escape($name) ?>" diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/Form/div_layout.html.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/Form/div_layout.html.twig index 782a0a5006..6f8ba5b49d 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/Form/div_layout.html.twig +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/Form/div_layout.html.twig @@ -151,7 +151,7 @@ {% block date_widget %} {% spaceless %} - {% if widget == 'text' %} + {% if widget == 'single-text' %} {{ block('text_widget') }} {% else %}
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php index 5c19151350..565459b9b0 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php @@ -35,29 +35,34 @@ class DateType extends AbstractType \DateTimeZone::UTC ); - if ($options['widget'] === 'text') { + if ($options['widget'] === 'single-text') { $builder->appendClientTransformer(new DateTimeToLocalizedStringTransformer($options['data_timezone'], $options['user_timezone'], $options['format'], \IntlDateFormatter::NONE)); } else { - // Only pass a subset of the options to children - $yearOptions = array( - 'choice_list' => new PaddedChoiceList( - array_combine($options['years'], $options['years']), 4, '0', STR_PAD_LEFT - ), - ); - $monthOptions = array( - 'choice_list' => new MonthChoiceList( - $formatter, $options['months'] - ), - ); - $dayOptions = array( - 'choice_list' => new PaddedChoiceList( - array_combine($options['days'], $options['days']), 2, '0', STR_PAD_LEFT - ), - ); + $yearOptions = $monthOptions = $dayOptions = array(); + $widget = $options['widget']; - $builder->add('year', 'choice', $yearOptions) - ->add('month', 'choice', $monthOptions) - ->add('day', 'choice', $dayOptions) + if ($widget === 'choice') { + // Only pass a subset of the options to children + $yearOptions = array( + 'choice_list' => new PaddedChoiceList( + array_combine($options['years'], $options['years']), 4, '0', STR_PAD_LEFT + ), + ); + $monthOptions = array( + 'choice_list' => new MonthChoiceList( + $formatter, $options['months'] + ), + ); + $dayOptions = array( + 'choice_list' => new PaddedChoiceList( + array_combine($options['days'], $options['days']), 2, '0', STR_PAD_LEFT + ), + ); + } + + $builder->add('year', $widget, $yearOptions) + ->add('month', $widget, $monthOptions) + ->add('day', $widget, $dayOptions) ->appendClientTransformer(new DateTimeToArrayTransformer($options['data_timezone'], $options['user_timezone'], array('year', 'month', 'day'))); } @@ -132,6 +137,7 @@ class DateType extends AbstractType 'array', ), 'widget' => array( + 'single-text', 'text', 'choice', ), @@ -146,7 +152,7 @@ class DateType extends AbstractType public function getParent(array $options) { - return $options['widget'] === 'text' ? 'field' : 'form'; + return $options['widget'] === 'single-text' ? 'field' : 'form'; } public function getName() diff --git a/tests/Symfony/Tests/Component/Form/AbstractLayoutTest.php b/tests/Symfony/Tests/Component/Form/AbstractLayoutTest.php index 63248687a6..338b165b24 100644 --- a/tests/Symfony/Tests/Component/Form/AbstractLayoutTest.php +++ b/tests/Symfony/Tests/Component/Form/AbstractLayoutTest.php @@ -597,6 +597,35 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase 'widget' => 'text', )); + $this->assertWidgetMatchesXpath($form->createView(), array(), +'/div + [ + ./input + [@id="na&me_month"] + [@type="text"] + [@value="2"] + /following-sibling::input + [@id="na&me_day"] + [@type="text"] + [@value="3"] + /following-sibling::input + [@id="na&me_year"] + [@type="text"] + [@value="2011"] + ] + [count(./input)=3] +' + ); + } + + public function testDateSingleText() + { + $form = $this->factory->createNamed('date', 'na&me', '2011-02-03', array( + 'property_path' => 'name', + 'input' => 'string', + 'widget' => 'single-text', + )); + $this->assertWidgetMatchesXpath($form->createView(), array(), '/input [@type="text"] diff --git a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/DateTypeTest.php b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/DateTypeTest.php index 3e910e93b6..d226e5598d 100644 --- a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/DateTypeTest.php +++ b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/DateTypeTest.php @@ -45,12 +45,12 @@ class DateTypeTest extends LocalizedTestCase )); } - public function testSubmitFromInputDateTime() + public function testSubmitFromSingleTextDateTime() { $form = $this->factory->create('date', null, array( 'data_timezone' => 'UTC', 'user_timezone' => 'UTC', - 'widget' => 'text', + 'widget' => 'single-text', 'input' => 'datetime', )); @@ -60,12 +60,12 @@ class DateTypeTest extends LocalizedTestCase $this->assertEquals('02.06.2010', $form->getClientData()); } - public function testSubmitFromInputString() + public function testSubmitFromSingleTextString() { $form = $this->factory->create('date', null, array( 'data_timezone' => 'UTC', 'user_timezone' => 'UTC', - 'widget' => 'text', + 'widget' => 'single-text', 'input' => 'string', )); @@ -75,12 +75,12 @@ class DateTypeTest extends LocalizedTestCase $this->assertEquals('02.06.2010', $form->getClientData()); } - public function testSubmitFromInputTimestamp() + public function testSubmitFromSingleTextTimestamp() { $form = $this->factory->create('date', null, array( 'data_timezone' => 'UTC', 'user_timezone' => 'UTC', - 'widget' => 'text', + 'widget' => 'single-text', 'input' => 'timestamp', )); @@ -92,12 +92,12 @@ class DateTypeTest extends LocalizedTestCase $this->assertEquals('02.06.2010', $form->getClientData()); } - public function testSubmitFromInputRaw() + public function testSubmitFromSingleTextRaw() { $form = $this->factory->create('date', null, array( 'data_timezone' => 'UTC', 'user_timezone' => 'UTC', - 'widget' => 'text', + 'widget' => 'single-text', 'input' => 'array', )); @@ -113,6 +113,28 @@ class DateTypeTest extends LocalizedTestCase $this->assertEquals('02.06.2010', $form->getClientData()); } + public function testSubmitFromText() + { + $form = $this->factory->create('date', null, array( + 'data_timezone' => 'UTC', + 'user_timezone' => 'UTC', + 'widget' => 'text', + )); + + $text = array( + 'day' => '2', + 'month' => '6', + 'year' => '2010', + ); + + $form->bind($text); + + $dateTime = new \DateTime('2010-06-02 UTC'); + + $this->assertDateTimeEquals($dateTime, $form->getData()); + $this->assertEquals($text, $form->getClientData()); + } + public function testSubmitFromChoice() { $form = $this->factory->create('date', null, array( @@ -163,7 +185,7 @@ class DateTypeTest extends LocalizedTestCase 'user_timezone' => 'Pacific/Tahiti', // don't do this test with DateTime, because it leads to wrong results! 'input' => 'string', - 'widget' => 'text', + 'widget' => 'single-text', )); $form->setData('2010-06-02'); @@ -178,7 +200,7 @@ class DateTypeTest extends LocalizedTestCase $form = $this->factory->create('date', null, array( 'data_timezone' => 'UTC', 'user_timezone' => 'UTC', - 'widget' => 'text', + 'widget' => 'single-text', 'years' => array(2010, 2011), )); @@ -194,7 +216,7 @@ class DateTypeTest extends LocalizedTestCase $form = $this->factory->create('date', null, array( 'data_timezone' => 'UTC', 'user_timezone' => 'UTC', - 'widget' => 'text', + 'widget' => 'single-text', 'years' => array(2010, 2011), )); @@ -230,7 +252,7 @@ class DateTypeTest extends LocalizedTestCase $form = $this->factory->create('date', null, array( 'data_timezone' => 'UTC', 'user_timezone' => 'UTC', - 'widget' => 'text', + 'widget' => 'single-text', 'years' => array(2010, 2012), )); @@ -246,7 +268,7 @@ class DateTypeTest extends LocalizedTestCase $form = $this->factory->create('date', null, array( 'data_timezone' => 'UTC', 'user_timezone' => 'UTC', - 'widget' => 'text', + 'widget' => 'single-text', 'months' => array(6, 7), )); @@ -262,7 +284,7 @@ class DateTypeTest extends LocalizedTestCase $form = $this->factory->create('date', null, array( 'data_timezone' => 'UTC', 'user_timezone' => 'UTC', - 'widget' => 'text', + 'widget' => 'single-text', 'months' => array(6, 7), )); @@ -298,7 +320,7 @@ class DateTypeTest extends LocalizedTestCase $form = $this->factory->create('date', null, array( 'data_timezone' => 'UTC', 'user_timezone' => 'UTC', - 'widget' => 'text', + 'widget' => 'single-text', 'months' => array(6, 8), )); @@ -314,7 +336,7 @@ class DateTypeTest extends LocalizedTestCase $form = $this->factory->create('date', null, array( 'data_timezone' => 'UTC', 'user_timezone' => 'UTC', - 'widget' => 'text', + 'widget' => 'single-text', 'days' => array(6, 7), )); @@ -330,7 +352,7 @@ class DateTypeTest extends LocalizedTestCase $form = $this->factory->create('date', null, array( 'data_timezone' => 'UTC', 'user_timezone' => 'UTC', - 'widget' => 'text', + 'widget' => 'single-text', 'days' => array(6, 7), )); @@ -368,7 +390,7 @@ class DateTypeTest extends LocalizedTestCase $form = $this->factory->create('date', null, array( 'data_timezone' => 'UTC', 'user_timezone' => 'UTC', - 'widget' => 'text', + 'widget' => 'single-text', 'days' => array(6, 8), )); @@ -377,14 +399,14 @@ class DateTypeTest extends LocalizedTestCase $this->assertFalse($form->isDayWithinRange()); } - public function testIsPartiallyFilledReturnsFalseIfInput() + public function testIsPartiallyFilledReturnsFalseIfSingleText() { $this->markTestIncomplete('Needs to be reimplemented using validators'); $form = $this->factory->create('date', null, array( 'data_timezone' => 'UTC', 'user_timezone' => 'UTC', - 'widget' => 'text', + 'widget' => 'single-text', )); $form->bind('7.6.2010'); @@ -460,7 +482,7 @@ class DateTypeTest extends LocalizedTestCase public function testDontPassDatePatternIfText() { $form = $this->factory->create('date', null, array( - 'widget' => 'text', + 'widget' => 'single-text', )); $view = $form->createView(); @@ -470,10 +492,10 @@ class DateTypeTest extends LocalizedTestCase public function testPassWidgetToView() { $form = $this->factory->create('date', null, array( - 'widget' => 'text', + 'widget' => 'single-text', )); $view = $form->createView(); - $this->assertSame('text', $view->get('widget')); + $this->assertSame('single-text', $view->get('widget')); } }