From 2a49449862ab74fbd80126384daa1957abaf2e0c Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Fri, 13 Apr 2012 14:11:09 +0200 Subject: [PATCH 1/4] [Form] Simplified CSRF mechanism and removed "csrf" type CSRF fields are now only added when the view is built. For this reason we already know if the form is the root form and avoid to create unnecessary CSRF fields for nested fields. --- CHANGELOG-2.1.md | 1 + .../Resources/config/form_csrf.xml | 5 +- .../FrameworkExtensionTest.php | 4 +- .../Form/Extension/Csrf/CsrfExtension.php | 16 +- .../EventListener/CsrfValidationListener.php | 26 ++- .../EventListener/EnsureCsrfFieldListener.php | 66 ------ .../Csrf/Type/ChoiceTypeCsrfExtension.php | 27 --- .../Form/Extension/Csrf/Type/CsrfType.php | 83 -------- .../Csrf/Type/DateTypeCsrfExtension.php | 27 --- .../Csrf/Type/FormTypeCsrfExtension.php | 54 ++--- .../Csrf/Type/RepeatedTypeCsrfExtension.php | 27 --- .../Csrf/Type/TimeTypeCsrfExtension.php | 27 --- src/Symfony/Component/Form/Form.php | 10 +- src/Symfony/Component/Form/FormView.php | 55 ++++- .../Form/Tests/AbstractDivLayoutTest.php | 35 +++- .../Form/Tests/AbstractLayoutTest.php | 36 ++-- .../Form/Tests/AbstractTableLayoutTest.php | 120 +++++++---- .../EnsureCsrfFieldListenerTest.php | 87 -------- .../Extension/Csrf/Type/CsrfTypeTest.php | 112 ---------- .../Csrf/Type/FormTypeCsrfExtensionTest.php | 197 +++++++++++++++--- .../Extension/Csrf/Type/TypeTestCase.php | 41 ---- src/Symfony/Component/Form/Tests/FormTest.php | 2 +- 22 files changed, 404 insertions(+), 654 deletions(-) delete mode 100644 src/Symfony/Component/Form/Extension/Csrf/EventListener/EnsureCsrfFieldListener.php delete mode 100644 src/Symfony/Component/Form/Extension/Csrf/Type/ChoiceTypeCsrfExtension.php delete mode 100644 src/Symfony/Component/Form/Extension/Csrf/Type/CsrfType.php delete mode 100644 src/Symfony/Component/Form/Extension/Csrf/Type/DateTypeCsrfExtension.php delete mode 100644 src/Symfony/Component/Form/Extension/Csrf/Type/RepeatedTypeCsrfExtension.php delete mode 100644 src/Symfony/Component/Form/Extension/Csrf/Type/TimeTypeCsrfExtension.php delete mode 100644 src/Symfony/Component/Form/Tests/Extension/Csrf/EventListener/EnsureCsrfFieldListenerTest.php delete mode 100644 src/Symfony/Component/Form/Tests/Extension/Csrf/Type/CsrfTypeTest.php delete mode 100644 src/Symfony/Component/Form/Tests/Extension/Csrf/Type/TypeTestCase.php diff --git a/CHANGELOG-2.1.md b/CHANGELOG-2.1.md index 39e668c405..ca7edd952a 100644 --- a/CHANGELOG-2.1.md +++ b/CHANGELOG-2.1.md @@ -269,6 +269,7 @@ To get the diff between two versions, go to https://github.com/symfony/symfony/c don't receive an options array anymore. * Deprecated FormValidatorInterface and substituted its implementations by event subscribers + * simplified CSRF protection and removed the csrf type ### HttpFoundation diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml index 188a099df2..72442bcbba 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml @@ -14,12 +14,9 @@ %kernel.secret% - - - - + %form.type_extension.csrf.enabled% %form.type_extension.csrf.field_name% diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 859ec489ab..2c7204e735 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -27,9 +27,9 @@ abstract class FrameworkExtensionTest extends TestCase $def = $container->getDefinition('form.type_extension.csrf'); $this->assertTrue($container->getParameter('form.type_extension.csrf.enabled')); - $this->assertEquals('%form.type_extension.csrf.enabled%', $def->getArgument(0)); + $this->assertEquals('%form.type_extension.csrf.enabled%', $def->getArgument(1)); $this->assertEquals('_csrf', $container->getParameter('form.type_extension.csrf.field_name')); - $this->assertEquals('%form.type_extension.csrf.field_name%', $def->getArgument(1)); + $this->assertEquals('%form.type_extension.csrf.field_name%', $def->getArgument(2)); $this->assertEquals('s3cr3t', $container->getParameterBag()->resolveValue($container->findDefinition('form.csrf_provider')->getArgument(1))); } diff --git a/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php b/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php index 42505e2b76..5330bbda45 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php +++ b/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php @@ -32,27 +32,13 @@ class CsrfExtension extends AbstractExtension $this->csrfProvider = $csrfProvider; } - /** - * {@inheritDoc} - */ - protected function loadTypes() - { - return array( - new Type\CsrfType($this->csrfProvider), - ); - } - /** * {@inheritDoc} */ protected function loadTypeExtensions() { return array( - new Type\ChoiceTypeCsrfExtension(), - new Type\DateTypeCsrfExtension(), - new Type\FormTypeCsrfExtension(), - new Type\RepeatedTypeCsrfExtension(), - new Type\TimeTypeCsrfExtension(), + new Type\FormTypeCsrfExtension($this->csrfProvider), ); } } diff --git a/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php b/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php index 3de52ab867..dd74d87b2e 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php +++ b/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php @@ -14,7 +14,7 @@ namespace Symfony\Component\Form\Extension\Csrf\EventListener; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormError; -use Symfony\Component\Form\Event\DataEvent; +use Symfony\Component\Form\Event\FilterDataEvent; use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; /** @@ -22,6 +22,12 @@ use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; */ class CsrfValidationListener implements EventSubscriberInterface { + /** + * The name of the CSRF field + * @var string + */ + private $fieldName; + /** * The provider for generating and validating CSRF tokens * @var CsrfProviderInterface @@ -45,24 +51,26 @@ class CsrfValidationListener implements EventSubscriberInterface ); } - public function __construct(CsrfProviderInterface $csrfProvider, $intention) + public function __construct($fieldName, CsrfProviderInterface $csrfProvider, $intention) { + $this->fieldName = $fieldName; $this->csrfProvider = $csrfProvider; $this->intention = $intention; } - public function onBindClientData(DataEvent $event) + public function onBindClientData(FilterDataEvent $event) { $form = $event->getForm(); $data = $event->getData(); - if ((!$form->hasParent() || $form->getParent()->isRoot()) - && !$this->csrfProvider->isCsrfTokenValid($this->intention, $data)) { - $form->addError(new FormError('The CSRF token is invalid. Please try to resubmit the form')); + if ($form->isRoot() && $form->hasChildren() && isset($data[$this->fieldName])) { + if (!$this->csrfProvider->isCsrfTokenValid($this->intention, $data[$this->fieldName])) { + $form->addError(new FormError('The CSRF token is invalid. Please try to resubmit the form')); + } - // If the session timed out, the token is invalid now. - // Regenerate the token so that a resubmission is possible. - $event->setData($this->csrfProvider->generateCsrfToken($this->intention)); + unset($data[$this->fieldName]); } + + $event->setData($data); } } diff --git a/src/Symfony/Component/Form/Extension/Csrf/EventListener/EnsureCsrfFieldListener.php b/src/Symfony/Component/Form/Extension/Csrf/EventListener/EnsureCsrfFieldListener.php deleted file mode 100644 index 480d67c323..0000000000 --- a/src/Symfony/Component/Form/Extension/Csrf/EventListener/EnsureCsrfFieldListener.php +++ /dev/null @@ -1,66 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Form\Extension\Csrf\EventListener; - -use Symfony\Component\Form\Event\DataEvent; -use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; -use Symfony\Component\Form\FormFactoryInterface; - -/** - * Ensures the CSRF field. - * - * @author Bulat Shakirzyanov - * @author Kris Wallsmith - */ -class EnsureCsrfFieldListener -{ - private $factory; - private $name; - private $intention; - private $provider; - - /** - * Constructor. - * - * @param FormFactoryInterface $factory The form factory - * @param string $name A name for the CSRF field - * @param string $intention The intention string - * @param CsrfProviderInterface $provider The CSRF provider - */ - public function __construct(FormFactoryInterface $factory, $name, $intention = null, CsrfProviderInterface $provider = null) - { - $this->factory = $factory; - $this->name = $name; - $this->intention = $intention; - $this->provider = $provider; - } - - /** - * Ensures a root form has a CSRF field. - * - * This method should be connected to both form.pre_set_data and form.pre_bind. - */ - public function ensureCsrfField(DataEvent $event) - { - $form = $event->getForm(); - - $options = array(); - if ($this->intention) { - $options['intention'] = $this->intention; - } - if ($this->provider) { - $options['csrf_provider'] = $this->provider; - } - - $form->add($this->factory->createNamed('csrf', $this->name, null, $options)); - } -} diff --git a/src/Symfony/Component/Form/Extension/Csrf/Type/ChoiceTypeCsrfExtension.php b/src/Symfony/Component/Form/Extension/Csrf/Type/ChoiceTypeCsrfExtension.php deleted file mode 100644 index dc1e6db15b..0000000000 --- a/src/Symfony/Component/Form/Extension/Csrf/Type/ChoiceTypeCsrfExtension.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Form\Extension\Csrf\Type; - -use Symfony\Component\Form\AbstractTypeExtension; - -class ChoiceTypeCsrfExtension extends AbstractTypeExtension -{ - public function getDefaultOptions() - { - return array('csrf_protection' => false); - } - - public function getExtendedType() - { - return 'choice'; - } -} diff --git a/src/Symfony/Component/Form/Extension/Csrf/Type/CsrfType.php b/src/Symfony/Component/Form/Extension/Csrf/Type/CsrfType.php deleted file mode 100644 index 97c69d8a5a..0000000000 --- a/src/Symfony/Component/Form/Extension/Csrf/Type/CsrfType.php +++ /dev/null @@ -1,83 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Form\Extension\Csrf\Type; - - -use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\FormBuilder; -use Symfony\Component\Form\Extension\Csrf\EventListener\CsrfValidationListener; -use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; - -class CsrfType extends AbstractType -{ - private $csrfProvider; - - /** - * Constructor. - * - * @param CsrfProviderInterface $csrfProvider The provider to use to generate the token - */ - public function __construct(CsrfProviderInterface $csrfProvider) - { - $this->csrfProvider = $csrfProvider; - } - - /** - * Builds the CSRF field. - * - * A validator is added to check the token value when the CSRF field is added to - * a root form - * - * @param FormBuilder $builder The form builder - * @param array $options The options - */ - public function buildForm(FormBuilder $builder, array $options) - { - $csrfProvider = $options['csrf_provider']; - $intention = $options['intention']; - - $builder - ->setData($csrfProvider->generateCsrfToken($intention)) - ->addEventSubscriber(new CsrfValidationListener($csrfProvider, $intention)) - ; - } - - /** - * {@inheritDoc} - */ - public function getDefaultOptions() - { - return array( - 'csrf_provider' => $this->csrfProvider, - 'intention' => null, - 'property_path' => false, - ); - } - - /** - * {@inheritDoc} - */ - public function getParent(array $options) - { - return 'hidden'; - } - - /** - * Returns the name of this form. - * - * @return string 'csrf' - */ - public function getName() - { - return 'csrf'; - } -} diff --git a/src/Symfony/Component/Form/Extension/Csrf/Type/DateTypeCsrfExtension.php b/src/Symfony/Component/Form/Extension/Csrf/Type/DateTypeCsrfExtension.php deleted file mode 100644 index dc54e94ddf..0000000000 --- a/src/Symfony/Component/Form/Extension/Csrf/Type/DateTypeCsrfExtension.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Form\Extension\Csrf\Type; - -use Symfony\Component\Form\AbstractTypeExtension; - -class DateTypeCsrfExtension extends AbstractTypeExtension -{ - public function getDefaultOptions() - { - return array('csrf_protection' => false); - } - - public function getExtendedType() - { - return 'date'; - } -} diff --git a/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php b/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php index 8e2a57459a..c8687d4af4 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php +++ b/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php @@ -12,21 +12,27 @@ namespace Symfony\Component\Form\Extension\Csrf\Type; use Symfony\Component\Form\AbstractTypeExtension; -use Symfony\Component\Form\Extension\Csrf\EventListener\EnsureCsrfFieldListener; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; +use Symfony\Component\Form\Extension\Csrf\EventListener\CsrfValidationListener; use Symfony\Component\Form\FormBuilder; use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormInterface; +/** + * @author Bernhard Schussek + */ class FormTypeCsrfExtension extends AbstractTypeExtension { - private $enabled; - private $fieldName; + private $defaultCsrfProvider; + private $defaultEnabled; + private $defaultFieldName; - public function __construct($enabled = true, $fieldName = '_token') + public function __construct(CsrfProviderInterface $defaultCsrfProvider, $defaultEnabled = true, $defaultFieldName = '_token') { - $this->enabled = $enabled; - $this->fieldName = $fieldName; + $this->defaultCsrfProvider = $defaultCsrfProvider; + $this->defaultEnabled = $defaultEnabled; + $this->defaultFieldName = $defaultFieldName; } /** @@ -41,35 +47,35 @@ class FormTypeCsrfExtension extends AbstractTypeExtension return; } - $listener = new EnsureCsrfFieldListener( - $builder->getFormFactory(), - $options['csrf_field_name'], - $options['intention'], - $options['csrf_provider'] - ); - // use a low priority so higher priority listeners don't remove the field $builder ->setAttribute('csrf_field_name', $options['csrf_field_name']) - ->addEventListener(FormEvents::PRE_SET_DATA, array($listener, 'ensureCsrfField'), -10) - ->addEventListener(FormEvents::PRE_BIND, array($listener, 'ensureCsrfField'), -10) + ->setAttribute('csrf_provider', $options['csrf_provider']) + ->setAttribute('csrf_intention', $options['intention']) + ->setAttribute('csrf_factory', $builder->getFormFactory()) + ->addEventSubscriber(new CsrfValidationListener($options['csrf_field_name'], $options['csrf_provider'], $options['intention'])) ; } /** - * Removes CSRF fields from all the form views except the root one. + * Adds a CSRF field to the root form view. * * @param FormView $view The form view * @param FormInterface $form The form */ - public function buildViewBottomUp(FormView $view, FormInterface $form) + public function buildView(FormView $view, FormInterface $form) { - if ($view->hasParent() && $form->hasAttribute('csrf_field_name')) { + if ($form->isRoot() && $form->hasChildren() && $form->hasAttribute('csrf_field_name')) { $name = $form->getAttribute('csrf_field_name'); + $csrfProvider = $form->getAttribute('csrf_provider'); + $intention = $form->getAttribute('csrf_intention'); + $factory = $form->getAttribute('csrf_factory'); + $data = $csrfProvider->generateCsrfToken($intention); + $csrfForm = $factory->createNamed('hidden', $name, $data, array( + 'property_path' => false, + )); - if (isset($view[$name])) { - unset($view[$name]); - } + $view->addChild($csrfForm->createView($view)); } } @@ -79,9 +85,9 @@ class FormTypeCsrfExtension extends AbstractTypeExtension public function getDefaultOptions() { return array( - 'csrf_protection' => $this->enabled, - 'csrf_field_name' => $this->fieldName, - 'csrf_provider' => null, + 'csrf_protection' => $this->defaultEnabled, + 'csrf_field_name' => $this->defaultFieldName, + 'csrf_provider' => $this->defaultCsrfProvider, 'intention' => 'unknown', ); } diff --git a/src/Symfony/Component/Form/Extension/Csrf/Type/RepeatedTypeCsrfExtension.php b/src/Symfony/Component/Form/Extension/Csrf/Type/RepeatedTypeCsrfExtension.php deleted file mode 100644 index 1115ea4d69..0000000000 --- a/src/Symfony/Component/Form/Extension/Csrf/Type/RepeatedTypeCsrfExtension.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Form\Extension\Csrf\Type; - -use Symfony\Component\Form\AbstractTypeExtension; - -class RepeatedTypeCsrfExtension extends AbstractTypeExtension -{ - public function getDefaultOptions() - { - return array('csrf_protection' => false); - } - - public function getExtendedType() - { - return 'repeated'; - } -} diff --git a/src/Symfony/Component/Form/Extension/Csrf/Type/TimeTypeCsrfExtension.php b/src/Symfony/Component/Form/Extension/Csrf/Type/TimeTypeCsrfExtension.php deleted file mode 100644 index dbd7c0d2df..0000000000 --- a/src/Symfony/Component/Form/Extension/Csrf/Type/TimeTypeCsrfExtension.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Form\Extension\Csrf\Type; - -use Symfony\Component\Form\AbstractTypeExtension; - -class TimeTypeCsrfExtension extends AbstractTypeExtension -{ - public function getDefaultOptions() - { - return array('csrf_protection' => false); - } - - public function getExtendedType() - { - return 'time'; - } -} diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index 8f9e7b6de2..4798c7e5cd 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -975,7 +975,7 @@ class Form implements \IteratorAggregate, FormInterface $parent = $this->parent->createView(); } - $view = new FormView(); + $view = new FormView($this->name); $view->setParent($parent); @@ -989,14 +989,10 @@ class Form implements \IteratorAggregate, FormInterface } } - $childViews = array(); - - foreach ($this->children as $key => $child) { - $childViews[$key] = $child->createView($view); + foreach ($this->children as $child) { + $view->addChild($child->createView($view)); } - $view->setChildren($childViews); - foreach ($types as $type) { $type->buildViewBottomUp($view, $this); diff --git a/src/Symfony/Component/Form/FormView.php b/src/Symfony/Component/Form/FormView.php index 5c8ab13da1..5d88346edc 100644 --- a/src/Symfony/Component/Form/FormView.php +++ b/src/Symfony/Component/Form/FormView.php @@ -11,8 +11,17 @@ namespace Symfony\Component\Form; -class FormView implements \ArrayAccess, \IteratorAggregate, \Countable +use ArrayAccess; +use IteratorAggregate; +use Countable; + +/** + * @author Bernhard Schussek + */ +class FormView implements ArrayAccess, IteratorAggregate, Countable { + private $name; + private $vars = array( 'value' => null, 'attr' => array(), @@ -33,6 +42,16 @@ class FormView implements \ArrayAccess, \IteratorAggregate, \Countable */ private $rendered = false; + public function __construct($name) + { + $this->name = $name; + } + + public function getName() + { + return $this->name; + } + /** * @param string $name * @param mixed $value @@ -177,15 +196,29 @@ class FormView implements \ArrayAccess, \IteratorAggregate, \Countable } /** - * Sets the children view. + * Adds a child view. * - * @param array $children The children as instances of FormView + * @param FormView $child The child view to add. * * @return FormView The current view */ - public function setChildren(array $children) + public function addChild(FormView $child) { - $this->children = $children; + $this->children[$child->getName()] = $child; + + return $this; + } + + /** + * Removes a child view. + * + * @param string $name The name of the removed child view. + * + * @return FormView The current view + */ + public function removeChild($name) + { + unset($this->children[$name]); return $this; } @@ -222,6 +255,18 @@ class FormView implements \ArrayAccess, \IteratorAggregate, \Countable return count($this->children) > 0; } + /** + * Returns whether this view has a given child. + * + * @param string $name The name of the child + * + * @return Boolean Whether the child with the given name exists + */ + public function hasChild($name) + { + return isset($this->children[$name]); + } + /** * Returns a child by name (implements \ArrayAccess). * diff --git a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php index d22113245a..986f99b7b4 100644 --- a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php @@ -363,6 +363,31 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest ); } + public function testCsrf() + { + $this->csrfProvider->expects($this->any()) + ->method('generateCsrfToken') + ->will($this->returnValue('foo&bar')); + + $form = $this->factory->createNamedBuilder('form', 'name') + ->add($this->factory + // No CSRF protection on nested forms + ->createNamedBuilder('form', 'child') + ->add($this->factory->createNamedBuilder('text', 'grandchild')) + ) + ->getForm(); + + $this->assertWidgetMatchesXpath($form->createView(), array(), +'/div + [ + ./input[@type="hidden"][@id="name__token"][@value="foo&bar"] + /following-sibling::div + ] + [count(.//input[@type="hidden"])=1] +' + ); + } + public function testRepeated() { $form = $this->factory->createNamed('repeated', 'name', 'foobar', array( @@ -372,7 +397,8 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest $this->assertWidgetMatchesXpath($form->createView(), array(), '/div [ - ./div + ./input[@type="hidden"][@id="name__token"] + /following-sibling::div [ ./label[@for="name_first"] /following-sibling::input[@type="text"][@id="name_first"] @@ -383,7 +409,7 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest /following-sibling::input[@type="text"][@id="name_second"] ] ] - [count(.//input)=2] + [count(.//input)=3] ' ); } @@ -399,7 +425,8 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest $this->assertWidgetMatchesXpath($form->createView(), array(), '/div [ - ./div + ./input[@type="hidden"][@id="name__token"] + /following-sibling::div [ ./label[@for="name_first"][.="[trans]Test[/trans]"] /following-sibling::input[@type="text"][@id="name_first"][@required="required"] @@ -410,7 +437,7 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest /following-sibling::input[@type="text"][@id="name_second"][@required="required"] ] ] - [count(.//input)=2] + [count(.//input)=3] ' ); } diff --git a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php index 32aabb86a6..5d48b9921c 100644 --- a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php @@ -646,12 +646,13 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase $this->assertWidgetMatchesXpath($form->createView(), array(), '/div [ - ./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked] + ./input[@type="hidden"][@id="name__token"] + /following-sibling::input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked] /following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"] /following-sibling::input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)] /following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"] ] - [count(./input)=2] + [count(./input)=3] ' ); } @@ -668,12 +669,13 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase $this->assertWidgetMatchesXpath($form->createView(), array(), '/div [ - ./input[@type="radio"][@name="name"][@id="name_0"][@checked] + ./input[@type="hidden"][@id="name__token"] + /following-sibling::input[@type="radio"][@name="name"][@id="name_0"][@checked] /following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"] /following-sibling::input[@type="radio"][@name="name"][@id="name_1"][not(@checked)] /following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"] ] - [count(./input)=2] + [count(./input)=3] ' ); } @@ -689,12 +691,13 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase $this->assertWidgetMatchesXpath($form->createView(), array(), '/div [ - ./input[@type="radio"][@name="name"][@id="name_0"][@checked] + ./input[@type="hidden"][@id="name__token"] + /following-sibling::input[@type="radio"][@name="name"][@id="name_0"][@checked] /following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"] /following-sibling::input[@type="radio"][@name="name"][@id="name_1"][not(@checked)] /following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"] ] - [count(./input)=2] + [count(./input)=3] ' ); } @@ -711,14 +714,15 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase $this->assertWidgetMatchesXpath($form->createView(), array(), '/div [ - ./input[@type="checkbox"][@name="name[]"][@id="name_0"][@checked][not(@required)] + ./input[@type="hidden"][@id="name__token"] + /following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_0"][@checked][not(@required)] /following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"] /following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_1"][not(@checked)][not(@required)] /following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"] /following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_2"][@checked][not(@required)] /following-sibling::label[@for="name_2"][.="[trans]Choice&C[/trans]"] ] - [count(./input)=3] + [count(./input)=4] ' ); } @@ -753,22 +757,6 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase ); } - public function testCsrf() - { - $this->csrfProvider->expects($this->any()) - ->method('generateCsrfToken') - ->will($this->returnValue('foo&bar')); - - $form = $this->factory->createNamed('csrf', 'name'); - - $this->assertWidgetMatchesXpath($form->createView(), array(), -'/input - [@type="hidden"] - [@value="foo&bar"] -' - ); - } - public function testDateTime() { $form = $this->factory->createNamed('datetime', 'name', '2011-02-03 04:05:06', array( diff --git a/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php index d47b15564f..5ea8780d89 100644 --- a/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php @@ -45,36 +45,10 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest $html = $this->renderRow($form->createView()); $this->assertMatchesXpath($html, -'/tr - [ - ./td - [./label[@for="name_first"]] - /following-sibling::td - [./input[@id="name_first"]] - ] -/following-sibling::tr - [ - ./td - [./label[@for="name_second"]] - /following-sibling::td - [./input[@id="name_second"]] - ] - [count(../tr)=2] -' - ); - } - - public function testRepeatedRowWithErrors() - { - $form = $this->factory->createNamed('repeated', 'name'); - $form->addError(new FormError('Error!')); - $view = $form->createView(); - $html = $this->renderRow($view); - - $this->assertMatchesXpath($html, -'/tr - [./td[@colspan="2"]/ul - [./li[.="[trans]Error![/trans]"]] +'/tr[@style="display: none"] + [./td[@colspan="2"]/input + [@type="hidden"] + [@id="name__token"] ] /following-sibling::tr [ @@ -95,6 +69,42 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest ); } + public function testRepeatedRowWithErrors() + { + $form = $this->factory->createNamed('repeated', 'name'); + $form->addError(new FormError('Error!')); + $view = $form->createView(); + $html = $this->renderRow($view); + + $this->assertMatchesXpath($html, +'/tr + [./td[@colspan="2"]/ul + [./li[.="[trans]Error![/trans]"]] + ] +/following-sibling::tr[@style="display: none"] + [./td[@colspan="2"]/input + [@type="hidden"] + [@id="name__token"] + ] +/following-sibling::tr + [ + ./td + [./label[@for="name_first"]] + /following-sibling::td + [./input[@id="name_first"]] + ] +/following-sibling::tr + [ + ./td + [./label[@for="name_second"]] + /following-sibling::td + [./input[@id="name_second"]] + ] + [count(../tr)=4] +' + ); + } + public function testRest() { $view = $this->factory->createNamedBuilder('form', 'name') @@ -151,9 +161,9 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest $this->assertWidgetMatchesXpath($form->createView(), array(), '/table [ - ./tr[./td/input[@type="text"][@value="a"]] + ./tr[@style="display: none"][./td[@colspan="2"]/input[@type="hidden"][@id="name__token"]] + /following-sibling::tr[./td/input[@type="text"][@value="a"]] /following-sibling::tr[./td/input[@type="text"][@value="b"]] - /following-sibling::tr[./td/input[@type="hidden"][@id="name__token"]] ] [count(./tr[./td/input])=3] ' @@ -217,6 +227,34 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest ); } + public function testCsrf() + { + $this->csrfProvider->expects($this->any()) + ->method('generateCsrfToken') + ->will($this->returnValue('foo&bar')); + + $form = $this->factory->createNamedBuilder('form', 'name') + ->add($this->factory + // No CSRF protection on nested forms + ->createNamedBuilder('form', 'child') + ->add($this->factory->createNamedBuilder('text', 'grandchild')) + ) + ->getForm(); + + $this->assertWidgetMatchesXpath($form->createView(), array(), +'/table + [ + ./tr[@style="display: none"] + [./td[@colspan="2"]/input + [@type="hidden"] + [@id="name__token"] + ] + ] + [count(.//input[@type="hidden"])=1] +' + ); + } + public function testRepeated() { $form = $this->factory->createNamed('repeated', 'name', 'foobar', array( @@ -226,7 +264,12 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest $this->assertWidgetMatchesXpath($form->createView(), array(), '/table [ - ./tr + ./tr[@style="display: none"] + [./td[@colspan="2"]/input + [@type="hidden"] + [@id="name__token"] + ] + /following-sibling::tr [ ./td [./label[@for="name_first"]] @@ -241,7 +284,7 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest [./input[@type="text"][@id="name_second"]] ] ] - [count(.//input)=2] + [count(.//input)=3] ' ); } @@ -257,7 +300,12 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest $this->assertWidgetMatchesXpath($form->createView(), array(), '/table [ - ./tr + ./tr[@style="display: none"] + [./td[@colspan="2"]/input + [@type="hidden"] + [@id="name__token"] + ] + /following-sibling::tr [ ./td [./label[@for="name_first"][.="[trans]Test[/trans]"]] @@ -272,7 +320,7 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest [./input[@type="password"][@id="name_second"][@required="required"]] ] ] - [count(.//input)=2] + [count(.//input)=3] ' ); } diff --git a/src/Symfony/Component/Form/Tests/Extension/Csrf/EventListener/EnsureCsrfFieldListenerTest.php b/src/Symfony/Component/Form/Tests/Extension/Csrf/EventListener/EnsureCsrfFieldListenerTest.php deleted file mode 100644 index 9b87717e5d..0000000000 --- a/src/Symfony/Component/Form/Tests/Extension/Csrf/EventListener/EnsureCsrfFieldListenerTest.php +++ /dev/null @@ -1,87 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Form\Tests\Extension\Csrf\EventListener; - -use Symfony\Component\Form\Event\DataEvent; -use Symfony\Component\Form\Extension\Csrf\EventListener\EnsureCsrfFieldListener; - -class EnsureCsrfFieldListenerTest extends \PHPUnit_Framework_TestCase -{ - private $form; - private $formFactory; - private $field; - private $event; - - protected function setUp() - { - if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) { - $this->markTestSkipped('The "EventDispatcher" component is not available'); - } - - $this->formFactory = $this->getMock('Symfony\\Component\\Form\\FormFactoryInterface'); - $this->form = $this->getMock('Symfony\\Component\\Form\\Tests\\FormInterface'); - $this->field = $this->getMock('Symfony\\Component\\Form\\Tests\\FormInterface'); - $this->event = new DataEvent($this->form, array()); - } - - protected function tearDown() - { - $this->form = null; - $this->formFactory = null; - $this->field = null; - $this->event = null; - } - - public function testAddField() - { - $this->formFactory->expects($this->once()) - ->method('createNamed') - ->with('csrf', '_token', null, array()) - ->will($this->returnValue($this->field)); - $this->form->expects($this->once()) - ->method('add') - ->with($this->isInstanceOf('Symfony\\Component\\Form\\Tests\\FormInterface')); - - $listener = new EnsureCsrfFieldListener($this->formFactory, '_token'); - $listener->ensureCsrfField($this->event); - } - - public function testIntention() - { - $this->formFactory->expects($this->once()) - ->method('createNamed') - ->with('csrf', '_token', null, array('intention' => 'something')) - ->will($this->returnValue($this->field)); - $this->form->expects($this->once()) - ->method('add') - ->with($this->isInstanceOf('Symfony\\Component\\Form\\Tests\\FormInterface')); - - $listener = new EnsureCsrfFieldListener($this->formFactory, '_token', 'something'); - $listener->ensureCsrfField($this->event); - } - - public function testProvider() - { - $provider = $this->getMock('Symfony\\Component\\Form\\Extension\\Csrf\\CsrfProvider\\CsrfProviderInterface'); - - $this->formFactory->expects($this->once()) - ->method('createNamed') - ->with('csrf', '_token', null, array('csrf_provider' => $provider)) - ->will($this->returnValue($this->field)); - $this->form->expects($this->once()) - ->method('add') - ->with($this->isInstanceOf('Symfony\\Component\\Form\\Tests\\FormInterface')); - - $listener = new EnsureCsrfFieldListener($this->formFactory, '_token', null, $provider); - $listener->ensureCsrfField($this->event); - } -} diff --git a/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/CsrfTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/CsrfTypeTest.php deleted file mode 100644 index 4483e7f392..0000000000 --- a/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/CsrfTypeTest.php +++ /dev/null @@ -1,112 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Form\Tests\Extension\Csrf\Type; - -class CsrfTypeTest extends TypeTestCase -{ - protected $provider; - - protected function setUp() - { - parent::setUp(); - - $this->provider = $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface'); - } - - protected function tearDown() - { - parent::tearDown(); - - $this->provider = null; - } - - protected function getNonRootForm() - { - $form = $this->getMock('Symfony\Component\Form\Tests\FormInterface'); - $form->expects($this->any()) - ->method('isRoot') - ->will($this->returnValue(false)); - - return $form; - } - - public function testGenerateCsrfToken() - { - $this->provider->expects($this->once()) - ->method('generateCsrfToken') - ->with('%INTENTION%') - ->will($this->returnValue('token')); - - $form = $this->factory->create('csrf', null, array( - 'csrf_provider' => $this->provider, - 'intention' => '%INTENTION%' - )); - - $this->assertEquals('token', $form->getData()); - } - - public function testValidateTokenOnBind() - { - $this->provider->expects($this->once()) - ->method('isCsrfTokenValid') - ->with('%INTENTION%', 'token') - ->will($this->returnValue(true)); - - $form = $this->factory->create('csrf', null, array( - 'csrf_provider' => $this->provider, - 'intention' => '%INTENTION%' - )); - $form->bind('token'); - - $this->assertEquals('token', $form->getData()); - } - - public function testDontValidateTokenIfParentIsNotRoot() - { - $this->provider->expects($this->never()) - ->method('isCsrfTokenValid'); - - $form = $this->factory->create('csrf', null, array( - 'csrf_provider' => $this->provider, - 'intention' => '%INTENTION%' - )); - $form->setParent($this->getNonRootForm()); - $form->bind('token'); - } - - public function testCsrfTokenIsRegeneratedIfValidationFails() - { - $this->provider->expects($this->at(0)) - ->method('generateCsrfToken') - ->with('%INTENTION%') - ->will($this->returnValue('token1')); - $this->provider->expects($this->at(1)) - ->method('isCsrfTokenValid') - ->with('%INTENTION%', 'invalid') - ->will($this->returnValue(false)); - - // The token is regenerated to avoid stalled tokens, for example when - // the session ID changed - $this->provider->expects($this->at(2)) - ->method('generateCsrfToken') - ->with('%INTENTION%') - ->will($this->returnValue('token2')); - - $form = $this->factory->create('csrf', null, array( - 'csrf_provider' => $this->provider, - 'intention' => '%INTENTION%' - )); - $form->bind('invalid'); - - $this->assertEquals('token2', $form->getData()); - } -} diff --git a/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php index 20da376d79..9024002b52 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php @@ -11,43 +11,188 @@ namespace Symfony\Component\Form\Tests\Extension\Csrf\Type; +use Symfony\Component\Form\Extension\Csrf\CsrfExtension; +use Symfony\Component\Form\Tests\Extension\Core\Type\TypeTestCase; + class FormTypeCsrfExtensionTest extends TypeTestCase { - public function testCsrfProtectionByDefault() - { - $form = $this->factory->create('form', null, array( - 'csrf_field_name' => 'csrf', - )); + protected $csrfProvider; - $this->assertTrue($form->has('csrf')); + protected function setUp() + { + $this->csrfProvider = $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface'); + + parent::setUp(); + } + + protected function tearDown() + { + $this->csrfProvider = null; + + parent::tearDown(); + } + + protected function getExtensions() + { + return array_merge(parent::getExtensions(), array( + new CsrfExtension($this->csrfProvider), + )); + } + + public function testCsrfProtectionByDefaultIfRootAndChildren() + { + $view = $this->factory + ->createBuilder('form', null, array( + 'csrf_field_name' => 'csrf', + )) + ->add($this->factory->createNamedBuilder('form', 'child')) + ->getForm() + ->createView(); + + $this->assertTrue($view->hasChild('csrf')); + } + + public function testNoCsrfProtectionByDefaultIfChildrenButNotRoot() + { + $view = $this->factory + ->createNamedBuilder('form', 'root') + ->add($this->factory + ->createNamedBuilder('form', 'form', null, array( + 'csrf_field_name' => 'csrf', + )) + ->add($this->factory->createNamedBuilder('form', 'child')) + ) + ->getForm() + ->get('form') + ->createView(); + + $this->assertFalse($view->hasChild('csrf')); + } + + public function testNoCsrfProtectionByDefaultIfRootButNoChildren() + { + $view = $this->factory + ->createBuilder('form', null, array( + 'csrf_field_name' => 'csrf', + )) + ->getForm() + ->createView(); + + $this->assertFalse($view->hasChild('csrf')); } public function testCsrfProtectionCanBeDisabled() { - $form = $this->factory->create('form', null, array( - 'csrf_protection' => false, - )); - - $this->assertCount(0, $form); - } - - public function testCsrfTokenIsOnlyIncludedInRootView() - { - $view = - $this->factory->createBuilder('form', null, array( + $view = $this->factory + ->createBuilder('form', null, array( 'csrf_field_name' => 'csrf', + 'csrf_protection' => false, )) - ->add('notCsrf', 'text') - ->add( - $this->factory->createNamedBuilder('form', 'child', null, array( - 'csrf_field_name' => 'csrf', - )) - ->add('notCsrf', 'text') - ) + ->add($this->factory->createNamedBuilder('form', 'child')) ->getForm() ->createView(); - $this->assertEquals(array('csrf', 'notCsrf', 'child'), array_keys(iterator_to_array($view))); - $this->assertEquals(array('notCsrf'), array_keys(iterator_to_array($view['child']))); + $this->assertFalse($view->hasChild('csrf')); + } + + public function testGenerateCsrfToken() + { + $this->csrfProvider->expects($this->once()) + ->method('generateCsrfToken') + ->with('%INTENTION%') + ->will($this->returnValue('token')); + + $view = $this->factory + ->createBuilder('form', null, array( + 'csrf_field_name' => 'csrf', + 'csrf_provider' => $this->csrfProvider, + 'intention' => '%INTENTION%' + )) + ->add($this->factory->createNamedBuilder('form', 'child')) + ->getForm() + ->createView(); + + $this->assertEquals('token', $view->getChild('csrf')->get('value')); + } + + public function provideBoolean() + { + return array( + array(true), + array(false), + ); + } + + /** + * @dataProvider provideBoolean + */ + public function testValidateTokenOnBindIfRootAndChildren($valid) + { + $this->csrfProvider->expects($this->once()) + ->method('isCsrfTokenValid') + ->with('%INTENTION%', 'token') + ->will($this->returnValue($valid)); + + $form = $this->factory + ->createBuilder('form', null, array( + 'csrf_field_name' => 'csrf', + 'csrf_provider' => $this->csrfProvider, + 'intention' => '%INTENTION%' + )) + ->add($this->factory->createNamedBuilder('form', 'child')) + ->getForm(); + + $form->bind(array( + 'child' => 'foobar', + 'csrf' => 'token', + )); + + // Remove token from data + $this->assertSame(array('child' => 'foobar'), $form->getData()); + + // Validate accordingly + $this->assertSame($valid, $form->isValid()); + } + + public function testDontValidateTokenIfChildrenButNoRoot() + { + $this->csrfProvider->expects($this->never()) + ->method('isCsrfTokenValid'); + + $form = $this->factory + ->createNamedBuilder('form', 'root') + ->add($this->factory + ->createNamedBuilder('form', 'form', null, array( + 'csrf_field_name' => 'csrf', + 'csrf_provider' => $this->csrfProvider, + 'intention' => '%INTENTION%' + )) + ->add($this->factory->createNamedBuilder('form', 'child')) + ) + ->getForm() + ->get('form'); + + $form->bind(array( + 'child' => 'foobar', + 'csrf' => 'token', + )); + } + + public function testDontValidateTokenIfRootButNoChildren() + { + $this->csrfProvider->expects($this->never()) + ->method('isCsrfTokenValid'); + + $form = $this->factory + ->createBuilder('form', null, array( + 'csrf_field_name' => 'csrf', + 'csrf_provider' => $this->csrfProvider, + 'intention' => '%INTENTION%' + )) + ->getForm(); + + $form->bind(array( + 'csrf' => 'token', + )); } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/TypeTestCase.php b/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/TypeTestCase.php deleted file mode 100644 index 3b1ce91783..0000000000 --- a/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/TypeTestCase.php +++ /dev/null @@ -1,41 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Form\Tests\Extension\Csrf\Type; - -use Symfony\Component\Form\Tests\Extension\Core\Type\TypeTestCase as BaseTestCase; -use Symfony\Component\Form\Extension\Csrf\CsrfExtension; - -abstract class TypeTestCase extends BaseTestCase -{ - protected $csrfProvider; - - protected function setUp() - { - $this->csrfProvider = $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface'); - - parent::setUp(); - } - - protected function tearDown() - { - $this->csrfProvider = null; - - parent::tearDown(); - } - - protected function getExtensions() - { - return array_merge(parent::getExtensions(), array( - new CsrfExtension($this->csrfProvider), - )); - } -} diff --git a/src/Symfony/Component/Form/Tests/FormTest.php b/src/Symfony/Component/Form/Tests/FormTest.php index a5c20dcbcf..bc25e16a1f 100644 --- a/src/Symfony/Component/Form/Tests/FormTest.php +++ b/src/Symfony/Component/Form/Tests/FormTest.php @@ -1247,7 +1247,7 @@ class FormTest extends \PHPUnit_Framework_TestCase public function testCreateViewAcceptsParent() { - $parent = new FormView(); + $parent = new FormView('form'); $form = $this->getBuilder()->getForm(); $view = $form->createView($parent); From bfa7ef2d9ba31837dd9c83919ed8da14f467d7f1 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Fri, 13 Apr 2012 14:40:47 +0200 Subject: [PATCH 2/4] [Form] Removed obsolete exceptions --- .../Form/Exception/DanglingFieldException.php | 21 ------------------- .../Exception/FieldDefinitionException.php | 16 -------------- 2 files changed, 37 deletions(-) delete mode 100644 src/Symfony/Component/Form/Exception/DanglingFieldException.php delete mode 100644 src/Symfony/Component/Form/Exception/FieldDefinitionException.php diff --git a/src/Symfony/Component/Form/Exception/DanglingFieldException.php b/src/Symfony/Component/Form/Exception/DanglingFieldException.php deleted file mode 100644 index 29d10be36b..0000000000 --- a/src/Symfony/Component/Form/Exception/DanglingFieldException.php +++ /dev/null @@ -1,21 +0,0 @@ - - * - * 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 field is expected to be added to a form but is not - * - * @author Bernhard Schussek - */ -class DanglingFieldException extends FormException -{ -} diff --git a/src/Symfony/Component/Form/Exception/FieldDefinitionException.php b/src/Symfony/Component/Form/Exception/FieldDefinitionException.php deleted file mode 100644 index 6cd904ad4a..0000000000 --- a/src/Symfony/Component/Form/Exception/FieldDefinitionException.php +++ /dev/null @@ -1,16 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Form\Exception; - -class FieldDefinitionException extends FormException -{ -} From fcb2227ac961875f686177b6006c8a75cba386cb Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Fri, 13 Apr 2012 16:06:32 +0200 Subject: [PATCH 3/4] [Form] Deprecated FieldType, which has been merged into FormType --- CHANGELOG-2.1.md | 5 +- UPGRADE-2.1.md | 114 +++++- .../views/Form/form_div_layout.html.twig | 135 ++++--- .../views/Form/form_table_layout.html.twig | 45 +-- .../Tests/Extension/child_label.html.twig | 4 +- .../Tests/Extension/parent_label.html.twig | 4 +- .../Twig/Tests/Extension/theme.html.twig | 4 +- .../Tests/Extension/theme_extends.html.twig | 4 +- .../Twig/Tests/Extension/theme_use.html.twig | 4 +- .../FrameworkBundle/Resources/config/form.xml | 5 +- .../Resources/views/Form/date_widget.html.php | 2 +- .../views/Form/datetime_widget.html.php | 2 +- .../views/Form/email_widget.html.php | 2 +- .../views/Form/field_enctype.html.php | 2 +- .../views/Form/field_errors.html.php | 22 +- .../Resources/views/Form/field_label.html.php | 3 +- .../Resources/views/Form/field_rest.html.php | 6 +- .../Resources/views/Form/field_row.html.php | 6 +- .../Resources/views/Form/field_rows.html.php | 5 +- .../views/Form/field_widget.html.php | 6 +- .../views/Form/form_enctype.html.php | 1 + .../Resources/views/Form/form_errors.html.php | 21 + .../Resources/views/Form/form_label.html.php | 1 + .../Resources/views/Form/form_rest.html.php | 5 + .../Resources/views/Form/form_row.html.php | 3 + .../Resources/views/Form/form_rows.html.php | 4 + .../Resources/views/Form/form_widget.html.php | 7 +- .../views/Form/hidden_widget.html.php | 2 +- .../Resources/views/Form/input.html.php | 5 + .../views/Form/integer_widget.html.php | 2 +- .../views/Form/money_widget.html.php | 2 +- .../views/Form/number_widget.html.php | 2 +- .../views/Form/password_widget.html.php | 2 +- .../views/Form/percent_widget.html.php | 2 +- .../views/Form/repeated_row.html.php | 2 +- .../views/Form/search_widget.html.php | 2 +- .../Resources/views/Form/time_widget.html.php | 2 +- .../Resources/views/Form/url_widget.html.php | 2 +- .../views/FormTable/field_row.html.php | 9 - .../views/FormTable/form_errors.html.php | 58 ++- .../views/FormTable/form_row.html.php | 3 + .../views/FormTable/form_widget.html.php | 7 +- .../Templating/Helper/FormHelper.php | 6 +- ...eld_label.html.php => form_label.html.php} | 0 ...eld_label.html.php => form_label.html.php} | 0 .../{field_widget.html.php => input.html.php} | 0 .../Form/DataTransformerInterface.php | 4 +- .../Form/Extension/Core/Type/ChoiceType.php | 10 +- .../Form/Extension/Core/Type/DateTimeType.php | 4 +- .../Form/Extension/Core/Type/DateType.php | 4 +- .../Form/Extension/Core/Type/FieldType.php | 183 +-------- .../Form/Extension/Core/Type/FileType.php | 14 +- .../Form/Extension/Core/Type/FormType.php | 166 +++++++- .../Form/Extension/Core/Type/TimeType.php | 4 +- ...ion.php => FormTypeValidatorExtension.php} | 4 +- .../Validator/ValidatorExtension.php | 2 +- src/Symfony/Component/Form/Form.php | 42 +- src/Symfony/Component/Form/FormBuilder.php | 4 +- src/Symfony/Component/Form/FormInterface.php | 2 +- .../Component/Form/FormTypeGuesserChain.php | 2 +- .../Form/Tests/AbstractDivLayoutTest.php | 9 +- .../Form/Tests/AbstractTableLayoutTest.php | 5 +- .../Extension/Core/Type/ChoiceTypeTest.php | 12 +- .../Core/Type/CollectionTypeTest.php | 22 +- .../Extension/Core/Type/FieldTypeTest.php | 380 ------------------ .../Extension/Core/Type/FormTypeTest.php | 367 ++++++++++++++++- .../Extension/Core/Type/RepeatedTypeTest.php | 14 +- .../DelegatingValidationListenerTest.php | 1 + ...php => FormTypeValidatorExtensionTest.php} | 16 +- .../Component/Form/Tests/FormBuilderTest.php | 2 +- .../Component/Form/Tests/FormFactoryTest.php | 6 +- src/Symfony/Component/Form/Tests/FormTest.php | 33 +- 72 files changed, 1016 insertions(+), 826 deletions(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_enctype.html.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_errors.html.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_rest.html.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_rows.html.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/input.html.php delete mode 100644 src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/field_row.html.php rename src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Child/{field_label.html.php => form_label.html.php} (100%) rename src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Parent/{field_label.html.php => form_label.html.php} (100%) rename src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Parent/{field_widget.html.php => input.html.php} (100%) rename src/Symfony/Component/Form/Extension/Validator/Type/{FieldTypeValidatorExtension.php => FormTypeValidatorExtension.php} (95%) delete mode 100644 src/Symfony/Component/Form/Tests/Extension/Core/Type/FieldTypeTest.php rename src/Symfony/Component/Form/Tests/Extension/Validator/Type/{FieldTypeValidatorExtensionTest.php => FormTypeValidatorExtensionTest.php} (79%) diff --git a/CHANGELOG-2.1.md b/CHANGELOG-2.1.md index ca7edd952a..c9755fdf88 100644 --- a/CHANGELOG-2.1.md +++ b/CHANGELOG-2.1.md @@ -267,9 +267,12 @@ To get the diff between two versions, go to https://github.com/symfony/symfony/c in their name anymore. Their names terminate with "[]" now. * [BC BREAK] FormType::getDefaultOptions() and FormType::getAllowedOptionValues() don't receive an options array anymore. - * Deprecated FormValidatorInterface and substituted its implementations + * deprecated FormValidatorInterface and substituted its implementations by event subscribers * simplified CSRF protection and removed the csrf type + * deprecated FieldType and merged it into FormType + * [BC BREAK] renamed "field_*" theme blocks to "form_*" and "field_widget" to + "input" ### HttpFoundation diff --git a/UPGRADE-2.1.md b/UPGRADE-2.1.md index d410262a30..a7d577d950 100644 --- a/UPGRADE-2.1.md +++ b/UPGRADE-2.1.md @@ -94,7 +94,7 @@ ``` * The custom factories for the firewall configuration are now registered during the build method of bundles instead of being registered - by the end-user. This means that you will you need to remove the 'factories' + by the end-user. This means that you will you need to remove the 'factories' keys in your security configuration. * The Firewall listener is now registered after the Router listener. This @@ -372,29 +372,29 @@ return isset($options['widget']) && 'single_text' === $options['widget'] ? 'text' : 'choice'; } ``` - + * The methods `getDefaultOptions()` and `getAllowedOptionValues()` of form types no longer receive an option array. - + You can specify options that depend on other options using closures instead. - + Before: - + ``` public function getDefaultOptions(array $options) { $defaultOptions = array(); - + if ($options['multiple']) { $defaultOptions['empty_data'] = array(); } - + return $defaultOptions; } ``` - + After: - + ``` public function getDefaultOptions() { @@ -405,7 +405,7 @@ ); } ``` - + The second argument `$previousValue` does not have to be specified if not needed. @@ -425,6 +425,100 @@ (or any other of the BIND events). In case you used the CallbackValidator class, you should now pass the callback directly to `addEventListener`. + * FieldType was merged into FormType. FieldType itself was deprecated and + will be removed in Symfony 2.3. + + ##### Update your field types + + You are advised to update your custom types that extend FieldType to extend + FormType instead. + + Before: + + ``` + public function getParent(array $options) + { + return 'field'; + } + ``` + + After: + + ``` + public function getParent(array $options) + { + return 'form'; + } + ``` + + You can also remove the getParent() method as this is the default + implementation in AbstractType. + + ##### Update your form themes + + Previously, FieldType was the super type for all other types. Now, since + it is deprecated, FieldType is a subtype of FormType, which is now the base + type for all other types. + + A consequence of this change is that any "form_*" blocks defined in your + themes are now also used for other types that extend "field", unless you + explicitely override the "field_*" or "mytype_*" blocks. + + "field_*" blocks are deprecated and will be unsupported as of Symfony 2.3. + You should merge them into the corresponding "form_*" blocks instead. + + Before: + + ``` + {% block field_label %} + {% spaceless %} + {% set attr = attr|merge({'for': id}) %} + {{ block('generic_label') }} + {% endspaceless %} + {% endblock field_label %} + + {% block form_label %} + {% spaceless %} + {{ block('generic_label') }} + {% endspaceless %} + {% endblock form_label %} + ``` + + After: + + ``` + {% block form_label %} + {% spaceless %} + {% if form.children|length == 0 %} + {% set attr = attr|merge({'for': id}) %} + {% endif %} + {{ block('generic_label') }} + {% endspaceless %} + {% endblock form_label %} + ``` + + The block "field_widget" was renamed to "input" and is still supported. + + Before: + + ``` + {% block field_widget %} + {% spaceless %} + + {% endspaceless %} + {% endblock field_widget %} + ``` + + After: + + ``` + {% block input %} + {% spaceless %} + + {% endspaceless %} + {% endblock input %} + ``` + ### Session * Flash messages now return an array based on their type. The old method is diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig index cc3f3fd01d..46420a7122 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig @@ -2,10 +2,14 @@ {% block form_widget %} {% spaceless %} -
- {{ block('field_rows') }} - {{ form_rest(form) }} -
+ {% if form.children|length > 0 %} +
+ {{ block('form_rows') }} + {{ form_rest(form) }} +
+ {% else %} + {{ block('input') }} + {% endif %} {% endspaceless %} {% endblock form_widget %} @@ -83,7 +87,7 @@ {% block datetime_widget %} {% spaceless %} {% if widget == 'single_text' %} - {{ block('field_widget') }} + {{ block('input') }} {% else %}
{{ form_errors(form.date) }} @@ -98,7 +102,7 @@ {% block date_widget %} {% spaceless %} {% if widget == 'single_text' %} - {{ block('field_widget') }} + {{ block('input') }} {% else %}
{{ date_pattern|replace({ @@ -114,7 +118,7 @@ {% block time_widget %} {% spaceless %} {% if widget == 'single_text' %} - {{ block('field_widget') }} + {{ block('input') }} {% else %}
{{ form_widget(form.hour, { 'attr': { 'size': '1' } }) }}:{{ form_widget(form.minute, { 'attr': { 'size': '1' } }) }}{% if with_seconds %}:{{ form_widget(form.second, { 'attr': { 'size': '1' } }) }}{% endif %} @@ -127,67 +131,60 @@ {% spaceless %} {# type="number" doesn't work with floats #} {% set type = type|default('text') %} - {{ block('field_widget') }} + {{ block('input') }} {% endspaceless %} {% endblock number_widget %} {% block integer_widget %} {% spaceless %} {% set type = type|default('number') %} - {{ block('field_widget') }} + {{ block('input') }} {% endspaceless %} {% endblock integer_widget %} {% block money_widget %} {% spaceless %} - {{ money_pattern|replace({ '{{ widget }}': block('field_widget') })|raw }} + {{ money_pattern|replace({ '{{ widget }}': block('input') })|raw }} {% endspaceless %} {% endblock money_widget %} {% block url_widget %} {% spaceless %} {% set type = type|default('url') %} - {{ block('field_widget') }} + {{ block('input') }} {% endspaceless %} {% endblock url_widget %} {% block search_widget %} {% spaceless %} {% set type = type|default('search') %} - {{ block('field_widget') }} + {{ block('input') }} {% endspaceless %} {% endblock search_widget %} {% block percent_widget %} {% spaceless %} {% set type = type|default('text') %} - {{ block('field_widget') }} % + {{ block('input') }} % {% endspaceless %} {% endblock percent_widget %} -{% block field_widget %} -{% spaceless %} - {% set type = type|default('text') %} - -{% endspaceless %} -{% endblock field_widget %} - {% block password_widget %} {% spaceless %} {% set type = type|default('password') %} - {{ block('field_widget') }} + {{ block('input') }} {% endspaceless %} {% endblock password_widget %} {% block hidden_widget %} {% set type = type|default('hidden') %} - {{ block('field_widget') }} + {{ block('input') }} {% endblock hidden_widget %} {% block email_widget %} {% spaceless %} {% set type = type|default('email') %} - {{ block('field_widget') }} + {{ block('input') }} {% endspaceless %} {% endblock email_widget %} @@ -202,15 +199,11 @@ {% endspaceless %} {% endblock %} -{% block field_label %} -{% spaceless %} - {% set attr = attr|merge({'for': id}) %} - {{ block('generic_label') }} -{% endspaceless %} -{% endblock field_label %} - {% block form_label %} {% spaceless %} + {% if form.children|length == 0 %} + {% set attr = attr|merge({'for': id}) %} + {% endif %} {{ block('generic_label') }} {% endspaceless %} {% endblock form_label %} @@ -219,24 +212,17 @@ {% block repeated_row %} {% spaceless %} - {{ block('field_rows') }} + {{ block('form_rows') }} {% endspaceless %} {% endblock repeated_row %} -{% block field_row %} -{% spaceless %} -
- {{ form_label(form, label|default(null)) }} - {{ form_errors(form) }} - {{ form_widget(form) }} -
-{% endspaceless %} -{% endblock field_row %} - {% block form_row %} {% spaceless %}
{{ form_label(form, label|default(null)) }} + {% if form.children|length == 0 %} + {{ form_errors(form) }} + {% endif %} {{ form_widget(form) }}
{% endspaceless %} @@ -248,13 +234,13 @@ {# Misc #} -{% block field_enctype %} +{% block form_enctype %} {% spaceless %} {% if multipart %}enctype="multipart/form-data"{% endif %} {% endspaceless %} -{% endblock field_enctype %} +{% endblock form_enctype %} -{% block field_errors %} +{% block form_errors %} {% spaceless %} {% if errors|length > 0 %}
    @@ -270,9 +256,9 @@
{% endif %} {% endspaceless %} -{% endblock field_errors %} +{% endblock form_errors %} -{% block field_rest %} +{% block form_rest %} {% spaceless %} {% for child in form %} {% if not child.rendered %} @@ -280,18 +266,25 @@ {% endif %} {% endfor %} {% endspaceless %} -{% endblock field_rest %} +{% endblock form_rest %} {# Support #} -{% block field_rows %} +{% block form_rows %} {% spaceless %} {{ form_errors(form) }} {% for child in form %} {{ form_row(child) }} {% endfor %} {% endspaceless %} -{% endblock field_rows %} +{% endblock form_rows %} + +{% block input %} +{% spaceless %} + {% set type = type|default('text') %} + +{% endspaceless %} +{% endblock input %} {% block widget_attributes %} {% spaceless %} @@ -306,3 +299,47 @@ {% for attrname,attrvalue in attr %}{{attrname}}="{{attrvalue}}" {% endfor %} {% endspaceless %} {% endblock widget_container_attributes %} + +{# Deprecated in Symfony 2.1, to be removed in 2.3 #} + +{% block field_widget %} +{% spaceless %} + {{ block('input') }} +{% endspaceless %} +{% endblock field_widget %} + +{% block field_label %} +{% spaceless %} + {{ block('form_label') }} +{% endspaceless %} +{% endblock field_label %} + +{% block field_row %} +{% spaceless %} + {{ block('form_row') }} +{% endspaceless %} +{% endblock field_row %} + +{% block field_enctype %} +{% spaceless %} + {{ block('form_enctype') }} +{% endspaceless %} +{% endblock field_enctype %} + +{% block field_errors %} +{% spaceless %} + {{ block('form_errors') }} +{% endspaceless %} +{% endblock field_errors %} + +{% block field_rest %} +{% spaceless %} + {{ block('form_rest') }} +{% endspaceless %} +{% endblock field_rest %} + +{% block field_rows %} +{% spaceless %} + {{ block('form_rows') }} +{% endspaceless %} +{% endblock field_rows %} \ No newline at end of file diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig index 1046c3de36..b053c59e6f 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig @@ -1,19 +1,5 @@ {% use "form_div_layout.html.twig" %} -{% block field_row %} -{% spaceless %} - - - {{ form_label(form, label|default(null)) }} - - - {{ form_errors(form) }} - {{ form_widget(form) }} - - -{% endspaceless %} -{% endblock field_row %} - {% block form_row %} {% spaceless %} @@ -21,6 +7,9 @@ {{ form_label(form, label|default(null)) }} + {% if form.children|length == 0 %} + {{ form_errors(form) }} + {% endif %} {{ form_widget(form) }} @@ -29,12 +18,16 @@ {% block form_errors %} {% spaceless %} - {% if errors|length > 0 %} - - - {{ block('field_errors') }} - - + {% if form.children|length > 0 %} + {% if errors|length > 0 %} + + + {{ parent() }} + + + {% endif %} + {% else %} + {{ parent() }} {% endif %} {% endspaceless %} {% endblock form_errors %} @@ -51,9 +44,13 @@ {% block form_widget %} {% spaceless %} - - {{ block('field_rows') }} - {{ form_rest(form) }} -
+ {% if form.children|length > 0 %} + + {{ block('form_rows') }} + {{ form_rest(form) }} +
+ {% else %} + {{ parent() }} + {% endif %} {% endspaceless %} {% endblock form_widget %} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/child_label.html.twig b/src/Symfony/Bridge/Twig/Tests/Extension/child_label.html.twig index 16c137aaca..061ef428c2 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/child_label.html.twig +++ b/src/Symfony/Bridge/Twig/Tests/Extension/child_label.html.twig @@ -1,3 +1,3 @@ -{% block field_label %} +{% block form_label %} -{% endblock field_label %} +{% endblock form_label %} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/parent_label.html.twig b/src/Symfony/Bridge/Twig/Tests/Extension/parent_label.html.twig index fc59d708be..e96278b8f6 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/parent_label.html.twig +++ b/src/Symfony/Bridge/Twig/Tests/Extension/parent_label.html.twig @@ -1,3 +1,3 @@ -{% block field_label %} +{% block form_label %} -{% endblock field_label %} +{% endblock form_label %} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/theme.html.twig b/src/Symfony/Bridge/Twig/Tests/Extension/theme.html.twig index d5016842ea..ee5b19e073 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/theme.html.twig +++ b/src/Symfony/Bridge/Twig/Tests/Extension/theme.html.twig @@ -1,6 +1,6 @@ -{% block field_widget %} +{% block input %} {% spaceless %} {% set type = type|default('text') %} {% endspaceless %} -{% endblock field_widget %} +{% endblock input %} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/theme_extends.html.twig b/src/Symfony/Bridge/Twig/Tests/Extension/theme_extends.html.twig index 96bfea20dc..f58e589498 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/theme_extends.html.twig +++ b/src/Symfony/Bridge/Twig/Tests/Extension/theme_extends.html.twig @@ -1,8 +1,8 @@ {% extends 'form_div_layout.html.twig' %} -{% block field_widget %} +{% block input %} {% spaceless %} {% set type = type|default('text') %} {% endspaceless %} -{% endblock field_widget %} +{% endblock input %} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/theme_use.html.twig b/src/Symfony/Bridge/Twig/Tests/Extension/theme_use.html.twig index 5aee4708a2..9304e9dcfa 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/theme_use.html.twig +++ b/src/Symfony/Bridge/Twig/Tests/Extension/theme_use.html.twig @@ -1,8 +1,8 @@ {% use 'form_div_layout.html.twig' %} -{% block field_widget %} +{% block input %} {% spaceless %} {% set type = type|default('text') %} {% endspaceless %} -{% endblock field_widget %} +{% endblock input %} diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml index 1422e792b8..bddb2a96e8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml @@ -52,7 +52,6 @@ - @@ -133,8 +132,8 @@ - - + + 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 4a4fa106e3..bd2c2769af 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,5 +1,5 @@ - renderBlock('field_widget'); ?> + renderBlock('input'); ?>
renderBlock('container_attributes') ?>> - renderBlock('field_widget'); ?> + renderBlock('input'); ?>
renderBlock('container_attributes') ?>> widget($form['date']).' '.$view['form']->widget($form['time']) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/email_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/email_widget.html.php index c30cfb35c3..a00dda278e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/email_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/email_widget.html.php @@ -1 +1 @@ -renderBlock('field_widget', array('type' => isset($type) ? $type : 'email')) ?> +renderBlock('input', array('type' => isset($type) ? $type : 'email')) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_enctype.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_enctype.html.php index 424d425969..aa4ff39bba 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_enctype.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_enctype.html.php @@ -1 +1 @@ -get('multipart')): ?>enctype="multipart/form-data" +renderBlock('form_enctype') ?> \ No newline at end of file diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_errors.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_errors.html.php index 339e3d0009..d7b87ea2bc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_errors.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_errors.html.php @@ -1,21 +1 @@ - -
    - -
  • getMessagePluralization()) { - echo $view['translator']->trans( - $error->getMessageTemplate(), - $error->getMessageParameters(), - 'validators' - ); - } else { - echo $view['translator']->transChoice( - $error->getMessageTemplate(), - $error->getMessagePluralization(), - $error->getMessageParameters(), - 'validators' - ); - }?>
  • - -
- +renderBlock('form_errors') ?> \ No newline at end of file diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_label.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_label.html.php index 1434301d4c..7c0c1b7559 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_label.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_label.html.php @@ -1,2 +1 @@ - - +renderBlock('form_label') ?> \ No newline at end of file diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_rest.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_rest.html.php index 89041c6ec6..a570b1045a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_rest.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_rest.html.php @@ -1,5 +1 @@ - - isRendered()): ?> - row($child) ?> - - +renderBlock('form_rest') ?> \ No newline at end of file diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_row.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_row.html.php index 091807020d..c91dcb5543 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_row.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_row.html.php @@ -1,5 +1 @@ -
- label($form, isset($label) ? $label : null) ?> - errors($form) ?> - widget($form) ?> -
+renderBlock('form_row') ?> \ No newline at end of file diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_rows.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_rows.html.php index a5f1dfbf5f..ef0bf384a6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_rows.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_rows.html.php @@ -1,4 +1 @@ -errors($form) ?> - - row($child) ?> - +renderBlock('form_rows') ?> \ No newline at end of file diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_widget.html.php index 0c86483386..f1ca2edadb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_widget.html.php @@ -1,5 +1 @@ -" - value="escape($value) ?>" - renderBlock('attributes') ?> -/> +renderBlock('input') ?> \ No newline at end of file diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_enctype.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_enctype.html.php new file mode 100644 index 0000000000..424d425969 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_enctype.html.php @@ -0,0 +1 @@ +get('multipart')): ?>enctype="multipart/form-data" diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_errors.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_errors.html.php new file mode 100644 index 0000000000..339e3d0009 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_errors.html.php @@ -0,0 +1,21 @@ + +
    + +
  • getMessagePluralization()) { + echo $view['translator']->trans( + $error->getMessageTemplate(), + $error->getMessageParameters(), + 'validators' + ); + } else { + echo $view['translator']->transChoice( + $error->getMessageTemplate(), + $error->getMessagePluralization(), + $error->getMessageParameters(), + 'validators' + ); + }?>
  • + +
+ diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_label.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_label.html.php index e5c5843c46..3ddc300fd4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_label.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_label.html.php @@ -1,2 +1,3 @@ +hasChildren()) { $attr['for'] = $id; } ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_rest.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_rest.html.php new file mode 100644 index 0000000000..89041c6ec6 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_rest.html.php @@ -0,0 +1,5 @@ + + isRendered()): ?> + row($child) ?> + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_row.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_row.html.php index 0a79a0cc53..02fb9ae9b6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_row.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_row.html.php @@ -1,4 +1,7 @@
label($form, isset($label) ? $label : null) ?> + hasChildren()): ?> + errors($form) ?> + widget($form) ?>
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_rows.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_rows.html.php new file mode 100644 index 0000000000..a5f1dfbf5f --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_rows.html.php @@ -0,0 +1,4 @@ +errors($form) ?> + + row($child) ?> + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_widget.html.php index 77fa483c3f..1c9368693b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_widget.html.php @@ -1,5 +1,8 @@ +hasChildren()): ?>
renderBlock('container_attributes') ?>> - renderBlock('field_rows') ?> + renderBlock('form_rows') ?> rest($form) ?>
- + +renderBlock('input')?> + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/hidden_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/hidden_widget.html.php index 11942cfe2b..50a42451ae 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/hidden_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/hidden_widget.html.php @@ -1 +1 @@ -renderBlock('field_widget', array('type' => isset($type) ? $type : "hidden")) ?> +renderBlock('input', array('type' => isset($type) ? $type : "hidden")) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/input.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/input.html.php new file mode 100644 index 0000000000..0c86483386 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/input.html.php @@ -0,0 +1,5 @@ +" + value="escape($value) ?>" + renderBlock('attributes') ?> +/> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/integer_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/integer_widget.html.php index 012211ab5a..1fc6ace34b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/integer_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/integer_widget.html.php @@ -1 +1 @@ -renderBlock('field_widget', array('type' => isset($type) ? $type : "number")) ?> +renderBlock('input', array('type' => isset($type) ? $type : "number")) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/money_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/money_widget.html.php index 3151ecbd84..a68ad5ddac 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/money_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/money_widget.html.php @@ -1 +1 @@ -renderBlock('field_widget'), $money_pattern) ?> +renderBlock('input'), $money_pattern) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/number_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/number_widget.html.php index 9a08222c91..7e1a2776a7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/number_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/number_widget.html.php @@ -1 +1 @@ -renderBlock('field_widget', array('type' => isset($type) ? $type : "text")) ?> +renderBlock('input', array('type' => isset($type) ? $type : "text")) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/password_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/password_widget.html.php index 78319fcbae..7aff242ef4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/password_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/password_widget.html.php @@ -1 +1 @@ -renderBlock('field_widget', array('type' => isset($type) ? $type : "password")) ?> +renderBlock('input', array('type' => isset($type) ? $type : "password")) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/percent_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/percent_widget.html.php index 4245b52a0c..328321f21f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/percent_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/percent_widget.html.php @@ -1 +1 @@ -renderBlock('field_widget', array('type' => isset($type) ? $type : "text")) ?> % +renderBlock('input', array('type' => isset($type) ? $type : "text")) ?> % diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/repeated_row.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/repeated_row.html.php index a84bad54df..b9a07bc466 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/repeated_row.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/repeated_row.html.php @@ -1 +1 @@ -renderBlock('field_rows') ?> +renderBlock('form_rows') ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/search_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/search_widget.html.php index bbfd593dbc..d8a773544e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/search_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/search_widget.html.php @@ -1 +1 @@ -renderBlock('field_widget', array('type' => isset($type) ? $type : "search")) ?> +renderBlock('input', array('type' => isset($type) ? $type : "search")) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/time_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/time_widget.html.php index 599750d20a..2178974c74 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/time_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/time_widget.html.php @@ -1,5 +1,5 @@ - renderBlock('field_widget'); ?> + renderBlock('input'); ?>
renderBlock('container_attributes') ?>> renderBlock('field_widget', array('type' => isset($type) ? $type : "url")) ?> +renderBlock('input', array('type' => isset($type) ? $type : "url")) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/field_row.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/field_row.html.php deleted file mode 100644 index b9e5c5639c..0000000000 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/field_row.html.php +++ /dev/null @@ -1,9 +0,0 @@ - - - label($form, isset($label) ? $label : null) ?> - - - errors($form) ?> - widget($form) ?> - - diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_errors.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_errors.html.php index ac4315f957..05d8c4aea8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_errors.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_errors.html.php @@ -1,7 +1,51 @@ - - - - renderBlock('field_errors'); ?> - - - +hasChildren()): ?> + 0): ?> + + + +
    + +
  • getMessagePluralization()) { + echo $view['translator']->trans( + $error->getMessageTemplate(), + $error->getMessageParameters(), + 'validators' + ); + } else { + echo $view['translator']->transChoice( + $error->getMessageTemplate(), + $error->getMessagePluralization(), + $error->getMessageParameters(), + 'validators' + ); + }?>
  • + +
