Merge remote branch 'bschussek/form_validator'

* bschussek/form_validator:
  [Form] Renamed the value "text" of the "widget" option of the "date" type to "single-text"
  [Form] Implemented getAllowedOptionValues() for core types
  [Form] Removed unused option
  [Form] Added FormTypeInterface::getAllowedOptionValues() to better validate passed options
  [Form] Improved test coverage of FormFactory and improved error handling
  [Form] Added getType() to FormFactoryInterface
  [Validator] Refactoring DateTimeValidator and DateValidator
  [Validator] Date: check if the value is a DateTime instance
This commit is contained in:
Fabien Potencier 2011-05-14 10:59:02 +02:00
commit 9687d1661b
29 changed files with 876 additions and 166 deletions

View File

@ -139,6 +139,10 @@ beta1 to beta2
* Serializer: The `$properties` argument has been dropped from all interfaces. * 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 PR12 to beta1
------------- -------------

View File

@ -42,7 +42,6 @@ class EntityType extends AbstractType
public function getDefaultOptions(array $options) public function getDefaultOptions(array $options)
{ {
$defaultOptions = array( $defaultOptions = array(
'template' => 'choice',
'multiple' => false, 'multiple' => false,
'expanded' => false, 'expanded' => false,
'em' => $this->em, 'em' => $this->em,

View File

@ -1,4 +1,4 @@
<?php if ($widget == 'text'): ?> <?php if ($widget == 'single-text'): ?>
<input type="text" <input type="text"
<?php echo $view['form']->attributes() ?> <?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>" name="<?php echo $view->escape($name) ?>"

View File

@ -151,7 +151,7 @@
{% block date_widget %} {% block date_widget %}
{% spaceless %} {% spaceless %}
{% if widget == 'text' %} {% if widget == 'single-text' %}
{{ block('text_widget') }} {{ block('text_widget') }}
{% else %} {% else %}
<div {{ block('attributes') }}> <div {{ block('attributes') }}>

View File

@ -46,6 +46,18 @@ abstract class AbstractType implements FormTypeInterface
return array(); 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. * Returns the name of the parent type.
* *

View File

@ -29,4 +29,16 @@ abstract class AbstractTypeExtension implements FormTypeExtensionInterface
{ {
return array(); 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();
}
} }

View File

@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 <bernhard.schussek@symfony.com>
*/
class CreationException extends FormException
{
}

View File

@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 <bernhard.schussek@symfony.com>
*/
class TypeDefinitionException extends FormException
{
}

View File

@ -55,9 +55,6 @@ class DateTimeType extends AbstractType
if (isset($options['time_widget'])) { if (isset($options['time_widget'])) {
$timeOptions['widget'] = $options['time_widget']; $timeOptions['widget'] = $options['time_widget'];
} }
if (isset($options['time_format'])) {
$timeOptions['format'] = $options['time_format'];
}
$timeOptions['input'] = 'array'; $timeOptions['input'] = 'array';
@ -109,7 +106,6 @@ class DateTimeType extends AbstractType
'date_format' => null, 'date_format' => null,
'time_pattern' => null, 'time_pattern' => null,
'time_widget' => null, 'time_widget' => null,
'time_format' => null,
/* Defaults for date field */ /* Defaults for date field */
'years' => range(date('Y') - 5, date('Y') + 5), 'years' => range(date('Y') - 5, date('Y') + 5),
'months' => range(1, 12), '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() public function getName()
{ {
return 'datetime'; return 'datetime';

View File

@ -35,9 +35,13 @@ class DateType extends AbstractType
\DateTimeZone::UTC \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)); $builder->appendClientTransformer(new DateTimeToLocalizedStringTransformer($options['data_timezone'], $options['user_timezone'], $options['format'], \IntlDateFormatter::NONE));
} elseif ($options['widget'] == 'choice') { } else {
$yearOptions = $monthOptions = $dayOptions = array();
$widget = $options['widget'];
if ($widget === 'choice') {
// Only pass a subset of the options to children // Only pass a subset of the options to children
$yearOptions = array( $yearOptions = array(
'choice_list' => new PaddedChoiceList( 'choice_list' => new PaddedChoiceList(
@ -54,13 +58,12 @@ class DateType extends AbstractType
array_combine($options['days'], $options['days']), 2, '0', STR_PAD_LEFT array_combine($options['days'], $options['days']), 2, '0', STR_PAD_LEFT
), ),
); );
}
$builder->add('year', 'choice', $yearOptions) $builder->add('year', $widget, $yearOptions)
->add('month', 'choice', $monthOptions) ->add('month', $widget, $monthOptions)
->add('day', 'choice', $dayOptions) ->add('day', $widget, $dayOptions)
->appendClientTransformer(new DateTimeToArrayTransformer($options['data_timezone'], $options['user_timezone'], array('year', 'month', 'day'))); ->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') { if ($options['input'] === 'string') {
@ -124,9 +127,32 @@ class DateType extends AbstractType
); );
} }
public function getAllowedOptionValues(array $options)
{
return array(
'input' => array(
'datetime',
'string',
'timestamp',
'array',
),
'widget' => array(
'single-text',
'text',
'choice',
),
'format' => array(
\IntlDateFormatter::FULL,
\IntlDateFormatter::LONG,
\IntlDateFormatter::MEDIUM,
\IntlDateFormatter::SHORT,
),
);
}
public function getParent(array $options) public function getParent(array $options)
{ {
return $options['widget'] === 'text' ? 'field' : 'form'; return $options['widget'] === 'single-text' ? 'field' : 'form';
} }
public function getName() public function getName()

