[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');
This commit is contained in:
Bernhard Schussek 2011-02-01 13:45:27 +01:00
parent fb1f99137d
commit a28151a8af
6 changed files with 118 additions and 464 deletions

View File

@ -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>

View File

@ -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.
*

View File

@ -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;
protected $options = null;
/**
* Whether the new form should be CSRF protected
* @var Boolean
* 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
*/
protected $csrfProtection = false;
/**
* The field name used for the CSRF protection
* @var string
*/
protected $csrfFieldName = '_token';
/**
* @inheritDoc
*/
public function validator(ValidatorInterface $validator)
public static function buildDefault(ValidatorInterface $validator, $csrfSecret = null, $csrfProtection = true)
{
$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;
}
/**
* @inheritDoc
*/
public function csrfProtection($enabled)
{
$this->csrfProtection = $enabled;
return $this;
}
/**
* @inheritDoc
*/
public function csrfFieldName($name)
{
$this->csrfFieldName = $name;
return $this;
}
/**
* @inheritDoc
*/
public function csrfProvider(CsrfProviderInterface $csrfProvider)
{
$this->csrfProvider = $csrfProvider;
return $this;
}
/**
* @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)
$options = array(
'csrf_protection' => $csrfProtection,
'validator' => $validator,
);
if ($csrfProtection) {
if (empty($csrfSecret)) {
throw new FormException('Please provide a CSRF secret when CSRF protection is enabled');
}
$options['csrf_provider'] = new DefaultCsrfProvider($csrfSecret);
}
return new static($options);
}
/**
* Returns the validator used in the new form
* Constructor
*
* @return ValidatorInterface The validator instance
* Initializes the context with the settings stored in the given
* options.
*
* @param array $options
*/
public function getValidator()
public function __construct(array $options = array())
{
return $this->validator;
if (isset($options['csrf_protection'])) {
if (!$options['csrf_protection']) {
// don't include a CSRF provider if CSRF protection is disabled
unset($options['csrf_provider']);
}
unset($options['csrf_protection']);
}
$options['context'] = $this;
$this->options = $options;
}
/**
* Returns the validation groups validated by the new form
*
* @return string|array One or more validation groups
* {@inheritDoc}
*/
public function getValidationGroups()
public function getOptions()
{
return $this->validationGroups;
}
/**
* Returns the field factory used by the new form
*
* @return FieldFactoryInterface The field factory instance
*/
public function getFieldFactory()
{
return $this->fieldFactory;
}
/**
* Returns whether the new form will be CSRF protected
*
* @return Boolean Whether the form will be CSRF protected
*/
public function isCsrfProtectionEnabled()
{
return $this->csrfProtection;
}
/**
* Returns the field name used for CSRF protection in the new form
*
* @return string The CSRF field name
*/
public function getCsrfFieldName()
{
return $this->csrfFieldName;
}
/**
* Returns the CSRF provider used to generate and validate CSRF tokens
*
* @return CsrfProviderInterface The provider instance
*/
public function getCsrfProvider()
{
return $this->csrfProvider;
return $this->options;
}
}

View File

@ -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
* Returns the options used for creating a new form
*
* @param ValidatorInterface $validator The validator instance
* @return FormContextInterface This object
* @return array The form options
*/
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
*
* @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
*/
function getForm($name, $data = null, array $options = array());
public function getOptions();
}

View File

@ -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);
}
}

View File

@ -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);
}
}