Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

[Form] Cached the form type hierarchy in order to improve performance

  • Loading branch information...
commit 3bcb64f5c51b213191e7d72a6fa2c73eef3f205b 1 parent 4f00dc9
@webmozart webmozart authored
View
34 AbstractType.php
@@ -20,8 +20,9 @@
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();
@@ -49,14 +50,6 @@ public function finishView(FormViewInterface $view, FormInterface $form, array $
/**
* {@inheritdoc}
*/
- public function createBuilder($name, FormFactoryInterface $factory, array $options)
- {
- return null;
- }
-
- /**
- * {@inheritdoc}
- */
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults($this->getDefaultOptions());
@@ -98,21 +91,26 @@ public function getParent()
}
/**
- * {@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()
{
View
13 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
View
13 Extension/Core/Type/FormType.php
@@ -20,7 +20,6 @@
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 @@ public function buildView(FormViewInterface $view, FormInterface $form, array $o
}
$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) {
@@ -214,14 +213,6 @@ public function setDefaultOptions(OptionsResolverInterface $resolver)
/**
* {@inheritdoc}
*/
- public function createBuilder($name, FormFactoryInterface $factory, array $options)
- {
- return new FormBuilder($name, $options['data_class'], new EventDispatcher(), $factory, $options);
- }
-
- /**
- * {@inheritdoc}
- */
public function getParent()
{
return null;
View
39 Form.php
@@ -195,11 +195,17 @@ public function getPropertyPath()
* @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 @@ public function createView(FormViewInterface $parent = null)
$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);
}
/**
View
23 FormBuilder.php
@@ -115,10 +115,10 @@ public function create($name, $type = null, array $options = array())
}
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 @@ public function getIterator()
{
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;
+ }
}
View
12 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 @@ public function getCompound()
/**
* {@inheritdoc}
*/
- public function getTypes()
+ public function getType()
{
- return $this->types;
+ return $this->type;
}
/**
@@ -671,9 +671,9 @@ public function setCompound($compound)
/**
* {@inheritdoc}
*/
- public function setTypes(array $types)
+ public function setType(ResolvedFormTypeInterface $type)
{
- $this->types = $types;
+ $this->type = $type;
return $this;
}
View
4 FormConfigEditorInterface.php
@@ -213,11 +213,11 @@ public function setCompound($compound);
/**
* 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.
View
4 FormConfigInterface.php
@@ -79,9 +79,9 @@ public function getCompound();
/**
* 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.
View
277 FormFactory.php
@@ -18,92 +18,14 @@
class FormFactory implements FormFactoryInterface
{
- private static $requiredOptions = array(
- 'data',
- 'required',
- 'max_length',
- );
-
- /**
- * Extensions
- * @var array An array of FormExtensionInterface
- */
- private $extensions = array();
-
- /**
- * All known types (cache)
- * @var array An array of FormTypeInterface
- */
- private $types = array();
-
/**
- * The guesser chain
- * @var FormTypeGuesserChain
+ * @var FormRegistryInterface
*/
- private $guesser;
+ private $registry;
- /**
- * 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 @@ public function createForProperty($class, $property, $data = null, array $option
*/
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);
}
@@ -149,85 +73,18 @@ public function createNamedBuilder($name, $type, $data = null, array $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();
- }
-
- // 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()));
+ 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');
}
- $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 @@ public function createNamedBuilder($name, $type, $data = null, array $options =
*/
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 @@ public function createBuilderForProperty($class, $property, $data = null, array
}
/**
- * 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.
+ * Adds a type.
*
- * @param string $name The type name
+ * @param FormTypeInterface $type The type
*
- * @throws FormException if the type is not provided by any registered extension
+ * @deprecated Deprecated since version 2.1, to be removed in 2.3. Use
+ * {@link FormRegistryInterface::resolveType()} and
+ * {@link FormRegistryInterface::addType()} instead.
*/
- private function loadType($name)
+ public function addType(FormTypeInterface $type)
{
- $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;
+ $this->registry->addType($this->registry->resolveType($type));
}
/**
- * Loads the extensions for a given type.
+ * Returns a type by name.
*
- * @param FormTypeInterface $type The type
+ * 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.
*/
- private function loadTypeExtensions(FormTypeInterface $type)
- {
- $typeExtensions = array();
-
- foreach ($this->extensions as $extension) {
- $typeExtensions = array_merge(
- $typeExtensions,
- $extension->getTypeExtensions($type->getName())
- );
- }
-
- $type->setExtensions($typeExtensions);
- }
-
- private function validateFormTypeName(FormTypeInterface $type)
+ 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();
}
}
View
29 FormFactoryInterface.php
@@ -112,33 +112,4 @@ public function createNamedBuilder($name, $type, $data = null, array $options =
* @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);
}
View
164 FormRegistry.php
@@ -0,0 +1,164 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Form;
+
+use Symfony\Component\Form\Exception\UnexpectedTypeException;
+use Symfony\Component\Form\Exception\FormException;
+
+/**
+ * The central registry of the Form component.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+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;
+ }
+}
View
72 FormRegistryInterface.php
@@ -0,0 +1,72 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Form;
+
+/**
+ * The central registry of the Form component.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+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();
+}
View
30 FormTypeInterface.php
@@ -69,20 +69,6 @@ public function buildView(FormViewInterface $view, FormInterface $form, array $o
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.
*
* @param OptionsResolverInterface $resolver The resolver for the options.
@@ -102,20 +88,4 @@ public function getParent();
* @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();
}
View
213 ResolvedFormType.php
@@ -0,0 +1,213 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\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 <bschussek@gmail.com>
+ */
+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;
+ }
+}
View
70 ResolvedFormTypeInterface.php
@@ -0,0 +1,70 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Form;
+
+/**
+ * A wrapper for a form type and its extensions.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+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);
+}
View
11 Tests/AbstractLayoutTest.php
@@ -17,7 +17,7 @@
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 @@ protected function setUp()
$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()
View
46 Tests/CompoundFormPerformanceTest.php
@@ -0,0 +1,46 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Form\Tests;
+
+/**
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+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();
+ }
+ }
+}
View
97 Tests/CompoundFormTest.php
@@ -558,103 +558,6 @@ public function testBindGetRequestWithEmptyRootFormName()
$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()
View
29 Tests/Extension/Core/Type/TypeTestCase.php
@@ -12,18 +12,12 @@
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
*/
protected $builder;
@@ -35,29 +29,12 @@
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'));
View
32 Tests/Fixtures/FooSubType.php
@@ -0,0 +1,32 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\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';
+ }
+}
View
33 Tests/Fixtures/FooType.php
@@ -20,44 +20,11 @@
class FooType extends AbstractType
{
- public function buildForm(FormBuilderInterface $builder, array $options)
- {
- $builder->setAttribute('foo', 'x');
- $builder->setAttribute('data_option', isset($options['data']) ? $options['data'] : null);
- // Important: array_key_exists(), not isset()
- // -> The "data" option is optional in FormType
- // If it is given, the form's data will be locked to the value of the option
- // Thus "data" must not be set in the array unless explicitely specified
- $builder->setAttribute('data_option_set', array_key_exists('data', $options));
- }
-
public function getName()
{
return 'foo';
}
- public function createBuilder($name, FormFactoryInterface $factory, array $options)
- {
- return new FormBuilder($name, null, new EventDispatcher(), $factory);
- }
-
- public function setDefaultOptions(OptionsResolverInterface $resolver)
- {
- $resolver->setDefaults(array(
- 'required' => false,
- 'max_length' => null,
- 'a_or_b' => 'a',
- ));
-
- $resolver->setOptional(array(
- 'data',
- ));
-
- $resolver->setAllowedValues(array(
- 'a_or_b' => array('a', 'b'),
- ));
- }
-
public function getParent()
{
return null;
View
442 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\FooTypeBarExtension;
use Symfony\Component\Form\Tests\Fixtures\FooTypeBazExtension;
+/**
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
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,283 +57,252 @@ 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->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()
{
- $type = new FooType();
- $this->extension2->addType($type);
+ $this->registry->expects($this->once())
+ ->method('hasType')
+ ->with('name')
+ ->will($this->returnValue('RESULT'));
- $this->assertSame($type, $this->factory->getType('foo'));
+ $this->assertSame('RESULT', $this->factory->hasType('name'));
}
- public function testGetTypeAddsExtensions()
+ public function testGetType()
{
$type = new FooType();
- $ext1 = new FooTypeBarExtension();
- $ext2 = new FooTypeBazExtension();
+ $resolvedType = $this->getMockResolvedType();
- $this->extension1->addTypeExtension($ext1);
- $this->extension2->addTypeExtension($ext2);
- $this->extension2->addType($type);
+ $resolvedType->expects($this->once())
+ ->method('getInnerType')
+ ->will($this->returnValue($type));
- $type = $this->factory->getType('foo');
+ $this->registry->expects($this->once())
+ ->method('getType')
+ ->with('name')
+ ->will($this->returnValue($resolvedType));
- $this->assertEquals(array($ext1, $ext2), $type->getExtensions());
+ $this->assertEquals($type, $this->factory->getType('name'));
}
- /**
- * @expectedException Symfony\Component\Form\Exception\FormException
- */
- public function testGetTypeExpectsExistingType()
+ public function testCreateNamedBuilderWithTypeName()
{
- $this->factory->getType('bar');
- }
+ $options = array('a' => '1', 'b' => '2');
+ $resolvedType = $this->getMockResolvedType();
- public function testCreateNamedBuilder()
- {
- $type = new FooType();
- $this->extension1->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)
+ ->will($this->returnValue('BUILDER'));
- $this->assertTrue($builder instanceof FormBuilder);
- $this->assertEquals('bar', $builder->getName());
- $this->assertNull($builder->getParent());
+ $this->assertSame('BUILDER', $this->factory->createNamedBuilder('name', 'type', null, $options));
}
- public function testCreateNamedBuilderCallsBuildFormMethods()
+ public function testCreateNamedBuilderWithTypeInstance()
{
- $type = new FooType();
- $ext1 = new FooTypeBarExtension();
- $ext2 = new FooTypeBazExtension();
+ $options = array('a' => '1', 'b' => '2');
+ $type = $this->getMockType();
+ $resolvedType = $this->getMockResolvedType();
- $this->extension1->addTypeExtension($ext1);
- $this->extension2->addTypeExtension($ext2);
- $this->extension2->addType($type);
+ $this->registry->expects($this->once())
+ ->method('resolveType')
+ ->with($type)
+ ->will($this->returnValue($resolvedType));
- $builder = $this->factory->createNamedBuilder('bar', 'foo');
+ // 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->assertTrue($builder->hasAttribute('foo'));
- $this->assertTrue($builder->hasAttribute('bar'));
- $this->assertTrue($builder->hasAttribute('baz'));
+ $this->assertSame('BUILDER', $this->factory->createNamedBuilder('name', $type, null, $options));
}
- public function testCreateNamedBuilderFillsDataOption()
+ 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', 'xyz');
+ // The type is also implicitely added to the registry
+ $this->registry->expects($this->once())
+ ->method('addType')
+ ->with($resolvedType);
- // see FooType::buildForm()
- $this->assertTrue($builder->getAttribute('data_option_set'));
- $this->assertEquals('xyz', $builder->getAttribute('data_option'));
+ $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 testCreateNamedBuilderDoesNotSetDataOptionIfNull()
+ public function testCreateNamedBuilderWithParentBuilder()
{
- $type = new FooType();
- $this->extension1->addType($type);
+ $options = array('a' => '1', 'b' => '2');
+ $parentBuilder = $this->getMockFormBuilder();
+ $resolvedType = $this->getMockResolvedType();
+
+ $this->registry->expects($this->once())
+ ->method('getType')
+ ->with('type')
+ ->will($this->returnValue($resolvedType));
- $builder = $this->factory->createNamedBuilder('bar', 'foo', null);
+ $resolvedType->expects($this->once())
+ ->method('createBuilder')
+ ->with($this->factory, 'name', $options, $parentBuilder)
+ ->will($this->returnValue('BUILDER'));
- // see FooType::buildForm()
- $this->assertFalse($builder->getAttribute('data_option_set'));
+ $this->assertSame('BUILDER', $this->factory->createNamedBuilder('name', 'type', null, $options, $parentBuilder));
}
- public function testCreateNamedBuilderDoesNotOverrideExistingDataOption()
+ 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();
+
+ $this->registry->expects($this->once())
+ ->method('getType')
+ ->with('type')
+ ->will($this->returnValue($resolvedType));
- $builder = $this->factory->createNamedBuilder('bar', 'foo', 'xyz', array(
- 'data' => 'abc',
- ));
+ $resolvedType->expects($this->once())
+ ->method('createBuilder')
+ ->with($this->factory, 'name', $expectedOptions)
+ ->will($this->returnValue('BUILDER'));
- // see FooType::buildForm()
- $this->assertTrue($builder->getAttribute('data_option_set'));
- $this->assertEquals('abc', $builder->getAttribute('data_option'));
+ $this->assertSame('BUILDER', $this->factory->createNamedBuilder('name', 'type', 'DATA', $givenOptions));
}
- /**
- * @expectedException Symfony\Component\Form\Exception\TypeDefinitionException
- */
- public function testCreateNamedBuilderExpectsDataOptionToBeSupported()
+ public function testCreateNamedBuilderDoesNotOverrideExistingDataOption()
{
- $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()));
+ $options = array('a' => '1', 'b' => '2', 'data' => 'CUSTOM');
+ $resolvedType = $this->getMockResolvedType();
- $this->extension1->addType($type);
+ $this->registry->expects($this->once())
+ ->method('getType')
+ ->with('type')
+ ->will($this->returnValue($resolvedType));
- $this->factory->createNamedBuilder('bar', 'foo');
+ $resolvedType->expects($this->once())
+ ->method('createBuilder')
+ ->with($this->factory, 'name', $options)
+ ->will($this->returnValue('BUILDER'));
+
+ $this->assertSame('BUILDER', $this->factory->createNamedBuilder('name', 'type', 'DATA', $options));
}
/**
- * @expectedException Symfony\Component\Form\Exception\TypeDefinitionException
+ * @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException
+ * @expectedExceptionMessage Expected argument of type "string, Symfony\Component\Form\ResolvedFormTypeInterface or Symfony\Component\Form\FormTypeInterface", "stdClass" given
*/
- public function testCreateNamedBuilderExpectsRequiredOptionToBeSupported()
+ public function testCreateNamedBuilderThrowsUnderstandableException()
{
- $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');
+ $this->factory->createNamedBuilder('name', new \stdClass());
}
- /**
- * @expectedException Symfony\Component\Form\Exception\TypeDefinitionException
- */
- public function testCreateNamedBuilderExpectsMaxLengthOptionToBeSupported()
+ public function testCreateUsesTypeNameIfTypeGivenAsString()
{
- $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);
+ $options = array('a' => '1', 'b' => '2');
+ $resolvedType = $this->getMockResolvedType();
+ $builder = $this->getMockFormBuilder();
- $this->factory->createNamedBuilder('bar', 'foo');
- }
+ $this->registry->expects($this->once())
+ ->method('getType')
+ ->with('TYPE')
+ ->will($this->returnValue($resolvedType));
- /**
- * @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, 'TYPE', $options)
+ ->will($this->returnValue($builder));
- $this->extension1->addType($type);
+ $builder->expects($this->once())
+ ->method('getForm')
+ ->will($this->returnValue('FORM'));
- $this->factory->createNamedBuilder('bar', 'foo');
+ $this->assertSame('FORM', $this->factory->create('TYPE', null, $options));
}
- /**
- * @expectedException Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
- */
- public function testCreateNamedBuilderExpectsOptionsToExist()
+ public function testCreateUsesTypeNameIfTypeGivenAsObject()
{
- $type = new FooType();
- $this->extension1->addType($type);
+ $options = array('a' => '1', 'b' => '2');
+ $resolvedType = $this->getMockResolvedType();
+ $builder = $this->getMockFormBuilder();
- $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);
+ $resolvedType->expects($this->once())
+ ->method('getName')
+ ->will($this->returnValue('TYPE'));
- $this->factory->createNamedBuilder('bar', 'foo', null, array(
- 'a_or_b' => 'c',
- ));
- }
+ $resolvedType->expects($this->once())
+ ->method('createBuilder')
+ ->with($this->factory, 'TYPE', $options)
+ ->will($this->returnValue($builder));
- public function testCreateNamedBuilderAllowsExtensionsToExtendAllowedOptionValues()
- {
- $type = new FooType();
- $this->extension1->addType($type);
- $this->extension1->addTypeExtension(new FooTypeBarExtension());
+ $builder->expects($this->once())
+ ->method('getForm')
+ ->will($this->returnValue('FORM'));
- // no exception this time
- $this->factory->createNamedBuilder('bar', 'foo', null, array(
- 'a_or_b' => 'c',
- ));
+ $this->assertSame('FORM', $this->factory->create($resolvedType, null, $options));
}
- public function testCreateNamedBuilderAddsTypeInstances()
+ public function testCreateNamed()
{
- $type = new FooType();
- $this->assertFalse($this->factory->hasType('foo'));
+ $options = array('a' => '1', 'b' => '2');
+ $resolvedType = $this->getMockResolvedType();
+ $builder = $this->getMockFormBuilder();
- $builder = $this->factory->createNamedBuilder('bar', $type);
+ $this->registry->expects($this->once())
+ ->method('getType')
+ ->with('type')
+ ->will($this->returnValue($resolvedType));
- $this->assertTrue($builder instanceof FormBuilder);
- $this->assertTrue($this->factory->hasType('foo'));
- }
-
- /**
- * @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException
- * @expectedExceptionMessage Expected argument of type "string or Symfony\Component\Form\FormTypeInterface", "stdClass" given
- */
- public function testCreateNamedBuilderThrowsUnderstandableException()
- {
- $this->factory->createNamedBuilder('name', new \stdClass());
- }
-
- public function testCreateUsesTypeNameAsName()
- {
- $type = new FooType();
- $this->extension1->addType($type);
+ $resolvedType->expects($this->once())
+ ->method('createBuilder')
+ ->with($this->factory, 'name', $options)
+ ->will($this->returnValue($builder));
- $builder = $this->factory->createBuilder('foo');
+ $builder->expects($this->once())
+ ->method('getForm')
+ ->will($this->returnValue('FORM'));
- $this->assertEquals('foo', $builder->getName());
+ $this->assertSame('FORM', $this->factory->createNamed('name', 'type', null, $options));
}
public function testCreateBuilderForPropertyCreatesFormWithHighestConfidence()
@@ -342,7 +325,7 @@ public function testCreateBuilderForPropertyCreatesFormWithHighestConfidence()
Guess::HIGH_CONFIDENCE
)));
- $factory = $this->createMockFactory(array('createNamedBuilder'));
+ $factory = $this->getMockFactory(array('createNamedBuilder'));
$factory->expects($this->once())
->method('createNamedBuilder')
@@ -361,7 +344,7 @@ public function testCreateBuilderCreatesTextFormIfNoGuess()
->with('Application\Author', 'firstName')
->will($this->returnValue(null));
- $factory = $this->createMockFactory(array('createNamedBuilder'));
+ $factory = $this->getMockFactory(array('createNamedBuilder'));
$factory->expects($this->once())
->method('createNamedBuilder')
@@ -384,7 +367,7 @@ public function testOptionsCanBeOverridden()
Guess::MEDIUM_CONFIDENCE
)));
- $factory = $this->createMockFactory(array('createNamedBuilder'));
+ $factory = $this->getMockFactory(array('createNamedBuilder'));
$factory->expects($this->once())
->method('createNamedBuilder')
@@ -419,7 +402,7 @@ public function testCreateBuilderUsesMaxLengthIfFound()
Guess::HIGH_CONFIDENCE
)));
- $factory = $this->createMockFactory(array('createNamedBuilder'));
+ $factory = $this->getMockFactory(array('createNamedBuilder'));
$factory->expects($this->once())
->method('createNamedBuilder')
@@ -452,7 +435,7 @@ public function testCreateBuilderUsesMinLengthIfFound()
Guess::HIGH_CONFIDENCE
)));
- $factory = $this->createMockFactory(array('createNamedBuilder'));
+ $factory = $this->getMockFactory(array('createNamedBuilder'));
$factory->expects($this->once())
->method('createNamedBuilder')
@@ -487,7 +470,7 @@ public function testCreateBuilderPrefersPatternOverMinLength()
Guess::LOW_CONFIDENCE
)));
- $factory = $this->createMockFactory(array('createNamedBuilder'));
+ $factory = $this->getMockFactory(array('createNamedBuilder'));
$factory->expects($this->once())
->method('createNamedBuilder')
@@ -520,7 +503,7 @@ public function testCreateBuilderUsesRequiredSettingWithHighestConfidence()
Guess::HIGH_CONFIDENCE
)));
- $factory = $this->createMockFactory(array('createNamedBuilder'));
+ $factory = $this->getMockFactory(array('createNamedBuilder'));
$factory->expects($this->once())
->method('createNamedBuilder')
@@ -553,7 +536,7 @@ public function testCreateBuilderUsesPatternIfFound()
Guess::HIGH_CONFIDENCE
)));
- $factory = $this->createMockFactory(array('createNamedBuilder'));
+ $factory = $this->getMockFactory(array('createNamedBuilder'));
$factory->expects($this->once())
->method('createNamedBuilder')
@@ -568,41 +551,26 @@ public function testCreateBuilderUsesPatternIfFound()
$this->assertEquals('builderInstance', $builder);
}
- public function testCreateNamedBuilderFromParentBuilder()
+ private function getMockFactory(array $methods = array())
{
- $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());
+ return $this->getMockBuilder('Symfony\Component\Form\FormFactory')
+ ->setMethods($methods)
+ ->setConstructorArgs(array($this->registry))
+ ->getMock();
}