+ + + + + + +
    + +
  • getMessagePluralization()) { + echo $view['translator']->trans( + $error->getMessageTemplate(), + $error->getMessageParameters(), + 'validators' + ); + } else { + echo $view['translator']->transChoice( + $error->getMessageTemplate(), + $error->getMessagePluralization(), + $error->getMessageParameters(), + 'validators' + ); + }?>
  • + +
+ + \ No newline at end of file diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_row.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_row.html.php index d1bbbb1b2a..9262a1bae1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_row.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_row.html.php @@ -3,6 +3,9 @@ label($form, isset($label) ? $label : null) ?> + hasChildren()): ?> + errors($form) ?> + widget($form) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_widget.html.php index d802ccf050..171f1eb40c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_widget.html.php @@ -1,5 +1,8 @@ +hasChildren()): ?> renderBlock('container_attributes') ?>> - renderBlock('field_rows') ?> + renderBlock('form_rows') ?> rest($form) ?>
- + +renderBlock('input')?> + diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php index 2004b4e67e..8fd4adef89 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php @@ -119,7 +119,7 @@ class FormHelper extends Helper */ public function widget(FormView $view, array $variables = array()) { - return trim($this->renderSection($view, 'widget', $variables)); + return $this->renderSection($view, 'widget', $variables); } /** @@ -276,7 +276,7 @@ class FormHelper extends Helper $view->setRendered(); } - return $html; + return trim($html); } } while (--$typeIndex >= 0); @@ -311,7 +311,7 @@ class FormHelper extends Helper $variables = array_replace_recursive($context['variables'], $variables); - return $this->engine->render($template, $variables); + return trim($this->engine->render($template, $variables)); } public function getName() diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Child/field_label.html.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Child/form_label.html.php similarity index 100% rename from src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Child/field_label.html.php rename to src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Child/form_label.html.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Parent/field_label.html.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Parent/form_label.html.php similarity index 100% rename from src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Parent/field_label.html.php rename to src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Parent/form_label.html.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Parent/field_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Parent/input.html.php similarity index 100% rename from src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Parent/field_widget.html.php rename to src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Parent/input.html.php diff --git a/src/Symfony/Component/Form/DataTransformerInterface.php b/src/Symfony/Component/Form/DataTransformerInterface.php index add2ce3d9b..2f1543e91b 100644 --- a/src/Symfony/Component/Form/DataTransformerInterface.php +++ b/src/Symfony/Component/Form/DataTransformerInterface.php @@ -24,7 +24,7 @@ interface DataTransformerInterface * This method is called on two occasions inside a form field: * * 1. When the form field is initialized with the data attached from the datasource (object or array). - * 2. When data from a request is bound using {@link Field::bind()} to transform the new input data + * 2. When data from a request is bound using {@link Form::bind()} to transform the new input data * back into the renderable format. For example if you have a date field and bind '2009-10-10' onto * it you might accept this value because its easily parsed, but the transformer still writes back * "2009/10/10" onto the form field (for further displaying or other purposes). @@ -52,7 +52,7 @@ interface DataTransformerInterface * Transforms a value from the transformed representation to its original * representation. * - * This method is called when {@link Field::bind()} is called to transform the requests tainted data + * This method is called when {@link Form::bind()} is called to transform the requests tainted data * into an acceptable format for your data processing/model layer. * * This method must be able to deal with empty values. Usually this will diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php index 1ed495248b..1a8ba2d1be 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php @@ -44,8 +44,8 @@ class ChoiceType extends AbstractType } if ($options['expanded']) { - $this->addSubFields($builder, $options['choice_list']->getPreferredViews(), $options); - $this->addSubFields($builder, $options['choice_list']->getRemainingViews(), $options); + $this->addSubForms($builder, $options['choice_list']->getPreferredViews(), $options); + $this->addSubForms($builder, $options['choice_list']->getRemainingViews(), $options); } // empty value @@ -182,7 +182,7 @@ class ChoiceType extends AbstractType */ public function getParent(array $options) { - return isset($options['expanded']) && $options['expanded'] ? 'form' : 'field'; + return 'field'; } /** @@ -200,12 +200,12 @@ class ChoiceType extends AbstractType * @param array $choiceViews The choice view objects. * @param array $options The build options. */ - private function addSubFields(FormBuilder $builder, array $choiceViews, array $options) + private function addSubForms(FormBuilder $builder, array $choiceViews, array $options) { foreach ($choiceViews as $i => $choiceView) { if (is_array($choiceView)) { // Flatten groups - $this->addSubFields($builder, $choiceView, $options); + $this->addSubForms($builder, $choiceView, $options); } else { $choiceOpts = array( 'value' => $choiceView->getValue(), diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php index 6eba72d4f6..f428054ede 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php @@ -153,7 +153,7 @@ class DateTimeType extends AbstractType 'widget' => null, // This will overwrite "empty_value" child options 'empty_value' => null, - // If initialized with a \DateTime object, FieldType initializes + // If initialized with a \DateTime object, FormType initializes // this option to "\DateTime". Since the internal, normalized // representation is not \DateTime, but an array, we need to unset // this option. @@ -200,7 +200,7 @@ class DateTimeType extends AbstractType */ public function getParent(array $options) { - return isset($options['widget']) && 'single_text' === $options['widget'] ? 'field' : 'form'; + return 'field'; } /** diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php index e58d0d9340..c506714c7f 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php @@ -177,7 +177,7 @@ class DateType extends AbstractType // them like immutable value objects 'by_reference' => false, 'error_bubbling' => false, - // If initialized with a \DateTime object, FieldType initializes + // If initialized with a \DateTime object, FormType initializes // this option to "\DateTime". Since the internal, normalized // representation is not \DateTime, but an array, we need to unset // this option. @@ -210,7 +210,7 @@ class DateType extends AbstractType */ public function getParent(array $options) { - return isset($options['widget']) && 'single_text' === $options['widget'] ? 'field' : 'form'; + return 'field'; } /** diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FieldType.php b/src/Symfony/Component/Form/Extension/Core/Type/FieldType.php index b676d81984..c509abe6a5 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FieldType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FieldType.php @@ -23,179 +23,15 @@ use Symfony\Component\Form\Extension\Core\EventListener\ValidationListener; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\Form\Exception\FormException; +/** + * Deprecated. You should extend FormType instead. + * + * @author Bernhard Schussek + * + * @deprecated Deprecated since version 2.1, to be removed in 2.3. + */ class FieldType extends AbstractType { - /** - * {@inheritdoc} - */ - public function buildForm(FormBuilder $builder, array $options) - { - if (null === $options['property_path']) { - $options['property_path'] = $builder->getName(); - } - - if (false === $options['property_path'] || '' === $options['property_path']) { - $options['property_path'] = null; - } else { - $options['property_path'] = new PropertyPath($options['property_path']); - } - if (!is_array($options['attr'])) { - throw new FormException('The "attr" option must be "array".'); - } - - $builder - ->setRequired($options['required']) - ->setDisabled($options['disabled']) - ->setErrorBubbling($options['error_bubbling']) - ->setEmptyData($options['empty_data']) - ->setAttribute('read_only', $options['read_only']) - ->setAttribute('by_reference', $options['by_reference']) - ->setAttribute('property_path', $options['property_path']) - ->setAttribute('error_mapping', $options['error_mapping']) - ->setAttribute('max_length', $options['max_length']) - ->setAttribute('pattern', $options['pattern']) - ->setAttribute('label', $options['label'] ?: $this->humanize($builder->getName())) - ->setAttribute('attr', $options['attr'] ?: array()) - ->setAttribute('invalid_message', $options['invalid_message']) - ->setAttribute('invalid_message_parameters', $options['invalid_message_parameters']) - ->setAttribute('translation_domain', $options['translation_domain']) - ->setData($options['data']) - ->addEventSubscriber(new ValidationListener()) - ; - - if ($options['trim']) { - $builder->addEventSubscriber(new TrimListener()); - } - } - - /** - * {@inheritdoc} - */ - public function buildView(FormView $view, FormInterface $form) - { - $name = $form->getName(); - $readOnly = $form->getAttribute('read_only'); - - if ($view->hasParent()) { - if ('' === $name) { - throw new FormException('Form node with empty name can be used only as root form node.'); - } - - if ('' !== ($parentFullName = $view->getParent()->get('full_name'))) { - $id = sprintf('%s_%s', $view->getParent()->get('id'), $name); - $fullName = sprintf('%s[%s]', $parentFullName, $name); - } else { - $id = $name; - $fullName = $name; - } - - // Complex fields are read-only if themselves or their parent is. - $readOnly = $readOnly || $view->getParent()->get('read_only'); - } else { - $id = $name; - $fullName = $name; - - // Strip leading underscores and digits. These are allowed in - // form names, but not in HTML4 ID attributes. - // http://www.w3.org/TR/html401/struct/global.html#adef-id - $id = ltrim($id, '_0123456789'); - } - - $types = array(); - foreach ($form->getTypes() as $type) { - $types[] = $type->getName(); - } - - $view - ->set('form', $view) - ->set('id', $id) - ->set('name', $name) - ->set('full_name', $fullName) - ->set('read_only', $readOnly) - ->set('errors', $form->getErrors()) - ->set('value', $form->getClientData()) - ->set('disabled', $form->isDisabled()) - ->set('required', $form->isRequired()) - ->set('max_length', $form->getAttribute('max_length')) - ->set('pattern', $form->getAttribute('pattern')) - ->set('size', null) - ->set('label', $form->getAttribute('label')) - ->set('multipart', false) - ->set('attr', $form->getAttribute('attr')) - ->set('types', $types) - ->set('translation_domain', $form->getAttribute('translation_domain')) - ; - } - - /** - * {@inheritdoc} - */ - public function getDefaultOptions() - { - // Derive "data_class" option from passed "data" object - $dataClass = function (Options $options) { - if (is_object($options['data'])) { - return get_class($options['data']); - } - - return null; - }; - - // Derive "empty_data" closure from "data_class" option - $emptyData = function (Options $options) { - $class = $options['data_class']; - - if (null !== $class) { - return function (FormInterface $form) use ($class) { - if ($form->isEmpty() && !$form->isRequired()) { - return null; - } - - return new $class(); - }; - } - - return ''; - }; - - return array( - 'data' => null, - 'data_class' => $dataClass, - 'empty_data' => $emptyData, - 'trim' => true, - 'required' => true, - 'read_only' => false, - 'disabled' => false, - 'max_length' => null, - 'pattern' => null, - 'property_path' => null, - 'by_reference' => true, - 'error_bubbling' => false, - 'error_mapping' => array(), - 'label' => null, - 'attr' => array(), - 'invalid_message' => 'This value is not valid', - 'invalid_message_parameters' => array(), - 'translation_domain' => 'messages', - ); - } - - /** - * {@inheritdoc} - */ - public function createBuilder($name, FormFactoryInterface $factory, array $options) - { - return new FormBuilder($name, $factory, new EventDispatcher(), $options['data_class']); - } - - /** - * {@inheritdoc} - */ - public function getParent(array $options) - { - return null; - } - /** * {@inheritdoc} */ @@ -203,9 +39,4 @@ class FieldType extends AbstractType { return 'field'; } - - private function humanize($text) - { - return ucfirst(trim(strtolower(preg_replace('/[_\s]+/', ' ', $text)))); - } } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php index 30f37203b2..468c65a0f3 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php @@ -23,12 +23,24 @@ class FileType extends AbstractType public function buildView(FormView $view, FormInterface $form) { $view - ->set('multipart', true) ->set('type', 'file') ->set('value', '') ; } + /** + * {@inheritdoc} + */ + public function buildViewBottomUp(FormView $view, FormInterface $form) + { + $view + ->set('multipart', true) + ; + } + + /** + * {@inheritdoc} + */ public function getParent(array $options) { return 'field'; diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php index 36e0c0c63b..7288cd177c 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php @@ -13,10 +13,16 @@ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Options; +use Symfony\Component\Form\Util\PropertyPath; use Symfony\Component\Form\FormBuilder; use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\Form\FormView; +use Symfony\Component\Form\Extension\Core\EventListener\TrimListener; +use Symfony\Component\Form\Extension\Core\EventListener\ValidationListener; use Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\Form\Exception\FormException; class FormType extends AbstractType { @@ -25,9 +31,102 @@ class FormType extends AbstractType */ public function buildForm(FormBuilder $builder, array $options) { + if (null === $options['property_path']) { + $options['property_path'] = $builder->getName(); + } + + if (false === $options['property_path'] || '' === $options['property_path']) { + $options['property_path'] = null; + } else { + $options['property_path'] = new PropertyPath($options['property_path']); + } + if (!is_array($options['attr'])) { + throw new FormException('The "attr" option must be "array".'); + } + $builder + ->setRequired($options['required']) + ->setDisabled($options['disabled']) + ->setErrorBubbling($options['error_bubbling']) + ->setEmptyData($options['empty_data']) + ->setAttribute('read_only', $options['read_only']) + ->setAttribute('by_reference', $options['by_reference']) + ->setAttribute('property_path', $options['property_path']) + ->setAttribute('error_mapping', $options['error_mapping']) + ->setAttribute('max_length', $options['max_length']) + ->setAttribute('pattern', $options['pattern']) + ->setAttribute('label', $options['label'] ?: $this->humanize($builder->getName())) + ->setAttribute('attr', $options['attr'] ?: array()) + ->setAttribute('invalid_message', $options['invalid_message']) + ->setAttribute('invalid_message_parameters', $options['invalid_message_parameters']) + ->setAttribute('translation_domain', $options['translation_domain']) ->setAttribute('virtual', $options['virtual']) + ->setData($options['data']) ->setDataMapper(new PropertyPathMapper($options['data_class'])) + ->addEventSubscriber(new ValidationListener()) + ; + + if ($options['trim']) { + $builder->addEventSubscriber(new TrimListener()); + } + } + + /** + * {@inheritdoc} + */ + public function buildView(FormView $view, FormInterface $form) + { + $name = $form->getName(); + $readOnly = $form->getAttribute('read_only'); + + if ($view->hasParent()) { + if ('' === $name) { + throw new FormException('Form node with empty name can be used only as root form node.'); + } + + if ('' !== ($parentFullName = $view->getParent()->get('full_name'))) { + $id = sprintf('%s_%s', $view->getParent()->get('id'), $name); + $fullName = sprintf('%s[%s]', $parentFullName, $name); + } else { + $id = $name; + $fullName = $name; + } + + // Complex fields are read-only if themselves or their parent is. + $readOnly = $readOnly || $view->getParent()->get('read_only'); + } else { + $id = $name; + $fullName = $name; + + // Strip leading underscores and digits. These are allowed in + // form names, but not in HTML4 ID attributes. + // http://www.w3.org/TR/html401/struct/global.html#adef-id + $id = ltrim($id, '_0123456789'); + } + + $types = array(); + foreach ($form->getTypes() as $type) { + $types[] = $type->getName(); + } + + $view + ->set('form', $view) + ->set('id', $id) + ->set('name', $name) + ->set('full_name', $fullName) + ->set('read_only', $readOnly) + ->set('errors', $form->getErrors()) + ->set('value', $form->getClientData()) + ->set('disabled', $form->isDisabled()) + ->set('required', $form->isRequired()) + ->set('max_length', $form->getAttribute('max_length')) + ->set('pattern', $form->getAttribute('pattern')) + ->set('size', null) + ->set('label', $form->getAttribute('label')) + ->set('multipart', false) + ->set('attr', $form->getAttribute('attr')) + ->set('types', $types) + ->set('translation_domain', $form->getAttribute('translation_domain')) ; } @@ -53,29 +152,75 @@ class FormType extends AbstractType */ public function getDefaultOptions() { - $emptyData = function (Options $options, $currentValue) { - if (empty($options['data_class'])) { - return array(); + // Derive "data_class" option from passed "data" object + $dataClass = function (Options $options) { + if (is_object($options['data'])) { + return get_class($options['data']); } - return $currentValue; + return null; }; + // Derive "empty_data" closure from "data_class" option + $emptyData = function (Options $options) { + $class = $options['data_class']; + + if (null !== $class) { + return function (FormInterface $form) use ($class) { + if ($form->isEmpty() && !$form->isRequired()) { + return null; + } + + return new $class(); + }; + } + + return function (FormInterface $form) { + if ($form->hasChildren()) { + return array(); + } + }; + + return ''; + }; + return array( + 'data' => null, + 'data_class' => $dataClass, 'empty_data' => $emptyData, + 'trim' => true, + 'required' => true, + 'read_only' => false, + 'disabled' => false, + 'max_length' => null, + 'pattern' => null, + 'property_path' => null, + 'by_reference' => true, + 'error_bubbling' => false, + 'error_mapping' => array(), + 'label' => null, + 'attr' => array(), 'virtual' => false, - // Errors in forms bubble by default, so that form errors will - // end up as global errors in the root form - 'error_bubbling' => true, + 'invalid_message' => 'This value is not valid', + 'invalid_message_parameters' => array(), + 'translation_domain' => 'messages', ); } + /** + * {@inheritdoc} + */ + public function createBuilder($name, FormFactoryInterface $factory, array $options) + { + return new FormBuilder($name, $factory, new EventDispatcher(), $options['data_class']); + } + /** * {@inheritdoc} */ public function getParent(array $options) { - return 'field'; + return null; } /** @@ -85,4 +230,9 @@ class FormType extends AbstractType { return 'form'; } + + private function humanize($text) + { + return ucfirst(trim(strtolower(preg_replace('/[_\s]+/', ' ', $text)))); + } } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php index 3f7258bb82..546220d0ab 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php @@ -150,7 +150,7 @@ class TimeType extends AbstractType // them like immutable value objects 'by_reference' => false, 'error_bubbling' => false, - // If initialized with a \DateTime object, FieldType initializes + // If initialized with a \DateTime object, FormType initializes // this option to "\DateTime". Since the internal, normalized // representation is not \DateTime, but an array, we need to unset // this option. @@ -183,7 +183,7 @@ class TimeType extends AbstractType */ public function getParent(array $options) { - return isset($options['widget']) && 'single_text' === $options['widget'] ? 'field' : 'form'; + return 'field'; } /** diff --git a/src/Symfony/Component/Form/Extension/Validator/Type/FieldTypeValidatorExtension.php b/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php similarity index 95% rename from src/Symfony/Component/Form/Extension/Validator/Type/FieldTypeValidatorExtension.php rename to src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php index 8d0db3f4f6..77754669f3 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Type/FieldTypeValidatorExtension.php +++ b/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php @@ -19,7 +19,7 @@ use Symfony\Component\Validator\ValidatorInterface; /** * @author Bernhard Schussek */ -class FieldTypeValidatorExtension extends AbstractTypeExtension +class FormTypeValidatorExtension extends AbstractTypeExtension { private $validator; @@ -57,6 +57,6 @@ class FieldTypeValidatorExtension extends AbstractTypeExtension public function getExtendedType() { - return 'field'; + return 'form'; } } diff --git a/src/Symfony/Component/Form/Extension/Validator/ValidatorExtension.php b/src/Symfony/Component/Form/Extension/Validator/ValidatorExtension.php index 94b6ea699d..b4efa1679a 100644 --- a/src/Symfony/Component/Form/Extension/Validator/ValidatorExtension.php +++ b/src/Symfony/Component/Form/Extension/Validator/ValidatorExtension.php @@ -38,7 +38,7 @@ class ValidatorExtension extends AbstractExtension protected function loadTypeExtensions() { return array( - new Type\FieldTypeValidatorExtension($this->validator), + new Type\FormTypeValidatorExtension($this->validator), ); } } diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index 4798c7e5cd..5669e0ead4 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -191,7 +191,7 @@ class Form implements \IteratorAggregate, FormInterface array $types = array(), array $clientTransformers = array(), array $normTransformers = array(), DataMapperInterface $dataMapper = null, array $validators = array(), - $required = false, $disabled = false, $errorBubbling = false, + $required = false, $disabled = false, $errorBubbling = null, $emptyData = null, array $attributes = array()) { $name = (string) $name; @@ -225,7 +225,10 @@ class Form implements \IteratorAggregate, FormInterface $this->validators = $validators; $this->required = (Boolean) $required; $this->disabled = (Boolean) $disabled; - $this->errorBubbling = (Boolean) $errorBubbling; + // NULL is the default meaning: + // bubble up if the form has children (complex forms) + // don't bubble up if the form has no children (primitive fields) + $this->errorBubbling = null === $errorBubbling ? null : (Boolean) $errorBubbling; $this->emptyData = $emptyData; $this->attributes = $attributes; @@ -312,9 +315,9 @@ class Form implements \IteratorAggregate, FormInterface } /** - * Returns the parent field. + * Returns the parent form. * - * @return FormInterface The parent field + * @return FormInterface The parent form */ public function getParent() { @@ -342,7 +345,7 @@ class Form implements \IteratorAggregate, FormInterface } /** - * Returns whether the field is the root of the form tree. + * Returns whether the form is the root of the form tree. * * @return Boolean */ @@ -374,7 +377,7 @@ class Form implements \IteratorAggregate, FormInterface } /** - * Updates the field with default data. + * Updates the form with default data. * * @param array $appData The data formatted as expected for the underlying object * @@ -408,7 +411,7 @@ class Form implements \IteratorAggregate, FormInterface $this->clientData = $clientData; $this->synchronized = true; - if ($this->dataMapper) { + if (count($this->children) > 0 && $this->dataMapper) { // Update child forms from the data $this->dataMapper->mapDataToForms($clientData, $this->children); } @@ -450,7 +453,7 @@ class Form implements \IteratorAggregate, FormInterface } /** - * Binds data to the field, transforms and validates it. + * Binds data to the form, transforms and validates it. * * @param string|array $clientData The data * @@ -626,11 +629,11 @@ class Form implements \IteratorAggregate, FormInterface } /** - * Returns the normalized data of the field. + * Returns the normalized data of the form. * - * @return mixed When the field is not bound, the default data is returned. - * When the field is bound, the normalized bound data is - * returned if the field is valid, null otherwise. + * @return mixed When the form is not bound, the default data is returned. + * When the form is bound, the normalized bound data is + * returned if the form is valid, null otherwise. */ public function getNormData() { @@ -646,7 +649,7 @@ class Form implements \IteratorAggregate, FormInterface */ public function addError(FormError $error) { - if ($this->parent && $this->errorBubbling) { + if ($this->parent && $this->getErrorBubbling()) { $this->parent->addError($error); } else { $this->errors[] = $error; @@ -662,11 +665,11 @@ class Form implements \IteratorAggregate, FormInterface */ public function getErrorBubbling() { - return $this->errorBubbling; + return null === $this->errorBubbling ? $this->hasChildren() : $this->errorBubbling; } /** - * Returns whether the field is bound. + * Returns whether the form is bound. * * @return Boolean true if the form is bound to input values, false otherwise */ @@ -702,7 +705,7 @@ class Form implements \IteratorAggregate, FormInterface } /** - * Returns whether the field is valid. + * Returns whether the form is valid. * * @return Boolean */ @@ -735,9 +738,8 @@ class Form implements \IteratorAggregate, FormInterface public function hasErrors() { // Don't call isValid() here, as its semantics are slightly different - // Field groups are not valid if their children are invalid, but - // hasErrors() returns only true if a field/field group itself has - // errors + // Forms are not valid if their children are invalid, but + // hasErrors() returns only true if a form itself has errors return count($this->errors) > 0; } @@ -894,7 +896,7 @@ class Form implements \IteratorAggregate, FormInterface return $this->children[$name]; } - throw new \InvalidArgumentException(sprintf('Field "%s" does not exist.', $name)); + throw new \InvalidArgumentException(sprintf('Child "%s" does not exist.', $name)); } /** diff --git a/src/Symfony/Component/Form/FormBuilder.php b/src/Symfony/Component/Form/FormBuilder.php index 208a518518..b8f7da16c6 100644 --- a/src/Symfony/Component/Form/FormBuilder.php +++ b/src/Symfony/Component/Form/FormBuilder.php @@ -106,7 +106,7 @@ class FormBuilder * Whether added errors should bubble up to the parent * @var Boolean */ - private $errorBubbling = false; + private $errorBubbling; /** * Data used for the client data when no value is bound @@ -243,7 +243,7 @@ class FormBuilder */ public function setErrorBubbling($errorBubbling) { - $this->errorBubbling = (Boolean) $errorBubbling; + $this->errorBubbling = null === $errorBubbling ? null : (Boolean) $errorBubbling; return $this; } diff --git a/src/Symfony/Component/Form/FormInterface.php b/src/Symfony/Component/Form/FormInterface.php index d0d8a80a8d..f3ca837356 100644 --- a/src/Symfony/Component/Form/FormInterface.php +++ b/src/Symfony/Component/Form/FormInterface.php @@ -183,7 +183,7 @@ interface FormInterface extends \ArrayAccess, \Traversable, \Countable * The content of a disabled form is displayed, but not allowed to be * modified. The validation of modified disabled forms should fail. * - * Fields whose parents are disabled are considered disabled regardless of + * Forms whose parents are disabled are considered disabled regardless of * their own state. * * @return Boolean diff --git a/src/Symfony/Component/Form/FormTypeGuesserChain.php b/src/Symfony/Component/Form/FormTypeGuesserChain.php index b3ca91a460..9d915698ca 100644 --- a/src/Symfony/Component/Form/FormTypeGuesserChain.php +++ b/src/Symfony/Component/Form/FormTypeGuesserChain.php @@ -75,7 +75,7 @@ class FormTypeGuesserChain implements FormTypeGuesserInterface * @param \Closure $closure The closure to execute. Accepts a guesser * as argument and should return a Guess instance * - * @return FieldFactoryGuess The guess with the highest confidence + * @return Guess The guess with the highest confidence */ private function guess(\Closure $closure) { diff --git a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php index 986f99b7b4..7d1512f003 100644 --- a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php @@ -202,7 +202,7 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest ); } - public function testRestAndRepeatedWithRowPerField() + public function testRestAndRepeatedWithRowPerChild() { $view = $this->factory->createNamedBuilder('form', 'name') ->add('first', 'text') @@ -230,7 +230,7 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest ); } - public function testRestAndRepeatedWithWidgetPerField() + public function testRestAndRepeatedWithWidgetPerChild() { $view = $this->factory->createNamedBuilder('form', 'name') ->add('first', 'text') @@ -348,7 +348,10 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest public function testNestedFormError() { $form = $this->factory->createNamedBuilder('form', 'name') - ->add('child', 'form', array('error_bubbling' => false)) + ->add($this->factory + ->createNamedBuilder('form', 'child', null, array('error_bubbling' => false)) + ->add('grandChild', 'form') + ) ->getForm(); $form->get('child')->addError(new FormError('Error!')); diff --git a/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php index 5ea8780d89..0943b06c41 100644 --- a/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php @@ -210,7 +210,10 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest public function testNestedFormError() { $form = $this->factory->createNamedBuilder('form', 'name') - ->add('child', 'form', array('error_bubbling' => false)) + ->add($this->factory + ->createNamedBuilder('form', 'child', null, array('error_bubbling' => false)) + ->add('grandChild', 'form') + ) ->getForm(); $form->get('child')->addError(new FormError('Error!')); diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php index 9cf54e25ed..fbda65a3b6 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php @@ -98,7 +98,7 @@ class ChoiceTypeTest extends TypeTestCase )); } - public function testExpandedChoicesOptionsTurnIntoFields() + public function testExpandedChoicesOptionsTurnIntoChildren() { $form = $this->factory->create('choice', null, array( 'expanded' => true, @@ -141,7 +141,7 @@ class ChoiceTypeTest extends TypeTestCase } } - public function testExpandedRadiosAreRequiredIfChoiceFieldIsRequired() + public function testExpandedRadiosAreRequiredIfChoiceChildIsRequired() { $form = $this->factory->create('choice', null, array( 'multiple' => false, @@ -155,7 +155,7 @@ class ChoiceTypeTest extends TypeTestCase } } - public function testExpandedRadiosAreNotRequiredIfChoiceFieldIsNotRequired() + public function testExpandedRadiosAreNotRequiredIfChoiceChildIsNotRequired() { $form = $this->factory->create('choice', null, array( 'multiple' => false, @@ -288,7 +288,7 @@ class ChoiceTypeTest extends TypeTestCase $this->assertNull($form[4]->getClientData()); } - public function testBindSingleExpandedWithFalseDoesNotHaveExtraFields() + public function testBindSingleExpandedWithFalseDoesNotHaveExtraChildren() { $form = $this->factory->create('choice', null, array( 'multiple' => false, @@ -302,7 +302,7 @@ class ChoiceTypeTest extends TypeTestCase $this->assertNull($form->getData()); } - public function testBindSingleExpandedWithEmptyField() + public function testBindSingleExpandedWithEmptyChild() { $form = $this->factory->create('choice', null, array( 'multiple' => false, @@ -422,7 +422,7 @@ class ChoiceTypeTest extends TypeTestCase $this->assertNull($form[4]->getClientData()); } - public function testBindMultipleExpandedWithEmptyField() + public function testBindMultipleExpandedWithEmptyChild() { $form = $this->factory->create('choice', null, array( 'multiple' => true, diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php index b0bb62508f..5461258e95 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php @@ -15,10 +15,10 @@ use Symfony\Component\Form\Form; class CollectionTypeTest extends TypeTestCase { - public function testContainsNoFieldByDefault() + public function testContainsNoChildByDefault() { $form = $this->factory->create('collection', null, array( - 'type' => 'field', + 'type' => 'form', )); $this->assertCount(0, $form); @@ -27,7 +27,7 @@ class CollectionTypeTest extends TypeTestCase public function testSetDataAdjustsSize() { $form = $this->factory->create('collection', null, array( - 'type' => 'field', + 'type' => 'form', 'options' => array( 'max_length' => 20, ), @@ -53,7 +53,7 @@ class CollectionTypeTest extends TypeTestCase public function testThrowsExceptionIfObjectIsNotTraversable() { $form = $this->factory->create('collection', null, array( - 'type' => 'field', + 'type' => 'form', )); $this->setExpectedException('Symfony\Component\Form\Exception\UnexpectedTypeException'); $form->setData(new \stdClass()); @@ -62,7 +62,7 @@ class CollectionTypeTest extends TypeTestCase public function testNotResizedIfBoundWithMissingData() { $form = $this->factory->create('collection', null, array( - 'type' => 'field', + 'type' => 'form', )); $form->setData(array('foo@foo.com', 'bar@bar.com')); $form->bind(array('foo@bar.com')); @@ -76,7 +76,7 @@ class CollectionTypeTest extends TypeTestCase public function testResizedDownIfBoundWithMissingDataAndAllowDelete() { $form = $this->factory->create('collection', null, array( - 'type' => 'field', + 'type' => 'form', 'allow_delete' => true, )); $form->setData(array('foo@foo.com', 'bar@bar.com')); @@ -91,7 +91,7 @@ class CollectionTypeTest extends TypeTestCase public function testNotResizedIfBoundWithExtraData() { $form = $this->factory->create('collection', null, array( - 'type' => 'field', + 'type' => 'form', )); $form->setData(array('foo@bar.com')); $form->bind(array('foo@foo.com', 'bar@bar.com')); @@ -104,7 +104,7 @@ class CollectionTypeTest extends TypeTestCase public function testResizedUpIfBoundWithExtraDataAndAllowAdd() { $form = $this->factory->create('collection', null, array( - 'type' => 'field', + 'type' => 'form', 'allow_add' => true, )); $form->setData(array('foo@bar.com')); @@ -120,7 +120,7 @@ class CollectionTypeTest extends TypeTestCase public function testAllowAddButNoPrototype() { $form = $this->factory->create('collection', null, array( - 'type' => 'field', + 'type' => 'form', 'allow_add' => true, 'prototype' => false, )); @@ -169,7 +169,7 @@ class CollectionTypeTest extends TypeTestCase public function testPrototypeNameOption() { $form = $this->factory->create('collection', null, array( - 'type' => 'field', + 'type' => 'form', 'prototype' => true, 'allow_add' => true, )); @@ -177,7 +177,7 @@ class CollectionTypeTest extends TypeTestCase $this->assertSame('__name__', $form->getAttribute('prototype')->getName(), '__name__ is the default'); $form = $this->factory->create('collection', null, array( - 'type' => 'field', + 'type' => 'form', 'prototype' => true, 'allow_add' => true, 'prototype_name' => '__test__', diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FieldTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FieldTypeTest.php deleted file mode 100644 index b588d4433f..0000000000 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FieldTypeTest.php +++ /dev/null @@ -1,380 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Form\Tests\Extension\Core\Type; - -use Symfony\Component\Form\Util\PropertyPath; -use Symfony\Component\Form\Form; -use Symfony\Component\Form\Tests\Fixtures\Author; -use Symfony\Component\Form\Tests\Fixtures\FixedDataTransformer; - -class FieldTypeTest extends TypeTestCase -{ - public function testGetPropertyPathDefaultPath() - { - $form = $this->factory->createNamed('field', 'title'); - - $this->assertEquals(new PropertyPath('title'), $form->getAttribute('property_path')); - } - - public function testGetPropertyPathPathIsZero() - { - $form = $this->factory->create('field', null, array('property_path' => '0')); - - $this->assertEquals(new PropertyPath('0'), $form->getAttribute('property_path')); - } - - public function testGetPropertyPathPathIsEmpty() - { - $form = $this->factory->create('field', null, array('property_path' => '')); - - $this->assertNull($form->getAttribute('property_path')); - } - - public function testGetPropertyPathPathIsFalse() - { - $form = $this->factory->create('field', null, array('property_path' => false)); - - $this->assertNull($form->getAttribute('property_path')); - } - - public function testGetPropertyPathPathIsNull() - { - $form = $this->factory->createNamed('field', 'title', null, array('property_path' => null)); - - $this->assertEquals(new PropertyPath('title'), $form->getAttribute('property_path')); - } - - public function testPassRequiredAsOption() - { - $form = $this->factory->create('field', null, array('required' => false)); - - $this->assertFalse($form->isRequired()); - - $form = $this->factory->create('field', null, array('required' => true)); - - $this->assertTrue($form->isRequired()); - } - - public function testPassDisabledAsOption() - { - $form = $this->factory->create('field', null, array('disabled' => true)); - - $this->assertTrue($form->isDisabled()); - } - - public function testBoundDataIsTrimmedBeforeTransforming() - { - $form = $this->factory->createBuilder('field') - ->appendClientTransformer(new FixedDataTransformer(array( - null => '', - 'reverse[a]' => 'a', - ))) - ->getForm(); - - $form->bind(' a '); - - $this->assertEquals('a', $form->getClientData()); - $this->assertEquals('reverse[a]', $form->getData()); - } - - public function testBoundDataIsNotTrimmedBeforeTransformingIfNoTrimming() - { - $form = $this->factory->createBuilder('field', null, array('trim' => false)) - ->appendClientTransformer(new FixedDataTransformer(array( - null => '', - 'reverse[ a ]' => ' a ', - ))) - ->getForm(); - - $form->bind(' a '); - - $this->assertEquals(' a ', $form->getClientData()); - $this->assertEquals('reverse[ a ]', $form->getData()); - } - - public function testPassIdAndNameToView() - { - $form = $this->factory->createNamed('field', 'name'); - $view = $form->createView(); - - $this->assertEquals('name', $view->get('id')); - $this->assertEquals('name', $view->get('name')); - $this->assertEquals('name', $view->get('full_name')); - } - - public function testStripLeadingUnderscoresAndDigitsFromId() - { - $form = $this->factory->createNamed('field', '_09name'); - $view = $form->createView(); - - $this->assertEquals('name', $view->get('id')); - $this->assertEquals('_09name', $view->get('name')); - $this->assertEquals('_09name', $view->get('full_name')); - } - - public function testPassIdAndNameToViewWithParent() - { - $parent = $this->factory->createNamed('field', 'parent'); - $parent->add($this->factory->createNamed('field', 'child')); - $view = $parent->createView(); - - $this->assertEquals('parent_child', $view['child']->get('id')); - $this->assertEquals('child', $view['child']->get('name')); - $this->assertEquals('parent[child]', $view['child']->get('full_name')); - } - - public function testPassIdAndNameToViewWithGrandParent() - { - $parent = $this->factory->createNamed('field', 'parent'); - $parent->add($this->factory->createNamed('field', 'child')); - $parent['child']->add($this->factory->createNamed('field', 'grand_child')); - $view = $parent->createView(); - - $this->assertEquals('parent_child_grand_child', $view['child']['grand_child']->get('id')); - $this->assertEquals('grand_child', $view['child']['grand_child']->get('name')); - $this->assertEquals('parent[child][grand_child]', $view['child']['grand_child']->get('full_name')); - } - - public function testNonReadOnlyFieldWithReadOnlyParentBeingReadOnly() - { - $parent = $this->factory->createNamed('field', 'parent', null, array('read_only' => true)); - $child = $this->factory->createNamed('field', 'child'); - $view = $parent->add($child)->createView(); - - $this->assertTrue($view['child']->get('read_only')); - } - - public function testReadOnlyFieldWithNonReadOnlyParentBeingReadOnly() - { - $parent = $this->factory->createNamed('field', 'parent'); - $child = $this->factory->createNamed('field', 'child', null, array('read_only' => true)); - $view = $parent->add($child)->createView(); - - $this->assertTrue($view['child']->get('read_only')); - } - - public function testNonReadOnlyFieldWithNonReadOnlyParentBeingNonReadOnly() - { - $parent = $this->factory->createNamed('field', 'parent'); - $child = $this->factory->createNamed('field', 'child'); - $view = $parent->add($child)->createView(); - - $this->assertFalse($view['child']->get('read_only')); - } - - public function testPassMaxLengthToView() - { - $form = $this->factory->create('field', null, array('max_length' => 10)); - $view = $form->createView(); - - $this->assertSame(10, $view->get('max_length')); - } - - public function testPassTranslationDomainToView() - { - $form = $this->factory->create('field', null, array('translation_domain' => 'test')); - $view = $form->createView(); - - $this->assertSame('test', $view->get('translation_domain')); - } - - public function testPassDefaultLabelToView() - { - $form = $this->factory->createNamed('field', '__test___field'); - $view = $form->createView(); - - $this->assertSame('Test field', $view->get('label')); - } - - public function testPassLabelToView() - { - $form = $this->factory->createNamed('field', '__test___field', null, array('label' => 'My label')); - $view = $form->createView(); - - $this->assertSame('My label', $view->get('label')); - } - - public function testDefaultTranslationDomain() - { - $form = $this->factory->create('field'); - $view = $form->createView(); - - $this->assertSame('messages', $view->get('translation_domain')); - } - - public function testBindWithEmptyDataCreatesObjectIfClassAvailable() - { - $form = $this->factory->create('form', null, array( - 'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author', - 'required' => false, - )); - $form->add($this->factory->createNamed('field', 'firstName')); - $form->add($this->factory->createNamed('field', 'lastName')); - - $form->setData(null); - // partially empty, still an object is created - $form->bind(array('firstName' => 'Bernhard', 'lastName' => '')); - - $author = new Author(); - $author->firstName = 'Bernhard'; - $author->setLastName(''); - - $this->assertEquals($author, $form->getData()); - } - - public function testBindWithEmptyDataCreatesObjectIfInitiallyBoundWithObject() - { - $form = $this->factory->create('form', null, array( - // data class is inferred from the passed object - 'data' => new Author(), - 'required' => false, - )); - $form->add($this->factory->createNamed('field', 'firstName')); - $form->add($this->factory->createNamed('field', 'lastName')); - - $form->setData(null); - // partially empty, still an object is created - $form->bind(array('firstName' => 'Bernhard', 'lastName' => '')); - - $author = new Author(); - $author->firstName = 'Bernhard'; - $author->setLastName(''); - - $this->assertEquals($author, $form->getData()); - } - - public function testBindWithEmptyDataDoesNotCreateObjectIfDataClassIsNull() - { - $form = $this->factory->create('form', null, array( - 'data' => new Author(), - 'data_class' => null, - 'required' => false, - )); - $form->add($this->factory->createNamed('field', 'firstName')); - - $form->setData(null); - $form->bind(array('firstName' => 'Bernhard')); - - $this->assertSame(array('firstName' => 'Bernhard'), $form->getData()); - } - - public function testBindEmptyWithEmptyDataCreatesNoObjectIfNotRequired() - { - $form = $this->factory->create('form', null, array( - 'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author', - 'required' => false, - )); - $form->add($this->factory->createNamed('field', 'firstName')); - $form->add($this->factory->createNamed('field', 'lastName')); - - $form->setData(null); - $form->bind(array('firstName' => '', 'lastName' => '')); - - $this->assertNull($form->getData()); - } - - public function testBindEmptyWithEmptyDataCreatesObjectIfRequired() - { - $form = $this->factory->create('form', null, array( - 'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author', - 'required' => true, - )); - $form->add($this->factory->createNamed('field', 'firstName')); - $form->add($this->factory->createNamed('field', 'lastName')); - - $form->setData(null); - $form->bind(array('firstName' => '', 'lastName' => '')); - - $this->assertEquals(new Author(), $form->getData()); - } - - /* - * We need something to write the field values into - */ - public function testBindWithEmptyDataStoresArrayIfNoClassAvailable() - { - $form = $this->factory->create('form'); - $form->add($this->factory->createNamed('field', 'firstName')); - - $form->setData(null); - $form->bind(array('firstName' => 'Bernhard')); - - $this->assertSame(array('firstName' => 'Bernhard'), $form->getData()); - } - - public function testBindWithEmptyDataUsesEmptyDataOption() - { - $author = new Author(); - - $form = $this->factory->create('form', null, array( - 'empty_data' => $author, - )); - $form->add($this->factory->createNamed('field', 'firstName')); - - $form->bind(array('firstName' => 'Bernhard')); - - $this->assertSame($author, $form->getData()); - $this->assertEquals('Bernhard', $author->firstName); - } - - public function testGetAttributesIsEmpty() - { - $form = $this->factory->create('field', null, array('attr' => array())); - - $this->assertCount(0, $form->getAttribute('attr')); - } - - /** - * @see https://github.com/symfony/symfony/issues/1986 - */ - public function testSetDataThroughParamsWithZero() - { - $form = $this->factory->create('field', null, array('data' => 0)); - $view = $form->createView(); - - $this->assertFalse($form->isEmpty()); - - $this->assertSame('0', $view->get('value')); - $this->assertSame('0', $form->getData()); - - $form = $this->factory->create('field', null, array('data' => '0')); - $view = $form->createView(); - - $this->assertFalse($form->isEmpty()); - - $this->assertSame('0', $view->get('value')); - $this->assertSame('0', $form->getData()); - - $form = $this->factory->create('field', null, array('data' => '00000')); - $view = $form->createView(); - - $this->assertFalse($form->isEmpty()); - - $this->assertSame('00000', $view->get('value')); - $this->assertSame('00000', $form->getData()); - } - - /** - * @expectedException Symfony\Component\Form\Exception\FormException - */ - public function testAttributesException() - { - $form = $this->factory->create('field', null, array('attr' => '')); - } - - public function testNameCanBeEmptyString() - { - $form = $this->factory->createNamed('field', ''); - - $this->assertEquals('', $form->getName()); - } -} diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php index eff0719692..72ba0fc98e 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php @@ -11,9 +11,11 @@ namespace Symfony\Component\Form\Tests\Extension\Core\Type; +use Symfony\Component\Form\Util\PropertyPath; use Symfony\Component\Form\Form; use Symfony\Component\Form\CallbackTransformer; use Symfony\Component\Form\Tests\Fixtures\Author; +use Symfony\Component\Form\Tests\Fixtures\FixedDataTransformer; class FormTest_AuthorWithoutRefSetter { @@ -49,13 +51,372 @@ class FormTest_AuthorWithoutRefSetter class FormTypeTest extends TypeTestCase { + public function testGetPropertyPathDefaultPath() + { + $form = $this->factory->createNamed('form', 'title'); + + $this->assertEquals(new PropertyPath('title'), $form->getAttribute('property_path')); + } + + public function testGetPropertyPathPathIsZero() + { + $form = $this->factory->create('form', null, array('property_path' => '0')); + + $this->assertEquals(new PropertyPath('0'), $form->getAttribute('property_path')); + } + + public function testGetPropertyPathPathIsEmpty() + { + $form = $this->factory->create('form', null, array('property_path' => '')); + + $this->assertNull($form->getAttribute('property_path')); + } + + public function testGetPropertyPathPathIsFalse() + { + $form = $this->factory->create('form', null, array('property_path' => false)); + + $this->assertNull($form->getAttribute('property_path')); + } + + public function testGetPropertyPathPathIsNull() + { + $form = $this->factory->createNamed('form', 'title', null, array('property_path' => null)); + + $this->assertEquals(new PropertyPath('title'), $form->getAttribute('property_path')); + } + + public function testPassRequiredAsOption() + { + $form = $this->factory->create('form', null, array('required' => false)); + + $this->assertFalse($form->isRequired()); + + $form = $this->factory->create('form', null, array('required' => true)); + + $this->assertTrue($form->isRequired()); + } + + public function testPassDisabledAsOption() + { + $form = $this->factory->create('form', null, array('disabled' => true)); + + $this->assertTrue($form->isDisabled()); + } + + public function testBoundDataIsTrimmedBeforeTransforming() + { + $form = $this->factory->createBuilder('form') + ->appendClientTransformer(new FixedDataTransformer(array( + null => '', + 'reverse[a]' => 'a', + ))) + ->getForm(); + + $form->bind(' a '); + + $this->assertEquals('a', $form->getClientData()); + $this->assertEquals('reverse[a]', $form->getData()); + } + + public function testBoundDataIsNotTrimmedBeforeTransformingIfNoTrimming() + { + $form = $this->factory->createBuilder('form', null, array('trim' => false)) + ->appendClientTransformer(new FixedDataTransformer(array( + null => '', + 'reverse[ a ]' => ' a ', + ))) + ->getForm(); + + $form->bind(' a '); + + $this->assertEquals(' a ', $form->getClientData()); + $this->assertEquals('reverse[ a ]', $form->getData()); + } + + public function testPassIdAndNameToView() + { + $form = $this->factory->createNamed('form', 'name'); + $view = $form->createView(); + + $this->assertEquals('name', $view->get('id')); + $this->assertEquals('name', $view->get('name')); + $this->assertEquals('name', $view->get('full_name')); + } + + public function testStripLeadingUnderscoresAndDigitsFromId() + { + $form = $this->factory->createNamed('form', '_09name'); + $view = $form->createView(); + + $this->assertEquals('name', $view->get('id')); + $this->assertEquals('_09name', $view->get('name')); + $this->assertEquals('_09name', $view->get('full_name')); + } + + public function testPassIdAndNameToViewWithParent() + { + $parent = $this->factory->createNamed('form', 'parent'); + $parent->add($this->factory->createNamed('form', 'child')); + $view = $parent->createView(); + + $this->assertEquals('parent_child', $view['child']->get('id')); + $this->assertEquals('child', $view['child']->get('name')); + $this->assertEquals('parent[child]', $view['child']->get('full_name')); + } + + public function testPassIdAndNameToViewWithGrandParent() + { + $parent = $this->factory->createNamed('form', 'parent'); + $parent->add($this->factory->createNamed('form', 'child')); + $parent['child']->add($this->factory->createNamed('form', 'grand_child')); + $view = $parent->createView(); + + $this->assertEquals('parent_child_grand_child', $view['child']['grand_child']->get('id')); + $this->assertEquals('grand_child', $view['child']['grand_child']->get('name')); + $this->assertEquals('parent[child][grand_child]', $view['child']['grand_child']->get('full_name')); + } + + public function testNonReadOnlyFormWithReadOnlyParentBeingReadOnly() + { + $parent = $this->factory->createNamed('form', 'parent', null, array('read_only' => true)); + $child = $this->factory->createNamed('form', 'child'); + $view = $parent->add($child)->createView(); + + $this->assertTrue($view['child']->get('read_only')); + } + + public function testReadOnlyFormWithNonReadOnlyParentBeingReadOnly() + { + $parent = $this->factory->createNamed('form', 'parent'); + $child = $this->factory->createNamed('form', 'child', null, array('read_only' => true)); + $view = $parent->add($child)->createView(); + + $this->assertTrue($view['child']->get('read_only')); + } + + public function testNonReadOnlyFormWithNonReadOnlyParentBeingNonReadOnly() + { + $parent = $this->factory->createNamed('form', 'parent'); + $child = $this->factory->createNamed('form', 'child'); + $view = $parent->add($child)->createView(); + + $this->assertFalse($view['child']->get('read_only')); + } + + public function testPassMaxLengthToView() + { + $form = $this->factory->create('form', null, array('max_length' => 10)); + $view = $form->createView(); + + $this->assertSame(10, $view->get('max_length')); + } + + public function testPassTranslationDomainToView() + { + $form = $this->factory->create('form', null, array('translation_domain' => 'test')); + $view = $form->createView(); + + $this->assertSame('test', $view->get('translation_domain')); + } + + public function testPassDefaultLabelToView() + { + $form = $this->factory->createNamed('form', '__test___field'); + $view = $form->createView(); + + $this->assertSame('Test field', $view->get('label')); + } + + public function testPassLabelToView() + { + $form = $this->factory->createNamed('form', '__test___field', null, array('label' => 'My label')); + $view = $form->createView(); + + $this->assertSame('My label', $view->get('label')); + } + + public function testDefaultTranslationDomain() + { + $form = $this->factory->create('form'); + $view = $form->createView(); + + $this->assertSame('messages', $view->get('translation_domain')); + } + + public function testBindWithEmptyDataCreatesObjectIfClassAvailable() + { + $form = $this->factory->create('form', null, array( + 'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author', + 'required' => false, + )); + $form->add($this->factory->createNamed('form', 'firstName')); + $form->add($this->factory->createNamed('form', 'lastName')); + + $form->setData(null); + // partially empty, still an object is created + $form->bind(array('firstName' => 'Bernhard', 'lastName' => '')); + + $author = new Author(); + $author->firstName = 'Bernhard'; + $author->setLastName(''); + + $this->assertEquals($author, $form->getData()); + } + + public function testBindWithEmptyDataCreatesObjectIfInitiallyBoundWithObject() + { + $form = $this->factory->create('form', null, array( + // data class is inferred from the passed object + 'data' => new Author(), + 'required' => false, + )); + $form->add($this->factory->createNamed('form', 'firstName')); + $form->add($this->factory->createNamed('form', 'lastName')); + + $form->setData(null); + // partially empty, still an object is created + $form->bind(array('firstName' => 'Bernhard', 'lastName' => '')); + + $author = new Author(); + $author->firstName = 'Bernhard'; + $author->setLastName(''); + + $this->assertEquals($author, $form->getData()); + } + + public function testBindWithEmptyDataDoesNotCreateObjectIfDataClassIsNull() + { + $form = $this->factory->create('form', null, array( + 'data' => new Author(), + 'data_class' => null, + 'required' => false, + )); + $form->add($this->factory->createNamed('form', 'firstName')); + + $form->setData(null); + $form->bind(array('firstName' => 'Bernhard')); + + $this->assertSame(array('firstName' => 'Bernhard'), $form->getData()); + } + + public function testBindEmptyWithEmptyDataCreatesNoObjectIfNotRequired() + { + $form = $this->factory->create('form', null, array( + 'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author', + 'required' => false, + )); + $form->add($this->factory->createNamed('form', 'firstName')); + $form->add($this->factory->createNamed('form', 'lastName')); + + $form->setData(null); + $form->bind(array('firstName' => '', 'lastName' => '')); + + $this->assertNull($form->getData()); + } + + public function testBindEmptyWithEmptyDataCreatesObjectIfRequired() + { + $form = $this->factory->create('form', null, array( + 'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author', + 'required' => true, + )); + $form->add($this->factory->createNamed('form', 'firstName')); + $form->add($this->factory->createNamed('form', 'lastName')); + + $form->setData(null); + $form->bind(array('firstName' => '', 'lastName' => '')); + + $this->assertEquals(new Author(), $form->getData()); + } + + /* + * We need something to write the field values into + */ + public function testBindWithEmptyDataStoresArrayIfNoClassAvailable() + { + $form = $this->factory->create('form'); + $form->add($this->factory->createNamed('form', 'firstName')); + + $form->setData(null); + $form->bind(array('firstName' => 'Bernhard')); + + $this->assertSame(array('firstName' => 'Bernhard'), $form->getData()); + } + + public function testBindWithEmptyDataUsesEmptyDataOption() + { + $author = new Author(); + + $form = $this->factory->create('form', null, array( + 'empty_data' => $author, + )); + $form->add($this->factory->createNamed('form', 'firstName')); + + $form->bind(array('firstName' => 'Bernhard')); + + $this->assertSame($author, $form->getData()); + $this->assertEquals('Bernhard', $author->firstName); + } + + public function testGetAttributesIsEmpty() + { + $form = $this->factory->create('form', null, array('attr' => array())); + + $this->assertCount(0, $form->getAttribute('attr')); + } + + /** + * @see https://github.com/symfony/symfony/issues/1986 + */ + public function testSetDataThroughParamsWithZero() + { + $form = $this->factory->create('form', null, array('data' => 0)); + $view = $form->createView(); + + $this->assertFalse($form->isEmpty()); + + $this->assertSame('0', $view->get('value')); + $this->assertSame('0', $form->getData()); + + $form = $this->factory->create('form', null, array('data' => '0')); + $view = $form->createView(); + + $this->assertFalse($form->isEmpty()); + + $this->assertSame('0', $view->get('value')); + $this->assertSame('0', $form->getData()); + + $form = $this->factory->create('form', null, array('data' => '00000')); + $view = $form->createView(); + + $this->assertFalse($form->isEmpty()); + + $this->assertSame('00000', $view->get('value')); + $this->assertSame('00000', $form->getData()); + } + + /** + * @expectedException Symfony\Component\Form\Exception\FormException + */ + public function testAttributesException() + { + $form = $this->factory->create('form', null, array('attr' => '')); + } + + public function testNameCanBeEmptyString() + { + $form = $this->factory->createNamed('form', ''); + + $this->assertEquals('', $form->getName()); + } public function testSubformDoesntCallSetters() { $author = new FormTest_AuthorWithoutRefSetter(new Author()); $builder = $this->factory->createBuilder('form'); $builder->add('reference', 'form'); - $builder->get('reference')->add('firstName', 'field'); + $builder->get('reference')->add('firstName', 'form'); $builder->setData($author); $form = $builder->getForm(); @@ -77,7 +438,7 @@ class FormTypeTest extends TypeTestCase $builder = $this->factory->createBuilder('form'); $builder->add('referenceCopy', 'form'); - $builder->get('referenceCopy')->add('firstName', 'field'); + $builder->get('referenceCopy')->add('firstName', 'form'); $builder->setData($author); $form = $builder->getForm(); @@ -99,7 +460,7 @@ class FormTypeTest extends TypeTestCase $builder = $this->factory->createBuilder('form'); $builder->add('referenceCopy', 'form', array('by_reference' => false)); - $builder->get('referenceCopy')->add('firstName', 'field'); + $builder->get('referenceCopy')->add('firstName', 'form'); $builder->setData($author); $form = $builder->getForm(); diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/RepeatedTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/RepeatedTypeTest.php index df571ba9d8..6d9139633e 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/RepeatedTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/RepeatedTypeTest.php @@ -21,7 +21,7 @@ class RepeatedTypeTest extends TypeTestCase parent::setUp(); $this->form = $this->factory->create('repeated', null, array( - 'type' => 'field', + 'type' => 'form', )); $this->form->setData(null); } @@ -37,7 +37,7 @@ class RepeatedTypeTest extends TypeTestCase public function testSetOptions() { $form = $this->factory->create('repeated', null, array( - 'type' => 'field', + 'type' => 'form', 'options' => array('label' => 'Global'), )); @@ -47,11 +47,11 @@ class RepeatedTypeTest extends TypeTestCase $this->assertTrue($form['second']->isRequired()); } - public function testSetOptionsPerField() + public function testSetOptionsPerChild() { $form = $this->factory->create('repeated', null, array( // the global required value cannot be overriden - 'type' => 'field', + 'type' => 'form', 'first_options' => array('label' => 'Test', 'required' => false), 'second_options' => array('label' => 'Test2') )); @@ -66,17 +66,17 @@ class RepeatedTypeTest extends TypeTestCase { $form = $this->factory->create('repeated', null, array( 'required' => false, - 'type' => 'field', + 'type' => 'form', )); $this->assertFalse($form['first']->isRequired()); $this->assertFalse($form['second']->isRequired()); } - public function testSetOptionsPerFieldAndOverwrite() + public function testSetOptionsPerChildAndOverwrite() { $form = $this->factory->create('repeated', null, array( - 'type' => 'field', + 'type' => 'form', 'options' => array('label' => 'Label'), 'second_options' => array('label' => 'Second label') )); diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/EventListener/DelegatingValidationListenerTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/EventListener/DelegatingValidationListenerTest.php index 0071b36fd7..187ce1a2b7 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/EventListener/DelegatingValidationListenerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/EventListener/DelegatingValidationListenerTest.php @@ -93,6 +93,7 @@ class DelegatingValidationListenerTest extends \PHPUnit_Framework_TestCase $builder = new FormBuilder($name, $this->factory, $this->dispatcher); $builder->setAttribute('property_path', new PropertyPath($propertyPath ?: $name)); $builder->setAttribute('error_mapping', array()); + $builder->setErrorBubbling(false); return $builder; } diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/Type/FieldTypeValidatorExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Type/FormTypeValidatorExtensionTest.php similarity index 79% rename from src/Symfony/Component/Form/Tests/Extension/Validator/Type/FieldTypeValidatorExtensionTest.php rename to src/Symfony/Component/Form/Tests/Extension/Validator/Type/FormTypeValidatorExtensionTest.php index fdf011d66a..5cd0ea753b 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/Type/FieldTypeValidatorExtensionTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Type/FormTypeValidatorExtensionTest.php @@ -13,18 +13,18 @@ namespace Symfony\Component\Form\Tests\Extension\Validator\Type; use Symfony\Component\Form\FormInterface; -class FieldTypeValidatorExtensionTest extends TypeTestCase +class FormTypeValidatorExtensionTest extends TypeTestCase { public function testValidationGroupNullByDefault() { - $form = $this->factory->create('field'); + $form = $this->factory->create('form'); $this->assertNull($form->getAttribute('validation_groups')); } public function testValidationGroupsCanBeSetToString() { - $form = $this->factory->create('field', null, array( + $form = $this->factory->create('form', null, array( 'validation_groups' => 'group', )); @@ -33,7 +33,7 @@ class FieldTypeValidatorExtensionTest extends TypeTestCase public function testValidationGroupsCanBeSetToArray() { - $form = $this->factory->create('field', null, array( + $form = $this->factory->create('form', null, array( 'validation_groups' => array('group1', 'group2'), )); @@ -42,7 +42,7 @@ class FieldTypeValidatorExtensionTest extends TypeTestCase public function testValidationGroupsCanBeSetToCallback() { - $form = $this->factory->create('field', null, array( + $form = $this->factory->create('form', null, array( 'validation_groups' => array($this, 'testValidationGroupsCanBeSetToCallback'), )); @@ -51,7 +51,7 @@ class FieldTypeValidatorExtensionTest extends TypeTestCase public function testValidationGroupsCanBeSetToClosure() { - $form = $this->factory->create('field', null, array( + $form = $this->factory->create('form', null, array( 'validation_groups' => function(FormInterface $form){ return null; }, )); @@ -60,10 +60,10 @@ class FieldTypeValidatorExtensionTest extends TypeTestCase public function testBindValidatesData() { - $builder = $this->factory->createBuilder('field', null, array( + $builder = $this->factory->createBuilder('form', null, array( 'validation_groups' => 'group', )); - $builder->add('firstName', 'field'); + $builder->add('firstName', 'form'); $form = $builder->getForm(); $this->validator->expects($this->once()) diff --git a/src/Symfony/Component/Form/Tests/FormBuilderTest.php b/src/Symfony/Component/Form/Tests/FormBuilderTest.php index f5276f322e..bbae7ee2dd 100644 --- a/src/Symfony/Component/Form/Tests/FormBuilderTest.php +++ b/src/Symfony/Component/Form/Tests/FormBuilderTest.php @@ -71,7 +71,7 @@ class FormBuilderTest extends \PHPUnit_Framework_TestCase * Changing the name is not allowed, otherwise the name and property path * are not synchronized anymore * - * @see FieldType::buildForm + * @see FormType::buildForm */ public function testNoSetName() { diff --git a/src/Symfony/Component/Form/Tests/FormFactoryTest.php b/src/Symfony/Component/Form/Tests/FormFactoryTest.php index 96ceea5532..49e53e8337 100644 --- a/src/Symfony/Component/Form/Tests/FormFactoryTest.php +++ b/src/Symfony/Component/Form/Tests/FormFactoryTest.php @@ -346,7 +346,7 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase $this->assertEquals('foo', $builder->getName()); } - public function testCreateBuilderForPropertyCreatesFieldWithHighestConfidence() + public function testCreateBuilderForPropertyCreatesFormWithHighestConfidence() { $this->guesser1->expects($this->once()) ->method('guessType') @@ -378,7 +378,7 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase $this->assertEquals('builderInstance', $builder); } - public function testCreateBuilderCreatesTextFieldIfNoGuess() + public function testCreateBuilderCreatesTextFormIfNoGuess() { $this->guesser1->expects($this->once()) ->method('guessType') @@ -541,7 +541,7 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase $factory->createNamedBuilder($type, "text", "value", array("unknown" => "opt")); } - public function testFieldTypeCreatesDefaultValueForEmptyDataOption() + public function testFormTypeCreatesDefaultValueForEmptyDataOption() { $factory = new FormFactory(array(new \Symfony\Component\Form\Extension\Core\CoreExtension())); diff --git a/src/Symfony/Component/Form/Tests/FormTest.php b/src/Symfony/Component/Form/Tests/FormTest.php index bc25e16a1f..08ef363e4b 100644 --- a/src/Symfony/Component/Form/Tests/FormTest.php +++ b/src/Symfony/Component/Form/Tests/FormTest.php @@ -159,6 +159,35 @@ class FormTest extends \PHPUnit_Framework_TestCase $this->assertEquals(array(), $parent->getErrors()); } + public function testErrorsBubbleUpIfNullAndChildren() + { + $error = new FormError('Error!'); + $parent = $this->form; + $form = $this->getBuilder() + ->setErrorBubbling(null) + ->add($this->getBuilder('child')) + ->getForm(); + + $form->setParent($parent); + $form->addError($error); + + $this->assertEquals(array(), $form->getErrors()); + $this->assertEquals(array($error), $parent->getErrors()); + } + + public function testErrorsDontBubbleUpIfNullAndNoChildren() + { + $error = new FormError('Error!'); + $parent = $this->form; + $form = $this->getBuilder()->setErrorBubbling(null)->getForm(); + + $form->setParent($parent); + $form->addError($error); + + $this->assertEquals(array($error), $form->getErrors()); + $this->assertEquals(array(), $parent->getErrors()); + } + public function testValidIfAllChildrenAreValid() { $this->form->add($this->getValidForm('firstName')); @@ -1026,7 +1055,7 @@ class FormTest extends \PHPUnit_Framework_TestCase /** * @dataProvider requestMethodProvider */ - public function testBindPostOrPutRequestWithSingleFieldForm($method) + public function testBindPostOrPutRequestWithSingleChildForm($method) { if (!class_exists('Symfony\Component\HttpFoundation\Request')) { $this->markTestSkipped('The "HttpFoundation" component is not available'); @@ -1063,7 +1092,7 @@ class FormTest extends \PHPUnit_Framework_TestCase /** * @dataProvider requestMethodProvider */ - public function testBindPostOrPutRequestWithSingleFieldFormUploadedFile($method) + public function testBindPostOrPutRequestWithSingleChildFormUploadedFile($method) { if (!class_exists('Symfony\Component\HttpFoundation\Request')) { $this->markTestSkipped('The "HttpFoundation" component is not available'); From 6e4ed9e177fc6c9bf54bac9f0333457d222185fa Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Tue, 17 Apr 2012 17:29:15 +0200 Subject: [PATCH 4/4] [Form] Fixed regression: bind(null) was not converted to an empty string anymore --- .../Form/Extension/Core/Type/FormType.php | 4 ++-- .../Tests/Extension/Core/Type/FormTypeTest.php | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php index 7288cd177c..1738fb4f62 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php @@ -179,9 +179,9 @@ class FormType extends AbstractType if ($form->hasChildren()) { return array(); } - }; - return ''; + return ''; + }; }; return array( diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php index 72ba0fc98e..8275c61455 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php @@ -344,6 +344,22 @@ class FormTypeTest extends TypeTestCase $this->assertSame(array('firstName' => 'Bernhard'), $form->getData()); } + public function testBindWithEmptyDataPassesEmptyStringToTransformerIfNoChildren() + { + $form = $this->factory->createBuilder('form') + ->appendClientTransformer(new FixedDataTransformer(array( + // required for the initial, internal setData(null) + null => 'null', + // required to test that bind(null) is converted to '' + 'empty' => '', + ))) + ->getForm(); + + $form->bind(null); + + $this->assertSame('empty', $form->getData()); + } + public function testBindWithEmptyDataUsesEmptyDataOption() { $author = new Author();