From 2a49449862ab74fbd80126384daa1957abaf2e0c Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Fri, 13 Apr 2012 14:11:09 +0200 Subject: [PATCH] [Form] Simplified CSRF mechanism and removed "csrf" type CSRF fields are now only added when the view is built. For this reason we already know if the form is the root form and avoid to create unnecessary CSRF fields for nested fields. --- CHANGELOG-2.1.md | 1 + .../Resources/config/form_csrf.xml | 5 +- .../FrameworkExtensionTest.php | 4 +- .../Form/Extension/Csrf/CsrfExtension.php | 16 +- .../EventListener/CsrfValidationListener.php | 26 ++- .../EventListener/EnsureCsrfFieldListener.php | 66 ------ .../Csrf/Type/ChoiceTypeCsrfExtension.php | 27 --- .../Form/Extension/Csrf/Type/CsrfType.php | 83 -------- .../Csrf/Type/DateTypeCsrfExtension.php | 27 --- .../Csrf/Type/FormTypeCsrfExtension.php | 54 ++--- .../Csrf/Type/RepeatedTypeCsrfExtension.php | 27 --- .../Csrf/Type/TimeTypeCsrfExtension.php | 27 --- src/Symfony/Component/Form/Form.php | 10 +- src/Symfony/Component/Form/FormView.php | 55 ++++- .../Form/Tests/AbstractDivLayoutTest.php | 35 +++- .../Form/Tests/AbstractLayoutTest.php | 36 ++-- .../Form/Tests/AbstractTableLayoutTest.php | 120 +++++++---- .../EnsureCsrfFieldListenerTest.php | 87 -------- .../Extension/Csrf/Type/CsrfTypeTest.php | 112 ---------- .../Csrf/Type/FormTypeCsrfExtensionTest.php | 197 +++++++++++++++--- .../Extension/Csrf/Type/TypeTestCase.php | 41 ---- src/Symfony/Component/Form/Tests/FormTest.php | 2 +- 22 files changed, 404 insertions(+), 654 deletions(-) delete mode 100644 src/Symfony/Component/Form/Extension/Csrf/EventListener/EnsureCsrfFieldListener.php delete mode 100644 src/Symfony/Component/Form/Extension/Csrf/Type/ChoiceTypeCsrfExtension.php delete mode 100644 src/Symfony/Component/Form/Extension/Csrf/Type/CsrfType.php delete mode 100644 src/Symfony/Component/Form/Extension/Csrf/Type/DateTypeCsrfExtension.php delete mode 100644 src/Symfony/Component/Form/Extension/Csrf/Type/RepeatedTypeCsrfExtension.php delete mode 100644 src/Symfony/Component/Form/Extension/Csrf/Type/TimeTypeCsrfExtension.php delete mode 100644 src/Symfony/Component/Form/Tests/Extension/Csrf/EventListener/EnsureCsrfFieldListenerTest.php delete mode 100644 src/Symfony/Component/Form/Tests/Extension/Csrf/Type/CsrfTypeTest.php delete mode 100644 src/Symfony/Component/Form/Tests/Extension/Csrf/Type/TypeTestCase.php diff --git a/CHANGELOG-2.1.md b/CHANGELOG-2.1.md index 39e668c405..ca7edd952a 100644 --- a/CHANGELOG-2.1.md +++ b/CHANGELOG-2.1.md @@ -269,6 +269,7 @@ To get the diff between two versions, go to https://github.com/symfony/symfony/c don't receive an options array anymore. * Deprecated FormValidatorInterface and substituted its implementations by event subscribers + * simplified CSRF protection and removed the csrf type ### HttpFoundation diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml index 188a099df2..72442bcbba 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml @@ -14,12 +14,9 @@ %kernel.secret% - - - - + %form.type_extension.csrf.enabled% %form.type_extension.csrf.field_name% diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 859ec489ab..2c7204e735 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -27,9 +27,9 @@ abstract class FrameworkExtensionTest extends TestCase $def = $container->getDefinition('form.type_extension.csrf'); $this->assertTrue($container->getParameter('form.type_extension.csrf.enabled')); - $this->assertEquals('%form.type_extension.csrf.enabled%', $def->getArgument(0)); + $this->assertEquals('%form.type_extension.csrf.enabled%', $def->getArgument(1)); $this->assertEquals('_csrf', $container->getParameter('form.type_extension.csrf.field_name')); - $this->assertEquals('%form.type_extension.csrf.field_name%', $def->getArgument(1)); + $this->assertEquals('%form.type_extension.csrf.field_name%', $def->getArgument(2)); $this->assertEquals('s3cr3t', $container->getParameterBag()->resolveValue($container->findDefinition('form.csrf_provider')->getArgument(1))); } diff --git a/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php b/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php index 42505e2b76..5330bbda45 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php +++ b/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php @@ -32,27 +32,13 @@ class CsrfExtension extends AbstractExtension $this->csrfProvider = $csrfProvider; } - /** - * {@inheritDoc} - */ - protected function loadTypes() - { - return array( - new Type\CsrfType($this->csrfProvider), - ); - } - /** * {@inheritDoc} */ protected function loadTypeExtensions() { return array( - new Type\ChoiceTypeCsrfExtension(), - new Type\DateTypeCsrfExtension(), - new Type\FormTypeCsrfExtension(), - new Type\RepeatedTypeCsrfExtension(), - new Type\TimeTypeCsrfExtension(), + new Type\FormTypeCsrfExtension($this->csrfProvider), ); } } diff --git a/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php b/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php index 3de52ab867..dd74d87b2e 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php +++ b/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php @@ -14,7 +14,7 @@ namespace Symfony\Component\Form\Extension\Csrf\EventListener; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormError; -use Symfony\Component\Form\Event\DataEvent; +use Symfony\Component\Form\Event\FilterDataEvent; use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; /** @@ -22,6 +22,12 @@ use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; */ class CsrfValidationListener implements EventSubscriberInterface { + /** + * The name of the CSRF field + * @var string + */ + private $fieldName; + /** * The provider for generating and validating CSRF tokens * @var CsrfProviderInterface @@ -45,24 +51,26 @@ class CsrfValidationListener implements EventSubscriberInterface ); } - public function __construct(CsrfProviderInterface $csrfProvider, $intention) + public function __construct($fieldName, CsrfProviderInterface $csrfProvider, $intention) { + $this->fieldName = $fieldName; $this->csrfProvider = $csrfProvider; $this->intention = $intention; } - public function onBindClientData(DataEvent $event) + public function onBindClientData(FilterDataEvent $event) { $form = $event->getForm(); $data = $event->getData(); - if ((!$form->hasParent() || $form->getParent()->isRoot()) - && !$this->csrfProvider->isCsrfTokenValid($this->intention, $data)) { - $form->addError(new FormError('The CSRF token is invalid. Please try to resubmit the form')); + if ($form->isRoot() && $form->hasChildren() && isset($data[$this->fieldName])) { + if (!$this->csrfProvider->isCsrfTokenValid($this->intention, $data[$this->fieldName])) { + $form->addError(new FormError('The CSRF token is invalid. Please try to resubmit the form')); + } - // If the session timed out, the token is invalid now. - // Regenerate the token so that a resubmission is possible. - $event->setData($this->csrfProvider->generateCsrfToken($this->intention)); + unset($data[$this->fieldName]); } + + $event->setData($data); } } diff --git a/src/Symfony/Component/Form/Extension/Csrf/EventListener/EnsureCsrfFieldListener.php b/src/Symfony/Component/Form/Extension/Csrf/EventListener/EnsureCsrfFieldListener.php deleted file mode 100644 index 480d67c323..0000000000 --- a/src/Symfony/Component/Form/Extension/Csrf/EventListener/EnsureCsrfFieldListener.php +++ /dev/null @@ -1,66 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Form\Extension\Csrf\EventListener; - -use Symfony\Component\Form\Event\DataEvent; -use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; -use Symfony\Component\Form\FormFactoryInterface; - -/** - * Ensures the CSRF field. - * - * @author Bulat Shakirzyanov - * @author Kris Wallsmith - */ -class EnsureCsrfFieldListener -{ - private $factory; - private $name; - private $intention; - private $provider; - - /** - * Constructor. - * - * @param FormFactoryInterface $factory The form factory - * @param string $name A name for the CSRF field - * @param string $intention The intention string - * @param CsrfProviderInterface $provider The CSRF provider - */ - public function __construct(FormFactoryInterface $factory, $name, $intention = null, CsrfProviderInterface $provider = null) - { - $this->factory = $factory; - $this->name = $name; - $this->intention = $intention; - $this->provider = $provider; - } - - /** - * Ensures a root form has a CSRF field. - * - * This method should be connected to both form.pre_set_data and form.pre_bind. - */ - public function ensureCsrfField(DataEvent $event) - { - $form = $event->getForm(); - - $options = array(); - if ($this->intention) { - $options['intention'] = $this->intention; - } - if ($this->provider) { - $options['csrf_provider'] = $this->provider; - } - - $form->add($this->factory->createNamed('csrf', $this->name, null, $options)); - } -} diff --git a/src/Symfony/Component/Form/Extension/Csrf/Type/ChoiceTypeCsrfExtension.php b/src/Symfony/Component/Form/Extension/Csrf/Type/ChoiceTypeCsrfExtension.php deleted file mode 100644 index dc1e6db15b..0000000000 --- a/src/Symfony/Component/Form/Extension/Csrf/Type/ChoiceTypeCsrfExtension.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Form\Extension\Csrf\Type; - -use Symfony\Component\Form\AbstractTypeExtension; - -class ChoiceTypeCsrfExtension extends AbstractTypeExtension -{ - public function getDefaultOptions() - { - return array('csrf_protection' => false); - } - - public function getExtendedType() - { - return 'choice'; - } -} diff --git a/src/Symfony/Component/Form/Extension/Csrf/Type/CsrfType.php b/src/Symfony/Component/Form/Extension/Csrf/Type/CsrfType.php deleted file mode 100644 index 97c69d8a5a..0000000000 --- a/src/Symfony/Component/Form/Extension/Csrf/Type/CsrfType.php +++ /dev/null @@ -1,83 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Form\Extension\Csrf\Type; - - -use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\FormBuilder; -use Symfony\Component\Form\Extension\Csrf\EventListener\CsrfValidationListener; -use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; - -class CsrfType extends AbstractType -{ - private $csrfProvider; - - /** - * Constructor. - * - * @param CsrfProviderInterface $csrfProvider The provider to use to generate the token - */ - public function __construct(CsrfProviderInterface $csrfProvider) - { - $this->csrfProvider = $csrfProvider; - } - - /** - * Builds the CSRF field. - * - * A validator is added to check the token value when the CSRF field is added to - * a root form - * - * @param FormBuilder $builder The form builder - * @param array $options The options - */ - public function buildForm(FormBuilder $builder, array $options) - { - $csrfProvider = $options['csrf_provider']; - $intention = $options['intention']; - - $builder - ->setData($csrfProvider->generateCsrfToken($intention)) - ->addEventSubscriber(new CsrfValidationListener($csrfProvider, $intention)) - ; - } - - /** - * {@inheritDoc} - */ - public function getDefaultOptions() - { - return array( - 'csrf_provider' => $this->csrfProvider, - 'intention' => null, - 'property_path' => false, - ); - } - - /** - * {@inheritDoc} - */ - public function getParent(array $options) - { - return 'hidden'; - } - - /** - * Returns the name of this form. - * - * @return string 'csrf' - */ - public function getName() - { - return 'csrf'; - } -} diff --git a/src/Symfony/Component/Form/Extension/Csrf/Type/DateTypeCsrfExtension.php b/src/Symfony/Component/Form/Extension/Csrf/Type/DateTypeCsrfExtension.php deleted file mode 100644 index dc54e94ddf..0000000000 --- a/src/Symfony/Component/Form/Extension/Csrf/Type/DateTypeCsrfExtension.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Form\Extension\Csrf\Type; - -use Symfony\Component\Form\AbstractTypeExtension; - -class DateTypeCsrfExtension extends AbstractTypeExtension -{ - public function getDefaultOptions() - { - return array('csrf_protection' => false); - } - - public function getExtendedType() - { - return 'date'; - } -} diff --git a/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php b/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php index 8e2a57459a..c8687d4af4 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php +++ b/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php @@ -12,21 +12,27 @@ namespace Symfony\Component\Form\Extension\Csrf\Type; use Symfony\Component\Form\AbstractTypeExtension; -use Symfony\Component\Form\Extension\Csrf\EventListener\EnsureCsrfFieldListener; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; +use Symfony\Component\Form\Extension\Csrf\EventListener\CsrfValidationListener; use Symfony\Component\Form\FormBuilder; use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormInterface; +/** + * @author Bernhard Schussek + */ class FormTypeCsrfExtension extends AbstractTypeExtension { - private $enabled; - private $fieldName; + private $defaultCsrfProvider; + private $defaultEnabled; + private $defaultFieldName; - public function __construct($enabled = true, $fieldName = '_token') + public function __construct(CsrfProviderInterface $defaultCsrfProvider, $defaultEnabled = true, $defaultFieldName = '_token') { - $this->enabled = $enabled; - $this->fieldName = $fieldName; + $this->defaultCsrfProvider = $defaultCsrfProvider; + $this->defaultEnabled = $defaultEnabled; + $this->defaultFieldName = $defaultFieldName; } /** @@ -41,35 +47,35 @@ class FormTypeCsrfExtension extends AbstractTypeExtension return; } - $listener = new EnsureCsrfFieldListener( - $builder->getFormFactory(), - $options['csrf_field_name'], - $options['intention'], - $options['csrf_provider'] - ); - // use a low priority so higher priority listeners don't remove the field $builder ->setAttribute('csrf_field_name', $options['csrf_field_name']) - ->addEventListener(FormEvents::PRE_SET_DATA, array($listener, 'ensureCsrfField'), -10) - ->addEventListener(FormEvents::PRE_BIND, array($listener, 'ensureCsrfField'), -10) + ->setAttribute('csrf_provider', $options['csrf_provider']) + ->setAttribute('csrf_intention', $options['intention']) + ->setAttribute('csrf_factory', $builder->getFormFactory()) + ->addEventSubscriber(new CsrfValidationListener($options['csrf_field_name'], $options['csrf_provider'], $options['intention'])) ; } /** - * Removes CSRF fields from all the form views except the root one. + * Adds a CSRF field to the root form view. * * @param FormView $view The form view * @param FormInterface $form The form */ - public function buildViewBottomUp(FormView $view, FormInterface $form) + public function buildView(FormView $view, FormInterface $form) { - if ($view->hasParent() && $form->hasAttribute('csrf_field_name')) { + if ($form->isRoot() && $form->hasChildren() && $form->hasAttribute('csrf_field_name')) { $name = $form->getAttribute('csrf_field_name'); + $csrfProvider = $form->getAttribute('csrf_provider'); + $intention = $form->getAttribute('csrf_intention'); + $factory = $form->getAttribute('csrf_factory'); + $data = $csrfProvider->generateCsrfToken($intention); + $csrfForm = $factory->createNamed('hidden', $name, $data, array( + 'property_path' => false, + )); - if (isset($view[$name])) { - unset($view[$name]); - } + $view->addChild($csrfForm->createView($view)); } } @@ -79,9 +85,9 @@ class FormTypeCsrfExtension extends AbstractTypeExtension public function getDefaultOptions() { return array( - 'csrf_protection' => $this->enabled, - 'csrf_field_name' => $this->fieldName, - 'csrf_provider' => null, + 'csrf_protection' => $this->defaultEnabled, + 'csrf_field_name' => $this->defaultFieldName, + 'csrf_provider' => $this->defaultCsrfProvider, 'intention' => 'unknown', ); } diff --git a/src/Symfony/Component/Form/Extension/Csrf/Type/RepeatedTypeCsrfExtension.php b/src/Symfony/Component/Form/Extension/Csrf/Type/RepeatedTypeCsrfExtension.php deleted file mode 100644 index 1115ea4d69..0000000000 --- a/src/Symfony/Component/Form/Extension/Csrf/Type/RepeatedTypeCsrfExtension.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Form\Extension\Csrf\Type; - -use Symfony\Component\Form\AbstractTypeExtension; - -class RepeatedTypeCsrfExtension extends AbstractTypeExtension -{ - public function getDefaultOptions() - { - return array('csrf_protection' => false); - } - - public function getExtendedType() - { - return 'repeated'; - } -} diff --git a/src/Symfony/Component/Form/Extension/Csrf/Type/TimeTypeCsrfExtension.php b/src/Symfony/Component/Form/Extension/Csrf/Type/TimeTypeCsrfExtension.php deleted file mode 100644 index dbd7c0d2df..0000000000 --- a/src/Symfony/Component/Form/Extension/Csrf/Type/TimeTypeCsrfExtension.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Form\Extension\Csrf\Type; - -use Symfony\Component\Form\AbstractTypeExtension; - -class TimeTypeCsrfExtension extends AbstractTypeExtension -{ - public function getDefaultOptions() - { - return array('csrf_protection' => false); - } - - public function getExtendedType() - { - return 'time'; - } -} diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index 8f9e7b6de2..4798c7e5cd 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -975,7 +975,7 @@ class Form implements \IteratorAggregate, FormInterface $parent = $this->parent->createView(); } - $view = new FormView(); + $view = new FormView($this->name); $view->setParent($parent); @@ -989,14 +989,10 @@ class Form implements \IteratorAggregate, FormInterface } } - $childViews = array(); - - foreach ($this->children as $key => $child) { - $childViews[$key] = $child->createView($view); + foreach ($this->children as $child) { + $view->addChild($child->createView($view)); } - $view->setChildren($childViews); - foreach ($types as $type) { $type->buildViewBottomUp($view, $this); diff --git a/src/Symfony/Component/Form/FormView.php b/src/Symfony/Component/Form/FormView.php index 5c8ab13da1..5d88346edc 100644 --- a/src/Symfony/Component/Form/FormView.php +++ b/src/Symfony/Component/Form/FormView.php @@ -11,8 +11,17 @@ namespace Symfony\Component\Form; -class FormView implements \ArrayAccess, \IteratorAggregate, \Countable +use ArrayAccess; +use IteratorAggregate; +use Countable; + +/** + * @author Bernhard Schussek + */ +class FormView implements ArrayAccess, IteratorAggregate, Countable { + private $name; + private $vars = array( 'value' => null, 'attr' => array(), @@ -33,6 +42,16 @@ class FormView implements \ArrayAccess, \IteratorAggregate, \Countable */ private $rendered = false; + public function __construct($name) + { + $this->name = $name; + } + + public function getName() + { + return $this->name; + } + /** * @param string $name * @param mixed $value @@ -177,15 +196,29 @@ class FormView implements \ArrayAccess, \IteratorAggregate, \Countable } /** - * Sets the children view. + * Adds a child view. * - * @param array $children The children as instances of FormView + * @param FormView $child The child view to add. * * @return FormView The current view */ - public function setChildren(array $children) + public function addChild(FormView $child) { - $this->children = $children; + $this->children[$child->getName()] = $child; + + return $this; + } + + /** + * Removes a child view. + * + * @param string $name The name of the removed child view. + * + * @return FormView The current view + */ + public function removeChild($name) + { + unset($this->children[$name]); return $this; } @@ -222,6 +255,18 @@ class FormView implements \ArrayAccess, \IteratorAggregate, \Countable return count($this->children) > 0; } + /** + * Returns whether this view has a given child. + * + * @param string $name The name of the child + * + * @return Boolean Whether the child with the given name exists + */ + public function hasChild($name) + { + return isset($this->children[$name]); + } + /** * Returns a child by name (implements \ArrayAccess). * diff --git a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php index d22113245a..986f99b7b4 100644 --- a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php @@ -363,6 +363,31 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest ); } + public function testCsrf() + { + $this->csrfProvider->expects($this->any()) + ->method('generateCsrfToken') + ->will($this->returnValue('foo&bar')); + + $form = $this->factory->createNamedBuilder('form', 'name') + ->add($this->factory + // No CSRF protection on nested forms + ->createNamedBuilder('form', 'child') + ->add($this->factory->createNamedBuilder('text', 'grandchild')) + ) + ->getForm(); + + $this->assertWidgetMatchesXpath($form->createView(), array(), +'/div + [ + ./input[@type="hidden"][@id="name__token"][@value="foo&bar"] + /following-sibling::div + ] + [count(.//input[@type="hidden"])=1] +' + ); + } + public function testRepeated() { $form = $this->factory->createNamed('repeated', 'name', 'foobar', array( @@ -372,7 +397,8 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest $this->assertWidgetMatchesXpath($form->createView(), array(), '/div [ - ./div + ./input[@type="hidden"][@id="name__token"] + /following-sibling::div [ ./label[@for="name_first"] /following-sibling::input[@type="text"][@id="name_first"] @@ -383,7 +409,7 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest /following-sibling::input[@type="text"][@id="name_second"] ] ] - [count(.//input)=2] + [count(.//input)=3] ' ); } @@ -399,7 +425,8 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest $this->assertWidgetMatchesXpath($form->createView(), array(), '/div [ - ./div + ./input[@type="hidden"][@id="name__token"] + /following-sibling::div [ ./label[@for="name_first"][.="[trans]Test[/trans]"] /following-sibling::input[@type="text"][@id="name_first"][@required="required"] @@ -410,7 +437,7 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest /following-sibling::input[@type="text"][@id="name_second"][@required="required"] ] ] - [count(.//input)=2] + [count(.//input)=3] ' ); } diff --git a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php index 32aabb86a6..5d48b9921c 100644 --- a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php @@ -646,12 +646,13 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase $this->assertWidgetMatchesXpath($form->createView(), array(), '/div [ - ./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked] + ./input[@type="hidden"][@id="name__token"] + /following-sibling::input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked] /following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"] /following-sibling::input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)] /following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"] ] - [count(./input)=2] + [count(./input)=3] ' ); } @@ -668,12 +669,13 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase $this->assertWidgetMatchesXpath($form->createView(), array(), '/div [ - ./input[@type="radio"][@name="name"][@id="name_0"][@checked] + ./input[@type="hidden"][@id="name__token"] + /following-sibling::input[@type="radio"][@name="name"][@id="name_0"][@checked] /following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"] /following-sibling::input[@type="radio"][@name="name"][@id="name_1"][not(@checked)] /following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"] ] - [count(./input)=2] + [count(./input)=3] ' ); } @@ -689,12 +691,13 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase $this->assertWidgetMatchesXpath($form->createView(), array(), '/div [ - ./input[@type="radio"][@name="name"][@id="name_0"][@checked] + ./input[@type="hidden"][@id="name__token"] + /following-sibling::input[@type="radio"][@name="name"][@id="name_0"][@checked] /following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"] /following-sibling::input[@type="radio"][@name="name"][@id="name_1"][not(@checked)] /following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"] ] - [count(./input)=2] + [count(./input)=3] ' ); } @@ -711,14 +714,15 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase $this->assertWidgetMatchesXpath($form->createView(), array(), '/div [ - ./input[@type="checkbox"][@name="name[]"][@id="name_0"][@checked][not(@required)] + ./input[@type="hidden"][@id="name__token"] + /following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_0"][@checked][not(@required)] /following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"] /following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_1"][not(@checked)][not(@required)] /following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"] /following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_2"][@checked][not(@required)] /following-sibling::label[@for="name_2"][.="[trans]Choice&C[/trans]"] ] - [count(./input)=3] + [count(./input)=4] ' ); } @@ -753,22 +757,6 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase ); } - public function testCsrf() - { - $this->csrfProvider->expects($this->any()) - ->method('generateCsrfToken') - ->will($this->returnValue('foo&bar')); - - $form = $this->factory->createNamed('csrf', 'name'); - - $this->assertWidgetMatchesXpath($form->createView(), array(), -'/input - [@type="hidden"] - [@value="foo&bar"] -' - ); - } - public function testDateTime() { $form = $this->factory->createNamed('datetime', 'name', '2011-02-03 04:05:06', array( diff --git a/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php index d47b15564f..5ea8780d89 100644 --- a/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php @@ -45,36 +45,10 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest $html = $this->renderRow($form->createView()); $this->assertMatchesXpath($html, -'/tr - [ - ./td - [./label[@for="name_first"]] - /following-sibling::td - [./input[@id="name_first"]] - ] -/following-sibling::tr - [ - ./td - [./label[@for="name_second"]] - /following-sibling::td - [./input[@id="name_second"]] - ] - [count(../tr)=2] -' - ); - } - - public function testRepeatedRowWithErrors() - { - $form = $this->factory->createNamed('repeated', 'name'); - $form->addError(new FormError('Error!')); - $view = $form->createView(); - $html = $this->renderRow($view); - - $this->assertMatchesXpath($html, -'/tr - [./td[@colspan="2"]/ul - [./li[.="[trans]Error![/trans]"]] +'/tr[@style="display: none"] + [./td[@colspan="2"]/input + [@type="hidden"] + [@id="name__token"] ] /following-sibling::tr [ @@ -95,6 +69,42 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest ); } + public function testRepeatedRowWithErrors() + { + $form = $this->factory->createNamed('repeated', 'name'); + $form->addError(new FormError('Error!')); + $view = $form->createView(); + $html = $this->renderRow($view); + + $this->assertMatchesXpath($html, +'/tr + [./td[@colspan="2"]/ul + [./li[.="[trans]Error![/trans]"]] + ] +/following-sibling::tr[@style="display: none"] + [./td[@colspan="2"]/input + [@type="hidden"] + [@id="name__token"] + ] +/following-sibling::tr + [ + ./td + [./label[@for="name_first"]] + /following-sibling::td + [./input[@id="name_first"]] + ] +/following-sibling::tr + [ + ./td + [./label[@for="name_second"]] + /following-sibling::td + [./input[@id="name_second"]] + ] + [count(../tr)=4] +' + ); + } + public function testRest() { $view = $this->factory->createNamedBuilder('form', 'name') @@ -151,9 +161,9 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest $this->assertWidgetMatchesXpath($form->createView(), array(), '/table [ - ./tr[./td/input[@type="text"][@value="a"]] + ./tr[@style="display: none"][./td[@colspan="2"]/input[@type="hidden"][@id="name__token"]] + /following-sibling::tr[./td/input[@type="text"][@value="a"]] /following-sibling::tr[./td/input[@type="text"][@value="b"]] - /following-sibling::tr[./td/input[@type="hidden"][@id="name__token"]] ] [count(./tr[./td/input])=3] ' @@ -217,6 +227,34 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest ); } + public function testCsrf() + { + $this->csrfProvider->expects($this->any()) + ->method('generateCsrfToken') + ->will($this->returnValue('foo&bar')); + + $form = $this->factory->createNamedBuilder('form', 'name') + ->add($this->factory + // No CSRF protection on nested forms + ->createNamedBuilder('form', 'child') + ->add($this->factory->createNamedBuilder('text', 'grandchild')) + ) + ->getForm(); + + $this->assertWidgetMatchesXpath($form->createView(), array(), +'/table + [ + ./tr[@style="display: none"] + [./td[@colspan="2"]/input + [@type="hidden"] + [@id="name__token"] + ] + ] + [count(.//input[@type="hidden"])=1] +' + ); + } + public function testRepeated() { $form = $this->factory->createNamed('repeated', 'name', 'foobar', array( @@ -226,7 +264,12 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest $this->assertWidgetMatchesXpath($form->createView(), array(), '/table [ - ./tr + ./tr[@style="display: none"] + [./td[@colspan="2"]/input + [@type="hidden"] + [@id="name__token"] + ] + /following-sibling::tr [ ./td [./label[@for="name_first"]] @@ -241,7 +284,7 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest [./input[@type="text"][@id="name_second"]] ] ] - [count(.//input)=2] + [count(.//input)=3] ' ); } @@ -257,7 +300,12 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest $this->assertWidgetMatchesXpath($form->createView(), array(), '/table [ - ./tr + ./tr[@style="display: none"] + [./td[@colspan="2"]/input + [@type="hidden"] + [@id="name__token"] + ] + /following-sibling::tr [ ./td [./label[@for="name_first"][.="[trans]Test[/trans]"]] @@ -272,7 +320,7 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest [./input[@type="password"][@id="name_second"][@required="required"]] ] ] - [count(.//input)=2] + [count(.//input)=3] ' ); } diff --git a/src/Symfony/Component/Form/Tests/Extension/Csrf/EventListener/EnsureCsrfFieldListenerTest.php b/src/Symfony/Component/Form/Tests/Extension/Csrf/EventListener/EnsureCsrfFieldListenerTest.php deleted file mode 100644 index 9b87717e5d..0000000000 --- a/src/Symfony/Component/Form/Tests/Extension/Csrf/EventListener/EnsureCsrfFieldListenerTest.php +++ /dev/null @@ -1,87 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Form\Tests\Extension\Csrf\EventListener; - -use Symfony\Component\Form\Event\DataEvent; -use Symfony\Component\Form\Extension\Csrf\EventListener\EnsureCsrfFieldListener; - -class EnsureCsrfFieldListenerTest extends \PHPUnit_Framework_TestCase -{ - private $form; - private $formFactory; - private $field; - private $event; - - protected function setUp() - { - if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) { - $this->markTestSkipped('The "EventDispatcher" component is not available'); - } - - $this->formFactory = $this->getMock('Symfony\\Component\\Form\\FormFactoryInterface'); - $this->form = $this->getMock('Symfony\\Component\\Form\\Tests\\FormInterface'); - $this->field = $this->getMock('Symfony\\Component\\Form\\Tests\\FormInterface'); - $this->event = new DataEvent($this->form, array()); - } - - protected function tearDown() - { - $this->form = null; - $this->formFactory = null; - $this->field = null; - $this->event = null; - } - - public function testAddField() - { - $this->formFactory->expects($this->once()) - ->method('createNamed') - ->with('csrf', '_token', null, array()) - ->will($this->returnValue($this->field)); - $this->form->expects($this->once()) - ->method('add') - ->with($this->isInstanceOf('Symfony\\Component\\Form\\Tests\\FormInterface')); - - $listener = new EnsureCsrfFieldListener($this->formFactory, '_token'); - $listener->ensureCsrfField($this->event); - } - - public function testIntention() - { - $this->formFactory->expects($this->once()) - ->method('createNamed') - ->with('csrf', '_token', null, array('intention' => 'something')) - ->will($this->returnValue($this->field)); - $this->form->expects($this->once()) - ->method('add') - ->with($this->isInstanceOf('Symfony\\Component\\Form\\Tests\\FormInterface')); - - $listener = new EnsureCsrfFieldListener($this->formFactory, '_token', 'something'); - $listener->ensureCsrfField($this->event); - } - - public function testProvider() - { - $provider = $this->getMock('Symfony\\Component\\Form\\Extension\\Csrf\\CsrfProvider\\CsrfProviderInterface'); - - $this->formFactory->expects($this->once()) - ->method('createNamed') - ->with('csrf', '_token', null, array('csrf_provider' => $provider)) - ->will($this->returnValue($this->field)); - $this->form->expects($this->once()) - ->method('add') - ->with($this->isInstanceOf('Symfony\\Component\\Form\\Tests\\FormInterface')); - - $listener = new EnsureCsrfFieldListener($this->formFactory, '_token', null, $provider); - $listener->ensureCsrfField($this->event); - } -} diff --git a/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/CsrfTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/CsrfTypeTest.php deleted file mode 100644 index 4483e7f392..0000000000 --- a/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/CsrfTypeTest.php +++ /dev/null @@ -1,112 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Form\Tests\Extension\Csrf\Type; - -class CsrfTypeTest extends TypeTestCase -{ - protected $provider; - - protected function setUp() - { - parent::setUp(); - - $this->provider = $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface'); - } - - protected function tearDown() - { - parent::tearDown(); - - $this->provider = null; - } - - protected function getNonRootForm() - { - $form = $this->getMock('Symfony\Component\Form\Tests\FormInterface'); - $form->expects($this->any()) - ->method('isRoot') - ->will($this->returnValue(false)); - - return $form; - } - - public function testGenerateCsrfToken() - { - $this->provider->expects($this->once()) - ->method('generateCsrfToken') - ->with('%INTENTION%') - ->will($this->returnValue('token')); - - $form = $this->factory->create('csrf', null, array( - 'csrf_provider' => $this->provider, - 'intention' => '%INTENTION%' - )); - - $this->assertEquals('token', $form->getData()); - } - - public function testValidateTokenOnBind() - { - $this->provider->expects($this->once()) - ->method('isCsrfTokenValid') - ->with('%INTENTION%', 'token') - ->will($this->returnValue(true)); - - $form = $this->factory->create('csrf', null, array( - 'csrf_provider' => $this->provider, - 'intention' => '%INTENTION%' - )); - $form->bind('token'); - - $this->assertEquals('token', $form->getData()); - } - - public function testDontValidateTokenIfParentIsNotRoot() - { - $this->provider->expects($this->never()) - ->method('isCsrfTokenValid'); - - $form = $this->factory->create('csrf', null, array( - 'csrf_provider' => $this->provider, - 'intention' => '%INTENTION%' - )); - $form->setParent($this->getNonRootForm()); - $form->bind('token'); - } - - public function testCsrfTokenIsRegeneratedIfValidationFails() - { - $this->provider->expects($this->at(0)) - ->method('generateCsrfToken') - ->with('%INTENTION%') - ->will($this->returnValue('token1')); - $this->provider->expects($this->at(1)) - ->method('isCsrfTokenValid') - ->with('%INTENTION%', 'invalid') - ->will($this->returnValue(false)); - - // The token is regenerated to avoid stalled tokens, for example when - // the session ID changed - $this->provider->expects($this->at(2)) - ->method('generateCsrfToken') - ->with('%INTENTION%') - ->will($this->returnValue('token2')); - - $form = $this->factory->create('csrf', null, array( - 'csrf_provider' => $this->provider, - 'intention' => '%INTENTION%' - )); - $form->bind('invalid'); - - $this->assertEquals('token2', $form->getData()); - } -} diff --git a/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php index 20da376d79..9024002b52 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php @@ -11,43 +11,188 @@ namespace Symfony\Component\Form\Tests\Extension\Csrf\Type; +use Symfony\Component\Form\Extension\Csrf\CsrfExtension; +use Symfony\Component\Form\Tests\Extension\Core\Type\TypeTestCase; + class FormTypeCsrfExtensionTest extends TypeTestCase { - public function testCsrfProtectionByDefault() - { - $form = $this->factory->create('form', null, array( - 'csrf_field_name' => 'csrf', - )); + protected $csrfProvider; - $this->assertTrue($form->has('csrf')); + protected function setUp() + { + $this->csrfProvider = $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface'); + + parent::setUp(); + } + + protected function tearDown() + { + $this->csrfProvider = null; + + parent::tearDown(); + } + + protected function getExtensions() + { + return array_merge(parent::getExtensions(), array( + new CsrfExtension($this->csrfProvider), + )); + } + + public function testCsrfProtectionByDefaultIfRootAndChildren() + { + $view = $this->factory + ->createBuilder('form', null, array( + 'csrf_field_name' => 'csrf', + )) + ->add($this->factory->createNamedBuilder('form', 'child')) + ->getForm() + ->createView(); + + $this->assertTrue($view->hasChild('csrf')); + } + + public function testNoCsrfProtectionByDefaultIfChildrenButNotRoot() + { + $view = $this->factory + ->createNamedBuilder('form', 'root') + ->add($this->factory + ->createNamedBuilder('form', 'form', null, array( + 'csrf_field_name' => 'csrf', + )) + ->add($this->factory->createNamedBuilder('form', 'child')) + ) + ->getForm() + ->get('form') + ->createView(); + + $this->assertFalse($view->hasChild('csrf')); + } + + public function testNoCsrfProtectionByDefaultIfRootButNoChildren() + { + $view = $this->factory + ->createBuilder('form', null, array( + 'csrf_field_name' => 'csrf', + )) + ->getForm() + ->createView(); + + $this->assertFalse($view->hasChild('csrf')); } public function testCsrfProtectionCanBeDisabled() { - $form = $this->factory->create('form', null, array( - 'csrf_protection' => false, - )); - - $this->assertCount(0, $form); - } - - public function testCsrfTokenIsOnlyIncludedInRootView() - { - $view = - $this->factory->createBuilder('form', null, array( + $view = $this->factory + ->createBuilder('form', null, array( 'csrf_field_name' => 'csrf', + 'csrf_protection' => false, )) - ->add('notCsrf', 'text') - ->add( - $this->factory->createNamedBuilder('form', 'child', null, array( - 'csrf_field_name' => 'csrf', - )) - ->add('notCsrf', 'text') - ) + ->add($this->factory->createNamedBuilder('form', 'child')) ->getForm() ->createView(); - $this->assertEquals(array('csrf', 'notCsrf', 'child'), array_keys(iterator_to_array($view))); - $this->assertEquals(array('notCsrf'), array_keys(iterator_to_array($view['child']))); + $this->assertFalse($view->hasChild('csrf')); + } + + public function testGenerateCsrfToken() + { + $this->csrfProvider->expects($this->once()) + ->method('generateCsrfToken') + ->with('%INTENTION%') + ->will($this->returnValue('token')); + + $view = $this->factory + ->createBuilder('form', null, array( + 'csrf_field_name' => 'csrf', + 'csrf_provider' => $this->csrfProvider, + 'intention' => '%INTENTION%' + )) + ->add($this->factory->createNamedBuilder('form', 'child')) + ->getForm() + ->createView(); + + $this->assertEquals('token', $view->getChild('csrf')->get('value')); + } + + public function provideBoolean() + { + return array( + array(true), + array(false), + ); + } + + /** + * @dataProvider provideBoolean + */ + public function testValidateTokenOnBindIfRootAndChildren($valid) + { + $this->csrfProvider->expects($this->once()) + ->method('isCsrfTokenValid') + ->with('%INTENTION%', 'token') + ->will($this->returnValue($valid)); + + $form = $this->factory + ->createBuilder('form', null, array( + 'csrf_field_name' => 'csrf', + 'csrf_provider' => $this->csrfProvider, + 'intention' => '%INTENTION%' + )) + ->add($this->factory->createNamedBuilder('form', 'child')) + ->getForm(); + + $form->bind(array( + 'child' => 'foobar', + 'csrf' => 'token', + )); + + // Remove token from data + $this->assertSame(array('child' => 'foobar'), $form->getData()); + + // Validate accordingly + $this->assertSame($valid, $form->isValid()); + } + + public function testDontValidateTokenIfChildrenButNoRoot() + { + $this->csrfProvider->expects($this->never()) + ->method('isCsrfTokenValid'); + + $form = $this->factory + ->createNamedBuilder('form', 'root') + ->add($this->factory + ->createNamedBuilder('form', 'form', null, array( + 'csrf_field_name' => 'csrf', + 'csrf_provider' => $this->csrfProvider, + 'intention' => '%INTENTION%' + )) + ->add($this->factory->createNamedBuilder('form', 'child')) + ) + ->getForm() + ->get('form'); + + $form->bind(array( + 'child' => 'foobar', + 'csrf' => 'token', + )); + } + + public function testDontValidateTokenIfRootButNoChildren() + { + $this->csrfProvider->expects($this->never()) + ->method('isCsrfTokenValid'); + + $form = $this->factory + ->createBuilder('form', null, array( + 'csrf_field_name' => 'csrf', + 'csrf_provider' => $this->csrfProvider, + 'intention' => '%INTENTION%' + )) + ->getForm(); + + $form->bind(array( + 'csrf' => 'token', + )); } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/TypeTestCase.php b/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/TypeTestCase.php deleted file mode 100644 index 3b1ce91783..0000000000 --- a/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/TypeTestCase.php +++ /dev/null @@ -1,41 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Form\Tests\Extension\Csrf\Type; - -use Symfony\Component\Form\Tests\Extension\Core\Type\TypeTestCase as BaseTestCase; -use Symfony\Component\Form\Extension\Csrf\CsrfExtension; - -abstract class TypeTestCase extends BaseTestCase -{ - protected $csrfProvider; - - protected function setUp() - { - $this->csrfProvider = $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface'); - - parent::setUp(); - } - - protected function tearDown() - { - $this->csrfProvider = null; - - parent::tearDown(); - } - - protected function getExtensions() - { - return array_merge(parent::getExtensions(), array( - new CsrfExtension($this->csrfProvider), - )); - } -} diff --git a/src/Symfony/Component/Form/Tests/FormTest.php b/src/Symfony/Component/Form/Tests/FormTest.php index a5c20dcbcf..bc25e16a1f 100644 --- a/src/Symfony/Component/Form/Tests/FormTest.php +++ b/src/Symfony/Component/Form/Tests/FormTest.php @@ -1247,7 +1247,7 @@ class FormTest extends \PHPUnit_Framework_TestCase public function testCreateViewAcceptsParent() { - $parent = new FormView(); + $parent = new FormView('form'); $form = $this->getBuilder()->getForm(); $view = $form->createView($parent);