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 | 66 ++++++- .../EnsureCsrfFieldListenerTest.php | 87 --------- .../Extension/Csrf/Type/CsrfTypeTest.php | 112 ----------- .../Csrf/Type/FormTypeCsrfExtensionTest.php | 181 ++++++++++++++++-- .../Extension/Csrf/Type/TypeTestCase.php | 41 ---- src/Symfony/Component/Form/Tests/FormTest.php | 2 +- 22 files changed, 369 insertions(+), 619 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 39e668c405e5c..ca7edd952a500 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 188a099df29a1..72442bcbba682 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 859ec489ab12d..2c7204e7355eb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -27,9 +27,9 @@ public function testCsrfProtection() $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 42505e2b762bc..5330bbda45d8f 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php +++ b/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php @@ -32,27 +32,13 @@ public function __construct(CsrfProviderInterface $csrfProvider) $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 3de52ab867a59..dd74d87b2eaea 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 @@ 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 @@ */ 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 @@ static public function getSubscribedEvents() ); } - 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 480d67c323305..0000000000000 --- 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 dc1e6db15bd1f..0000000000000 --- 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 97c69d8a5ab1f..0000000000000 --- 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 dc54e94ddf6b4..0000000000000 --- 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 8e2a57459add1..c8687d4af4748 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 @@ public function buildForm(FormBuilder $builder, array $options) 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 @@ public function buildViewBottomUp(FormView $view, FormInterface $form) 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 1115ea4d69ce2..0000000000000 --- 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 dbd7c0d2dfbcb..0000000000000 --- 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 8f9e7b6de2403..4798c7e5cd601 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -975,7 +975,7 @@ public function createView(FormView $parent = null) $parent = $this->parent->createView(); } - $view = new FormView(); + $view = new FormView($this->name); $view->setParent($parent); @@ -989,14 +989,10 @@ public function createView(FormView $parent = null) } } - $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 5c8ab13da12bb..5d88346edc7f9 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 @@ public function hasParent() } /** - * 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 @@ public function hasChildren() 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 d22113245a09d..986f99b7b4fec 100644 --- a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php @@ -363,6 +363,31 @@ public function testNestedFormError() ); } + 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 @@ public function testRepeated() $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 @@ public function testRepeated() /following-sibling::input[@type="text"][@id="name_second"] ] ] - [count(.//input)=2] + [count(.//input)=3] ' ); } @@ -399,7 +425,8 @@ public function testRepeatedWithCustomOptions() $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 @@ public function testRepeatedWithCustomOptions() /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 32aabb86a6707..5d48b9921cffb 100644 --- a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php @@ -646,12 +646,13 @@ public function testSingleChoiceExpanded() $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 @@ public function testSingleChoiceExpandedSkipEmptyValue() $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 @@ public function testSingleChoiceExpandedWithBooleanValue() $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 @@ public function testMultipleChoiceExpanded() $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 @@ public function testCountryWithEmptyValue() ); } - 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 d47b15564f0f3..5ea8780d89f59 100644 --- a/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php @@ -45,7 +45,12 @@ public function testRepeatedRow() $html = $this->renderRow($form->createView()); $this->assertMatchesXpath($html, -'/tr +'/tr[@style="display: none"] + [./td[@colspan="2"]/input + [@type="hidden"] + [@id="name__token"] + ] +/following-sibling::tr [ ./td [./label[@for="name_first"]] @@ -59,7 +64,7 @@ public function testRepeatedRow() /following-sibling::td [./input[@id="name_second"]] ] - [count(../tr)=2] + [count(../tr)=3] ' ); } @@ -76,6 +81,11 @@ public function testRepeatedRowWithErrors() [./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 @@ -90,7 +100,7 @@ public function testRepeatedRowWithErrors() /following-sibling::td [./input[@id="name_second"]] ] - [count(../tr)=3] + [count(../tr)=4] ' ); } @@ -151,9 +161,9 @@ public function testCollection() $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 @@ public function testNestedFormError() ); } + 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 @@ public function testRepeated() $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 @@ public function testRepeated() [./input[@type="text"][@id="name_second"]] ] ] - [count(.//input)=2] + [count(.//input)=3] ' ); } @@ -257,7 +300,12 @@ public function testRepeatedWithCustomOptions() $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 @@ public function testRepeatedWithCustomOptions() [./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 9b87717e5d036..0000000000000 --- 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 4483e7f3925b3..0000000000000 --- 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 20da376d7996c..9024002b5223b 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() + 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() { - $form = $this->factory->create('form', null, array( - 'csrf_field_name' => 'csrf', + return array_merge(parent::getExtensions(), array( + new CsrfExtension($this->csrfProvider), )); + } - $this->assertTrue($form->has('csrf')); + 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, - )); + $view = $this->factory + ->createBuilder('form', null, array( + 'csrf_field_name' => 'csrf', + 'csrf_protection' => false, + )) + ->add($this->factory->createNamedBuilder('form', 'child')) + ->getForm() + ->createView(); + + $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->assertCount(0, $form); + $this->assertEquals('token', $view->getChild('csrf')->get('value')); } - public function testCsrfTokenIsOnlyIncludedInRootView() + public function provideBoolean() { - $view = - $this->factory->createBuilder('form', null, array( + 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('notCsrf', 'text') - ->add( - $this->factory->createNamedBuilder('form', 'child', null, array( + ->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('notCsrf', 'text') + ->add($this->factory->createNamedBuilder('form', 'child')) ) ->getForm() - ->createView(); + ->get('form'); - $this->assertEquals(array('csrf', 'notCsrf', 'child'), array_keys(iterator_to_array($view))); - $this->assertEquals(array('notCsrf'), array_keys(iterator_to_array($view['child']))); + $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 3b1ce91783ca7..0000000000000 --- 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 a5c20dcbcfb14..bc25e16a1fd6c 100644 --- a/src/Symfony/Component/Form/Tests/FormTest.php +++ b/src/Symfony/Component/Form/Tests/FormTest.php @@ -1247,7 +1247,7 @@ public function testCreateView() 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 29d10be36bbdd..0000000000000 --- 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 6cd904ad4a1b9..0000000000000 --- 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 ca7edd952a500..c9755fdf885cd 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 d410262a30c5e..a7d577d9509d3 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 cc3f3fd01d688..46420a71221fd 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 1046c3de36225..b053c59e6f7e1 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 16c137aaca26c..061ef428c2942 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 fc59d708bef60..e96278b8f6266 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 d5016842ea186..ee5b19e0737d8 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 96bfea20dc0ef..f58e589498dae 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 5aee4708a2cbf..9304e9dcfa94d 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 1422e792b8543..bddb2a96e809a 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 4a4fa106e3a3d..bd2c2769af420 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 c30cfb35c3f92..a00dda278e983 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 424d4259693c5..aa4ff39bba6bd 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 339e3d0009854..d7b87ea2bc5f6 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 1434301d4ca7f..7c0c1b7559cdc 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 89041c6ec6374..a570b1045a214 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 091807020d23a..c91dcb554372a 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 a5f1dfbf5f178..ef0bf384a6e76 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 0c8648338639a..f1ca2edadbfda 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 0000000000000..424d4259693c5 --- /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 0000000000000..339e3d0009854 --- /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 e5c5843c46f53..3ddc300fd4540 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 0000000000000..89041c6ec6374 --- /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 0a79a0cc532a2..02fb9ae9b635c 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 0000000000000..a5f1dfbf5f178 --- /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 77fa483c3fa97..1c9368693b49c 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 11942cfe2b1b8..50a42451aef26 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 0000000000000..0c8648338639a --- /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 012211ab5ac2c..1fc6ace34b4fb 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 3151ecbd84776..a68ad5ddacb10 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 9a08222c91206..7e1a2776a7d49 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 78319fcbaed42..7aff242ef43cc 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 4245b52a0c71a..328321f21fcae 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 a84bad54df978..b9a07bc4666bc 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 bbfd593dbcc86..d8a773544e713 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 599750d20aa65..2178974c74c4c 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 b9e5c5639ce46..0000000000000 --- 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 ac4315f957efa..05d8c4aea89d3 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 d1bbbb1b2a6f7..9262a1bae10b3 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 d802ccf050960..171f1eb40c74e 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 2004b4e67e3f1..8fd4adef89d30 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php @@ -119,7 +119,7 @@ public function enctype(FormView $view) */ 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 @@ protected function renderSection(FormView $view, $section, array $variables = ar $view->setRendered(); } - return $html; + return trim($html); } } while (--$typeIndex >= 0); @@ -311,7 +311,7 @@ public function renderBlock($name, $variables = array()) $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 add2ce3d9bc97..2f1543e91b58a 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 @@ function transform($value); * 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 1ed495248bb61..1a8ba2d1be6a2 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 @@ public function buildForm(FormBuilder $builder, array $options) } 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 @@ public function getDefaultOptions() */ public function getParent(array $options) { - return isset($options['expanded']) && $options['expanded'] ? 'form' : 'field'; + return 'field'; } /** @@ -200,12 +200,12 @@ public function getName() * @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 6eba72d4f6b6d..f428054edee74 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 @@ public function getDefaultOptions() '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 @@ public function getAllowedOptionValues() */ 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 e58d0d93408d4..c506714c7ffd3 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 @@ public function getDefaultOptions() // 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 @@ public function getAllowedOptionValues() */ 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 b676d8198475f..c509abe6a5e93 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\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 @@ public function getName() { 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 30f37203b286d..468c65a0f359a 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 36e0c0c63bb11..7288cd177c502 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 @@ 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 @@ public function buildViewBottomUp(FormView $view, FormInterface $form) */ 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 @@ public function getName() { 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 3f7258bb82e70..546220d0ab318 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 @@ public function getDefaultOptions() // 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 @@ public function getAllowedOptionValues() */ 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 8d0db3f4f6089..77754669f3606 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 @@ /** * @author Bernhard Schussek */ -class FieldTypeValidatorExtension extends AbstractTypeExtension +class FormTypeValidatorExtension extends AbstractTypeExtension { private $validator; @@ -57,6 +57,6 @@ public function getDefaultOptions() 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 94b6ea699d841..b4efa1679acdd 100644 --- a/src/Symfony/Component/Form/Extension/Validator/ValidatorExtension.php +++ b/src/Symfony/Component/Form/Extension/Validator/ValidatorExtension.php @@ -38,7 +38,7 @@ public function loadTypeGuesser() 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 4798c7e5cd601..5669e0ead4956 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -191,7 +191,7 @@ public function __construct($name, EventDispatcherInterface $dispatcher, 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 @@ public function __construct($name, EventDispatcherInterface $dispatcher, $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 @@ public function setParent(FormInterface $parent = null) } /** - * 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 @@ public function getRoot() } /** - * 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 @@ public function getAttribute($name) } /** - * 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 @@ public function setData($appData) $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 @@ public function getExtraData() } /** - * 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 @@ public function bindRequest(Request $request) } /** - * 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 @@ public function getNormData() */ 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 @@ public function addError(FormError $error) */ 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 @@ public function isEmpty() } /** - * Returns whether the field is valid. + * Returns whether the form is valid. * * @return Boolean */ @@ -735,9 +738,8 @@ public function isValid() 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 @@ public function get($name) 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 208a518518b41..b8f7da16c65a8 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 @@ public function getRequired() */ 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 d0d8a80a8d127..f3ca837356436 100644 --- a/src/Symfony/Component/Form/FormInterface.php +++ b/src/Symfony/Component/Form/FormInterface.php @@ -183,7 +183,7 @@ function isRequired(); * 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 b3ca91a460ad1..9d915698ca134 100644 --- a/src/Symfony/Component/Form/FormTypeGuesserChain.php +++ b/src/Symfony/Component/Form/FormTypeGuesserChain.php @@ -75,7 +75,7 @@ public function guessMinLength($class, $property) * @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 986f99b7b4fec..7d1512f0037bd 100644 --- a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php @@ -202,7 +202,7 @@ public function testRestAndRepeatedWithRow() ); } - public function testRestAndRepeatedWithRowPerField() + public function testRestAndRepeatedWithRowPerChild() { $view = $this->factory->createNamedBuilder('form', 'name') ->add('first', 'text') @@ -230,7 +230,7 @@ public function testRestAndRepeatedWithRowPerField() ); } - public function testRestAndRepeatedWithWidgetPerField() + public function testRestAndRepeatedWithWidgetPerChild() { $view = $this->factory->createNamedBuilder('form', 'name') ->add('first', 'text') @@ -348,7 +348,10 @@ public function testForm() 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 5ea8780d89f59..0943b06c4162d 100644 --- a/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php @@ -210,7 +210,10 @@ public function testForm() 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 9cf54e25ede73..fbda65a3b6bee 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 @@ public function testEitherChoiceListOrChoicesMustBeSet() )); } - public function testExpandedChoicesOptionsTurnIntoFields() + public function testExpandedChoicesOptionsTurnIntoChildren() { $form = $this->factory->create('choice', null, array( 'expanded' => true, @@ -141,7 +141,7 @@ public function testExpandedCheckboxesAreNeverRequired() } } - public function testExpandedRadiosAreRequiredIfChoiceFieldIsRequired() + public function testExpandedRadiosAreRequiredIfChoiceChildIsRequired() { $form = $this->factory->create('choice', null, array( 'multiple' => false, @@ -155,7 +155,7 @@ public function testExpandedRadiosAreRequiredIfChoiceFieldIsRequired() } } - public function testExpandedRadiosAreNotRequiredIfChoiceFieldIsNotRequired() + public function testExpandedRadiosAreNotRequiredIfChoiceChildIsNotRequired() { $form = $this->factory->create('choice', null, array( 'multiple' => false, @@ -288,7 +288,7 @@ public function testBindSingleExpandedNothingChecked() $this->assertNull($form[4]->getClientData()); } - public function testBindSingleExpandedWithFalseDoesNotHaveExtraFields() + public function testBindSingleExpandedWithFalseDoesNotHaveExtraChildren() { $form = $this->factory->create('choice', null, array( 'multiple' => false, @@ -302,7 +302,7 @@ public function testBindSingleExpandedWithFalseDoesNotHaveExtraFields() $this->assertNull($form->getData()); } - public function testBindSingleExpandedWithEmptyField() + public function testBindSingleExpandedWithEmptyChild() { $form = $this->factory->create('choice', null, array( 'multiple' => false, @@ -422,7 +422,7 @@ public function testBindMultipleExpandedEmpty() $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 b0bb62508f6fa..5461258e95571 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 @@ 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 @@ public function testContainsNoFieldByDefault() public function testSetDataAdjustsSize() { $form = $this->factory->create('collection', null, array( - 'type' => 'field', + 'type' => 'form', 'options' => array( 'max_length' => 20, ), @@ -53,7 +53,7 @@ public function testSetDataAdjustsSize() 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 @@ public function testThrowsExceptionIfObjectIsNotTraversable() 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 @@ public function testNotResizedIfBoundWithMissingData() 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 @@ public function testResizedDownIfBoundWithMissingDataAndAllowDelete() 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 @@ public function testNotResizedIfBoundWithExtraData() 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 @@ public function testResizedUpIfBoundWithExtraDataAndAllowAdd() public function testAllowAddButNoPrototype() { $form = $this->factory->create('collection', null, array( - 'type' => 'field', + 'type' => 'form', 'allow_add' => true, 'prototype' => false, )); @@ -169,7 +169,7 @@ public function testGetDataDoesNotContainsPrototypeNameAfterDataAreSet() public function testPrototypeNameOption() { $form = $this->factory->create('collection', null, array( - 'type' => 'field', + 'type' => 'form', 'prototype' => true, 'allow_add' => true, )); @@ -177,7 +177,7 @@ public function testPrototypeNameOption() $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 b588d4433f250..0000000000000 --- 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 eff0719692781..72ba0fc98e7d4 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 @@ public function setReferenceCopy($reference) 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 @@ public function testSubformCallsSettersIfTheObjectChanged() $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 @@ public function testSubformCallsSettersIfByReferenceIsFalse() $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 df571ba9d82a5..6d9139633ee94 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 @@ protected function setUp() parent::setUp(); $this->form = $this->factory->create('repeated', null, array( - 'type' => 'field', + 'type' => 'form', )); $this->form->setData(null); } @@ -37,7 +37,7 @@ public function testSetData() public function testSetOptions() { $form = $this->factory->create('repeated', null, array( - 'type' => 'field', + 'type' => 'form', 'options' => array('label' => 'Global'), )); @@ -47,11 +47,11 @@ public function testSetOptions() $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 @@ public function testSetRequired() { $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 0071b36fd728a..187ce1a2b7bbe 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 @@ protected function getBuilder($name = 'name', $propertyPath = null) $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 fdf011d66a7e2..5cd0ea753b8d1 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 @@ 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 @@ public function testValidationGroupsCanBeSetToString() 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 @@ public function testValidationGroupsCanBeSetToArray() 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 @@ public function testValidationGroupsCanBeSetToCallback() 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 @@ public function testValidationGroupsCanBeSetToClosure() 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 f5276f322e518..bbae7ee2dd881 100644 --- a/src/Symfony/Component/Form/Tests/FormBuilderTest.php +++ b/src/Symfony/Component/Form/Tests/FormBuilderTest.php @@ -71,7 +71,7 @@ public function testConstructAcceptsOnlyNamesValidAsIdsInHtml4($name, $accepted) * 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 96ceea55324de..49e53e833785c 100644 --- a/src/Symfony/Component/Form/Tests/FormFactoryTest.php +++ b/src/Symfony/Component/Form/Tests/FormFactoryTest.php @@ -346,7 +346,7 @@ public function testCreateUsesTypeNameAsName() $this->assertEquals('foo', $builder->getName()); } - public function testCreateBuilderForPropertyCreatesFieldWithHighestConfidence() + public function testCreateBuilderForPropertyCreatesFormWithHighestConfidence() { $this->guesser1->expects($this->once()) ->method('guessType') @@ -378,7 +378,7 @@ public function testCreateBuilderForPropertyCreatesFieldWithHighestConfidence() $this->assertEquals('builderInstance', $builder); } - public function testCreateBuilderCreatesTextFieldIfNoGuess() + public function testCreateBuilderCreatesTextFormIfNoGuess() { $this->guesser1->expects($this->once()) ->method('guessType') @@ -541,7 +541,7 @@ public function testUnknownOption() $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 bc25e16a1fd6c..08ef363e4bcf4 100644 --- a/src/Symfony/Component/Form/Tests/FormTest.php +++ b/src/Symfony/Component/Form/Tests/FormTest.php @@ -159,6 +159,35 @@ public function testErrorsDontBubbleUpIfDisabled() $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 @@ public function testBindPostOrPutRequestWithEmptyRootFormName($method) /** * @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 @@ public function testBindPostOrPutRequestWithSingleFieldForm($method) /** * @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 7288cd177c502..1738fb4f62246 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 @@ public function getDefaultOptions() 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 72ba0fc98e7d4..8275c61455999 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 @@ public function testBindWithEmptyDataStoresArrayIfNoClassAvailable() $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();