Browse Source

[Form] Added FormTypeExtensionInterface

With implementations of this interface, existing types can be amended.
The Csrf extension, for example, now contains a class FormTypeCsrfExtension
that adds CSRF capabilities to the "form" type.

To register new type extensions in the DIC, tag them with "form.type_extension"
and the name of the extended type as alias.
Bernhard Schussek 14 năm trước cách đây
mục cha
commit
1ce2db87e2
22 tập tin đã thay đổi với 431 bổ sung81 xóa
  1. 17 1
      src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/FormPass.php
  2. 15 5
      src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml
  3. 60 9
      src/Symfony/Component/Form/AbstractExtension.php
  4. 18 0
      src/Symfony/Component/Form/AbstractType.php
  5. 32 0
      src/Symfony/Component/Form/AbstractTypeExtension.php
  6. 0 14
      src/Symfony/Component/Form/Extension/Core/Type/FormType.php
  7. 4 1
      src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php
  8. 46 0
      src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php
  9. 27 7
      src/Symfony/Component/Form/Extension/DependencyInjection/DependencyInjectionExtension.php
  10. 8 0
      src/Symfony/Component/Form/Form.php
  11. 4 0
      src/Symfony/Component/Form/FormExtensionInterface.php
  12. 53 12
      src/Symfony/Component/Form/FormFactory.php
  13. 25 0
      src/Symfony/Component/Form/FormTypeExtensionInterface.php
  14. 4 0
      src/Symfony/Component/Form/FormTypeInterface.php
  15. 4 0
      tests/Symfony/Tests/Component/Form/AbstractExtensionTest.php
  16. 2 4
      tests/Symfony/Tests/Component/Form/Extension/Core/Type/CollectionTypeTest.php
  17. 0 18
      tests/Symfony/Tests/Component/Form/Extension/Core/Type/FormTypeTest.php
  18. 0 5
      tests/Symfony/Tests/Component/Form/Extension/Core/Type/TypeTestCase.php
  19. 0 2
      tests/Symfony/Tests/Component/Form/Extension/Csrf/Type/CsrfTypeTest.php
  20. 33 0
      tests/Symfony/Tests/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php
  21. 34 0
      tests/Symfony/Tests/Component/Form/Extension/Csrf/Type/TypeTestCase.php
  22. 45 3
      tests/Symfony/Tests/Component/Form/FormTest.php

+ 17 - 1
src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/FormPass.php

@@ -42,9 +42,25 @@ class FormPass implements CompilerPassInterface
 
         $container->getDefinition('form.extension')->replaceArgument(1, $types);
 
+        $typeExtensions = array();
+
+        foreach ($container->findTaggedServiceIds('form.type_extension') as $serviceId => $tag) {
+            $alias = isset($tag[0]['alias'])
+                ? $tag[0]['alias']
+                : $serviceId;
+
+            if (!isset($typeExtensions[$alias])) {
+                $typeExtensions[$alias] = array();
+            }
+
+            $typeExtensions[$alias][] = $serviceId;
+        }
+
+        $container->getDefinition('form.extension')->replaceArgument(2, $typeExtensions);
+
         // Find all services annotated with "form.type_guesser"
         $guessers = array_keys($container->findTaggedServiceIds('form.type_guesser'));
 
-        $container->getDefinition('form.extension')->replaceArgument(2, $guessers);
+        $container->getDefinition('form.extension')->replaceArgument(3, $guessers);
     }
 }

+ 15 - 5
src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml

@@ -42,6 +42,11 @@
             -->
             <argument type="collection" />
             <!--
+            All services with tag "form.type_extension" are inserted here by
+            InitFormsPass
+            -->
+            <argument type="collection" />
+            <!--
             All services with tag "form.type_guesser" are inserted here by
             InitFormsPass
             -->
@@ -68,7 +73,7 @@
             <argument>%file.temporary_storage.directory%</argument>
         </service>
         
-        <!-- FieldTypes -->
+        <!-- CoreExtension -->
         <service id="form.type.field" class="Symfony\Component\Form\Extension\Core\Type\FieldType">
             <tag name="form.type" alias="field" />
             <argument type="service" id="validator" />
