merged branch bschussek/performance (PR #4882)

Commits
-------

cd7835d [Form] Cached the form type hierarchy in order to improve performance
2ca753b [Form] Fixed choice list hashing in DoctrineType
2bf4d6c [Form] Fixed FormFactory not to set "data" option if not explicitely given
7149d26 [Form] Removed invalid PHPDoc text

Discussion
----------

[Form] WIP Improved performance of form building

Bug fix: no
Feature addition: no
Backwards compatibility break: no
Symfony2 tests pass: yes
Fixes the following tickets: -
Todo: **Update the Silex extension**

This PR is work in progress and up for discussion. It increases the performance of FormFactory::createForm() on a specific, heavy-weight form from **0.848** to **0.580** seconds.

Before, the FormFactory had to traverse the hierarchy and calculate the default options of each FormType everytime a form was created of that type.

Now, FormTypes are wrapped within instances of a new class `ResolvedFormType`, which caches the parent type, the type's extensions and its default options.

The updated responsibilities: `FormFactory` is a registry and proxy for `ResolvedFormType` objects, `FormType` specifies how a form can be built on a specific layer of the type hierarchy (e.g. "form", or "date", etc.) and `ResolvedFormType` *does the actual building* across all layers of the hierarchy (by delegating to the parent type, which delegates to its parent type etc.).

---------------------------------------------------------------------------

by schmittjoh at 2012-07-12T18:25:40Z

Maybe ResolvedFormType

---------------------------------------------------------------------------

by jmather at 2012-07-13T02:56:38Z

I really like ResolvedFormType. That's the naming method I took for my tag parser that handes the same conceptual issue.

---------------------------------------------------------------------------

by axelarge at 2012-07-13T05:25:00Z

ResolvedFormType sounds very clear.
This change is great and I desperately hope to see more of this kind

---------------------------------------------------------------------------

by Baachi at 2012-07-13T06:41:26Z

Yes `ResolvedFormType` sounds good :) 👍

---------------------------------------------------------------------------

by fabpot at 2012-07-13T07:11:33Z

I like `ResolvedFormType` as well.

---------------------------------------------------------------------------

by henrikbjorn at 2012-07-13T07:46:48Z

👍 `ResolvedFormType` :shipit:

---------------------------------------------------------------------------

by stof at 2012-07-13T18:01:51Z

This looks good to me
This commit is contained in:
Fabien Potencier 2012-07-13 21:26:31 +02:00
commit a27aeda8f4
33 changed files with 1568 additions and 779 deletions

View File

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

View File

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

View File

@ -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])) {

View File

@ -6,13 +6,14 @@
<parameters>
<parameter key="form.extension.class">Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension</parameter>
<parameter key="form.registry.class">Symfony\Component\Form\FormRegistry</parameter>
<parameter key="form.factory.class">Symfony\Component\Form\FormFactory</parameter>
<parameter key="form.type_guesser.validator.class">Symfony\Component\Form\Extension\Validator\ValidatorTypeGuesser</parameter>
</parameters>
<services>
<!-- FormFactory -->
<service id="form.factory" class="%form.factory.class%">
<!-- FormRegistry -->
<service id="form.registry" class="%form.registry.class%">
<argument type="collection">
<!--
We don't need to be able to add more extensions.
@ -23,6 +24,11 @@
</argument>
</service>
<!-- FormFactory -->
<service id="form.factory" class="%form.factory.class%">
<argument type="service" id="form.registry" />
</service>
<!-- DependencyInjectionExtension -->
<service id="form.extension" class="%form.extension.class%" public="false">
<argument type="service" id="service_container" />

View File

@ -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()
{

View File

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

@ -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}
*/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

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

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

View File

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

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,177 @@
<?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\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 <bschussek@gmail.com>
*/
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());
}
}

View File

@ -0,0 +1,16 @@
<?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;
interface FormViewInterface extends \Iterator, \Symfony\Component\Form\FormViewInterface
{
}

View File

@ -0,0 +1,285 @@
<?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;
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 <bschussek@gmail.com>
*/
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);
}
}

View File

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

View File

@ -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;
}
/**