Quellcode durchsuchen

[Form] Removed FormFactory and improved the form instantiation process

With the form factory there was no reasonable way to implement instantiation of custom form classes. So the implementation was changed to let the classes instantiate themselves. A FormContext instance with default settings has to be passed to the creation method. This context is by default configured in the DI container.

	$context = $this->get('form.context');
	// or
	$context = FormContext::buildDefault();
	$form = MyFormClass::create($context, 'author');

If you want to circumvent this process, you can also create a form manually. Remember that the services stored in the default context won't be available then unless you pass them explicitely.

	$form = new MyFormClass('author');
Bernhard Schussek vor 14 Jahren
Ursprung
Commit
a28151a8af

+ 10 - 27
src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml

@@ -5,11 +5,10 @@
     xsi:schemaLocation="http://www.symfony-project.org/schema/dic/services http://www.symfony-project.org/schema/dic/services/services-1.0.xsd">
 
     <parameters>
-        <parameter key="form.factory.class">Symfony\Component\Form\FormFactory</parameter>
         <parameter key="form.field_factory.class">Symfony\Component\Form\FieldFactory\FieldFactory</parameter>
         <parameter key="form.field_factory.validator_guesser.class">Symfony\Component\Form\FieldFactory\ValidatorFieldFactoryGuesser</parameter>
         <parameter key="form.csrf_provider.class">Symfony\Component\Form\CsrfProvider\SessionCsrfProvider</parameter>
-        <parameter key="form.default_context.class">Symfony\Component\Form\FormContext</parameter>
+        <parameter key="form.context.class">Symfony\Component\Form\FormContext</parameter>
         <parameter key="form.csrf_protection.enabled">true</parameter>
         <parameter key="form.csrf_protection.field_name">_token</parameter>
         <parameter key="form.csrf_protection.secret">secret</parameter>
@@ -18,11 +17,6 @@
 
     <services>
 
-        <!-- FormFactory -->
-        <service id="form.factory" class="%form.factory.class%">
-            <argument type="service" id="form.default_context" />
-        </service>
-
         <!-- FieldFactory -->
         <service id="form.field_factory" class="%form.field_factory.class%">
             <!-- All services with tag "form.field_factory.guesser" are inserted here by AddFieldFactoryGuessersPass -->
@@ -42,26 +36,15 @@
         </service>
 
         <!-- FormContext -->
-        <service id="form.default_context" class="%form.default_context.class%">
-            <argument type="service" id="service_container" />
-            <call method="validator">
-                <argument type="service" id="validator" />
-            </call>
-            <call method="validationGroups">
-                <argument>%form.validation_groups%</argument>
-            </call>
-            <call method="fieldFactory">
-                <argument type="service" id="form.field_factory" />
-            </call>
-            <call method="csrfProtection">
-                <argument>%form.csrf_protection.enabled%</argument>
-            </call>
-            <call method="csrfFieldName">
-                <argument>%form.csrf_protection.field_name%</argument>
-            </call>
-            <call method="csrfProvider">
-                <argument type="service" id="form.csrf_provider" />
-            </call>
+        <service id="form.context" class="%form.context.class%">
+            <argument type="collection">
+                <argument key="validator" type="service" id="validator" />
+                <argument key="validation_groups">%form.validation_groups%</argument>
+                <argument key="field_factory" type="service" id="form.field_factory" />
+                <argument key="csrf_protection">%form.csrf_protection.enabled%</argument>
+                <argument key="csrf_field_name">%form.csrf_protection.field_name%</argument>
+                <argument key="csrf_provider" type="service" id="form.csrf_provider" />
+            </argument>
         </service>
         
     </services>

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

@@ -62,6 +62,25 @@ class Form extends Field implements \IteratorAggregate, FormInterface
      */
     protected $dataClass;
 