@@ -91,10 +96,6 @@
         <service id="form.type.country" class="Symfony\Component\Form\Extension\Core\Type\CountryType">
             <tag name="form.type" alias="country" />
         </service>
-        <service id="form.type.csrf" class="Symfony\Component\Form\Extension\Csrf\Type\CsrfType">
-            <tag name="form.type" alias="csrf" />
-            <argument type="service" id="form.csrf_provider" />
-        </service>
         <service id="form.type.date" class="Symfony\Component\Form\Extension\Core\Type\DateType">
             <tag name="form.type" alias="date" />
         </service>
@@ -153,6 +154,15 @@
         <service id="form.type.url" class="Symfony\Component\Form\Extension\Core\Type\UrlType">
             <tag name="form.type" alias="url" />
         </service>
+        
+        <!-- CsrfExtension -->
+        <service id="form.type.csrf" class="Symfony\Component\Form\Extension\Csrf\Type\CsrfType">
+            <tag name="form.type" alias="csrf" />
+            <argument type="service" id="form.csrf_provider" />
+        </service>
+        <service id="form.type_extension.csrf" class="Symfony\Component\Form\Extension\Csrf\Type\FormTypeCsrfExtension">
+            <tag name="form.type_extension" alias="form" />
+        </service>
 
     </services>
 </container>

+ 60 - 9
src/Symfony/Component/Form/AbstractExtension.php

@@ -25,9 +25,9 @@ abstract class AbstractExtension implements FormExtensionInterface
     private $types;
 
     /**
-     * @var Boolean
+     * @var array
      */
-    private $typesLoaded = false;
+    private $typeExtensions;
 
     /**
      * @var FormTypeGuesserInterface
@@ -39,14 +39,23 @@ abstract class AbstractExtension implements FormExtensionInterface
      */
     private $typeGuesserLoaded = false;
 
-    abstract protected function loadTypes();
+    protected function loadTypes()
+    {
+        return array();
+    }
 
-    abstract protected function loadTypeGuesser();
+    protected function loadTypeExtensions()
+    {
+        return array();
+    }
 
-    private function initTypes()
+    protected function loadTypeGuesser()
     {
-        $this->typesLoaded = true;
+        return null;
+    }
 
+    private function initTypes()
+    {
         $types = $this->loadTypes();
         $typesByName = array();
 
@@ -61,6 +70,28 @@ abstract class AbstractExtension implements FormExtensionInterface
         $this->types = $typesByName;
     }
 
