[Form][FrameworkBundle] Implemented FormFactory and added it to the DI container

This commit is contained in:
Bernhard Schussek 2011-01-19 13:43:24 +01:00
parent fea37a3e95
commit 8f8f53d631
17 changed files with 805 additions and 317 deletions

View File

@ -0,0 +1,41 @@
<?php
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
/*
* 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\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
/**
* Adds all services with the tag "form.field_factory_guesser" as argument
* to the "form.field_factory" service
*
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
*/
class AddFieldFactoryGuessersPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('form.field_factory')) {
return;
}
$guessers = array_map(function($id) {
return new Reference($id);
}, array_keys($container->findTaggedServiceIds('form.field_factory.guesser')));
$definition = $container->getDefinition('form.field_factory');
$arguments = $definition->getArguments();
$arguments[0] = $guessers;
$definition->setArguments($arguments);
}
}

View File

@ -20,6 +20,7 @@ use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\Finder\Finder;
use Symfony\Component\HttpFoundation\RequestMatcher;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\Form\FormContext;
/**
* FrameworkExtension.
@ -42,6 +43,23 @@ class FrameworkExtension extends Extension
$loader->load('web.xml');
}
if (!$container->hasDefinition('form.factory')) {
$loader->load('form.xml');
}
if (isset($config['csrf_protection'])) {
foreach (array('enabled', 'field_name', 'field-name', 'secret') as $key) {
if (isset($config['csrf_protection'][$key])) {
$container->setParameter('form.csrf_protection.'.strtr($key, '-', '_'),
$config['csrf_protection'][$key]);
}
}
}
if (isset($config['i18n']) && $config['i18n']) {
FormContext::setLocale(\Locale::get());
}
if (isset($config['ide'])) {
switch ($config['ide']) {
case 'textmate':
@ -60,12 +78,6 @@ class FrameworkExtension extends Extension
$container->setParameter('debug.file_link_format', $pattern);
}
foreach (array('csrf_secret', 'csrf-secret') as $key) {
if (isset($config[$key])) {
$container->setParameter('csrf_secret', $config[$key]);
}
}
if (!$container->hasDefinition('event_dispatcher')) {
$loader = new XmlFileLoader($container, array(__DIR__.'/../Resources/config', __DIR__.'/Resources/config'));
$loader->load('services.xml');
@ -149,7 +161,8 @@ class FrameworkExtension extends Extension
'Symfony\\Component\\EventDispatcher\\EventDispatcher',
'Symfony\\Bundle\\FrameworkBundle\\EventDispatcher',
'Symfony\\Component\\Form\\FormConfiguration',
'Symfony\\Component\\Form\\FormContext',
'Symfony\\Component\\Form\\FormContextInterface',
));
}

View File

@ -12,6 +12,7 @@
namespace Symfony\Bundle\FrameworkBundle;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConstraintValidatorsPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddFieldFactoryGuessersPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TemplatingPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\RegisterKernelListenersPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddSecurityVotersPass;
@ -23,7 +24,6 @@ use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslatorPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\Form\FormConfiguration;
/**
* Bundle.
@ -37,28 +37,27 @@ class FrameworkBundle extends Bundle
*/
public function boot()
{
if ($this->container->has('error_handler')) {
$this->container->get('error_handler');
}
$container = $this->container;
FormConfiguration::clearDefaultCsrfSecrets();
if ($this->container->hasParameter('csrf_secret')) {
FormConfiguration::addDefaultCsrfSecret($this->container->getParameter('csrf_secret'));
FormConfiguration::enableDefaultCsrfProtection();
if ($container->has('error_handler')) {
$container->get('error_handler');
}
// the session ID should always be included in the CSRF token, even
// if default CSRF protection is not enabled
if ($this->container->has('session')) {
$container = $this->container;
FormConfiguration::addDefaultCsrfSecret(function () use ($container) {
if ($container->has('form.default_context') && $container->has('session')) {
$addSessionId = function () use ($container) {
// automatically starts the session when the CSRF token is
// generated
$container->get('session')->start();
return $container->get('session')->getId();
});
};
// $container->getDefinition('form.default_context')
// ->addMethodCall('addCsrfSecret', array($addSessionId));
//
// var_dump($container->getDefinition('form.default_context'));
}
}
@ -73,6 +72,7 @@ class FrameworkBundle extends Bundle
$container->addCompilerPass(new RegisterKernelListenersPass());
$container->addCompilerPass(new TemplatingPass());
$container->addCompilerPass(new AddConstraintValidatorsPass());
$container->addCompilerPass(new AddFieldFactoryGuessersPass());
$container->addCompilerPass(new AddClassesToCachePass());
$container->addCompilerPass(new TranslatorPass());
}

View File

@ -0,0 +1,62 @@
<?xml version="1.0" ?>
<container xmlns="http://www.symfony-project.org/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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.default_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>
<parameter key="form.validation_groups">Default</parameter>
</parameters>
<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%" public="false">
<!-- All services with tag "form.field_factory.guesser" are inserted here by AddFieldFactoryGuessersPass -->
</service>
<!-- ValidatorFieldFactoryGuesser -->
<service id="form.field_factory.validator_guesser" class="%form.field_factory.validator_guesser.class%" public="false">
<tag name="form.field_factory.guesser" />
<argument type="service" id="validator.mapping.class_metadata_factory" />
</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="csrfSecrets">
<argument type="collection">
<argument>%form.csrf_protection.secret%</argument>
</argument>
</call>
</service>
</services>
</container>

View File

@ -1,5 +1,7 @@
<?php
namespace Symfony\Component\Form;
/*
* This file is part of the Symfony package.
*
@ -9,8 +11,6 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form;
use Symfony\Component\Form\ValueTransformer\ValueTransformerInterface;
use Symfony\Component\Form\ValueTransformer\TransformationFailedException;
@ -74,7 +74,7 @@ class Field extends Configurable implements FieldInterface
$this->addOption('normalization_transformer');
$this->key = (string)$key;
$this->locale = FormConfiguration::getDefaultLocale();
$this->locale = FormContext::getLocale();
parent::__construct($options);

View File

@ -40,6 +40,7 @@ class FieldFactory implements FieldFactoryInterface
throw new UnexpectedTypeException($guesser, 'FieldFactoryGuesserInterface');
}
}
$this->guessers = $guessers;
}

View File

@ -27,14 +27,11 @@ use Symfony\Component\Form\Exception\FormException;
* is generated on the fly.
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
*/
class Form extends FieldGroup
{
protected $validator = null;
protected $validationGroups = null;
private $csrfSecret = null;
private $csrfFieldName = null;
/**
* Constructor.
@ -53,11 +50,15 @@ class Form extends FieldGroup
$this->setData($data);
}
if (FormConfiguration::isDefaultCsrfProtectionEnabled()) {
$this->enableCsrfProtection();
}
$this->addOption('csrf_protection');
$this->addOption('csrf_field_name', '_token');
$this->addOption('csrf_secrets', array(__FILE__.php_uname()));
$this->addOption('field_factory');
$this->addOption('validation_groups');
if (isset($options['validation_groups'])) {
$options['validation_groups'] = (array)$options['validation_groups'];
}
parent::__construct($name, $options);
@ -66,26 +67,16 @@ class Form extends FieldGroup
if (null !== $data) {
$this->setPropertyPath(null);
}
}
/**
* Sets the validation groups for this form.
*
* @param array|string $validationGroups
*/
public function setValidationGroups($validationGroups)
{
$this->validationGroups = null === $validationGroups ? $validationGroups : (array) $validationGroups;
}
// Enable CSRF protection, if necessary
if ($this->getOption('csrf_protection')) {
$field = new HiddenField($this->getOption('csrf_field_name'), array(
'property_path' => null,
));
$field->setData($this->generateCsrfToken($this->getOption('csrf_secrets')));
/**
* Returns the validation groups for this form.
*
* @return array
*/
public function getValidationGroups()
{
return $this->validationGroups;
$this->add($field);
}
}
/**
@ -99,6 +90,46 @@ class Form extends FieldGroup
return $this->getOption('field_factory');
}
/**
* Returns the validator used by the form
*
* @return ValidatorInterface The validator instance
*/
public function getValidator()
{
return $this->validator;
}
/**
* Returns the validation groups validated by the form
*
* @return array A list of validation groups or null
*/
public function getValidationGroups()
{
return $this->getOption('validation_groups');
}
/**
* Returns the name used for the CSRF protection field
*
* @return string The field name
*/
public function getCsrfFieldName()
{
return $this->getOption('csrf_field_name');
}
/**
* Returns the secret values used for the CSRF protection
*
* @return array A list of string valuesf
*/
public function getCsrfSecrets()
{
return $this->getOption('csrf_secrets');
}
/**
* Binds the form with values and files.
*
@ -132,7 +163,7 @@ class Form extends FieldGroup
throw new FormException('A validator is required for binding. Forgot to pass it to the constructor of the form?');
}
if ($violations = $this->validator->validate($this, $this->getValidationGroups())) {
if ($violations = $this->validator->validate($this, $this->getOption('validation_groups'))) {
// TODO: test me
foreach ($violations as $violation) {
$propertyPath = new PropertyPath($violation->getPropertyPath());
@ -172,20 +203,19 @@ class Form extends FieldGroup
*
* @return string A token string
*/
protected function generateCsrfToken($secret)
protected function generateCsrfToken(array $secrets)
{
$secret .= get_class($this);
$defaultSecrets = FormConfiguration::getDefaultCsrfSecrets();
$implodedSecrets = get_class($this);
foreach ($defaultSecrets as $defaultSecret) {
if ($defaultSecret instanceof \Closure) {
$defaultSecret = $defaultSecret();
foreach ($secrets as $secret) {
if ($secret instanceof \Closure) {
$secret = $secret();
}
$secret .= $defaultSecret;
$implodedSecrets .= $secret;
}
return md5($secret);
return md5($implodedSecrets);
}
/**
@ -193,65 +223,7 @@ class Form extends FieldGroup
*/
public function isCsrfProtected()
{
return $this->has($this->getCsrfFieldName());
}
/**
* Enables CSRF protection for this form.
*/
public function enableCsrfProtection($csrfFieldName = null, $csrfSecret = null)
{
if (!$this->isCsrfProtected()) {
if (null === $csrfFieldName) {
$csrfFieldName = FormConfiguration::getDefaultCsrfFieldName();
}
if (null === $csrfSecret) {
$csrfSecret = md5(__FILE__.php_uname());
}
$field = new HiddenField($csrfFieldName, array(
'property_path' => null,
));
$field->setData($this->generateCsrfToken($csrfSecret));
$this->add($field);
$this->csrfFieldName = $csrfFieldName;
$this->csrfSecret = $csrfSecret;
}
}
/**
* Disables CSRF protection for this form.
*/
public function disableCsrfProtection()
{
if ($this->isCsrfProtected()) {
$this->remove($this->getCsrfFieldName());
$this->csrfFieldName = null;
$this->csrfSecret = null;
}
}
/**
* Returns the CSRF field name used in this form
*
* @return string The CSRF field name
*/
public function getCsrfFieldName()
{
return $this->csrfFieldName;
}
/**
* Returns the CSRF secret used in this form
*
* @return string The CSRF secret
*/
public function getCsrfSecret()
{
return $this->csrfSecret;
return $this->has($this->getOption('csrf_field_name'));
}
/**
@ -264,7 +236,10 @@ class Form extends FieldGroup
if (!$this->isCsrfProtected()) {
return true;
} else {
return $this->get($this->getCsrfFieldName())->getDisplayedData() === $this->generateCsrfToken($this->getCsrfSecret());
$actual = $this->get($this->getOption('csrf_field_name'))->getDisplayedData();
$expected = $this->generateCsrfToken($this->getOption('csrf_secrets'));
return $actual === $expected;
}
}

View File

@ -1,128 +0,0 @@
<?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;
/**
* FormConfiguration holds the default configuration for forms (CSRF, locale, ...).
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class FormConfiguration
{
protected static $defaultCsrfSecrets = array();
protected static $defaultCsrfProtection = false;
protected static $defaultCsrfFieldName = '_token';
protected static $defaultLocale = null;
/**
* Sets the default locale for newly created forms.
*
* @param string $defaultLocale
*/
static public function setDefaultLocale($defaultLocale)
{
self::$defaultLocale = $defaultLocale;
}
/**
* Returns the default locale for newly created forms.
*
* @return string
*/
static public function getDefaultLocale()
{
return isset(self::$defaultLocale) ? self::$defaultLocale : \Locale::getDefault();
}
/**
* Enables CSRF protection for all new forms.
*/
static public function enableDefaultCsrfProtection()
{
self::$defaultCsrfProtection = true;
}
/**
* Checks if Csrf protection for all forms is enabled.
*/
static public function isDefaultCsrfProtectionEnabled()
{
return self::$defaultCsrfProtection;
}
/**
* Disables Csrf protection for all forms.
*/
static public function disableDefaultCsrfProtection()
{
self::$defaultCsrfProtection = false;
}
/**
* Sets the CSRF field name used in all new CSRF protected forms.
*
* @param string $name The CSRF field name
*/
static public function setDefaultCsrfFieldName($name)
{
self::$defaultCsrfFieldName = $name;
}
/**
* Returns the default CSRF field name.
*
* @return string The CSRF field name
*/
static public function getDefaultCsrfFieldName()
{
return self::$defaultCsrfFieldName;
}
/**
* Sets the default CSRF secrets to be used in all new CSRF protected forms.
*
* @param array $secrets
*/
static public function setDefaultCsrfSecrets(array $secrets)
{
self::$defaultCsrfSecrets = $secrets;
}
/**
* Adds CSRF secrets to be used in all new CSRF protected forms.
*
* @param string $secret
*/
static public function addDefaultCsrfSecret($secret)
{
self::$defaultCsrfSecrets[] = $secret;
}
/**
* Clears the default CSRF secrets.
*/
static public function clearDefaultCsrfSecrets()
{
self::$defaultCsrfSecrets = array();
}
/**
* Returns the default CSRF secrets.
*
* @return array
*/
static public function getDefaultCsrfSecrets()
{
return self::$defaultCsrfSecrets;
}
}

View File

@ -0,0 +1,235 @@
<?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\Validator\ValidatorInterface;
/**
* Default implementaton of FormContextInterface
*
* @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 secret strings used for CSRF protection
* @var array
*/
protected $csrfSecrets = 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';
/**
* Globally sets the locale for new forms and fields
*
* @param string $locale A valid locale, such as "en", "de_DE" etc.
*/
public static function setLocale($locale)
{
self::$locale = $locale;
}
/**
* Returns the locale used for new forms and fields
*
* @return string A valid locale, such as "en", "de_DE" etc.
*/
public static function getLocale()
{
return self::$locale;
}
/**
* @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;
}
/**
* @inheritDoc
*/
public function csrfProtection($enabled)
{
$this->csrfProtection = $enabled;
return $this;
}
/**
* @inheritDoc
*/
public function csrfFieldName($name)
{
$this->csrfFieldName = $name;
return $this;
}
/**
* @inheritDoc
*/
public function csrfSecrets(array $secrets)
{
$this->csrfSecrets = $secrets;
return $this;
}
/**
* @inheritDoc
*/
public function getForm($name, $data = null)
{
return new Form(
$name,
$data,
$this->validator,
array(
'csrf_protection' => $this->csrfProtection,
'csrf_field_name' => $this->csrfFieldName,
'csrf_secrets' => $this->csrfSecrets,
'validation_groups' => $this->validationGroups,
'field_factory' => $this->fieldFactory,
)
);
}
/**
* @inheritDoc
*/
public function addCsrfSecret($secret)
{
$this->csrfSecrets[] = $secret;
return $this;
}
/**
* Returns the validator used in the new form
*
* @return ValidatorInterface The validator instance
*/
public function getValidator()
{
return $this->validator;
}
/**
* Returns the validation groups validated by the new form
*
* @return string|array One or more validation groups
*/
public function getValidationGroups()
{
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 secret values used for CSRF protection in the new form
*
* @return array A list of secret string values
*/
public function getCsrfSecrets()
{
return $this->csrfSecrets;
}
}

View File

@ -0,0 +1,100 @@
<?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\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
* ->locale('en_US')
* ->validationGroups('Address')
* ->getForm('author');
* </code>
*
* @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 secrets to be used in the form
*
* @param array $secrets A list of secret values
* @return FormContextInterface This object
*/
function csrfSecrets(array $secrets);
/**
* Adds another CSRF secrets without removing the existing CSRF secrets
*
* @param string $secret A secret value
* @return FormContextInterface This object
* @see csrfSecrets()
*/
function addCsrfSecret($secret);
/**
* 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);
}

View File

@ -0,0 +1,175 @@
<?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\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->locale('en_US');
* $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
* ->locale('de_DE')
* ->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;
/**
* 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 secrets setting of the default context and returns
* the new context
*
* @param array $secrets The secrets to use for CSRF protection
* @return FormContextInterface The preconfigured form context
*/
public function csrfSecrets(array $secrets)
{
$context = clone $this->defaultContext;
return $context->csrfSecrets($secrets);
}
/**
* Adds a new CSRF secret to the ones in the default context and returns
* the new context
*
* @param string $secret The secret to add to the secrets used for
* CSRF protection
* @return FormContextInterface The preconfigured form context
*/
public function addCsrfSecret($secret)
{
$context = clone $this->defaultContext;
return $context->addCsrfSecret($secret);
}
/**
* 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)
{
return $this->defaultContext->getForm($name, $data);
}
}

View File

@ -12,13 +12,13 @@
namespace Symfony\Tests\Component\Form;
use Symfony\Component\Form\CountryField;
use Symfony\Component\Form\FormConfiguration;
use Symfony\Component\Form\FormContext;
class CountryFieldTest extends \PHPUnit_Framework_TestCase
{
public function testCountriesAreSelectable()
{
FormConfiguration::setDefaultLocale('de_AT');
FormContext::setLocale('de_AT');
$field = new CountryField('country');
$choices = $field->getOtherChoices();

View File

@ -14,13 +14,13 @@ namespace Symfony\Tests\Component\Form;
require_once __DIR__ . '/DateTimeTestCase.php';
use Symfony\Component\Form\DateField;
use Symfony\Component\Form\FormConfiguration;
use Symfony\Component\Form\FormContext;
class DateFieldTest extends DateTimeTestCase
{
protected function setUp()
{
FormConfiguration::setDefaultLocale('de_AT');
FormContext::setLocale('de_AT');
}
public function testBind_fromInput_dateTime()

View File

@ -19,7 +19,7 @@ require_once __DIR__ . '/Fixtures/RequiredOptionsField.php';
use Symfony\Component\Form\ValueTransformer\ValueTransformerInterface;
use Symfony\Component\Form\PropertyPath;
use Symfony\Component\Form\FieldError;
use Symfony\Component\Form\FormConfiguration;
use Symfony\Component\Form\FormContext;
use Symfony\Component\Form\ValueTransformer\TransformationFailedException;
use Symfony\Tests\Component\Form\Fixtures\Author;
use Symfony\Tests\Component\Form\Fixtures\TestField;
@ -147,7 +147,7 @@ class FieldTest extends \PHPUnit_Framework_TestCase
public function testLocaleIsPassedToValueTransformer()
{
FormConfiguration::setDefaultLocale('de_DE');
FormContext::setLocale('de_DE');
$transformer = $this->getMock('Symfony\Component\Form\ValueTransformer\ValueTransformerInterface');
$transformer->expects($this->exactly(1))

View File

@ -15,7 +15,7 @@ require_once __DIR__ . '/Fixtures/Author.php';
require_once __DIR__ . '/Fixtures/TestField.php';
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormConfiguration;
use Symfony\Component\Form\FormContext;
use Symfony\Component\Form\Field;
use Symfony\Component\Form\HiddenField;
use Symfony\Component\Form\FieldGroup;
@ -68,10 +68,11 @@ class FormTest extends \PHPUnit_Framework_TestCase
protected function setUp()
{
FormConfiguration::disableDefaultCsrfProtection();
FormConfiguration::setDefaultCsrfSecrets(array());
$this->validator = $this->createMockValidator();
$this->form = new Form('author', new Author(), $this->validator);
$this->form = new Form('author', new Author(), $this->validator, array(
'csrf_protection' => false,
'csrf_secrets' => array(),
));
}
public function testConstructInitializesObject()
@ -84,19 +85,6 @@ class FormTest extends \PHPUnit_Framework_TestCase
new TestSetDataBeforeConfigureForm($this, 'author', new Author(), $this->validator);
}
public function testIsCsrfProtected()
{
$this->assertFalse($this->form->isCsrfProtected());
$this->form->enableCsrfProtection();
$this->assertTrue($this->form->isCsrfProtected());
$this->form->disableCsrfProtection();
$this->assertFalse($this->form->isCsrfProtected());
}
public function testNoCsrfProtectionByDefault()
{
$form = new Form('author', new Author(), $this->validator);
@ -104,60 +92,72 @@ class FormTest extends \PHPUnit_Framework_TestCase
$this->assertFalse($form->isCsrfProtected());
}
public function testDefaultCsrfProtectionCanBeEnabled()
public function testCsrfProtectionCanBeEnabled()
{
FormConfiguration::enableDefaultCsrfProtection();
$form = new Form('author', new Author(), $this->validator);
$form = new Form('author', new Author(), $this->validator, array(
'csrf_protection' => true,
));
$this->assertTrue($form->isCsrfProtected());
}
public function testGeneratedCsrfSecretByDefault()
{
$form = new Form('author', new Author(), $this->validator);
$form->enableCsrfProtection();
$form = new Form('author', new Author(), $this->validator, array(
'csrf_protection' => true,
));
$this->assertTrue(strlen($form->getCsrfSecret()) >= 32);
$secrets = $form->getCsrfSecrets();
$this->assertEquals(1, count($secrets));
$this->assertTrue(strlen($secrets[0]) >= 32);
}
public function testDefaultCsrfSecretsCanBeAdded()
public function testCsrfSecretsCanBeSet()
{
FormConfiguration::addDefaultCsrfSecret('foobar');
$form = new Form('author', new Author(), $this->validator, array(
'csrf_protection' => true,
'csrf_field_name' => '_token',
'csrf_secrets' => array('foobar', 'secret'),
));
$form = new Form('author', new Author(), $this->validator);
$form->enableCsrfProtection('_token', 'secret');
$this->assertEquals(md5('secret'.get_class($form).'foobar'), $form['_token']->getData());
$this->assertEquals(md5(get_class($form).'foobarsecret'), $form['_token']->getData());
}
public function testDefaultCsrfSecretsCanBeAddedAsClosures()
public function testCsrfSecretsCanBeSetAsClosures()
{
FormConfiguration::addDefaultCsrfSecret(function () {
$closure = function () {
return 'foobar';
});
};
$form = new Form('author', new Author(), $this->validator);
$form->enableCsrfProtection('_token', 'secret');
$form = new Form('author', new Author(), $this->validator, array(
'csrf_protection' => true,
'csrf_field_name' => '_token',
'csrf_secrets' => array($closure, 'secret'),
));
$this->assertEquals(md5('secret'.get_class($form).'foobar'), $form['_token']->getData());
$this->assertEquals(md5(get_class($form).'foobarsecret'), $form['_token']->getData());
}
public function testDefaultCsrfFieldNameCanBeSet()
public function testCsrfFieldNameCanBeSet()
{
FormConfiguration::setDefaultCsrfFieldName('foobar');
$form = new Form('author', new Author(), $this->validator);
$form->enableCsrfProtection();
$form = new Form('author', new Author(), $this->validator, array(
'csrf_protection' => true,
'csrf_field_name' => 'foobar',
));
$this->assertEquals('foobar', $form->getCsrfFieldName());
}
public function testCsrfProtectedFormsHaveExtraField()
{
$this->form->enableCsrfProtection();
$form = new Form('author', new Author(), $this->validator, array(
'csrf_protection' => true,
));
$this->assertTrue($this->form->has($this->form->getCsrfFieldName()));
$this->assertTrue($form->has($this->form->getCsrfFieldName()));
$field = $this->form->get($this->form->getCsrfFieldName());
$field = $form->get($form->getCsrfFieldName());
$this->assertTrue($field instanceof HiddenField);
$this->assertGreaterThanOrEqual(32, strlen($field->getDisplayedData()));
@ -172,46 +172,61 @@ class FormTest extends \PHPUnit_Framework_TestCase
public function testIsCsrfTokenValidPasses()
{
$this->form->enableCsrfProtection();
$form = new Form('author', new Author(), $this->validator, array(
'csrf_protection' => true,
));
$field = $this->form->getCsrfFieldName();
$token = $this->form->get($field)->getDisplayedData();
$field = $form->getCsrfFieldName();
$token = $form->get($field)->getDisplayedData();
$this->form->bind(array($field => $token));
$form->bind(array($field => $token));
$this->assertTrue($this->form->isCsrfTokenValid());
$this->assertTrue($form->isCsrfTokenValid());
}
public function testIsCsrfTokenValidFails()
{
$this->form->enableCsrfProtection();
$form = new Form('author', new Author(), $this->validator, array(
'csrf_protection' => true,
));
$field = $this->form->getCsrfFieldName();
$field = $form->getCsrfFieldName();
$this->form->bind(array($field => 'foobar'));
$form->bind(array($field => 'foobar'));
$this->assertFalse($this->form->isCsrfTokenValid());
$this->assertFalse($form->isCsrfTokenValid());
}
public function testValidationGroupsCanBeSet()
public function testValidationGroupNullByDefault()
{
$form = new Form('author', new Author(), $this->validator);
$this->assertNull($this->form->getValidationGroups());
}
public function testValidationGroupsCanBeSetToString()
{
$form = new Form('author', new Author(), $this->validator, array(
'validation_groups' => 'group',
));
$this->assertNull($form->getValidationGroups());
$form->setValidationGroups('group');
$this->assertEquals(array('group'), $form->getValidationGroups());
$form->setValidationGroups(array('group1', 'group2'));
}
public function testValidationGroupsCanBeSetToArray()
{
$form = new Form('author', new Author(), $this->validator, array(
'validation_groups' => array('group1', 'group2'),
));
$this->assertEquals(array('group1', 'group2'), $form->getValidationGroups());
$form->setValidationGroups(null);
$this->assertNull($form->getValidationGroups());
}
public function testBindUsesValidationGroups()
{
$field = $this->createMockField('firstName');
$form = new Form('author', new Author(), $this->validator);
$form = new Form('author', new Author(), $this->validator, array(
'validation_groups' => 'group',
));
$form->add($field);
$form->setValidationGroups('group');
$this->validator->expects($this->once())
->method('validate')
@ -225,7 +240,6 @@ class FormTest extends \PHPUnit_Framework_TestCase
$field = $this->createMockField('firstName');
$form = new Form('author', new Author());
$form->add($field);
$form->setValidationGroups('group');
$this->setExpectedException('Symfony\Component\Form\Exception\FormException');

View File

@ -12,13 +12,13 @@
namespace Symfony\Tests\Component\Form;
use Symfony\Component\Form\LanguageField;
use Symfony\Component\Form\FormConfiguration;
use Symfony\Component\Form\FormContext;
class LanguageFieldTest extends \PHPUnit_Framework_TestCase
{
public function testCountriesAreSelectable()
{
FormConfiguration::setDefaultLocale('de_AT');
FormContext::setLocale('de_AT');
$field = new LanguageField('language');
$choices = $field->getOtherChoices();

View File

@ -12,13 +12,13 @@
namespace Symfony\Tests\Component\Form;
use Symfony\Component\Form\LocaleField;
use Symfony\Component\Form\FormConfiguration;
use Symfony\Component\Form\FormContext;
class LocaleFieldTest extends \PHPUnit_Framework_TestCase
{
public function testLocalesAreSelectable()
{
FormConfiguration::setDefaultLocale('de_AT');
FormContext::setLocale('de_AT');
$field = new LocaleField('language');
$choices = $field->getOtherChoices();