+    /**
+     * The context used when creating the form
+     * @var FormContext
+     */
+    protected $context = null;
+
+    /**
+     * Creates a new form with the options stored in the given context
+     *
+     * @param  FormContextInterface $context
+     * @param  string $name
+     * @param  array $options
+     * @return Form
+     */
+    public static function create(FormContextInterface $context, $name = null, array $options = array())
+    {
+        return new static($name, array_merge($context->getOptions(), $options));
+    }
+
     /**
      * Constructor.
      *
@@ -79,6 +98,7 @@ class Form extends Field implements \IteratorAggregate, FormInterface
         $this->addOption('validation_groups');
         $this->addOption('virtual', false);
         $this->addOption('validator');
+        $this->addOption('context');
 
         if (isset($options['validation_groups'])) {
             $options['validation_groups'] = (array)$options['validation_groups'];
@@ -821,6 +841,16 @@ class Form extends Field implements \IteratorAggregate, FormInterface
         return $this->dataClass;
     }
 
+    /**
+     * Returns the context used when creating this form
+     *
+     * @return FormContext  The context instance
+     */
+    public function getContext()
+    {
+        return $this->getOption('context');
+    }
+
     /**
      * Merges two arrays without reindexing numeric keys.
      *

+ 54 - 156
src/Symfony/Component/Form/FormContext.php

@@ -11,195 +11,93 @@ namespace Symfony\Component\Form;
  * file that was distributed with this source code.
  */
 
-use Symfony\Component\Form\FieldFactory\FieldFactoryInterface;
-use Symfony\Component\Form\CsrfProvider\CsrfProviderInterface;
+use Symfony\Component\Form\CsrfProvider\DefaultCsrfProvider;
+use Symfony\Component\Form\Exception\FormException;
 use Symfony\Component\Validator\ValidatorInterface;
 
 /**
  * Default implementaton of FormContextInterface
  *
+ * This class is immutable by design.
+ *
  * @author Fabien Potencier <fabien.potencier@symfony-project.com>
  * @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
  */
 class FormContext implements FormContextInterface
 {
     /**
-     * The locale used by new forms
-     * @var string
-     */
-    protected static $locale = 'en';
-
-    /**
-     * The validator used in the new form
-     * @var ValidatorInterface
-     */
-    protected $validator = null;
-
-    /**
-     * The validation group(s) validated in the new form
-     * @var string|array
-     */
-    protected $validationGroups = null;
-
-    /**
-     * The field factory used for automatically creating fields in the form
-     * @var FieldFactoryInterface
-     */
-    protected $fieldFactory = null;
-
-    /**
-     * The provider used to generate and validate CSRF tokens
+     * The options used in new forms
      * @var array
      */
-    protected $csrfProvider = null;
-
-    /**
-     * Whether the new form should be CSRF protected
-     * @var Boolean
-     */
-    protected $csrfProtection = false;
-
-    /**
-     * The field name used for the CSRF protection
-     * @var string
-     */
-    protected $csrfFieldName = '_token';
-
-    /**
-     * @inheritDoc
-     */
-    public function validator(ValidatorInterface $validator)
-    {
-        $this->validator = $validator;
-
-        return $this;
-    }
-
-    /**
-     * @inheritDoc
-     */
-    public function validationGroups($validationGroups)
-    {
-        $this->validationGroups = null === $validationGroups ? $validationGroups : (array) $validationGroups;
-
-        return $this;
-    }
-
-    /**
-     * @inheritDoc
-     */
-    public function fieldFactory(FieldFactoryInterface $fieldFactory)
-    {
-        $this->fieldFactory = $fieldFactory;
-
-        return $this;
-    }
+    protected $options = null;
 
     /**
-     * @inheritDoc
-     */
-    public function csrfProtection($enabled)
-    {
-        $this->csrfProtection = $enabled;
-
-        return $this;
-    }
-
-    /**
-     * @inheritDoc
-     */
-    public function csrfFieldName($name)
+     * Builds a context with default values
+     *
+     * By default, CSRF protection is enabled. In this case you have to provide
+     * a CSRF secret in the second parameter of this method. A recommended
+     * value is a generated value with at least 32 characters and mixed
+     * letters, digits and special characters.
+     *
+     * If you don't want to use CSRF protection, you can leave the CSRF secret
+     * empty and set the third parameter to false.
+     *
+     * @param ValidatorInterface $validator  The validator for validating
+     *                                       forms
+     * @param string $csrfSecret             The secret to be used for
+     *                                       generating CSRF tokens
+     * @param boolean $csrfProtection        Whether forms should be CSRF
+     *                                       protected
+     * @throws FormException                 When CSRF protection is enabled,
+     *                                       but no CSRF secret is passed
+     */
+    public static function buildDefault(ValidatorInterface $validator, $csrfSecret = null, $csrfProtection = true)
     {
-        $this->csrfFieldName = $name;
-
-        return $this;
-    }
+        $options = array(
+            'csrf_protection' => $csrfProtection,
+            'validator' => $validator,
+        );
 
-    /**
-     * @inheritDoc
-     */
-    public function csrfProvider(CsrfProviderInterface $csrfProvider)
-    {
-        $this->csrfProvider = $csrfProvider;
+        if ($csrfProtection) {
+            if (empty($csrfSecret)) {
+                throw new FormException('Please provide a CSRF secret when CSRF protection is enabled');
+            }
 
-        return $this;
-    }
+            $options['csrf_provider'] = new DefaultCsrfProvider($csrfSecret);
+        }
 
-    /**
-     * @inheritDoc
-     */
-    public function getForm($name, $data = null, array $options = array())
-    {
-        return new Form(
-            $name,
-            array_merge(array(
-                'data' => $data,
-                'validator' => $this->validator,
-                'csrf_field_name' => $this->csrfFieldName,
-                'csrf_provider' => $this->csrfProtection ? $this->csrfProvider : null,
-                'validation_groups' => $this->validationGroups,
-                'field_factory' => $this->fieldFactory,
-            ), $options)
-        );
+        return new static($options);
     }
 
     /**
-     * Returns the validator used in the new form
+     * Constructor
      *
-     * @return ValidatorInterface  The validator instance
-     */
-    public function getValidator()
-    {
-        return $this->validator;
-    }
-
-    /**
-     * Returns the validation groups validated by the new form
+     * Initializes the context with the settings stored in the given
+     * options.
      *
-     * @return string|array  One or more validation groups
+     * @param array $options
      */
-    public function getValidationGroups()
+    public function __construct(array $options = array())
     {
-        return $this->validationGroups;
-    }
+        if (isset($options['csrf_protection'])) {
+            if (!$options['csrf_protection']) {
+                // don't include a CSRF provider if CSRF protection is disabled
+                unset($options['csrf_provider']);
+            }
 
-    /**
-     * Returns the field factory used by the new form
-     *
-     * @return FieldFactoryInterface  The field factory instance
-     */
-    public function getFieldFactory()
-    {
-        return $this->fieldFactory;
-    }
+            unset($options['csrf_protection']);
+        }
 
-    /**
-     * Returns whether the new form will be CSRF protected
-     *
-     * @return Boolean  Whether the form will be CSRF protected
-     */
-    public function isCsrfProtectionEnabled()
-    {
-        return $this->csrfProtection;
-    }
+        $options['context'] = $this;
 
-    /**
-     * Returns the field name used for CSRF protection in the new form
-     *
-     * @return string  The CSRF field name
-     */
-    public function getCsrfFieldName()
-    {
-        return $this->csrfFieldName;
+        $this->options = $options;
     }
 
     /**
-     * Returns the CSRF provider used to generate and validate CSRF tokens
-     *
-     * @return CsrfProviderInterface  The provider instance
+     * {@inheritDoc}
      */
-    public function getCsrfProvider()
+    public function getOptions()
     {
-        return $this->csrfProvider;
+        return $this->options;
     }
 }