+    private function initTypeExtensions()
+    {
+        $extensions = $this->loadTypeExtensions();
+        $extensionsByType = array();
+
+        foreach ($extensions as $extension) {
+            if (!$extension instanceof FormTypeExtensionInterface) {
+                throw new UnexpectedTypeException($extension, 'Symfony\Component\Form\FormTypeExtensionInterface');
+            }
+
+            $type = $extension->getExtendedType();
+
+            if (!isset($extensionsByType[$type])) {
+                $extensionsByType[$type] = array();
+            }
+
+            $extensionsByType[$type][] = $extension;
+        }
+
+        $this->typeExtensions = $extensionsByType;
+    }
+
     private function initTypeGuesser()
     {
         $this->typeGuesserLoaded = true;
@@ -76,12 +107,12 @@ abstract class AbstractExtension implements FormExtensionInterface
 
     public function getType($name)
     {
-        if (!$this->typesLoaded) {
+        if (null === $this->types) {
             $this->initTypes();
         }
 
         if (!isset($this->types[$name])) {
-            throw new FormException(sprintf('The type "%s" can not be typesLoaded by this extension', $name));
+            throw new FormException(sprintf('The type "%s" can not be loaded by this extension', $name));
         }
 
         return $this->types[$name];
@@ -89,13 +120,33 @@ abstract class AbstractExtension implements FormExtensionInterface
 
     public function hasType($name)
     {
-        if (!$this->typesLoaded) {
+        if (null === $this->types) {
             $this->initTypes();
         }
 
         return isset($this->types[$name]);
     }
 
+    function getTypeExtensions($name)
+    {
+        if (null === $this->typeExtensions) {
+            $this->initTypeExtensions();
+        }
+
+        return isset($this->typeExtensions[$name])
+            ? $this->typeExtensions[$name]
+            : array();
+    }
+
+    function hasTypeExtensions($name)
+    {
+        if (null === $this->typeExtensions) {
+            $this->initTypeExtensions();
+        }
+
+        return isset($this->typeExtensions[$name]) && count($this->typeExtensions[$name]) > 0;
+    }
+
     public function getTypeGuesser()
     {
         if (!$this->typeGuesserLoaded) {

+ 18 - 0
src/Symfony/Component/Form/AbstractType.php

@@ -13,6 +13,8 @@ namespace Symfony\Component\Form;
 
 abstract class AbstractType implements FormTypeInterface
 {
+    private $extensions = array();
+
     public function buildForm(FormBuilder $builder, array $options)
     {
     }
@@ -46,4 +48,20 @@ abstract class AbstractType implements FormTypeInterface
 
         return strtolower($matches[1]);
     }
+
+    public function setExtensions(array $extensions)
+    {
+        foreach ($extensions as $extension) {
+            if (!$extension instanceof FormTypeExtensionInterface) {
+                throw new UnexpectedTypeException($extension, 'Symfony\Component\Form\FormTypeExtensionInterface');
+            }
+        }
+
+        $this->extensions = $extensions;
+    }
+
+    public function getExtensions()
+    {
+        return $this->extensions;
+    }
 }

+ 32 - 0
src/Symfony/Component/Form/AbstractTypeExtension.php

@@ -0,0 +1,32 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Form;
+
+abstract class AbstractTypeExtension implements FormTypeExtensionInterface
+{
+    public function buildForm(FormBuilder $builder, array $options)
+    {
+    }
+
+    public function buildView(FormView $view, FormInterface $form)
+    {
+    }
+
+    public function buildViewBottomUp(FormView $view, FormInterface $form)
+    {
+    }
+
+    public function getDefaultOptions(array $options)
+    {
+        return array();
+    }
+}

+ 0 - 14
src/Symfony/Component/Form/Extension/Core/Type/FormType.php

@@ -25,16 +25,6 @@ class FormType extends AbstractType
     {
         $builder->setAttribute('virtual', $options['virtual'])
             ->setDataMapper(new PropertyPathMapper($options['data_class']));
-
-        if ($options['csrf_protection']) {
-            $csrfOptions = array('page_id' => $options['csrf_page_id']);
-
-            if ($options['csrf_provider']) {
-                $csrfOptions['csrf_provider'] = $options['csrf_provider'];
-            }
-
-            $builder->add($options['csrf_field_name'], 'csrf', $csrfOptions);
-        }
     }
 
     public function buildViewBottomUp(FormView $view, FormInterface $form)
@@ -54,10 +44,6 @@ class FormType extends AbstractType
     public function getDefaultOptions(array $options)
     {
         $defaultOptions = array(
-            'csrf_protection' => true,
-            'csrf_field_name' => '_token',
-            'csrf_provider' => null,
-            'csrf_page_id' => get_class($this),
             'virtual' => false,
             // Errors in forms bubble by default, so that form errors will
             // end up as global errors in the root form

+ 4 - 1
src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php

@@ -31,7 +31,10 @@ class CsrfExtension extends AbstractExtension
         );
     }
 
-    protected function loadTypeGuesser()
+    protected function loadTypeExtensions()
     {
+        return array(
+            new Type\FormTypeCsrfExtension(),
+        );
     }
 }

+ 46 - 0
src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php

@@ -0,0 +1,46 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * 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;
+use Symfony\Component\Form\FormBuilder;
+
+class FormTypeCsrfExtension extends AbstractTypeExtension
+{
+    public function buildForm(FormBuilder $builder, array $options)
+    {
+        if ($options['csrf_protection']) {
+            $csrfOptions = array('page_id' => $options['csrf_page_id']);
+
+            if ($options['csrf_provider']) {
+                $csrfOptions['csrf_provider'] = $options['csrf_provider'];
+            }
+
+            $builder->add($options['csrf_field_name'], 'csrf', $csrfOptions);
+        }
+    }
+
+    public function getDefaultOptions(array $options)
+    {
+        return array(
+            'csrf_protection' => true,
+            'csrf_field_name' => '_token',
+            'csrf_provider' => null,
+            'csrf_page_id' => get_class($this),
+        );
+    }
+
+    public function getExtendedType()
+    {
+        return 'form';
+    }
+}

+ 27 - 7
src/Symfony/Component/Form/Extension/DependencyInjection/DependencyInjectionExtension.php

@@ -28,25 +28,45 @@ class DependencyInjectionExtension implements FormExtensionInterface
     private $guesserLoaded = false;
 
     public function __construct(ContainerInterface $container,
-        array $typeServiceIds, array $guesserServiceIds)
+        array $typeServiceIds, array $typeExtensionServiceIds,
+        array $guesserServiceIds)
     {
         $this->container = $container;
         $this->typeServiceIds = $typeServiceIds;
+        $this->typeExtensionServiceIds = $typeExtensionServiceIds;
         $this->guesserServiceIds = $guesserServiceIds;
     }
 
-    public function getType($identifier)
+    public function getType($name)
     {
-        if (!isset($this->typeServiceIds[$identifier])) {
-            throw new \InvalidArgumentException(sprintf('The field type "%s" is not registered with the service container.', $identifier));
+        if (!isset($this->typeServiceIds[$name])) {
+            throw new \InvalidArgumentException(sprintf('The field type "%s" is not registered with the service container.', $name));
         }
 
-        return $this->container->get($this->typeServiceIds[$identifier]);
+        return $this->container->get($this->typeServiceIds[$name]);
     }
 
-    public function hasType($identifier)
+    public function hasType($name)
     {
-        return isset($this->typeServiceIds[$identifier]);
+        return isset($this->typeServiceIds[$name]);
+    }
+
+    public function getTypeExtensions($name)
+    {
+        $extensions = array();
+
+        if (isset($this->typeExtensionServiceIds[$name])) {
+            foreach ($this->typeExtensionServiceIds[$name] as $serviceId) {
+                $extensions[] = $this->container->get($serviceId);
+            }
+        }
+
+        return $extensions;
+    }
+
+    public function hasTypeExtensions($name)
+    {
+        return isset($this->typeExtensionServiceIds[$name]);
     }
 
     public function getTypeGuesser()

+ 8 - 0
src/Symfony/Component/Form/Form.php

@@ -844,6 +844,10 @@ class Form implements \IteratorAggregate, FormInterface
 
         foreach ($types as $type) {
             $type->buildView($view, $this);
+
+            foreach ($type->getExtensions() as $typeExtension) {
+                $typeExtension->buildView($view, $this);
+            }
         }
 
         foreach ($this->children as $key => $child) {
@@ -854,6 +858,10 @@ class Form implements \IteratorAggregate, FormInterface
 
         foreach ($types as $type) {
             $type->buildViewBottomUp($view, $this);
+
+            foreach ($type->getExtensions() as $typeExtension) {
+                $typeExtension->buildViewBottomUp($view, $this);
+            }
         }
 
         return $view;

+ 4 - 0
src/Symfony/Component/Form/FormExtensionInterface.php

@@ -17,5 +17,9 @@ interface FormExtensionInterface
 
     function hasType($name);
 
+    function getTypeExtensions($name);
+
+    function hasTypeExtensions($name);
+
     function getTypeGuesser();
 }

+ 53 - 12
src/Symfony/Component/Form/FormFactory.php

@@ -20,6 +20,8 @@ class FormFactory implements FormFactoryInterface
 {
     private $extensions = array();
 
+    private $types = array();
+
     private $guesser;
 
     public function __construct(array $extensions)
@@ -48,6 +50,46 @@ class FormFactory implements FormFactoryInterface
         $this->guesser = new FormTypeGuesserChain($guessers);
     }
 
+    public function getType($name)
+    {
+        $type = null;
+
+        if ($name instanceof FormTypeInterface) {
+            $type = $name;
+            $name = $type->getName();
+        }
+
+        if (!isset($this->types[$name])) {
+            if (!$type) {
+                foreach ($this->extensions as $extension) {
+                    if ($extension->hasType($name)) {
+                        $type = $extension->getType($name);
+                        break;
+                    }
+                }
+
+                if (!$type) {
+                    throw new FormException(sprintf('Could not load type "%s"', $name));
+                }
+            }
+
+            $typeExtensions = array();
+
+            foreach ($this->extensions as $extension) {
+                $typeExtensions = array_merge(
+                    $typeExtensions,
+                    $extension->getTypeExtensions($name)
+                );
+            }
+
+            $type->setExtensions($typeExtensions);
+
+            $this->types[$name] = $type;
+        }
+
+        return $this->types[$name];
+    }
+
     public function create($type, $data = null, array $options = array())
     {
         return $this->createBuilder($type, $data, $options)->getForm();
@@ -77,27 +119,22 @@ class FormFactory implements FormFactoryInterface
     {
         $builder = null;
         $types = array();
+        $typeExtensions = array();
         $knownOptions = array();
         $passedOptions = array_keys($options);
 
         while (null !== $type) {
-            if (!$type instanceof FormTypeInterface) {
-                foreach ($this->extensions as $extension) {
-                    if ($extension->hasType($type)) {
-                        $type = $extension->getType($type);
-                        break;
-                    }
-                }
+            $type = $this->getType($type);
 
-                if (!$type) {
-                    throw new FormException(sprintf('Could not load type "%s"', $type));
-                }
+            $defaultOptions = $type->getDefaultOptions($options);
+
+            foreach ($type->getExtensions() as $typeExtension) {
+                $defaultOptions = array_merge($defaultOptions, $typeExtension->getDefaultOptions($options));
             }
 
-            array_unshift($types, $type);
-            $defaultOptions = $type->getDefaultOptions($options);
             $options = array_merge($defaultOptions, $options);
             $knownOptions = array_merge($knownOptions, array_keys($defaultOptions));
+            array_unshift($types, $type);
             $type = $type->getParent($options);
         }
 
@@ -117,6 +154,10 @@ class FormFactory implements FormFactoryInterface
 
         foreach ($types as $type) {
             $type->buildForm($builder, $options);
+
+            foreach ($type->getExtensions() as $typeExtension) {
+                $typeExtension->buildForm($builder, $options);
+            }
         }
 
         if (null !== $data) {

+ 25 - 0
src/Symfony/Component/Form/FormTypeExtensionInterface.php

@@ -0,0 +1,25 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Form;
+
+interface FormTypeExtensionInterface
+{
+    function buildForm(FormBuilder $builder, array $options);
+
+    function buildView(FormView $view, FormInterface $form);
+
+    function buildViewBottomUp(FormView $view, FormInterface $form);
+
+    function getDefaultOptions(array $options);
+
+    function getExtendedType();
+}

+ 4 - 0
src/Symfony/Component/Form/FormTypeInterface.php

@@ -26,4 +26,8 @@ interface FormTypeInterface
     function getParent(array $options);
 
     function getName();
+
+    function setExtensions(array $extensions);
+
+    function getExtensions();
 }

+ 4 - 0
tests/Symfony/Tests/Component/Form/AbstractExtensionTest.php

@@ -53,6 +53,10 @@ class TestType implements FormTypeInterface
     function getDefaultOptions(array $options) {}
 
     function getParent(array $options) {}
+
+    function setExtensions(array $extensions) {}
+
+    function getExtensions() {}
 }
 
 class TestExtension extends AbstractExtension

+ 2 - 4
tests/Symfony/Tests/Component/Form/Extension/Core/Type/CollectionTypeTest.php

@@ -16,15 +16,13 @@ use Symfony\Component\Form\Form;
 
 class CollectionFormTest extends TypeTestCase
 {
-    public function testContainsOnlyCsrfTokenByDefault()
+    public function testContainsNoFieldByDefault()
     {
         $form = $this->factory->create('collection', null, array(
             'type' => 'field',
-            'csrf_field_name' => 'abc',
         ));
 
-        $this->assertTrue($form->has('abc'));
-        $this->assertEquals(1, count($form));
+        $this->assertEquals(0, count($form));
     }
 
     public function testSetDataAdjustsSize()

+ 0 - 18
tests/Symfony/Tests/Component/Form/Extension/Core/Type/FormTypeTest.php

@@ -63,24 +63,6 @@ class FormTest_AuthorWithoutRefSetter
 
 class FormTypeTest extends TypeTestCase
 {
-    public function testCsrfProtectionByDefault()
-    {
-        $form =  $this->factory->create('form', null, array(
-            'csrf_field_name' => 'csrf',
-        ));
-
-        $this->assertTrue($form->has('csrf'));
-    }
-
-    public function testCsrfProtectionCanBeDisabled()
-    {
-        $form =  $this->factory->create('form', null, array(
-            'csrf_protection' => false,
-        ));
-
-        $this->assertEquals(0, count($form));
-    }
-
     public function testValidationGroupNullByDefault()
     {
         $form =  $this->factory->create('form');

+ 0 - 5
tests/Symfony/Tests/Component/Form/Extension/Core/Type/TypeTestCase.php

@@ -14,13 +14,10 @@ namespace Symfony\Tests\Component\Form\Extension\Core\Type;
 use Symfony\Component\Form\FormBuilder;
 use Symfony\Component\Form\FormFactory;
 use Symfony\Component\Form\Extension\Core\CoreExtension;
-use Symfony\Component\Form\Extension\Csrf\CsrfExtension;
 use Symfony\Component\EventDispatcher\EventDispatcher;
 
 abstract class TypeTestCase extends \PHPUnit_Framework_TestCase
 {
-    protected $csrfProvider;
-
     protected $validator;
 
     protected $storage;
@@ -35,7 +32,6 @@ abstract class TypeTestCase extends \PHPUnit_Framework_TestCase
 
     protected function setUp()
     {
-        $this->csrfProvider = $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface');
         $this->validator = $this->getMock('Symfony\Component\Validator\ValidatorInterface');
         $this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
         $this->storage = $this->getMockBuilder('Symfony\Component\HttpFoundation\File\TemporaryStorage')
@@ -49,7 +45,6 @@ abstract class TypeTestCase extends \PHPUnit_Framework_TestCase
     {
         return array(
             new CoreExtension($this->validator, $this->storage),
-            new CsrfExtension($this->csrfProvider),
         );
     }
 

+ 0 - 2
tests/Symfony/Tests/Component/Form/Extension/Csrf/Type/CsrfTypeTest.php

@@ -11,8 +11,6 @@
 
 namespace Symfony\Tests\Component\Form\Extension\Csrf\Type;
 
-use Symfony\Tests\Component\Form\Extension\Core\Type\TypeTestCase;
-
 class CsrfTypeTest extends TypeTestCase
 {
     protected $provider;

+ 33 - 0
tests/Symfony/Tests/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php

@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Tests\Component\Form\Extension\Csrf\Type;
+
+class FormTypeCsrfExtensionTest extends TypeTestCase
+{
+    public function testCsrfProtectionByDefault()
+    {
+        $form =  $this->factory->create('form', null, array(
+            'csrf_field_name' => 'csrf',
+        ));
+
+        $this->assertTrue($form->has('csrf'));
+    }
+
+    public function testCsrfProtectionCanBeDisabled()
+    {
+        $form =  $this->factory->create('form', null, array(
+            'csrf_protection' => false,
+        ));
+
+        $this->assertEquals(0, count($form));
+    }
+}

+ 34 - 0
tests/Symfony/Tests/Component/Form/Extension/Csrf/Type/TypeTestCase.php

@@ -0,0 +1,34 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Tests\Component\Form\Extension\Csrf\Type;
+
+use Symfony\Tests\Component\Form\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 getExtensions()
+    {
+        return array_merge(parent::getExtensions(), array(
+            new CsrfExtension($this->csrfProvider),
+        ));
+    }
+}

+ 45 - 3
tests/Symfony/Tests/Component/Form/FormTest.php

@@ -869,7 +869,15 @@ class FormTest extends \PHPUnit_Framework_TestCase
     {
         $test = $this;
         $type1 = $this->getMock('Symfony\Component\Form\FormTypeInterface');
+        $type1Extension = $this->getMock('Symfony\Component\Form\FormTypeExtensionInterface');
+        $type1->expects($this->any())
+            ->method('getExtensions')
+            ->will($this->returnValue(array($type1Extension)));
         $type2 = $this->getMock('Symfony\Component\Form\FormTypeInterface');
+        $type2Extension = $this->getMock('Symfony\Component\Form\FormTypeExtensionInterface');
+        $type2->expects($this->any())
+            ->method('getExtensions')
+            ->will($this->returnValue(array($type2Extension)));
         $calls = array();
 
         $type1->expects($this->once())
@@ -880,6 +888,14 @@ class FormTest extends \PHPUnit_Framework_TestCase
                 $test->assertFalse($view->hasChildren());
             }));
 
+        $type1Extension->expects($this->once())
+            ->method('buildView')
+            ->will($this->returnCallback(function (FormView $view, Form $form) use ($test, &$calls) {
+                $calls[] = 'type1ext::buildView';
+                $test->assertTrue($view->hasParent());
+                $test->assertFalse($view->hasChildren());
+            }));
+
         $type2->expects($this->once())
             ->method('buildView')
             ->will($this->returnCallback(function (FormView $view, Form $form) use ($test, &$calls) {
@@ -888,6 +904,14 @@ class FormTest extends \PHPUnit_Framework_TestCase
                 $test->assertFalse($view->hasChildren());
             }));
 
+        $type2Extension->expects($this->once())
+            ->method('buildView')
+            ->will($this->returnCallback(function (FormView $view, Form $form) use ($test, &$calls) {
+                $calls[] = 'type2ext::buildView';
+                $test->assertTrue($view->hasParent());
+                $test->assertFalse($view->hasChildren());
+            }));
+
         $type1->expects($this->once())
             ->method('buildViewBottomUp')
             ->will($this->returnCallback(function (FormView $view, Form $form) use ($test, &$calls) {
@@ -895,6 +919,13 @@ class FormTest extends \PHPUnit_Framework_TestCase
                 $test->assertTrue($view->hasChildren());
             }));
 
+        $type1Extension->expects($this->once())
+            ->method('buildViewBottomUp')
+            ->will($this->returnCallback(function (FormView $view, Form $form) use ($test, &$calls) {
+                $calls[] = 'type1ext::buildViewBottomUp';
+                $test->assertTrue($view->hasChildren());
+            }));
+
         $type2->expects($this->once())
             ->method('buildViewBottomUp')
             ->will($this->returnCallback(function (FormView $view, Form $form) use ($test, &$calls) {
@@ -902,6 +933,13 @@ class FormTest extends \PHPUnit_Framework_TestCase
                 $test->assertTrue($view->hasChildren());
             }));
 
+        $type2Extension->expects($this->once())
+            ->method('buildViewBottomUp')
+            ->will($this->returnCallback(function (FormView $view, Form $form) use ($test, &$calls) {
+                $calls[] = 'type2ext::buildViewBottomUp';
+                $test->assertTrue($view->hasChildren());
+            }));
+
         $form = $this->getBuilder()->setTypes(array($type1, $type2))->getForm();
         $form->setParent($this->getBuilder()->getForm());
         $form->add($this->getBuilder()->getForm());
@@ -910,9 +948,13 @@ class FormTest extends \PHPUnit_Framework_TestCase
 
         $this->assertEquals(array(
             0 => 'type1::buildView',
-            1 => 'type2::buildView',
-            2 => 'type1::buildViewBottomUp',
-            3 => 'type2::buildViewBottomUp',
+            1 => 'type1ext::buildView',
+            2 => 'type2::buildView',
+            3 => 'type2ext::buildView',
+            4 => 'type1::buildViewBottomUp',
+            5 => 'type1ext::buildViewBottomUp',
+            6 => 'type2::buildViewBottomUp',
+            7 => 'type2ext::buildViewBottomUp',
         ), $calls);
     }