diff --git a/UPGRADE-2.1.md b/UPGRADE-2.1.md
index 3ba03d1ea1..0af744d7c3 100644
--- a/UPGRADE-2.1.md
+++ b/UPGRADE-2.1.md
@@ -249,6 +249,16 @@
public function finishView(FormViewInterface $view, FormInterface $form, array $options)
```
+ * The method `createBuilder` was removed from `FormTypeInterface` for performance
+ reasons. It is now not possible anymore to use custom implementations of
+ `FormBuilderInterface` for specific form types.
+
+ If you are in such a situation, you can subclass `FormRegistry` instead and override
+ `resolveType` to return a custom `ResolvedFormTypeInterface` implementation, within
+ which you can create your own `FormBuilderInterface` implementation. You should
+ register this custom registry class under the service name "form.registry" in order
+ to replace the default implementation.
+
* If you previously inherited from `FieldType`, you should now inherit from
`FormType`. You should also set the option `compound` to `false` if your field
is not supposed to contain child fields.
@@ -1001,6 +1011,24 @@
));
```
+ * The methods `addType`, `hasType` and `getType` in `FormFactory` are deprecated
+ and will be removed in Symfony 2.3. You should use the methods with the same
+ name on the `FormRegistry` instead.
+
+ Before:
+
+ ```
+ $this->get('form.factory')->addType(new MyFormType());
+ ```
+
+ After:
+
+ ```
+ $registry = $this->get('form.registry');
+
+ $registry->addType($registry->resolveType(new MyFormType()));
+ ```
+
### Validator
* The methods `setMessage()`, `getMessageTemplate()` and
diff --git a/src/Symfony/Bridge/Doctrine/CHANGELOG.md b/src/Symfony/Bridge/Doctrine/CHANGELOG.md
index 1a5334ec38..30c3fe2f6f 100644
--- a/src/Symfony/Bridge/Doctrine/CHANGELOG.md
+++ b/src/Symfony/Bridge/Doctrine/CHANGELOG.md
@@ -7,3 +7,4 @@ CHANGELOG
* added a default implementation of the ManagerRegistry
* added a session storage for Doctrine DBAL
* DoctrineOrmTypeGuesser now guesses "collection" for array Doctrine type
+ * DoctrineType now caches its choice lists in order to improve performance
diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php
index 6f4433f4dc..31216ea319 100644
--- a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php
+++ b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php
@@ -68,17 +68,40 @@ abstract class DoctrineType extends AbstractType
$choiceList = function (Options $options) use ($registry, &$choiceListCache, &$time) {
$manager = $registry->getManager($options['em']);
- $choiceHashes = is_array($options['choices'])
- ? array_map('spl_object_hash', $options['choices'])
- : $options['choices'];
+ // Support for closures
+ $propertyHash = is_object($options['property'])
+ ? spl_object_hash($options['property'])
+ : $options['property'];
+
+ $choiceHashes = $options['choices'];
+
+ // Support for recursive arrays
+ if (is_array($choiceHashes)) {
+ // A second parameter ($key) is passed, so we cannot use
+ // spl_object_hash() directly (which strictly requires
+ // one parameter)
+ array_walk_recursive($choiceHashes, function ($value) {
+ return spl_object_hash($value);
+ });
+ }
+
+ // Support for custom loaders (with query builders)
+ $loaderHash = is_object($options['loader'])
+ ? spl_object_hash($options['loader'])
+ : $options['loader'];
+
+ // Support for closures
+ $groupByHash = is_object($options['group_by'])
+ ? spl_object_hash($options['group_by'])
+ : $options['group_by'];
$hash = md5(json_encode(array(
spl_object_hash($manager),
$options['class'],
- $options['property'],
- $options['loader'],
+ $propertyHash,
+ $loaderHash,
$choiceHashes,
- $options['group_by']
+ $groupByHash
)));
if (!isset($choiceListCache[$hash])) {
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml
index d3efd46741..8bef4bc28e 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml
@@ -6,13 +6,14 @@
Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension
+ Symfony\Component\Form\FormRegistry
Symfony\Component\Form\FormFactory
Symfony\Component\Form\Extension\Validator\ValidatorTypeGuesser
-
-
+
+
+
+
+
+
diff --git a/src/Symfony/Component/Form/AbstractType.php b/src/Symfony/Component/Form/AbstractType.php
index 281ee07f4d..42b24e3650 100644
--- a/src/Symfony/Component/Form/AbstractType.php
+++ b/src/Symfony/Component/Form/AbstractType.php
@@ -20,8 +20,9 @@ use Symfony\Component\OptionsResolver\OptionsResolverInterface;
abstract class AbstractType implements FormTypeInterface
{
/**
- * The extensions for this type
- * @var array An array of FormTypeExtensionInterface instances
+ * @var array
+ *
+ * @deprecated Deprecated since version 2.1, to be removed in 2.3.
*/
private $extensions = array();
@@ -46,14 +47,6 @@ abstract class AbstractType implements FormTypeInterface
{
}
- /**
- * {@inheritdoc}
- */
- public function createBuilder($name, FormFactoryInterface $factory, array $options)
- {
- return null;
- }
-
/**
* {@inheritdoc}
*/
@@ -98,21 +91,26 @@ abstract class AbstractType implements FormTypeInterface
}
/**
- * {@inheritdoc}
+ * Sets the extensions for this type.
+ *
+ * @param array $extensions An array of FormTypeExtensionInterface
+ *
+ * @throws Exception\UnexpectedTypeException if any extension does not implement FormTypeExtensionInterface
+ *
+ * @deprecated Deprecated since version 2.1, to be removed in 2.3.
*/
public function setExtensions(array $extensions)
{
- foreach ($extensions as $extension) {
- if (!$extension instanceof FormTypeExtensionInterface) {
- throw new UnexpectedTypeException($extension, 'Symfony\Component\Form\FormTypeExtensionInterface');
- }
- }
-
$this->extensions = $extensions;
}
/**
- * {@inheritdoc}
+ * Returns the extensions associated with this type.
+ *
+ * @return array An array of FormTypeExtensionInterface
+ *
+ * @deprecated Deprecated since version 2.1, to be removed in 2.3. Use
+ * {@link ResolvedFormTypeInterface::getTypeExtensions()} instead.
*/
public function getExtensions()
{
diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md
index 028bf20a8d..cbc37f0766 100644
--- a/src/Symfony/Component/Form/CHANGELOG.md
+++ b/src/Symfony/Component/Form/CHANGELOG.md
@@ -86,6 +86,7 @@ CHANGELOG
* `hasAttribute`
* `getClientData`
* added FormBuilder methods
+ * `getTypes`
* `addViewTransformer`
* `getViewTransformers`
* `resetViewTransformers`
@@ -157,3 +158,15 @@ CHANGELOG
* deprecated the options "data_timezone" and "user_timezone" in DateType, DateTimeType and TimeType
and renamed them to "model_timezone" and "view_timezone"
* fixed: TransformationFailedExceptions thrown in the model transformer are now caught by the form
+ * added FormRegistry and ResolvedFormTypeInterface
+ * deprecated FormFactory methods
+ * `addType`
+ * `hasType`
+ * `getType`
+ * [BC BREAK] FormFactory now expects a FormRegistryInterface as constructor argument
+ * [BC BREAK] The method `createBuilder` in FormTypeInterface is not supported anymore for performance reasons
+ * [BC BREAK] Removed `setTypes` from FormBuilder
+ * deprecated AbstractType methods
+ * `getExtensions`
+ * `setExtensions`
+ * ChoiceType now caches its created choice lists to improve performance
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php
index 12747fd175..0ef0def253 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php
@@ -20,7 +20,6 @@ use Symfony\Component\Form\FormViewInterface;
use Symfony\Component\Form\Extension\Core\EventListener\BindRequestListener;
use Symfony\Component\Form\Extension\Core\EventListener\TrimListener;
use Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper;
-use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\Form\Exception\FormException;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
@@ -93,8 +92,8 @@ class FormType extends AbstractType
}
$types = array();
- foreach ($form->getConfig()->getTypes() as $type) {
- $types[] = $type->getName();
+ for ($type = $form->getConfig()->getType(); null !== $type; $type = $type->getParent()) {
+ array_unshift($types, $type->getName());
}
if (!$translationDomain) {
@@ -149,7 +148,7 @@ class FormType extends AbstractType
{
// Derive "data_class" option from passed "data" object
$dataClass = function (Options $options) {
- return is_object($options['data']) ? get_class($options['data']) : null;
+ return isset($options['data']) && is_object($options['data']) ? get_class($options['data']) : null;
};
// Derive "empty_data" closure from "data_class" option
@@ -211,14 +210,6 @@ class FormType extends AbstractType
));
}
- /**
- * {@inheritdoc}
- */
- public function createBuilder($name, FormFactoryInterface $factory, array $options)
- {
- return new FormBuilder($name, $options['data_class'], new EventDispatcher(), $factory, $options);
- }
-
/**
* {@inheritdoc}
*/
diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php
index f9c56e377b..e191cc9669 100644
--- a/src/Symfony/Component/Form/Form.php
+++ b/src/Symfony/Component/Form/Form.php
@@ -195,11 +195,17 @@ class Form implements \IteratorAggregate, FormInterface
* @return array An array of FormTypeInterface
*
* @deprecated Deprecated since version 2.1, to be removed in 2.3. Use
- * {@link getConfig()} and {@link FormConfigInterface::getTypes()} instead.
+ * {@link getConfig()} and {@link FormConfigInterface::getType()} instead.
*/
public function getTypes()
{
- return $this->config->getTypes();
+ $types = array();
+
+ for ($type = $this->config->getType(); null !== $type; $type = $type->getParent()) {
+ array_unshift($types, $type->getInnerType());
+ }
+
+ return $types;
}
/**
@@ -948,34 +954,7 @@ class Form implements \IteratorAggregate, FormInterface
$parent = $this->parent->createView();
}
- $view = new FormView($this->config->getName());
-
- $view->setParent($parent);
-
- $types = (array) $this->config->getTypes();
- $options = $this->config->getOptions();
-
- foreach ($types as $type) {
- $type->buildView($view, $this, $options);
-
- foreach ($type->getExtensions() as $typeExtension) {
- $typeExtension->buildView($view, $this, $options);
- }
- }
-
- foreach ($this->children as $child) {
- $view->add($child->createView($view));
- }
-
- foreach ($types as $type) {
- $type->finishView($view, $this, $options);
-
- foreach ($type->getExtensions() as $typeExtension) {
- $typeExtension->finishView($view, $this, $options);
- }
- }
-
- return $view;
+ return $this->config->getType()->createView($this, $parent);
}
/**
diff --git a/src/Symfony/Component/Form/FormBuilder.php b/src/Symfony/Component/Form/FormBuilder.php
index be04e66bc3..4abec1824d 100644
--- a/src/Symfony/Component/Form/FormBuilder.php
+++ b/src/Symfony/Component/Form/FormBuilder.php
@@ -115,10 +115,10 @@ class FormBuilder extends FormConfig implements \IteratorAggregate, FormBuilderI
}
if (null !== $type) {
- return $this->getFormFactory()->createNamedBuilder($name, $type, null, $options, $this);
+ return $this->factory->createNamedBuilder($name, $type, null, $options, $this);
}
- return $this->getFormFactory()->createBuilderForProperty($this->getDataClass(), $name, null, $options, $this);
+ return $this->factory->createBuilderForProperty($this->getDataClass(), $name, null, $options, $this);
}
/**
@@ -266,4 +266,23 @@ class FormBuilder extends FormConfig implements \IteratorAggregate, FormBuilderI
{
return new \ArrayIterator($this->children);
}
+
+ /**
+ * Returns the types used by this builder.
+ *
+ * @return array An array of FormTypeInterface
+ *
+ * @deprecated Deprecated since version 2.1, to be removed in 2.3. Use
+ * {@link FormConfigInterface::getType()} instead.
+ */
+ public function getTypes()
+ {
+ $types = array();
+
+ for ($type = $this->getType(); null !== $type; $type = $type->getParent()) {
+ array_unshift($types, $type->getInnerType());
+ }
+
+ return $types;
+ }
}
diff --git a/src/Symfony/Component/Form/FormConfig.php b/src/Symfony/Component/Form/FormConfig.php
index 55a7303407..af68a13fce 100644
--- a/src/Symfony/Component/Form/FormConfig.php
+++ b/src/Symfony/Component/Form/FormConfig.php
@@ -59,9 +59,9 @@ class FormConfig implements FormConfigEditorInterface
private $compound = false;
/**
- * @var array
+ * @var ResolvedFormTypeInterface
*/
- private $types = array();
+ private $type;
/**
* @var array
@@ -377,9 +377,9 @@ class FormConfig implements FormConfigEditorInterface
/**
* {@inheritdoc}
*/
- public function getTypes()
+ public function getType()
{
- return $this->types;
+ return $this->type;
}
/**
@@ -671,9 +671,9 @@ class FormConfig implements FormConfigEditorInterface
/**
* {@inheritdoc}
*/
- public function setTypes(array $types)
+ public function setType(ResolvedFormTypeInterface $type)
{
- $this->types = $types;
+ $this->type = $type;
return $this;
}
diff --git a/src/Symfony/Component/Form/FormConfigEditorInterface.php b/src/Symfony/Component/Form/FormConfigEditorInterface.php
index 8463ad017c..b84dbb0eac 100644
--- a/src/Symfony/Component/Form/FormConfigEditorInterface.php
+++ b/src/Symfony/Component/Form/FormConfigEditorInterface.php
@@ -213,11 +213,11 @@ interface FormConfigEditorInterface extends FormConfigInterface
/**
* Set the types.
*
- * @param array $types An array FormTypeInterface
+ * @param ResolvedFormTypeInterface $type The type of the form.
*
* @return self The configuration object.
*/
- public function setTypes(array $types);
+ public function setType(ResolvedFormTypeInterface $type);
/**
* Sets the initial data of the form.
diff --git a/src/Symfony/Component/Form/FormConfigInterface.php b/src/Symfony/Component/Form/FormConfigInterface.php
index 25064b6adb..1274e4c7a5 100644
--- a/src/Symfony/Component/Form/FormConfigInterface.php
+++ b/src/Symfony/Component/Form/FormConfigInterface.php
@@ -79,9 +79,9 @@ interface FormConfigInterface
/**
* Returns the form types used to construct the form.
*
- * @return array An array of {@link FormTypeInterface} instances.
+ * @return ResolvedFormTypeInterface The form's type.
*/
- public function getTypes();
+ public function getType();
/**
* Returns the view transformers of the form.
diff --git a/src/Symfony/Component/Form/FormFactory.php b/src/Symfony/Component/Form/FormFactory.php
index 046804f039..a925b1a9bc 100644
--- a/src/Symfony/Component/Form/FormFactory.php
+++ b/src/Symfony/Component/Form/FormFactory.php
@@ -18,92 +18,14 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
class FormFactory implements FormFactoryInterface
{
- private static $requiredOptions = array(
- 'data',
- 'required',
- 'max_length',
- );
-
/**
- * Extensions
- * @var array An array of FormExtensionInterface
+ * @var FormRegistryInterface
*/
- private $extensions = array();
+ private $registry;
- /**
- * All known types (cache)
- * @var array An array of FormTypeInterface
- */
- private $types = array();
-
- /**
- * The guesser chain
- * @var FormTypeGuesserChain
- */
- private $guesser;
-
- /**
- * Constructor.
- *
- * @param array $extensions An array of FormExtensionInterface
- *
- * @throws UnexpectedTypeException if any extension does not implement FormExtensionInterface
- */
- public function __construct(array $extensions)
+ public function __construct(FormRegistryInterface $registry)
{
- foreach ($extensions as $extension) {
- if (!$extension instanceof FormExtensionInterface) {
- throw new UnexpectedTypeException($extension, 'Symfony\Component\Form\FormExtensionInterface');
- }
- }
-
- $this->extensions = $extensions;
- }
-
- /**
- * {@inheritdoc}
- */
- public function hasType($name)
- {
- if (isset($this->types[$name])) {
- return true;
- }
-
- try {
- $this->loadType($name);
- } catch (FormException $e) {
- return false;
- }
-
- return true;
- }
-
- /**
- * {@inheritdoc}
- */
- public function addType(FormTypeInterface $type)
- {
- $this->loadTypeExtensions($type);
-
- $this->validateFormTypeName($type);
-
- $this->types[$type->getName()] = $type;
- }
-
- /**
- * {@inheritdoc}
- */
- public function getType($name)
- {
- if (!is_string($name)) {
- throw new UnexpectedTypeException($name, 'string');
- }
-
- if (!isset($this->types[$name])) {
- $this->loadType($name);
- }
-
- return $this->types[$name];
+ $this->registry = $registry;
}
/**
@@ -135,7 +57,9 @@ class FormFactory implements FormFactoryInterface
*/
public function createBuilder($type, $data = null, array $options = array(), FormBuilderInterface $parent = null)
{
- $name = is_object($type) ? $type->getName() : $type;
+ $name = $type instanceof FormTypeInterface || $type instanceof ResolvedFormTypeInterface
+ ? $type->getName()
+ : $type;
return $this->createNamedBuilder($name, $type, $data, $options, $parent);
}
@@ -145,89 +69,22 @@ class FormFactory implements FormFactoryInterface
*/
public function createNamedBuilder($name, $type, $data = null, array $options = array(), FormBuilderInterface $parent = null)
{
- if (!array_key_exists('data', $options)) {
+ if (null !== $data && !array_key_exists('data', $options)) {
$options['data'] = $data;
}
- $builder = null;
- $types = array();
- $optionsResolver = new OptionsResolver();
-
- // Bottom-up determination of the type hierarchy
- // Start with the actual type and look for the parent type
- // The complete hierarchy is saved in $types, the first entry being
- // the root and the last entry being the leaf (the concrete type)
- while (null !== $type) {
- if ($type instanceof FormTypeInterface) {
- if ($type->getName() == $type->getParent($options)) {
- throw new FormException(sprintf('The form type name "%s" for class "%s" cannot be the same as the parent type.', $type->getName(), get_class($type)));
- }
-
- $this->addType($type);
- } elseif (is_string($type)) {
- $type = $this->getType($type);
- } else {
- throw new UnexpectedTypeException($type, 'string or Symfony\Component\Form\FormTypeInterface');
- }
-
- array_unshift($types, $type);
-
- $type = $type->getParent();
+ if ($type instanceof ResolvedFormTypeInterface) {
+ $this->registry->addType($type);
+ } elseif ($type instanceof FormTypeInterface) {
+ $type = $this->registry->resolveType($type);
+ $this->registry->addType($type);
+ } elseif (is_string($type)) {
+ $type = $this->registry->getType($type);
+ } else {
+ throw new UnexpectedTypeException($type, 'string, Symfony\Component\Form\ResolvedFormTypeInterface or Symfony\Component\Form\FormTypeInterface');
}
- // Top-down determination of the default options
- foreach ($types as $type) {
- // Merge the default options of all types to an array of default
- // options. Default options of children override default options
- // of parents.
- /* @var FormTypeInterface $type */
- $type->setDefaultOptions($optionsResolver);
-
- foreach ($type->getExtensions() as $typeExtension) {
- /* @var FormTypeExtensionInterface $typeExtension */
- $typeExtension->setDefaultOptions($optionsResolver);
- }
- }
-
- // Resolve concrete type
- $type = end($types);
-
- // Validate options required by the factory
- $diff = array();
-
- foreach (self::$requiredOptions as $requiredOption) {
- if (!$optionsResolver->isKnown($requiredOption)) {
- $diff[] = $requiredOption;
- }
- }
-
- if (count($diff) > 0) {
- throw new TypeDefinitionException(sprintf('Type "%s" should support the option(s) "%s"', $type->getName(), implode('", "', $diff)));
- }
-
- // Resolve options
- $options = $optionsResolver->resolve($options);
-
- for ($i = 0, $l = count($types); $i < $l && !$builder; ++$i) {
- $builder = $types[$i]->createBuilder($name, $this, $options);
- }
-
- if (!$builder) {
- throw new TypeDefinitionException(sprintf('Type "%s" or any of its parents should return a FormBuilderInterface instance from createBuilder()', $type->getName()));
- }
-
- $builder->setTypes($types);
- $builder->setParent($parent);
-
- foreach ($types as $type) {
- $type->buildForm($builder, $options);
-
- foreach ($type->getExtensions() as $typeExtension) {
- $typeExtension->buildForm($builder, $options);
- }
- }
-
- return $builder;
+ return $type->createBuilder($this, $name, $options, $parent);
}
/**
@@ -235,16 +92,13 @@ class FormFactory implements FormFactoryInterface
*/
public function createBuilderForProperty($class, $property, $data = null, array $options = array(), FormBuilderInterface $parent = null)
{
- if (!$this->guesser) {
- $this->loadGuesser();
- }
-
- $typeGuess = $this->guesser->guessType($class, $property);
- $maxLengthGuess = $this->guesser->guessMaxLength($class, $property);
+ $guesser = $this->registry->getTypeGuesser();
+ $typeGuess = $guesser->guessType($class, $property);
+ $maxLengthGuess = $guesser->guessMaxLength($class, $property);
// Keep $minLengthGuess for BC until Symfony 2.3
- $minLengthGuess = $this->guesser->guessMinLength($class, $property);
- $requiredGuess = $this->guesser->guessRequired($class, $property);
- $patternGuess = $this->guesser->guessPattern($class, $property);
+ $minLengthGuess = $guesser->guessMinLength($class, $property);
+ $requiredGuess = $guesser->guessRequired($class, $property);
+ $patternGuess = $guesser->guessPattern($class, $property);
$type = $typeGuess ? $typeGuess->getType() : 'text';
@@ -278,75 +132,50 @@ class FormFactory implements FormFactoryInterface
}
/**
- * Initializes the guesser chain.
+ * Returns whether the given type is supported.
+ *
+ * @param string $name The name of the type
+ *
+ * @return Boolean Whether the type is supported
+ *
+ * @deprecated Deprecated since version 2.1, to be removed in 2.3. Use
+ * {@link FormRegistryInterface::hasType()} instead.
*/
- private function loadGuesser()
+ public function hasType($name)
{
- $guessers = array();
-
- foreach ($this->extensions as $extension) {
- $guesser = $extension->getTypeGuesser();
-
- if ($guesser) {
- $guessers[] = $guesser;
- }
- }
-
- $this->guesser = new FormTypeGuesserChain($guessers);
+ return $this->registry->hasType($name);
}
/**
- * Loads a type.
- *
- * @param string $name The type name
- *
- * @throws FormException if the type is not provided by any registered extension
- */
- private function loadType($name)
- {
- $type = null;
-
- 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));
- }
-
- $this->loadTypeExtensions($type);
-
- $this->validateFormTypeName($type);
-
- $this->types[$name] = $type;
- }
-
- /**
- * Loads the extensions for a given type.
+ * Adds a type.
*
* @param FormTypeInterface $type The type
+ *
+ * @deprecated Deprecated since version 2.1, to be removed in 2.3. Use
+ * {@link FormRegistryInterface::resolveType()} and
+ * {@link FormRegistryInterface::addType()} instead.
*/
- private function loadTypeExtensions(FormTypeInterface $type)
+ public function addType(FormTypeInterface $type)
{
- $typeExtensions = array();
-
- foreach ($this->extensions as $extension) {
- $typeExtensions = array_merge(
- $typeExtensions,
- $extension->getTypeExtensions($type->getName())
- );
- }
-
- $type->setExtensions($typeExtensions);
+ $this->registry->addType($this->registry->resolveType($type));
}
- private function validateFormTypeName(FormTypeInterface $type)
+ /**
+ * Returns a type by name.
+ *
+ * This methods registers the type extensions from the form extensions.
+ *
+ * @param string $name The name of the type
+ *
+ * @return FormTypeInterface The type
+ *
+ * @throws Exception\FormException if the type can not be retrieved from any extension
+ *
+ * @deprecated Deprecated since version 2.1, to be removed in 2.3. Use
+ * {@link FormRegistryInterface::getType()} instead.
+ */
+ public function getType($name)
{
- if (!preg_match('/^[a-z0-9_]*$/i', $type->getName())) {
- throw new FormException(sprintf('The "%s" form type name ("%s") is not valid. Names must only contain letters, numbers, and "_".', get_class($type), $type->getName()));
- }
+ return $this->registry->getType($name)->getInnerType();
}
}
diff --git a/src/Symfony/Component/Form/FormFactoryInterface.php b/src/Symfony/Component/Form/FormFactoryInterface.php
index 457be5e24f..73f31aa373 100644
--- a/src/Symfony/Component/Form/FormFactoryInterface.php
+++ b/src/Symfony/Component/Form/FormFactoryInterface.php
@@ -112,33 +112,4 @@ interface FormFactoryInterface
* @throws Exception\FormException if any given option is not applicable to the form type
*/
public function createBuilderForProperty($class, $property, $data = null, array $options = array(), FormBuilderInterface $parent = null);
-
- /**
- * Returns a type by name.
- *
- * This methods registers the type extensions from the form extensions.
- *
- * @param string $name The name of the type
- *
- * @return FormTypeInterface The type
- *
- * @throws Exception\FormException if the type can not be retrieved from any extension
- */
- public function getType($name);
-
- /**
- * Returns whether the given type is supported.
- *
- * @param string $name The name of the type
- *
- * @return Boolean Whether the type is supported
- */
- public function hasType($name);
-
- /**
- * Adds a type.
- *
- * @param FormTypeInterface $type The type
- */
- public function addType(FormTypeInterface $type);
}
diff --git a/src/Symfony/Component/Form/FormRegistry.php b/src/Symfony/Component/Form/FormRegistry.php
new file mode 100644
index 0000000000..12c610153e
--- /dev/null
+++ b/src/Symfony/Component/Form/FormRegistry.php
@@ -0,0 +1,164 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Form;
+
+use Symfony\Component\Form\Exception\UnexpectedTypeException;
+use Symfony\Component\Form\Exception\FormException;
+
+/**
+ * The central registry of the Form component.
+ *
+ * @author Bernhard Schussek
+ */
+class FormRegistry implements FormRegistryInterface
+{
+ /**
+ * Extensions
+ * @var array An array of FormExtensionInterface
+ */
+ private $extensions = array();
+
+ /**
+ * @var array
+ */
+ private $types = array();
+
+ /**
+ * @var FormTypeGuesserInterface
+ */
+ private $guesser;
+
+ /**
+ * Constructor.
+ *
+ * @param array $extensions An array of FormExtensionInterface
+ *
+ * @throws UnexpectedTypeException if any extension does not implement FormExtensionInterface
+ */
+ public function __construct(array $extensions)
+ {
+ foreach ($extensions as $extension) {
+ if (!$extension instanceof FormExtensionInterface) {
+ throw new UnexpectedTypeException($extension, 'Symfony\Component\Form\FormExtensionInterface');
+ }
+ }
+
+ $this->extensions = $extensions;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function resolveType(FormTypeInterface $type)
+ {
+ $typeExtensions = array();
+
+ foreach ($this->extensions as $extension) {
+ /* @var FormExtensionInterface $extension */
+ $typeExtensions = array_merge(
+ $typeExtensions,
+ $extension->getTypeExtensions($type->getName())
+ );
+ }
+
+ $parent = $type->getParent() ? $this->getType($type->getParent()) : null;
+
+ return new ResolvedFormType($type, $typeExtensions, $parent);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addType(ResolvedFormTypeInterface $type)
+ {
+ $this->types[$type->getName()] = $type;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getType($name)
+ {
+ if (!is_string($name)) {
+ throw new UnexpectedTypeException($name, 'string');
+ }
+
+ if (!isset($this->types[$name])) {
+ $type = null;
+
+ foreach ($this->extensions as $extension) {
+ /* @var FormExtensionInterface $extension */
+ if ($extension->hasType($name)) {
+ $type = $extension->getType($name);
+ break;
+ }
+ }
+
+ if (!$type) {
+ throw new FormException(sprintf('Could not load type "%s"', $name));
+ }
+
+ $this->addType($this->resolveType($type));
+ }
+
+ return $this->types[$name];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasType($name)
+ {
+ if (isset($this->types[$name])) {
+ return true;
+ }
+
+ try {
+ $this->getType($name);
+ } catch (FormException $e) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getTypeGuesser()
+ {
+ if (null === $this->guesser) {
+ $guessers = array();
+
+ foreach ($this->extensions as $extension) {
+ /* @var FormExtensionInterface $extension */
+ $guesser = $extension->getTypeGuesser();
+
+ if ($guesser) {
+ $guessers[] = $guesser;
+ }
+ }
+
+ $this->guesser = new FormTypeGuesserChain($guessers);
+ }
+
+ return $this->guesser;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getExtensions()
+ {
+ return $this->extensions;
+ }
+}
diff --git a/src/Symfony/Component/Form/FormRegistryInterface.php b/src/Symfony/Component/Form/FormRegistryInterface.php
new file mode 100644
index 0000000000..e4f4099dcd
--- /dev/null
+++ b/src/Symfony/Component/Form/FormRegistryInterface.php
@@ -0,0 +1,72 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Form;
+
+/**
+ * The central registry of the Form component.
+ *
+ * @author Bernhard Schussek
+ */
+interface FormRegistryInterface
+{
+ /**
+ * Adds a form type.
+ *
+ * @param ResolvedFormTypeInterface $type The type
+ */
+ public function addType(ResolvedFormTypeInterface $type);
+
+ /**
+ * Returns a form type by name.
+ *
+ * This methods registers the type extensions from the form extensions.
+ *
+ * @param string $name The name of the type
+ *
+ * @return ResolvedFormTypeInterface The type
+ *
+ * @throws Exception\FormException if the type can not be retrieved from any extension
+ */
+ public function getType($name);
+
+ /**
+ * Returns whether the given form type is supported.
+ *
+ * @param string $name The name of the type
+ *
+ * @return Boolean Whether the type is supported
+ */
+ public function hasType($name);
+
+ /**
+ * Resolves a form type.
+ *
+ * @param FormTypeInterface $type
+ *
+ * @return ResolvedFormTypeInterface
+ */
+ public function resolveType(FormTypeInterface $type);
+
+ /**
+ * Returns the guesser responsible for guessing types.
+ *
+ * @return FormTypeGuesserInterface
+ */
+ public function getTypeGuesser();
+
+ /**
+ * Returns the extensions loaded by the framework.
+ *
+ * @return array
+ */
+ public function getExtensions();
+}
diff --git a/src/Symfony/Component/Form/FormTypeInterface.php b/src/Symfony/Component/Form/FormTypeInterface.php
index 0e5d20d723..00e96dd25b 100644
--- a/src/Symfony/Component/Form/FormTypeInterface.php
+++ b/src/Symfony/Component/Form/FormTypeInterface.php
@@ -68,20 +68,6 @@ interface FormTypeInterface
*/
public function finishView(FormViewInterface $view, FormInterface $form, array $options);
- /**
- * Returns a builder for the current type.
- *
- * The builder is retrieved by going up in the type hierarchy when a type does
- * not provide one.
- *
- * @param string $name The name of the builder
- * @param FormFactoryInterface $factory The form factory
- * @param array $options The options
- *
- * @return FormBuilderInterface|null A form builder or null when the type does not have a builder
- */
- public function createBuilder($name, FormFactoryInterface $factory, array $options);
-
/**
* Sets the default options for this type.
*
@@ -102,20 +88,4 @@ interface FormTypeInterface
* @return string The name of this type
*/
public function getName();
-
- /**
- * Sets the extensions for this type.
- *
- * @param array $extensions An array of FormTypeExtensionInterface
- *
- * @throws Exception\UnexpectedTypeException if any extension does not implement FormTypeExtensionInterface
- */
- public function setExtensions(array $extensions);
-
- /**
- * Returns the extensions associated with this type.
- *
- * @return array An array of FormTypeExtensionInterface
- */
- public function getExtensions();
}
diff --git a/src/Symfony/Component/Form/ResolvedFormType.php b/src/Symfony/Component/Form/ResolvedFormType.php
new file mode 100644
index 0000000000..3a85679e26
--- /dev/null
+++ b/src/Symfony/Component/Form/ResolvedFormType.php
@@ -0,0 +1,213 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Form;
+
+use Symfony\Component\Form\Exception\FormException;
+use Symfony\Component\Form\Exception\UnexpectedTypeException;
+use Symfony\Component\Form\Exception\TypeDefinitionException;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * A wrapper for a form type and its extensions.
+ *
+ * @author Bernhard Schussek
+ */
+class ResolvedFormType implements ResolvedFormTypeInterface
+{
+ /**
+ * @var FormTypeInterface
+ */
+ private $innerType;
+
+ /**
+ * @var array
+ */
+ private $typeExtensions;
+
+ /**
+ * @var ResolvedFormType
+ */
+ private $parent;
+
+ /**
+ * @var OptionsResolver
+ */
+ private $optionsResolver;
+
+ public function __construct(FormTypeInterface $innerType, array $typeExtensions = array(), ResolvedFormType $parent = null)
+ {
+ if (!preg_match('/^[a-z0-9_]*$/i', $innerType->getName())) {
+ throw new FormException(sprintf(
+ 'The "%s" form type name ("%s") is not valid. Names must only contain letters, numbers, and "_".',
+ get_class($innerType),
+ $innerType->getName()
+ ));
+ }
+
+ foreach ($typeExtensions as $extension) {
+ if (!$extension instanceof FormTypeExtensionInterface) {
+ throw new UnexpectedTypeException($extension, 'Symfony\Component\Form\FormTypeExtensionInterface');
+ }
+ }
+
+ // BC
+ if ($innerType instanceof AbstractType) {
+ /* @var AbstractType $innerType */
+ $innerType->setExtensions($typeExtensions);
+ }
+
+ $this->innerType = $innerType;
+ $this->typeExtensions = $typeExtensions;
+ $this->parent = $parent;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return $this->innerType->getName();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getInnerType()
+ {
+ return $this->innerType;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getTypeExtensions()
+ {
+ // BC
+ if ($this->innerType instanceof AbstractType) {
+ return $this->innerType->getExtensions();
+ }
+
+ return $this->typeExtensions;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createBuilder(FormFactoryInterface $factory, $name, array $options = array(), FormBuilderInterface $parent = null)
+ {
+ $options = $this->getOptionsResolver()->resolve($options);
+
+ // Should be decoupled from the specific option at some point
+ $dataClass = isset($options['data_class']) ? $options['data_class'] : null;
+
+ $builder = new FormBuilder($name, $dataClass, new EventDispatcher(), $factory, $options);
+ $builder->setType($this);
+ $builder->setParent($parent);
+
+ $this->buildForm($builder, $options);
+
+ return $builder;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createView(FormInterface $form, FormViewInterface $parent = null)
+ {
+ $options = $form->getConfig()->getOptions();
+
+ $view = new FormView($form->getConfig()->getName());
+ $view->setParent($parent);
+
+ $this->buildView($view, $form, $options);
+
+ foreach ($form as $child) {
+ /* @var FormInterface $child */
+ $view->add($child->createView($view));
+ }
+
+ $this->finishView($view, $form, $options);
+
+ return $view;
+ }
+
+ private function buildForm(FormBuilderInterface $builder, array $options)
+ {
+ if (null !== $this->parent) {
+ $this->parent->buildForm($builder, $options);
+ }
+
+ $this->innerType->buildForm($builder, $options);
+
+ foreach ($this->typeExtensions as $extension) {
+ /* @var FormTypeExtensionInterface $extension */
+ $extension->buildForm($builder, $options);
+ }
+ }
+
+ private function buildView(FormViewInterface $view, FormInterface $form, array $options)
+ {
+ if (null !== $this->parent) {
+ $this->parent->buildView($view, $form, $options);
+ }
+
+ $this->innerType->buildView($view, $form, $options);
+
+ foreach ($this->typeExtensions as $extension) {
+ /* @var FormTypeExtensionInterface $extension */
+ $extension->buildView($view, $form, $options);
+ }
+ }
+
+ private function finishView(FormViewInterface $view, FormInterface $form, array $options)
+ {
+ if (null !== $this->parent) {
+ $this->parent->finishView($view, $form, $options);
+ }
+
+ $this->innerType->finishView($view, $form, $options);
+
+ foreach ($this->typeExtensions as $extension) {
+ /* @var FormTypeExtensionInterface $extension */
+ $extension->finishView($view, $form, $options);
+ }
+ }
+
+ private function getOptionsResolver()
+ {
+ if (null === $this->optionsResolver) {
+ if (null !== $this->parent) {
+ $this->optionsResolver = clone $this->parent->getOptionsResolver();
+ } else {
+ $this->optionsResolver = new OptionsResolver();
+ }
+
+ $this->innerType->setDefaultOptions($this->optionsResolver);
+
+ foreach ($this->typeExtensions as $extension) {
+ /* @var FormTypeExtensionInterface $extension */
+ $extension->setDefaultOptions($this->optionsResolver);
+ }
+ }
+
+ return $this->optionsResolver;
+ }
+}
diff --git a/src/Symfony/Component/Form/ResolvedFormTypeInterface.php b/src/Symfony/Component/Form/ResolvedFormTypeInterface.php
new file mode 100644
index 0000000000..0620981544
--- /dev/null
+++ b/src/Symfony/Component/Form/ResolvedFormTypeInterface.php
@@ -0,0 +1,70 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Form;
+
+/**
+ * A wrapper for a form type and its extensions.
+ *
+ * @author Bernhard Schussek
+ */
+interface ResolvedFormTypeInterface
+{
+ /**
+ * Returns the name of the type.
+ *
+ * @return string The type name.
+ */
+ public function getName();
+
+ /**
+ * Returns the parent type.
+ *
+ * @return ResolvedFormTypeInterface The parent type or null.
+ */
+ public function getParent();
+
+ /**
+ * Returns the wrapped form type.
+ *
+ * @return FormTypeInterface The wrapped form type.
+ */
+ public function getInnerType();
+
+ /**
+ * Returns the extensions of the wrapped form type.
+ *
+ * @return array An array of {@link FormTypeExtensionInterface} instances.
+ */
+ public function getTypeExtensions();
+
+ /**
+ * Creates a new form builder for this type.
+ *
+ * @param FormFactoryInterface $factory The form factory.
+ * @param string $name The name for the builder.
+ * @param array $options The builder options.
+ * @param FormBuilderInterface $parent The parent builder object or null.
+ *
+ * @return FormBuilderInterface The created form builder.
+ */
+ public function createBuilder(FormFactoryInterface $factory, $name, array $options = array(), FormBuilderInterface $parent = null);
+
+ /**
+ * Creates a new form view for a form of this type.
+ *
+ * @param FormInterface $form The form to create a view for.
+ * @param FormViewInterface $parent The parent view or null.
+ *
+ * @return FormViewInterface The created form view.
+ */
+ public function createView(FormInterface $form, FormViewInterface $parent = null);
+}
diff --git a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php
index 4398adf70a..40195c2f3d 100644
--- a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php
+++ b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php
@@ -17,7 +17,7 @@ use Symfony\Component\Form\FormFactory;
use Symfony\Component\Form\Extension\Core\CoreExtension;
use Symfony\Component\Form\Extension\Csrf\CsrfExtension;
-abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
+abstract class AbstractLayoutTest extends FormIntegrationTestCase
{
protected $csrfProvider;
@@ -33,10 +33,15 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
$this->csrfProvider = $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface');
- $this->factory = new FormFactory(array(
+ parent::setUp();
+ }
+
+ protected function getExtensions()
+ {
+ return array(
new CoreExtension(),
new CsrfExtension($this->csrfProvider),
- ));
+ );
}
protected function tearDown()
diff --git a/src/Symfony/Component/Form/Tests/CompoundFormPerformanceTest.php b/src/Symfony/Component/Form/Tests/CompoundFormPerformanceTest.php
new file mode 100644
index 0000000000..4da8afbbe3
--- /dev/null
+++ b/src/Symfony/Component/Form/Tests/CompoundFormPerformanceTest.php
@@ -0,0 +1,46 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Form\Tests;
+
+/**
+ * @author Bernhard Schussek
+ */
+class CompoundFormPerformanceTest extends FormPerformanceTestCase
+{
+ /**
+ * Create a compound form multiple times, as happens in a collection form
+ */
+ public function testArrayBasedForm()
+ {
+ $this->setMaxRunningTime(1);
+
+ for ($i = 0; $i < 40; ++$i) {
+ $form = $this->factory->createBuilder('form')
+ ->add('firstName', 'text')
+ ->add('lastName', 'text')
+ ->add('gender', 'choice', array(
+ 'choices' => array('male' => 'Male', 'female' => 'Female'),
+ 'required' => false,
+ ))
+ ->add('age', 'number')
+ ->add('birthDate', 'birthday')
+ ->add('city', 'choice', array(
+ // simulate 300 different cities
+ 'choices' => range(1, 300),
+ ))
+ ->getForm();
+
+ // load the form into a view
+ $form->createView();
+ }
+ }
+}
diff --git a/src/Symfony/Component/Form/Tests/CompoundFormTest.php b/src/Symfony/Component/Form/Tests/CompoundFormTest.php
index 8c9652d30b..1fecee9c44 100644
--- a/src/Symfony/Component/Form/Tests/CompoundFormTest.php
+++ b/src/Symfony/Component/Form/Tests/CompoundFormTest.php
@@ -558,103 +558,6 @@ class FormTest extends AbstractFormTest
$this->assertEquals(array('extra' => 'data'), $form->getExtraData());
}
- public function testCreateView()
- {
- $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())
- ->method('buildView')
- ->will($this->returnCallback(function (FormView $view, Form $form) use ($test, &$calls) {
- $calls[] = 'type1::buildView';
- $test->assertTrue($view->hasParent());
- $test->assertEquals(0, count($view));
- }));
-
- $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->assertEquals(0, count($view));
- }));
-
- $type2->expects($this->once())
- ->method('buildView')
- ->will($this->returnCallback(function (FormView $view, Form $form) use ($test, &$calls) {
- $calls[] = 'type2::buildView';
- $test->assertTrue($view->hasParent());
- $test->assertEquals(0, count($view));
- }));
-
- $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->assertEquals(0, count($view));
- }));
-
- $type1->expects($this->once())
- ->method('finishView')
- ->will($this->returnCallback(function (FormView $view, Form $form) use ($test, &$calls) {
- $calls[] = 'type1::finishView';
- $test->assertGreaterThan(0, count($view));
- }));
-
- $type1Extension->expects($this->once())
- ->method('finishView')
- ->will($this->returnCallback(function (FormView $view, Form $form) use ($test, &$calls) {
- $calls[] = 'type1ext::finishView';
- $test->assertGreaterThan(0, count($view));
- }));
-
- $type2->expects($this->once())
- ->method('finishView')
- ->will($this->returnCallback(function (FormView $view, Form $form) use ($test, &$calls) {
- $calls[] = 'type2::finishView';
- $test->assertGreaterThan(0, count($view));
- }));
-
- $type2Extension->expects($this->once())
- ->method('finishView')
- ->will($this->returnCallback(function (FormView $view, Form $form) use ($test, &$calls) {
- $calls[] = 'type2ext::finishView';
- $test->assertGreaterThan(0, count($view));
- }));
-
- $form = $this->getBuilder()
- ->setCompound(true)
- ->setDataMapper($this->getDataMapper())
- ->setTypes(array($type1, $type2))
- ->getForm();
- $form->setParent($this->getBuilder()->getForm());
- $form->add($this->getBuilder()->getForm());
-
- $form->createView();
-
- $this->assertEquals(array(
- 0 => 'type1::buildView',
- 1 => 'type1ext::buildView',
- 2 => 'type2::buildView',
- 3 => 'type2ext::buildView',
- 4 => 'type1::finishView',
- 5 => 'type1ext::finishView',
- 6 => 'type2::finishView',
- 7 => 'type2ext::finishView',
- ), $calls);
- }
-
public function testGetErrorsAsStringDeep()
{
$parent = $this->getBuilder()
diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/TypeTestCase.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/TypeTestCase.php
index 6acda436c4..9101e45308 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/TypeTestCase.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/TypeTestCase.php
@@ -12,17 +12,11 @@
namespace Symfony\Component\Form\Tests\Extension\Core\Type;
use Symfony\Component\Form\FormBuilder;
-use Symfony\Component\Form\FormFactory;
-use Symfony\Component\Form\Extension\Core\CoreExtension;
+use Symfony\Component\Form\Tests\FormIntegrationTestCase;
use Symfony\Component\EventDispatcher\EventDispatcher;
-abstract class TypeTestCase extends \PHPUnit_Framework_TestCase
+abstract class TypeTestCase extends FormIntegrationTestCase
{
- /**
- * @var FormFactory
- */
- protected $factory;
-
/**
* @var FormBuilder
*/
@@ -35,29 +29,12 @@ abstract class TypeTestCase extends \PHPUnit_Framework_TestCase
protected function setUp()
{
- if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) {
- $this->markTestSkipped('The "EventDispatcher" component is not available');
- }
+ parent::setUp();
$this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
- $this->factory = new FormFactory($this->getExtensions());
$this->builder = new FormBuilder(null, null, $this->dispatcher, $this->factory);
}
- protected function tearDown()
- {
- $this->builder = null;
- $this->dispatcher = null;
- $this->factory = null;
- }
-
- protected function getExtensions()
- {
- return array(
- new CoreExtension(),
- );
- }
-
public static function assertDateTimeEquals(\DateTime $expected, \DateTime $actual)
{
self::assertEquals($expected->format('c'), $actual->format('c'));
diff --git a/src/Symfony/Component/Form/Tests/Fixtures/FooSubType.php b/src/Symfony/Component/Form/Tests/Fixtures/FooSubType.php
new file mode 100644
index 0000000000..4f7ba6d4a7
--- /dev/null
+++ b/src/Symfony/Component/Form/Tests/Fixtures/FooSubType.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Form\Tests\Fixtures;
+
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\FormBuilder;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\Form\FormFactoryInterface;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\OptionsResolver\OptionsResolverInterface;
+
+class FooSubType extends AbstractType
+{
+ public function getName()
+ {
+ return 'foo_sub_type';
+ }
+
+ public function getParent()
+ {
+ return 'foo';
+ }
+}
diff --git a/src/Symfony/Component/Form/Tests/Fixtures/FooType.php b/src/Symfony/Component/Form/Tests/Fixtures/FooType.php
index feae68900f..d26d3f7683 100644
--- a/src/Symfony/Component/Form/Tests/Fixtures/FooType.php
+++ b/src/Symfony/Component/Form/Tests/Fixtures/FooType.php
@@ -16,42 +16,15 @@ use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class FooType extends AbstractType
{
- public function buildForm(FormBuilderInterface $builder, array $options)
- {
- $builder->setAttribute('foo', 'x');
- $builder->setAttribute('data_option', $options['data']);
- }
-
public function getName()
{
return 'foo';
}
- public function createBuilder($name, FormFactoryInterface $factory, array $options)
- {
- return new FormBuilder($name, null, new EventDispatcher(), $factory);
- }
-
- public function getDefaultOptions()
- {
- return array(
- 'data' => null,
- 'required' => false,
- 'max_length' => null,
- 'a_or_b' => 'a',
- );
- }
-
- public function getAllowedOptionValues()
- {
- return array(
- 'a_or_b' => array('a', 'b'),
- );
- }
-
public function getParent()
{
return null;
diff --git a/src/Symfony/Component/Form/Tests/FormFactoryTest.php b/src/Symfony/Component/Form/Tests/FormFactoryTest.php
index 189cdeec8c..6165fb97b5 100644
--- a/src/Symfony/Component/Form/Tests/FormFactoryTest.php
+++ b/src/Symfony/Component/Form/Tests/FormFactoryTest.php
@@ -12,6 +12,7 @@
namespace Symfony\Component\Form\Tests;
use Symfony\Component\Form\FormBuilder;
+use Symfony\Component\Form\FormTypeGuesserChain;
use Symfony\Component\Form\FormFactory;
use Symfony\Component\Form\Guess\Guess;
use Symfony\Component\Form\Guess\ValueGuess;
@@ -23,16 +24,29 @@ use Symfony\Component\Form\Tests\Fixtures\FooType;
use Symfony\Component\Form\Tests\Fixtures\FooTypeBarExtension;
use Symfony\Component\Form\Tests\Fixtures\FooTypeBazExtension;
+/**
+ * @author Bernhard Schussek
+ */
class FormFactoryTest extends \PHPUnit_Framework_TestCase
{
- private $extension1;
-
- private $extension2;
-
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
private $guesser1;
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
private $guesser2;
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ private $registry;
+
+ /**
+ * @var FormFactory
+ */
private $factory;
protected function setUp()
@@ -43,270 +57,252 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
$this->guesser1 = $this->getMock('Symfony\Component\Form\FormTypeGuesserInterface');
$this->guesser2 = $this->getMock('Symfony\Component\Form\FormTypeGuesserInterface');
- $this->extension1 = new TestExtension($this->guesser1);
- $this->extension2 = new TestExtension($this->guesser2);
- $this->factory = new FormFactory(array($this->extension1, $this->extension2));
- }
+ $this->registry = $this->getMock('Symfony\Component\Form\FormRegistryInterface');
+ $this->factory = new FormFactory($this->registry);
- protected function tearDown()
- {
- $this->extension1 = null;
- $this->extension2 = null;
- $this->guesser1 = null;
- $this->guesser2 = null;
- $this->factory = null;
+ $this->registry->expects($this->any())
+ ->method('getTypeGuesser')
+ ->will($this->returnValue(new FormTypeGuesserChain(array(
+ $this->guesser1,
+ $this->guesser2,
+ ))));
}
public function testAddType()
{
- $this->assertFalse($this->factory->hasType('foo'));
-
$type = new FooType();
- $this->factory->addType($type);
+ $resolvedType = $this->getMockResolvedType();
- $this->assertTrue($this->factory->hasType('foo'));
- $this->assertSame($type, $this->factory->getType('foo'));
- }
+ $this->registry->expects($this->once())
+ ->method('resolveType')
+ ->with($type)
+ ->will($this->returnValue($resolvedType));
- public function testAddTypeAddsExtensions()
- {
- $type = new FooType();
- $ext1 = new FooTypeBarExtension();
- $ext2 = new FooTypeBazExtension();
-
- $this->extension1->addTypeExtension($ext1);
- $this->extension2->addTypeExtension($ext2);
+ $this->registry->expects($this->once())
+ ->method('addType')
+ ->with($resolvedType);
$this->factory->addType($type);
-
- $this->assertEquals(array($ext1, $ext2), $type->getExtensions());
}
- public function testGetTypeFromExtension()
+ public function testHasType()
+ {
+ $this->registry->expects($this->once())
+ ->method('hasType')
+ ->with('name')
+ ->will($this->returnValue('RESULT'));
+
+ $this->assertSame('RESULT', $this->factory->hasType('name'));
+ }
+
+ public function testGetType()
{
$type = new FooType();
- $this->extension2->addType($type);
+ $resolvedType = $this->getMockResolvedType();
- $this->assertSame($type, $this->factory->getType('foo'));
+ $resolvedType->expects($this->once())
+ ->method('getInnerType')
+ ->will($this->returnValue($type));
+
+ $this->registry->expects($this->once())
+ ->method('getType')
+ ->with('name')
+ ->will($this->returnValue($resolvedType));
+
+ $this->assertEquals($type, $this->factory->getType('name'));
}
- public function testGetTypeAddsExtensions()
+ public function testCreateNamedBuilderWithTypeName()
{
- $type = new FooType();
- $ext1 = new FooTypeBarExtension();
- $ext2 = new FooTypeBazExtension();
+ $options = array('a' => '1', 'b' => '2');
+ $resolvedType = $this->getMockResolvedType();
- $this->extension1->addTypeExtension($ext1);
- $this->extension2->addTypeExtension($ext2);
- $this->extension2->addType($type);
+ $this->registry->expects($this->once())
+ ->method('getType')
+ ->with('type')
+ ->will($this->returnValue($resolvedType));
- $type = $this->factory->getType('foo');
+ $resolvedType->expects($this->once())
+ ->method('createBuilder')
+ ->with($this->factory, 'name', $options)
+ ->will($this->returnValue('BUILDER'));
- $this->assertEquals(array($ext1, $ext2), $type->getExtensions());
+ $this->assertSame('BUILDER', $this->factory->createNamedBuilder('name', 'type', null, $options));
}
- /**
- * @expectedException Symfony\Component\Form\Exception\FormException
- */
- public function testGetTypeExpectsExistingType()
+ public function testCreateNamedBuilderWithTypeInstance()
{
- $this->factory->getType('bar');
+ $options = array('a' => '1', 'b' => '2');
+ $type = $this->getMockType();
+ $resolvedType = $this->getMockResolvedType();
+
+ $this->registry->expects($this->once())
+ ->method('resolveType')
+ ->with($type)
+ ->will($this->returnValue($resolvedType));
+
+ // The type is also implicitely added to the registry
+ $this->registry->expects($this->once())
+ ->method('addType')
+ ->with($resolvedType);
+
+ $resolvedType->expects($this->once())
+ ->method('createBuilder')
+ ->with($this->factory, 'name', $options)
+ ->will($this->returnValue('BUILDER'));
+
+ $this->assertSame('BUILDER', $this->factory->createNamedBuilder('name', $type, null, $options));
}
- public function testCreateNamedBuilder()
+ public function testCreateNamedBuilderWithResolvedTypeInstance()
{
- $type = new FooType();
- $this->extension1->addType($type);
+ $options = array('a' => '1', 'b' => '2');
+ $resolvedType = $this->getMockResolvedType();
- $builder = $this->factory->createNamedBuilder('bar', 'foo');
+ // The type is also implicitely added to the registry
+ $this->registry->expects($this->once())
+ ->method('addType')
+ ->with($resolvedType);
- $this->assertTrue($builder instanceof FormBuilder);
- $this->assertEquals('bar', $builder->getName());
- $this->assertNull($builder->getParent());
+ $resolvedType->expects($this->once())
+ ->method('createBuilder')
+ ->with($this->factory, 'name', $options)
+ ->will($this->returnValue('BUILDER'));
+
+ $this->assertSame('BUILDER', $this->factory->createNamedBuilder('name', $resolvedType, null, $options));
}
- public function testCreateNamedBuilderCallsBuildFormMethods()
+ public function testCreateNamedBuilderWithParentBuilder()
{
- $type = new FooType();
- $ext1 = new FooTypeBarExtension();
- $ext2 = new FooTypeBazExtension();
+ $options = array('a' => '1', 'b' => '2');
+ $parentBuilder = $this->getMockFormBuilder();
+ $resolvedType = $this->getMockResolvedType();
- $this->extension1->addTypeExtension($ext1);
- $this->extension2->addTypeExtension($ext2);
- $this->extension2->addType($type);
+ $this->registry->expects($this->once())
+ ->method('getType')
+ ->with('type')
+ ->will($this->returnValue($resolvedType));
- $builder = $this->factory->createNamedBuilder('bar', 'foo');
+ $resolvedType->expects($this->once())
+ ->method('createBuilder')
+ ->with($this->factory, 'name', $options, $parentBuilder)
+ ->will($this->returnValue('BUILDER'));
- $this->assertTrue($builder->hasAttribute('foo'));
- $this->assertTrue($builder->hasAttribute('bar'));
- $this->assertTrue($builder->hasAttribute('baz'));
+ $this->assertSame('BUILDER', $this->factory->createNamedBuilder('name', 'type', null, $options, $parentBuilder));
}
public function testCreateNamedBuilderFillsDataOption()
{
- $type = new FooType();
- $this->extension1->addType($type);
+ $givenOptions = array('a' => '1', 'b' => '2');
+ $expectedOptions = array_merge($givenOptions, array('data' => 'DATA'));
+ $resolvedType = $this->getMockResolvedType();
- $builder = $this->factory->createNamedBuilder('bar', 'foo', 'xyz');
+ $this->registry->expects($this->once())
+ ->method('getType')
+ ->with('type')
+ ->will($this->returnValue($resolvedType));
- // see FooType::buildForm()
- $this->assertEquals('xyz', $builder->getAttribute('data_option'));
+ $resolvedType->expects($this->once())
+ ->method('createBuilder')
+ ->with($this->factory, 'name', $expectedOptions)
+ ->will($this->returnValue('BUILDER'));
+
+ $this->assertSame('BUILDER', $this->factory->createNamedBuilder('name', 'type', 'DATA', $givenOptions));
}
public function testCreateNamedBuilderDoesNotOverrideExistingDataOption()
{
- $type = new FooType();
- $this->extension1->addType($type);
+ $options = array('a' => '1', 'b' => '2', 'data' => 'CUSTOM');
+ $resolvedType = $this->getMockResolvedType();
- $builder = $this->factory->createNamedBuilder('bar', 'foo', 'xyz', array(
- 'data' => 'abc',
- ));
+ $this->registry->expects($this->once())
+ ->method('getType')
+ ->with('type')
+ ->will($this->returnValue($resolvedType));
- // see FooType::buildForm()
- $this->assertEquals('abc', $builder->getAttribute('data_option'));
- }
-
- /**
- * @expectedException Symfony\Component\Form\Exception\TypeDefinitionException
- */
- public function testCreateNamedBuilderExpectsDataOptionToBeSupported()
- {
- $type = $this->getMock('Symfony\Component\Form\FormTypeInterface');
- $type->expects($this->any())
- ->method('getName')
- ->will($this->returnValue('foo'));
- $type->expects($this->any())
- ->method('getExtensions')
- ->will($this->returnValue(array()));
-
- $this->extension1->addType($type);
-
- $this->factory->createNamedBuilder('bar', 'foo');
- }
-
- /**
- * @expectedException Symfony\Component\Form\Exception\TypeDefinitionException
- */
- public function testCreateNamedBuilderExpectsRequiredOptionToBeSupported()
- {
- $type = $this->getMock('Symfony\Component\Form\FormTypeInterface');
- $type->expects($this->any())
- ->method('getName')
- ->will($this->returnValue('foo'));
- $type->expects($this->any())
- ->method('getExtensions')
- ->will($this->returnValue(array()));
-
- $this->extension1->addType($type);
-
- $this->factory->createNamedBuilder('bar', 'foo');
- }
-
- /**
- * @expectedException Symfony\Component\Form\Exception\TypeDefinitionException
- */
- public function testCreateNamedBuilderExpectsMaxLengthOptionToBeSupported()
- {
- $type = $this->getMock('Symfony\Component\Form\FormTypeInterface');
- $type->expects($this->any())
- ->method('getName')
- ->will($this->returnValue('foo'));
- $type->expects($this->any())
- ->method('getExtensions')
- ->will($this->returnValue(array()));
-
- $this->extension1->addType($type);
-
- $this->factory->createNamedBuilder('bar', 'foo');
- }
-
- /**
- * @expectedException Symfony\Component\Form\Exception\TypeDefinitionException
- */
- public function testCreateNamedBuilderExpectsBuilderToBeReturned()
- {
- $type = $this->getMock('Symfony\Component\Form\FormTypeInterface');
- $type->expects($this->any())
- ->method('getName')
- ->will($this->returnValue('foo'));
- $type->expects($this->any())
- ->method('getExtensions')
- ->will($this->returnValue(array()));
- $type->expects($this->any())
+ $resolvedType->expects($this->once())
->method('createBuilder')
- ->will($this->returnValue(null));
+ ->with($this->factory, 'name', $options)
+ ->will($this->returnValue('BUILDER'));
- $this->extension1->addType($type);
-
- $this->factory->createNamedBuilder('bar', 'foo');
- }
-
- /**
- * @expectedException Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
- */
- public function testCreateNamedBuilderExpectsOptionsToExist()
- {
- $type = new FooType();
- $this->extension1->addType($type);
-
- $this->factory->createNamedBuilder('bar', 'foo', null, array(
- 'invalid' => 'xyz',
- ));
- }
-
- /**
- * @expectedException Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
- */
- public function testCreateNamedBuilderExpectsOptionsToBeInValidRange()
- {
- $type = new FooType();
- $this->extension1->addType($type);
-
- $this->factory->createNamedBuilder('bar', 'foo', null, array(
- 'a_or_b' => 'c',
- ));
- }
-
- public function testCreateNamedBuilderAllowsExtensionsToExtendAllowedOptionValues()
- {
- $type = new FooType();
- $this->extension1->addType($type);
- $this->extension1->addTypeExtension(new FooTypeBarExtension());
-
- // no exception this time
- $this->factory->createNamedBuilder('bar', 'foo', null, array(
- 'a_or_b' => 'c',
- ));
- }
-
- public function testCreateNamedBuilderAddsTypeInstances()
- {
- $type = new FooType();
- $this->assertFalse($this->factory->hasType('foo'));
-
- $builder = $this->factory->createNamedBuilder('bar', $type);
-
- $this->assertTrue($builder instanceof FormBuilder);
- $this->assertTrue($this->factory->hasType('foo'));
+ $this->assertSame('BUILDER', $this->factory->createNamedBuilder('name', 'type', 'DATA', $options));
}
/**
* @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException
- * @expectedExceptionMessage Expected argument of type "string or Symfony\Component\Form\FormTypeInterface", "stdClass" given
+ * @expectedExceptionMessage Expected argument of type "string, Symfony\Component\Form\ResolvedFormTypeInterface or Symfony\Component\Form\FormTypeInterface", "stdClass" given
*/
public function testCreateNamedBuilderThrowsUnderstandableException()
{
$this->factory->createNamedBuilder('name', new \stdClass());
}
- public function testCreateUsesTypeNameAsName()
+ public function testCreateUsesTypeNameIfTypeGivenAsString()
{
- $type = new FooType();
- $this->extension1->addType($type);
+ $options = array('a' => '1', 'b' => '2');
+ $resolvedType = $this->getMockResolvedType();
+ $builder = $this->getMockFormBuilder();
- $builder = $this->factory->createBuilder('foo');
+ $this->registry->expects($this->once())
+ ->method('getType')
+ ->with('TYPE')
+ ->will($this->returnValue($resolvedType));
- $this->assertEquals('foo', $builder->getName());
+ $resolvedType->expects($this->once())
+ ->method('createBuilder')
+ ->with($this->factory, 'TYPE', $options)
+ ->will($this->returnValue($builder));
+
+ $builder->expects($this->once())
+ ->method('getForm')
+ ->will($this->returnValue('FORM'));
+
+ $this->assertSame('FORM', $this->factory->create('TYPE', null, $options));
+ }
+
+ public function testCreateUsesTypeNameIfTypeGivenAsObject()
+ {
+ $options = array('a' => '1', 'b' => '2');
+ $resolvedType = $this->getMockResolvedType();
+ $builder = $this->getMockFormBuilder();
+
+ $resolvedType->expects($this->once())
+ ->method('getName')
+ ->will($this->returnValue('TYPE'));
+
+ $resolvedType->expects($this->once())
+ ->method('createBuilder')
+ ->with($this->factory, 'TYPE', $options)
+ ->will($this->returnValue($builder));
+
+ $builder->expects($this->once())
+ ->method('getForm')
+ ->will($this->returnValue('FORM'));
+
+ $this->assertSame('FORM', $this->factory->create($resolvedType, null, $options));
+ }
+
+ public function testCreateNamed()
+ {
+ $options = array('a' => '1', 'b' => '2');
+ $resolvedType = $this->getMockResolvedType();
+ $builder = $this->getMockFormBuilder();
+
+ $this->registry->expects($this->once())
+ ->method('getType')
+ ->with('type')
+ ->will($this->returnValue($resolvedType));
+
+ $resolvedType->expects($this->once())
+ ->method('createBuilder')
+ ->with($this->factory, 'name', $options)
+ ->will($this->returnValue($builder));
+
+ $builder->expects($this->once())
+ ->method('getForm')
+ ->will($this->returnValue('FORM'));
+
+ $this->assertSame('FORM', $this->factory->createNamed('name', 'type', null, $options));
}
public function testCreateBuilderForPropertyCreatesFormWithHighestConfidence()
@@ -329,7 +325,7 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
Guess::HIGH_CONFIDENCE
)));
- $factory = $this->createMockFactory(array('createNamedBuilder'));
+ $factory = $this->getMockFactory(array('createNamedBuilder'));
$factory->expects($this->once())
->method('createNamedBuilder')
@@ -348,7 +344,7 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
->with('Application\Author', 'firstName')
->will($this->returnValue(null));
- $factory = $this->createMockFactory(array('createNamedBuilder'));
+ $factory = $this->getMockFactory(array('createNamedBuilder'));
$factory->expects($this->once())
->method('createNamedBuilder')
@@ -371,7 +367,7 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
Guess::MEDIUM_CONFIDENCE
)));
- $factory = $this->createMockFactory(array('createNamedBuilder'));
+ $factory = $this->getMockFactory(array('createNamedBuilder'));
$factory->expects($this->once())
->method('createNamedBuilder')
@@ -406,7 +402,7 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
Guess::HIGH_CONFIDENCE
)));
- $factory = $this->createMockFactory(array('createNamedBuilder'));
+ $factory = $this->getMockFactory(array('createNamedBuilder'));
$factory->expects($this->once())
->method('createNamedBuilder')
@@ -439,7 +435,7 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
Guess::HIGH_CONFIDENCE
)));
- $factory = $this->createMockFactory(array('createNamedBuilder'));
+ $factory = $this->getMockFactory(array('createNamedBuilder'));
$factory->expects($this->once())
->method('createNamedBuilder')
@@ -474,7 +470,7 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
Guess::LOW_CONFIDENCE
)));
- $factory = $this->createMockFactory(array('createNamedBuilder'));
+ $factory = $this->getMockFactory(array('createNamedBuilder'));
$factory->expects($this->once())
->method('createNamedBuilder')
@@ -507,7 +503,7 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
Guess::HIGH_CONFIDENCE
)));
- $factory = $this->createMockFactory(array('createNamedBuilder'));
+ $factory = $this->getMockFactory(array('createNamedBuilder'));
$factory->expects($this->once())
->method('createNamedBuilder')
@@ -540,7 +536,7 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
Guess::HIGH_CONFIDENCE
)));
- $factory = $this->createMockFactory(array('createNamedBuilder'));
+ $factory = $this->getMockFactory(array('createNamedBuilder'));
$factory->expects($this->once())
->method('createNamedBuilder')
@@ -555,41 +551,26 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('builderInstance', $builder);
}
- public function testCreateNamedBuilderFromParentBuilder()
- {
- $type = new FooType();
- $this->extension1->addType($type);
-
- $parentBuilder = $this->getMockBuilder('Symfony\Component\Form\FormBuilder')
- ->setConstructorArgs(array('name', null, $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'), $this->factory))
- ->getMock()
- ;
-
- $builder = $this->factory->createNamedBuilder('bar', 'foo', null, array(), $parentBuilder);
-
- $this->assertNotEquals($builder, $builder->getParent());
- $this->assertEquals($parentBuilder, $builder->getParent());
- }
-
- public function testFormTypeCreatesDefaultValueForEmptyDataOption()
- {
- $factory = new FormFactory(array(new \Symfony\Component\Form\Extension\Core\CoreExtension()));
-
- $form = $factory->createNamedBuilder('author', new AuthorType())->getForm();
- $form->bind(array('firstName' => 'John', 'lastName' => 'Smith'));
-
- $author = new Author();
- $author->firstName = 'John';
- $author->setLastName('Smith');
-
- $this->assertEquals($author, $form->getData());
- }
-
- private function createMockFactory(array $methods = array())
+ private function getMockFactory(array $methods = array())
{
return $this->getMockBuilder('Symfony\Component\Form\FormFactory')
->setMethods($methods)
- ->setConstructorArgs(array(array($this->extension1, $this->extension2)))
+ ->setConstructorArgs(array($this->registry))
->getMock();
}
+
+ private function getMockResolvedType()
+ {
+ return $this->getMock('Symfony\Component\Form\ResolvedFormTypeInterface');
+ }
+
+ private function getMockType()
+ {
+ return $this->getMock('Symfony\Component\Form\FormTypeInterface');
+ }
+
+ private function getMockFormBuilder()
+ {
+ return $this->getMock('Symfony\Component\Form\Tests\FormBuilderInterface');
+ }
}
diff --git a/src/Symfony/Component/Form/Tests/FormIntegrationTestCase.php b/src/Symfony/Component/Form/Tests/FormIntegrationTestCase.php
index 50923492ba..fa03a4002e 100644
--- a/src/Symfony/Component/Form/Tests/FormIntegrationTestCase.php
+++ b/src/Symfony/Component/Form/Tests/FormIntegrationTestCase.php
@@ -12,6 +12,7 @@
namespace Symfony\Component\Form\Tests;
use Symfony\Component\Form\FormFactory;
+use Symfony\Component\Form\FormRegistry;
use Symfony\Component\Form\Extension\Core\CoreExtension;
/**
@@ -20,7 +21,12 @@ use Symfony\Component\Form\Extension\Core\CoreExtension;
class FormIntegrationTestCase extends \PHPUnit_Framework_TestCase
{
/**
- * @var \Symfony\Component\Form\FormFactoryInterface
+ * @var FormRegistry
+ */
+ protected $registry;
+
+ /**
+ * @var FormFactory
*/
protected $factory;
@@ -30,7 +36,8 @@ class FormIntegrationTestCase extends \PHPUnit_Framework_TestCase
$this->markTestSkipped('The "EventDispatcher" component is not available');
}
- $this->factory = new FormFactory($this->getExtensions());
+ $this->registry = new FormRegistry($this->getExtensions());
+ $this->factory = new FormFactory($this->registry);
}
protected function getExtensions()
diff --git a/src/Symfony/Component/Form/Tests/FormPerformanceTestCase.php b/src/Symfony/Component/Form/Tests/FormPerformanceTestCase.php
index be381074f1..d68d67a7b4 100644
--- a/src/Symfony/Component/Form/Tests/FormPerformanceTestCase.php
+++ b/src/Symfony/Component/Form/Tests/FormPerformanceTestCase.php
@@ -49,7 +49,6 @@ class FormPerformanceTestCase extends FormIntegrationTestCase
/**
* @param integer $maxRunningTime
* @throws InvalidArgumentException
- * @since Method available since Release 2.3.0
*/
public function setMaxRunningTime($maxRunningTime)
{
diff --git a/src/Symfony/Component/Form/Tests/FormRegistryTest.php b/src/Symfony/Component/Form/Tests/FormRegistryTest.php
new file mode 100644
index 0000000000..ae0946fc33
--- /dev/null
+++ b/src/Symfony/Component/Form/Tests/FormRegistryTest.php
@@ -0,0 +1,177 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Form;
+
+use Symfony\Component\Form\Tests\Fixtures\TestExtension;
+use Symfony\Component\Form\Tests\Fixtures\FooSubType;
+use Symfony\Component\Form\Tests\Fixtures\FooTypeBazExtension;
+use Symfony\Component\Form\Tests\Fixtures\FooTypeBarExtension;
+use Symfony\Component\Form\Tests\Fixtures\FooType;
+
+/**
+ * @author Bernhard Schussek
+ */
+class FormRegistryTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @var FormRegistry
+ */
+ private $registry;
+
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ private $guesser1;
+
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ private $guesser2;
+
+ /**
+ * @var TestExtension
+ */
+ private $extension1;
+
+ /**
+ * @var TestExtension
+ */
+ private $extension2;
+
+ protected function setUp()
+ {
+ $this->guesser1 = $this->getMock('Symfony\Component\Form\FormTypeGuesserInterface');
+ $this->guesser2 = $this->getMock('Symfony\Component\Form\FormTypeGuesserInterface');
+ $this->extension1 = new TestExtension($this->guesser1);
+ $this->extension2 = new TestExtension($this->guesser2);
+ $this->registry = new FormRegistry(array(
+ $this->extension1,
+ $this->extension2,
+ ));
+ }
+
+ public function testResolveType()
+ {
+ $type = new FooType();
+ $ext1 = new FooTypeBarExtension();
+ $ext2 = new FooTypeBazExtension();
+
+ $this->extension1->addTypeExtension($ext1);
+ $this->extension2->addTypeExtension($ext2);
+
+ $resolvedType = $this->registry->resolveType($type);
+
+ $this->assertEquals($type, $resolvedType->getInnerType());
+ $this->assertEquals(array($ext1, $ext2), $resolvedType->getTypeExtensions());
+ }
+
+ public function testResolveTypeConnectsParent()
+ {
+ $parentType = new FooType();
+ $type = new FooSubType();
+
+ $resolvedParentType = $this->registry->resolveType($parentType);
+
+ $this->registry->addType($resolvedParentType);
+
+ $resolvedType = $this->registry->resolveType($type);
+
+ $this->assertSame($resolvedParentType, $resolvedType->getParent());
+ }
+
+ /**
+ * @expectedException Symfony\Component\Form\Exception\FormException
+ */
+ public function testResolveTypeThrowsExceptionIfParentNotFound()
+ {
+ $type = new FooSubType();
+
+ $this->registry->resolveType($type);
+ }
+
+ public function testGetTypeReturnsAddedType()
+ {
+ $type = new FooType();
+
+ $resolvedType = $this->registry->resolveType($type);
+
+ $this->registry->addType($resolvedType);
+
+ $this->assertSame($resolvedType, $this->registry->getType('foo'));
+ }
+
+ public function testGetTypeFromExtension()
+ {
+ $type = new FooType();
+
+ $this->extension2->addType($type);
+
+ $resolvedType = $this->registry->getType('foo');
+
+ $this->assertInstanceOf('Symfony\Component\Form\ResolvedFormTypeInterface', $resolvedType);
+ $this->assertSame($type, $resolvedType->getInnerType());
+ }
+
+ /**
+ * @expectedException Symfony\Component\Form\Exception\FormException
+ */
+ public function testGetTypeThrowsExceptionIfTypeNotFound()
+ {
+ $this->registry->getType('bar');
+ }
+
+ /**
+ * @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException
+ */
+ public function testGetTypeThrowsExceptionIfNoString()
+ {
+ $this->registry->getType(array());
+ }
+
+ public function testHasTypeAfterAdding()
+ {
+ $type = new FooType();
+
+ $resolvedType = $this->registry->resolveType($type);
+
+ $this->assertFalse($this->registry->hasType('foo'));
+
+ $this->registry->addType($resolvedType);
+
+ $this->assertTrue($this->registry->hasType('foo'));
+ }
+
+ public function testHasTypeAfterLoadingFromExtension()
+ {
+ $type = new FooType();
+
+ $this->assertFalse($this->registry->hasType('foo'));
+
+ $this->extension2->addType($type);
+
+ $this->assertTrue($this->registry->hasType('foo'));
+ }
+
+ public function testGetTypeGuesser()
+ {
+ $expectedGuesser = new FormTypeGuesserChain(array($this->guesser1, $this->guesser2));
+
+ $this->assertEquals($expectedGuesser, $this->registry->getTypeGuesser());
+ }
+
+ public function testGetExtensions()
+ {
+ $expectedExtensions = array($this->extension1, $this->extension2);
+
+ $this->assertEquals($expectedExtensions, $this->registry->getExtensions());
+ }
+}
diff --git a/src/Symfony/Component/Form/Tests/FormViewInterface.php b/src/Symfony/Component/Form/Tests/FormViewInterface.php
new file mode 100644
index 0000000000..431dd3a05c
--- /dev/null
+++ b/src/Symfony/Component/Form/Tests/FormViewInterface.php
@@ -0,0 +1,16 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Form\Tests;
+
+interface FormViewInterface extends \Iterator, \Symfony\Component\Form\FormViewInterface
+{
+}
diff --git a/src/Symfony/Component/Form/Tests/ResolvedFormTypeTest.php b/src/Symfony/Component/Form/Tests/ResolvedFormTypeTest.php
new file mode 100644
index 0000000000..e89e650bad
--- /dev/null
+++ b/src/Symfony/Component/Form/Tests/ResolvedFormTypeTest.php
@@ -0,0 +1,285 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Form\Tests;
+
+use Symfony\Component\Form\ResolvedFormType;
+use Symfony\Component\Form\FormView;
+use Symfony\Component\Form\FormViewInterface;
+use Symfony\Component\Form\FormBuilder;
+use Symfony\Component\Form\FormConfig;
+use Symfony\Component\Form\Form;
+use Symfony\Component\OptionsResolver\OptionsResolverInterface;
+
+/**
+ * @author Bernhard Schussek
+ */
+class ResolvedFormTypeTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ private $dispatcher;
+
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ private $factory;
+
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ private $dataMapper;
+
+ protected function setUp()
+ {
+ if (!class_exists('Symfony\Component\OptionsResolver\OptionsResolver')) {
+ $this->markTestSkipped('The "OptionsResolver" component is not available');
+ }
+
+ if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) {
+ $this->markTestSkipped('The "EventDispatcher" component is not available');
+ }
+
+ $this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
+ $this->factory = $this->getMock('Symfony\Component\Form\FormFactoryInterface');
+ $this->dataMapper = $this->getMock('Symfony\Component\Form\DataMapperInterface');
+ }
+
+ public function testCreateBuilder()
+ {
+ $parentType = $this->getMockFormType();
+ $type = $this->getMockFormType();
+ $extension1 = $this->getMockFormTypeExtension();
+ $extension2 = $this->getMockFormTypeExtension();
+
+ $parentResolvedType = new ResolvedFormType($parentType);
+ $resolvedType = new ResolvedFormType($type, array($extension1, $extension2), $parentResolvedType);
+
+ $test = $this;
+ $i = 0;
+
+ $assertIndex = function ($index) use (&$i, $test) {
+ return function () use (&$i, $test, $index) {
+ /* @var \PHPUnit_Framework_TestCase $test */
+ $test->assertEquals($index, $i, 'Executed at index ' . $index);
+
+ ++$i;
+ };
+ };
+
+ $assertIndexAndAddOption = function ($index, $option, $default) use ($assertIndex) {
+ $assertIndex = $assertIndex($index);
+
+ return function (OptionsResolverInterface $resolver) use ($assertIndex, $index, $option, $default) {
+ $assertIndex();
+
+ $resolver->setDefaults(array($option => $default));
+ };
+ };
+
+ // First the default options are generated for the super type
+ $parentType->expects($this->once())
+ ->method('setDefaultOptions')
+ ->will($this->returnCallback($assertIndexAndAddOption(0, 'a', 'a_default')));
+
+ // The form type itself
+ $type->expects($this->once())
+ ->method('setDefaultOptions')
+ ->will($this->returnCallback($assertIndexAndAddOption(1, 'b', 'b_default')));
+
+ // And its extensions
+ $extension1->expects($this->once())
+ ->method('setDefaultOptions')
+ ->will($this->returnCallback($assertIndexAndAddOption(2, 'c', 'c_default')));
+
+ $extension2->expects($this->once())
+ ->method('setDefaultOptions')
+ ->will($this->returnCallback($assertIndexAndAddOption(3, 'd', 'd_default')));
+
+ // Can only be uncommented when the following PHPUnit "bug" is fixed:
+ // https://github.com/sebastianbergmann/phpunit-mock-objects/issues/47
+ // $givenOptions = array('a' => 'a_custom', 'c' => 'c_custom');
+ // $resolvedOptions = array('a' => 'a_custom', 'b' => 'b_default', 'c' => 'c_custom', 'd' => 'd_default');
+
+ $givenOptions = array();
+ $resolvedOptions = array();
+
+ // Then the form is built for the super type
+ $parentType->expects($this->once())
+ ->method('buildForm')
+ ->with($this->anything(), $resolvedOptions)
+ ->will($this->returnCallback($assertIndex(4)));
+
+ // Then the type itself
+ $type->expects($this->once())
+ ->method('buildForm')
+ ->with($this->anything(), $resolvedOptions)
+ ->will($this->returnCallback($assertIndex(5)));
+
+ // Then its extensions
+ $extension1->expects($this->once())
+ ->method('buildForm')
+ ->with($this->anything(), $resolvedOptions)
+ ->will($this->returnCallback($assertIndex(6)));
+
+ $extension2->expects($this->once())
+ ->method('buildForm')
+ ->with($this->anything(), $resolvedOptions)
+ ->will($this->returnCallback($assertIndex(7)));
+
+ $factory = $this->getMockFormFactory();
+ $parentBuilder = $this->getBuilder('parent');
+ $builder = $resolvedType->createBuilder($factory, 'name', $givenOptions, $parentBuilder);
+
+ $this->assertSame($parentBuilder, $builder->getParent());
+ $this->assertSame($resolvedType, $builder->getType());
+ }
+
+ public function testCreateView()
+ {
+ $parentType = $this->getMockFormType();
+ $type = $this->getMockFormType();
+ $field1Type = $this->getMockFormType();
+ $field2Type = $this->getMockFormType();
+ $extension1 = $this->getMockFormTypeExtension();
+ $extension2 = $this->getMockFormTypeExtension();
+
+ $parentResolvedType = new ResolvedFormType($parentType);
+ $resolvedType = new ResolvedFormType($type, array($extension1, $extension2), $parentResolvedType);
+ $field1ResolvedType = new ResolvedFormType($field1Type);
+ $field2ResolvedType = new ResolvedFormType($field2Type);
+
+ $options = array('a' => '1', 'b' => '2');
+ $form = $this->getBuilder('name', $options)
+ ->setCompound(true)
+ ->setDataMapper($this->dataMapper)
+ ->setType($resolvedType)
+ ->add($this->getBuilder('foo')->setType($field1ResolvedType))
+ ->add($this->getBuilder('bar')->setType($field2ResolvedType))
+ ->getForm();
+
+ $test = $this;
+ $i = 0;
+
+ $assertIndexAndNbOfChildViews = function ($index, $nbOfChildViews) use (&$i, $test) {
+ return function (FormViewInterface $view) use (&$i, $test, $index, $nbOfChildViews) {
+ /* @var \PHPUnit_Framework_TestCase $test */
+ $test->assertEquals($index, $i, 'Executed at index ' . $index);
+ $test->assertCount($nbOfChildViews, $view);
+
+ ++$i;
+ };
+ };
+
+ // First the super type
+ $parentType->expects($this->once())
+ ->method('buildView')
+ ->with($this->anything(), $form, $options)
+ ->will($this->returnCallback($assertIndexAndNbOfChildViews(0, 0)));
+
+ // Then the type itself
+ $type->expects($this->once())
+ ->method('buildView')
+ ->with($this->anything(), $form, $options)
+ ->will($this->returnCallback($assertIndexAndNbOfChildViews(1, 0)));
+
+ // Then its extensions
+ $extension1->expects($this->once())
+ ->method('buildView')
+ ->with($this->anything(), $form, $options)
+ ->will($this->returnCallback($assertIndexAndNbOfChildViews(2, 0)));
+
+ $extension2->expects($this->once())
+ ->method('buildView')
+ ->with($this->anything(), $form, $options)
+ ->will($this->returnCallback($assertIndexAndNbOfChildViews(3, 0)));
+
+ // Now the first child form
+ $field1Type->expects($this->once())
+ ->method('buildView')
+ ->will($this->returnCallback($assertIndexAndNbOfChildViews(4, 0)));
+ $field1Type->expects($this->once())
+ ->method('finishView')
+ ->will($this->returnCallback($assertIndexAndNbOfChildViews(5, 0)));
+
+ // And the second child form
+ $field2Type->expects($this->once())
+ ->method('buildView')
+ ->will($this->returnCallback($assertIndexAndNbOfChildViews(6, 0)));
+ $field2Type->expects($this->once())
+ ->method('finishView')
+ ->will($this->returnCallback($assertIndexAndNbOfChildViews(7, 0)));
+
+ // Again first the parent
+ $parentType->expects($this->once())
+ ->method('finishView')
+ ->with($this->anything(), $form, $options)
+ ->will($this->returnCallback($assertIndexAndNbOfChildViews(8, 2)));
+
+ // Then the type itself
+ $type->expects($this->once())
+ ->method('finishView')
+ ->with($this->anything(), $form, $options)
+ ->will($this->returnCallback($assertIndexAndNbOfChildViews(9, 2)));
+
+ // Then its extensions
+ $extension1->expects($this->once())
+ ->method('finishView')
+ ->with($this->anything(), $form, $options)
+ ->will($this->returnCallback($assertIndexAndNbOfChildViews(10, 2)));
+
+ $extension2->expects($this->once())
+ ->method('finishView')
+ ->with($this->anything(), $form, $options)
+ ->will($this->returnCallback($assertIndexAndNbOfChildViews(11, 2)));
+
+ $parentView = new FormView('parent');
+ $view = $resolvedType->createView($form, $parentView);
+
+ $this->assertSame($parentView, $view->getParent());
+ }
+
+ /**
+ * @return \PHPUnit_Framework_MockObject_MockObject
+ */
+ private function getMockFormType()
+ {
+ return $this->getMock('Symfony\Component\Form\FormTypeInterface');
+ }
+
+ /**
+ * @return \PHPUnit_Framework_MockObject_MockObject
+ */
+ private function getMockFormTypeExtension()
+ {
+ return $this->getMock('Symfony\Component\Form\FormTypeExtensionInterface');
+ }
+
+ /**
+ * @return \PHPUnit_Framework_MockObject_MockObject
+ */
+ private function getMockFormFactory()
+ {
+ return $this->getMock('Symfony\Component\Form\FormFactoryInterface');
+ }
+
+ /**
+ * @param string $name
+ * @param array $options
+ *
+ * @return FormBuilder
+ */
+ protected function getBuilder($name = 'name', array $options = array())
+ {
+ return new FormBuilder($name, null, $this->dispatcher, $this->factory, $options);
+ }
+}
diff --git a/src/Symfony/Component/Form/Tests/SimpleFormTest.php b/src/Symfony/Component/Form/Tests/SimpleFormTest.php
index 12a16fc28a..f9280280d0 100644
--- a/src/Symfony/Component/Form/Tests/SimpleFormTest.php
+++ b/src/Symfony/Component/Form/Tests/SimpleFormTest.php
@@ -577,13 +577,54 @@ class SimpleFormTest extends AbstractFormTest
$this->assertSame(array(), $this->form->getErrors());
}
- public function testCreateViewAcceptsParent()
+ public function testCreateView()
{
- $parent = new FormView('form');
+ $type = $this->getMock('Symfony\Component\Form\ResolvedFormTypeInterface');
+ $view = $this->getMock('Symfony\Component\Form\Tests\FormViewInterface');
+ $form = $this->getBuilder()->setType($type)->getForm();
- $view = $this->form->createView($parent);
+ $type->expects($this->once())
+ ->method('createView')
+ ->with($form)
+ ->will($this->returnValue($view));
- $this->assertSame($parent, $view->getParent());
+ $this->assertSame($view, $form->createView());
+ }
+
+ public function testCreateViewWithParent()
+ {
+ $type = $this->getMock('Symfony\Component\Form\ResolvedFormTypeInterface');
+ $view = $this->getMock('Symfony\Component\Form\Tests\FormViewInterface');
+ $parentForm = $this->getMock('Symfony\Component\Form\Tests\FormInterface');
+ $parentView = $this->getMock('Symfony\Component\Form\Tests\FormViewInterface');
+ $form = $this->getBuilder()->setType($type)->getForm();
+ $form->setParent($parentForm);
+
+ $parentForm->expects($this->once())
+ ->method('createView')
+ ->will($this->returnValue($parentView));
+
+ $type->expects($this->once())
+ ->method('createView')
+ ->with($form, $parentView)
+ ->will($this->returnValue($view));
+
+ $this->assertSame($view, $form->createView());
+ }
+
+ public function testCreateViewWithExplicitParent()
+ {
+ $type = $this->getMock('Symfony\Component\Form\ResolvedFormTypeInterface');
+ $view = $this->getMock('Symfony\Component\Form\Tests\FormViewInterface');
+ $parentView = $this->getMock('Symfony\Component\Form\Tests\FormViewInterface');
+ $form = $this->getBuilder()->setType($type)->getForm();
+
+ $type->expects($this->once())
+ ->method('createView')
+ ->with($form, $parentView)
+ ->will($this->returnValue($view));
+
+ $this->assertSame($view, $form->createView($parentView));
}
public function testGetErrorsAsString()
diff --git a/src/Symfony/Component/Form/UnmodifiableFormConfig.php b/src/Symfony/Component/Form/UnmodifiableFormConfig.php
index a39e351eb7..cb586a8ca0 100644
--- a/src/Symfony/Component/Form/UnmodifiableFormConfig.php
+++ b/src/Symfony/Component/Form/UnmodifiableFormConfig.php
@@ -57,9 +57,9 @@ class UnmodifiableFormConfig implements FormConfigInterface
private $compound;
/**
- * @var array
+ * @var ResolvedFormTypeInterface
*/
- private $types;
+ private $type;
/**
* @var array
@@ -145,7 +145,7 @@ class UnmodifiableFormConfig implements FormConfigInterface
$this->byReference = $config->getByReference();
$this->virtual = $config->getVirtual();
$this->compound = $config->getCompound();
- $this->types = $config->getTypes();
+ $this->type = $config->getType();
$this->viewTransformers = $config->getViewTransformers();
$this->modelTransformers = $config->getModelTransformers();
$this->dataMapper = $config->getDataMapper();
@@ -220,9 +220,9 @@ class UnmodifiableFormConfig implements FormConfigInterface
/**
* {@inheritdoc}
*/
- public function getTypes()
+ public function getType()
{
- return $this->types;
+ return $this->type;
}
/**