+ 4 - 64
src/Symfony/Component/Form/FormContextInterface.php

@@ -16,76 +16,16 @@ use Symfony\Component\Form\CsrfProvider\CsrfProviderInterface;
 use Symfony\Component\Validator\ValidatorInterface;
 
 /**
- * Stores settings for creating a new form and creates forms
- *
- * The methods in this class are chainable, i.e. they return the form context
- * object itself. When you have finished configuring the new form, call
- * getForm() to create the form.
- *
- * <code>
- * $form = $context
- *     ->validationGroups('Address')
- *     ->getForm('author');
- * </code>
+ * Stores options for creating new forms
  *
  * @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
  */
 interface FormContextInterface
 {
     /**
-     * Sets the validator used for validating the form
-     *
-     * @param  ValidatorInterface $validator  The validator instance
-     * @return FormContextInterface           This object
-     */
-    function validator(ValidatorInterface $validator);
-
-    /**
-     * Sets the validation groups validated by the form
-     *
-     * @param  string|array $validationGroups  One or more validation groups
-     * @return FormContextInterface            This object
-     */
-    function validationGroups($validationGroups);
-
-    /**
-     * Sets the field factory used for automatically creating fields in the form
-     *
-     * @param  FieldFactoryInterface $fieldFactory  The field factory instance
-     * @return FormContextInterface                 This object
-     */
-    function fieldFactory(FieldFactoryInterface $fieldFactory);
-
-    /**
-     * Enables or disables CSRF protection for the  form
-     *
-     * @param Boolean $enabled       Whether the form should be CSRF protected
-     * @return FormContextInterface  This object
-     */
-    function csrfProtection($enabled);
-
-    /**
-     * Sets the field name used for CSRF protection in the form
-     *
-     * @param  string $name          The CSRF field name
-     * @return FormContextInterface  This object
-     */
-    function csrfFieldName($name);
-
-    /**
-     * Sets the CSRF provider used to generate and validate CSRF tokens
-     *
-     * @param  CsrfProviderInterface $provider  The provider instance
-     * @return FormContextInterface             This object
-     */
-    function csrfProvider(CsrfProviderInterface $csrfProvider);
-
-    /**
-     * Creates a new form with the settings stored in this context
+     * Returns the options used for creating a new form
      *
-     * @param  string $name        The name for the form
-     * @param  array|object $data  The data displayed and modified by the form
-     * @return Form                The new form
+     * @return array  The form options
      */
-    function getForm($name, $data = null, array $options = array());
+    public function getOptions();
 }