View File

@ -61,6 +61,16 @@ class FileType extends AbstractType
); );
} }
public function getAllowedOptionValues(array $options)
{
return array(
'type' => array(
'string',
'file',
),
);
}
public function getName() public function getName()
{ {
return 'file'; return 'file';

View File

@ -29,7 +29,22 @@ class IntegerType extends AbstractType
'precision' => null, 'precision' => null,
'grouping' => false, 'grouping' => false,
// Integer cast rounds towards 0, so do the same when displaying fractions // 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,
),
); );
} }

View File

@ -28,7 +28,22 @@ class NumberType extends AbstractType
// default precision is locale specific (usually around 3) // default precision is locale specific (usually around 3)
'precision' => null, 'precision' => null,
'grouping' => false, '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,
),
); );
} }

View File

@ -30,6 +30,16 @@ class PercentType extends AbstractType
); );
} }
public function getAllowedOptionValues(array $options)
{
return array(
'type' => array(
'fractional',
'integer',
),
);
}
public function getParent(array $options) public function getParent(array $options)
{ {
return 'field'; return 'field';

View File

@ -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() public function getName()
{ {
return 'time'; return 'time';

View File

@ -13,9 +13,17 @@ namespace Symfony\Component\Form;
use Symfony\Component\Form\Exception\FormException; use Symfony\Component\Form\Exception\FormException;
use Symfony\Component\Form\Exception\UnexpectedTypeException; use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\Form\Exception\TypeDefinitionException;
use Symfony\Component\Form\Exception\CreationException;
class FormFactory implements FormFactoryInterface class FormFactory implements FormFactoryInterface
{ {
private static $requiredOptions = array(
'data',
'required',
'max_length',
);
private $extensions = array(); private $extensions = array();
private $types = array(); private $types = array();
@ -40,41 +48,36 @@ class FormFactory implements FormFactoryInterface
$this->extensions = $extensions; $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) public function getType($name)
{ {
$type = null; if (!is_string($name)) {
throw new UnexpectedTypeException($name, 'string');
if ($name instanceof FormTypeInterface) {
$type = $name;
$name = $type->getName();
} }
if (!isset($this->types[$name])) { if (!isset($this->types[$name])) {
if (!$type) { $this->loadType($name);
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;
} }
return $this->types[$name]; return $this->types[$name];
@ -111,37 +114,63 @@ class FormFactory implements FormFactoryInterface
$types = array(); $types = array();
$knownOptions = array(); $knownOptions = array();
$passedOptions = array_keys($options); $passedOptions = array_keys($options);
$optionValues = array();
if (!array_key_exists('data', $options)) { if (!array_key_exists('data', $options)) {
$options['data'] = $data; $options['data'] = $data;
} }
while (null !== $type) { while (null !== $type) {
if ($type instanceof FormTypeInterface) {
$this->addType($type);
} else {
$type = $this->getType($type); $type = $this->getType($type);
$defaultOptions = $type->getDefaultOptions($options);
foreach ($type->getExtensions() as $typeExtension) {
$defaultOptions = array_merge($defaultOptions, $typeExtension->getDefaultOptions($options));
} }
$options = array_merge($defaultOptions, $options); $defaultOptions = $type->getDefaultOptions($options);
$optionValues = array_merge_recursive($optionValues, $type->getAllowedOptionValues($options));
foreach ($type->getExtensions() as $typeExtension) {
$defaultOptions = array_replace($defaultOptions, $typeExtension->getDefaultOptions($options));
$optionValues = array_merge_recursive($optionValues, $typeExtension->getAllowedOptionValues($options));
}
$options = array_replace($defaultOptions, $options);
$knownOptions = array_merge($knownOptions, array_keys($defaultOptions)); $knownOptions = array_merge($knownOptions, array_keys($defaultOptions));
array_unshift($types, $type); array_unshift($types, $type);
$type = $type->getParent($options); $type = $type->getParent($options);
} }
$diff = array_diff($passedOptions, $knownOptions); $type = end($types);
$diff = array_diff(self::$requiredOptions, $knownOptions);
if (count($diff) > 0) { 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]));
}
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) { for ($i = 0, $l = count($types); $i < $l && !$builder; ++$i) {
$builder = $types[$i]->createBuilder($name, $this, $options); $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); $builder->setTypes($types);
@ -198,4 +227,38 @@ class FormFactory implements FormFactoryInterface
$this->guesser = new FormTypeGuesserChain($guessers); $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);
}
} }

View File

@ -24,4 +24,10 @@ interface FormFactoryInterface
function createNamedBuilder($type, $name, $data = null, array $options = array()); function createNamedBuilder($type, $name, $data = null, array $options = array());
function createBuilderForProperty($class, $property, $data = null, array $options = array()); function createBuilderForProperty($class, $property, $data = null, array $options = array());
function getType($name);
function hasType($name);
function addType(FormTypeInterface $type);
} }

