Преглед изворни кода

[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 година
родитељ
комит
1ce2db87e2
22 измењених фајлова са 431 додато и 81 уклоњено
  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);
     }