+ 0 - 198
src/Symfony/Component/Form/FormFactory.php

@@ -1,198 +0,0 @@
-<?php
-
-namespace Symfony\Component\Form;
-
-/*
- * 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.
- */
-
-use Symfony\Component\Form\FieldFactory\FieldFactoryInterface;
-use Symfony\Component\Form\CsrfProvider\CsrfProviderInterface;
-use Symfony\Component\Form\CsrfProvider\DefaultCsrfProvider;
-use Symfony\Component\Form\Exception\FormException;
-use Symfony\Component\Validator\ValidatorInterface;
-
-/**
- * Creates and configures new form objects
- *
- * The default configuration of form objects can be passed to the constructor
- * as a FormContextInterface object. Call getForm() to create new form objects.
- *
- * <code>
- * $defaultContext = new FormContext();
- * $defaultContext->csrfProtection(true);
- * $factory = new FormFactory($defaultContext);
- *
- * $form = $factory->getForm('author');
- * </code>
- *
- * You can also override the default configuration by calling any of the
- * methods in this class. These methods return a FormContextInterface object
- * on which you can override further settings or call getForm() to create
- * a form.
- *
- * <code>
- * $form = $factory
- *     ->csrfProtection(false)
- *     ->getForm('author');
- * </code>
- *
- * FormFactory instances should be cached and reused in your application.
- *
- * @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
- */
-class FormFactory implements FormContextInterface
-{
-    /**
-     * Holds the context with the default configuration
-     * @var FormContextInterface
-     */
-    protected $defaultContext;
-
-    /**
-     * Builds a form factory with default values
-     *
-     * By default, CSRF protection is enabled. In this case you have to provide
-     * a CSRF secret in the second parameter of this method. A recommended
-     * value is a generated value with at least 32 characters and mixed
-     * letters, digits and special characters.
-     *
-     * If you don't want to use CSRF protection, you can leave the CSRF secret
-     * empty and set the third parameter to false.
-     *
-     * @param ValidatorInterface $validator  The validator for validating
-     *                                       forms
-     * @param string $csrfSecret             The secret to be used for
-     *                                       generating CSRF tokens
-     * @param boolean $csrfProtection        Whether forms should be CSRF
-     *                                       protected
-     * @throws FormException                 When CSRF protection is enabled,
-     *                                       but no CSRF secret is passed
-     */
-    public static function buildDefault(ValidatorInterface $validator, $csrfSecret = null, $csrfProtection = true)
-    {
-        $context = new FormContext();
-        $context->csrfProtection($csrfProtection);
-        $context->validator($validator);
-
-        if ($csrfProtection) {
-            if (empty($csrfSecret)) {
-                throw new FormException('Please provide a CSRF secret when CSRF protection is enabled');
-            }
-
-            $context->csrfProvider(new DefaultCsrfProvider($csrfSecret));
-        }
-
-        return new static($context);
-    }
-
-    /**
-     * Sets the given context as default context
-     *
-     * @param FormContextInterface $defaultContext  A preconfigured context
-     */
-    public function __construct(FormContextInterface $defaultContext = null)
-    {
-        $this->defaultContext = null === $defaultContext ? new FormContext() : $defaultContext;
-    }
-
-    /**
-     * Overrides the validator of the default context and returns the new context
-     *
-     * @param  ValidatorInterface $validator  The new validator instance
-     * @return FormContextInterface           The preconfigured form context
-     */
-    public function validator(ValidatorInterface $validator)
-    {
-        $context = clone $this->defaultContext;
-
-        return $context->validator($validator);
-    }
-
-    /**
-     * Overrides the validation groups of the default context and returns
-     * the new context
-     *
-     * @param  string|array $validationGroups  One or more validation groups
-     * @return FormContextInterface            The preconfigured form context
-     */
-    public function validationGroups($validationGroups)
-    {
-        $context = clone $this->defaultContext;
-
-        return $context->validationGroups($validationGroups);
-    }
-
-    /**
-     * Overrides the field factory of the default context and returns
-     * the new context
-     *
-     * @param  FieldFactoryInterface $fieldFactory  A field factory instance
-     * @return FormContextInterface                 The preconfigured form context
-     */
-    public function fieldFactory(FieldFactoryInterface $fieldFactory)
-    {
-        $context = clone $this->defaultContext;
-
-        return $context->fieldFactory($fieldFactory);
-    }
-
-    /**
-     * Overrides the CSRF protection setting of the default context and returns
-     * the new context
-     *
-     * @param  boolean $enabled      Whether the form should be CSRF protected
-     * @return FormContextInterface  The preconfigured form context
-     */
-    public function csrfProtection($enabled)
-    {
-        $context = clone $this->defaultContext;
-
-        return $context->csrfProtection($enabled);
-    }
-
-    /**
-     * Overrides the CSRF field name setting of the default context and returns
-     * the new context
-     *
-     * @param  string $name          The field name to use for CSRF protection
-     * @return FormContextInterface  The preconfigured form context
-     */
-    public function csrfFieldName($name)
-    {
-        $context = clone $this->defaultContext;
-
-        return $context->csrfFieldName($name);
-    }
-
-    /**
-     * Overrides the CSRF provider setting of the default context and returns
-     * the new context
-     *
-     * @param  CsrfProviderInterface $provider  The provider instance
-     * @return FormContextInterface             The preconfigured form context
-     */
-    public function csrfProvider(CsrfProviderInterface $csrfProvider)
-    {
-        $context = clone $this->defaultContext;
-
-        return $context->csrfProvider($csrfProvider);
-    }
-
-    /**
-     * Creates a new form with the settings stored in the default context
-     *
-     * @param  string $name        The name for the form
-     * @param  array|object $data  The data displayed and modified by the form
-     * @return Form                The new form
-     */
-    public function getForm($name, $data = null, array $options = array())
-    {
-        return $this->defaultContext->getForm($name, $data, $options);
-    }
-}