View File

@ -21,5 +21,14 @@ interface FormTypeExtensionInterface
function getDefaultOptions(array $options); 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(); function getExtendedType();
} }

View File

@ -30,6 +30,15 @@ interface FormTypeInterface
*/ */
function getDefaultOptions(array $options); 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. * Returns the name of the parent type.
* *

View File

@ -11,36 +11,7 @@
namespace Symfony\Component\Validator\Constraints; namespace Symfony\Component\Validator\Constraints;
use Symfony\Component\Validator\Constraint; class DateTimeValidator extends DateValidator
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
class DateTimeValidator extends ConstraintValidator
{ {
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])$/'; 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]);
}
} }

View File

@ -25,13 +25,17 @@ class DateValidator extends ConstraintValidator
return true; return true;
} }
if ($value instanceof \DateTime) {
return true;
}
if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString'))) { if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString'))) {
throw new UnexpectedTypeException($value, 'string'); throw new UnexpectedTypeException($value, 'string');
} }
$value = (string) $value; $value = (string) $value;
if (!preg_match(self::PATTERN, $value, $matches)) { if (!preg_match(static::PATTERN, $value, $matches)) {
$this->setMessage($constraint->message, array('{{ value }}' => $value)); $this->setMessage($constraint->message, array('{{ value }}' => $value));
return false; return false;

View File

@ -17,53 +17,29 @@ use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormBuilder; use Symfony\Component\Form\FormBuilder;
use Symfony\Tests\Component\Form\Fixtures\FooType;
class AbstractExtensionTest extends \PHPUnit_Framework_TestCase class AbstractExtensionTest extends \PHPUnit_Framework_TestCase
{ {
public function testHasType() public function testHasType()
{ {
$loader = new TestExtension(); $loader = new ConcreteExtension();
$this->assertTrue($loader->hasType('foo')); $this->assertTrue($loader->hasType('foo'));
$this->assertFalse($loader->hasType('bar')); $this->assertFalse($loader->hasType('bar'));
} }
public function testGetType() public function testGetType()
{ {
$loader = new TestExtension(array($type)); $loader = new ConcreteExtension();
$this->assertInstanceOf(__NAMESPACE__.'\TestType', $loader->getType('foo')); $this->assertTrue($loader->getType('foo') instanceof FooType);
$this->assertSame($loader->getType('foo'), $loader->getType('foo'));
} }
} }
class TestType implements FormTypeInterface class ConcreteExtension extends AbstractExtension
{
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
{ {
protected function loadTypes() protected function loadTypes()
{ {
return array(new TestType()); return array(new FooType());
} }
protected function loadTypeGuesser() protected function loadTypeGuesser()

View File

@ -597,6 +597,35 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
'widget' => 'text', '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(), $this->assertWidgetMatchesXpath($form->createView(), array(),
'/input '/input
[@type="text"] [@type="text"]

View File

@ -45,12 +45,12 @@ class DateTypeTest extends LocalizedTestCase
)); ));
} }
public function testSubmitFromInputDateTime() public function testSubmitFromSingleTextDateTime()
{ {
$form = $this->factory->create('date', null, array( $form = $this->factory->create('date', null, array(
'data_timezone' => 'UTC', 'data_timezone' => 'UTC',
'user_timezone' => 'UTC', 'user_timezone' => 'UTC',
'widget' => 'text', 'widget' => 'single-text',
'input' => 'datetime', 'input' => 'datetime',
)); ));
@ -60,12 +60,12 @@ class DateTypeTest extends LocalizedTestCase
$this->assertEquals('02.06.2010', $form->getClientData()); $this->assertEquals('02.06.2010', $form->getClientData());
} }
public function testSubmitFromInputString() public function testSubmitFromSingleTextString()
{ {
$form = $this->factory->create('date', null, array( $form = $this->factory->create('date', null, array(
'data_timezone' => 'UTC', 'data_timezone' => 'UTC',
'user_timezone' => 'UTC', 'user_timezone' => 'UTC',
'widget' => 'text', 'widget' => 'single-text',
'input' => 'string', 'input' => 'string',
)); ));
@ -75,12 +75,12 @@ class DateTypeTest extends LocalizedTestCase
$this->assertEquals('02.06.2010', $form->getClientData()); $this->assertEquals('02.06.2010', $form->getClientData());
} }
public function testSubmitFromInputTimestamp() public function testSubmitFromSingleTextTimestamp()
{ {
$form = $this->factory->create('date', null, array( $form = $this->factory->create('date', null, array(
'data_timezone' => 'UTC', 'data_timezone' => 'UTC',
'user_timezone' => 'UTC', 'user_timezone' => 'UTC',
'widget' => 'text', 'widget' => 'single-text',
'input' => 'timestamp', 'input' => 'timestamp',
)); ));
@ -92,12 +92,12 @@ class DateTypeTest extends LocalizedTestCase
$this->assertEquals('02.06.2010', $form->getClientData()); $this->assertEquals('02.06.2010', $form->getClientData());
} }
public function testSubmitFromInputRaw() public function testSubmitFromSingleTextRaw()
{ {
$form = $this->factory->create('date', null, array( $form = $this->factory->create('date', null, array(
'data_timezone' => 'UTC', 'data_timezone' => 'UTC',
'user_timezone' => 'UTC', 'user_timezone' => 'UTC',
'widget' => 'text', 'widget' => 'single-text',
'input' => 'array', 'input' => 'array',
)); ));
@ -113,6 +113,28 @@ class DateTypeTest extends LocalizedTestCase
$this->assertEquals('02.06.2010', $form->getClientData()); $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() public function testSubmitFromChoice()
{ {
$form = $this->factory->create('date', null, array( $form = $this->factory->create('date', null, array(
@ -163,7 +185,7 @@ class DateTypeTest extends LocalizedTestCase
'user_timezone' => 'Pacific/Tahiti', 'user_timezone' => 'Pacific/Tahiti',
// don't do this test with DateTime, because it leads to wrong results! // don't do this test with DateTime, because it leads to wrong results!
'input' => 'string', 'input' => 'string',
'widget' => 'text', 'widget' => 'single-text',
)); ));
$form->setData('2010-06-02'); $form->setData('2010-06-02');
@ -178,7 +200,7 @@ class DateTypeTest extends LocalizedTestCase
$form = $this->factory->create('date', null, array( $form = $this->factory->create('date', null, array(
'data_timezone' => 'UTC', 'data_timezone' => 'UTC',
'user_timezone' => 'UTC', 'user_timezone' => 'UTC',
'widget' => 'text', 'widget' => 'single-text',
'years' => array(2010, 2011), 'years' => array(2010, 2011),
)); ));
@ -194,7 +216,7 @@ class DateTypeTest extends LocalizedTestCase
$form = $this->factory->create('date', null, array( $form = $this->factory->create('date', null, array(
'data_timezone' => 'UTC', 'data_timezone' => 'UTC',
'user_timezone' => 'UTC', 'user_timezone' => 'UTC',
'widget' => 'text', 'widget' => 'single-text',
'years' => array(2010, 2011), 'years' => array(2010, 2011),
)); ));
@ -230,7 +252,7 @@ class DateTypeTest extends LocalizedTestCase
$form = $this->factory->create('date', null, array( $form = $this->factory->create('date', null, array(
'data_timezone' => 'UTC', 'data_timezone' => 'UTC',
'user_timezone' => 'UTC', 'user_timezone' => 'UTC',
'widget' => 'text', 'widget' => 'single-text',
'years' => array(2010, 2012), 'years' => array(2010, 2012),
)); ));
@ -246,7 +268,7 @@ class DateTypeTest extends LocalizedTestCase
$form = $this->factory->create('date', null, array( $form = $this->factory->create('date', null, array(
'data_timezone' => 'UTC', 'data_timezone' => 'UTC',
'user_timezone' => 'UTC', 'user_timezone' => 'UTC',
'widget' => 'text', 'widget' => 'single-text',
'months' => array(6, 7), 'months' => array(6, 7),
)); ));
@ -262,7 +284,7 @@ class DateTypeTest extends LocalizedTestCase
$form = $this->factory->create('date', null, array( $form = $this->factory->create('date', null, array(
'data_timezone' => 'UTC', 'data_timezone' => 'UTC',
'user_timezone' => 'UTC', 'user_timezone' => 'UTC',
'widget' => 'text', 'widget' => 'single-text',
'months' => array(6, 7), 'months' => array(6, 7),
)); ));
@ -298,7 +320,7 @@ class DateTypeTest extends LocalizedTestCase
$form = $this->factory->create('date', null, array( $form = $this->factory->create('date', null, array(
'data_timezone' => 'UTC', 'data_timezone' => 'UTC',
'user_timezone' => 'UTC', 'user_timezone' => 'UTC',
'widget' => 'text', 'widget' => 'single-text',
'months' => array(6, 8), 'months' => array(6, 8),
)); ));
@ -314,7 +336,7 @@ class DateTypeTest extends LocalizedTestCase
$form = $this->factory->create('date', null, array( $form = $this->factory->create('date', null, array(
'data_timezone' => 'UTC', 'data_timezone' => 'UTC',
'user_timezone' => 'UTC', 'user_timezone' => 'UTC',
'widget' => 'text', 'widget' => 'single-text',
'days' => array(6, 7), 'days' => array(6, 7),
)); ));
@ -330,7 +352,7 @@ class DateTypeTest extends LocalizedTestCase
$form = $this->factory->create('date', null, array( $form = $this->factory->create('date', null, array(
'data_timezone' => 'UTC', 'data_timezone' => 'UTC',
'user_timezone' => 'UTC', 'user_timezone' => 'UTC',
'widget' => 'text', 'widget' => 'single-text',
'days' => array(6, 7), 'days' => array(6, 7),
)); ));
@ -368,7 +390,7 @@ class DateTypeTest extends LocalizedTestCase
$form = $this->factory->create('date', null, array( $form = $this->factory->create('date', null, array(
'data_timezone' => 'UTC', 'data_timezone' => 'UTC',
'user_timezone' => 'UTC', 'user_timezone' => 'UTC',
'widget' => 'text', 'widget' => 'single-text',
'days' => array(6, 8), 'days' => array(6, 8),
)); ));
@ -377,14 +399,14 @@ class DateTypeTest extends LocalizedTestCase
$this->assertFalse($form->isDayWithinRange()); $this->assertFalse($form->isDayWithinRange());
} }
public function testIsPartiallyFilledReturnsFalseIfInput() public function testIsPartiallyFilledReturnsFalseIfSingleText()
{ {
$this->markTestIncomplete('Needs to be reimplemented using validators'); $this->markTestIncomplete('Needs to be reimplemented using validators');
$form = $this->factory->create('date', null, array( $form = $this->factory->create('date', null, array(
'data_timezone' => 'UTC', 'data_timezone' => 'UTC',
'user_timezone' => 'UTC', 'user_timezone' => 'UTC',
'widget' => 'text', 'widget' => 'single-text',
)); ));
$form->bind('7.6.2010'); $form->bind('7.6.2010');
@ -460,7 +482,7 @@ class DateTypeTest extends LocalizedTestCase
public function testDontPassDatePatternIfText() public function testDontPassDatePatternIfText()
{ {
$form = $this->factory->create('date', null, array( $form = $this->factory->create('date', null, array(
'widget' => 'text', 'widget' => 'single-text',
)); ));
$view = $form->createView(); $view = $form->createView();
@ -470,10 +492,10 @@ class DateTypeTest extends LocalizedTestCase
public function testPassWidgetToView() public function testPassWidgetToView()
{ {
$form = $this->factory->create('date', null, array( $form = $this->factory->create('date', null, array(
'widget' => 'text', 'widget' => 'single-text',
)); ));
$view = $form->createView(); $view = $form->createView();
$this->assertSame('text', $view->get('widget')); $this->assertSame('single-text', $view->get('widget'));
} }
} }

View File

@ -0,0 +1,49 @@
<?php
namespace Symfony\Tests\Component\Form\Fixtures;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
class FooType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->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,
'a_or_b' => 'a',
);
}
public function getAllowedOptionValues(array $options)
{
return array(
'a_or_b' => array('a', 'b'),
);
}
public function getParent(array $options)
{
return null;
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace Symfony\Tests\Component\Form\Fixtures;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormBuilder;
class FooTypeBarExtension extends AbstractTypeExtension
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->setAttribute('bar', 'x');
}
public function getAllowedOptionValues(array $options)
{
return array(
'a_or_b' => array('c'),
);
}
public function getExtendedType()
{
return 'foo';
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace Symfony\Tests\Component\Form\Fixtures;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormBuilder;
class FooTypeBazExtension extends AbstractTypeExtension
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->setAttribute('baz', 'x');
}
public function getExtendedType()
{
return 'foo';
}
}

View File

@ -0,0 +1,63 @@
<?php
namespace Symfony\Tests\Component\Form\Fixtures;
use Symfony\Component\Form\FormTypeInterface;
use Symfony\Component\Form\FormTypeExtensionInterface;
use Symfony\Component\Form\FormTypeGuesserInterface;
use Symfony\Component\Form\FormExtensionInterface;
class TestExtension implements FormExtensionInterface
{
private $types = array();
private $extensions = array();
private $guesser;
public function __construct(FormTypeGuesserInterface $guesser)
{
$this->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;
}
}

View File

@ -11,10 +11,15 @@
namespace Symfony\Tests\Component\Form; namespace Symfony\Tests\Component\Form;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormFactory; use Symfony\Component\Form\FormFactory;
use Symfony\Component\Form\Guess\Guess; use Symfony\Component\Form\Guess\Guess;
use Symfony\Component\Form\Guess\ValueGuess; use Symfony\Component\Form\Guess\ValueGuess;
use Symfony\Component\Form\Guess\TypeGuess; 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 class FormFactoryTest extends \PHPUnit_Framework_TestCase
{ {
@ -32,17 +37,290 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
{ {
$this->guesser1 = $this->getMock('Symfony\Component\Form\FormTypeGuesserInterface'); $this->guesser1 = $this->getMock('Symfony\Component\Form\FormTypeGuesserInterface');
$this->guesser2 = $this->getMock('Symfony\Component\Form\FormTypeGuesserInterface'); $this->guesser2 = $this->getMock('Symfony\Component\Form\FormTypeGuesserInterface');
$this->extension1 = $this->getMock('Symfony\Component\Form\FormExtensionInterface'); $this->extension1 = new TestExtension($this->guesser1);
$this->extension1->expects($this->any()) $this->extension2 = new TestExtension($this->guesser2);
->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->factory = new FormFactory(array($this->extension1, $this->extension2)); $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('getAllowedOptionValues')
->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('getAllowedOptionValues')
->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('getAllowedOptionValues')
->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('getAllowedOptionValues')
->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',
));
}
/**
* @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();
$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() public function testCreateBuilderForPropertyCreatesFieldWithHighestConfidence()
{ {
$this->guesser1->expects($this->once()) $this->guesser1->expects($this->once())