+ 15 - 14
tests/Symfony/Tests/Component/Form/FormFactoryTest.php

@@ -14,11 +14,10 @@ namespace Symfony\Tests\Component\Form;
 require_once __DIR__ . '/Fixtures/Author.php';
 require_once __DIR__ . '/Fixtures/TestField.php';
 
-use Symfony\Component\Form\FormFactory;
 use Symfony\Component\Form\FormContext;
 use Symfony\Component\Form\CsrfProvider\DefaultCsrfProvider;
 
-class FormFactoryTest extends \PHPUnit_Framework_TestCase
+class FormContextTest extends \PHPUnit_Framework_TestCase
 {
     protected $validator;
 
@@ -29,25 +28,27 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
 
     public function testBuildDefaultWithCsrfProtection()
     {
-        $factory = FormFactory::buildDefault($this->validator, 'secret');
+        $context = FormContext::buildDefault($this->validator, 'secret');
 
-        $context = new FormContext();
-        $context->validator($this->validator);
-        $context->csrfProtection(true);
-        $context->csrfProvider(new DefaultCsrfProvider('secret'));
+        $expected = array(
+            'validator' => $this->validator,
+            'csrf_provider' => new DefaultCsrfProvider('secret'),
+            'context' => $context,
+        );
 
-        $this->assertEquals(new FormFactory($context), $factory);
+        $this->assertEquals($expected, $context->getOptions());
     }
 
     public function testBuildDefaultWithoutCsrfProtection()
     {
-        $factory = FormFactory::buildDefault($this->validator, null, false);
+        $context = FormContext::buildDefault($this->validator, null, false);
 
-        $context = new FormContext();
-        $context->validator($this->validator);
-        $context->csrfProtection(false);
+        $expected = array(
+            'validator' => $this->validator,
+            'context' => $context,
+        );
 
-        $this->assertEquals(new FormFactory($context), $factory);
+        $this->assertEquals($expected, $context->getOptions());
     }
 
     /**
@@ -55,6 +56,6 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
      */
     public function testBuildDefaultWithoutCsrfSecretThrowsException()
     {
-        FormFactory::buildDefault($this->validator, null, true);
+        FormContext::buildDefault($this->validator, null, true);
     }
 }