Skip to content

Commit

Permalink
[Form] Fixed: FormTypeInterface::getParent() supports returning FormT…
Browse files Browse the repository at this point in the history
…ypeInterface instances again
  • Loading branch information
webmozart committed Aug 22, 2012
1 parent 7a233bc commit a38232a
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 26 deletions.
39 changes: 29 additions & 10 deletions src/Symfony/Component/Form/FormFactory.php
Expand Up @@ -80,16 +80,7 @@ public function createNamedBuilder($name, $type = 'form', $data = null, array $o
}

if ($type instanceof FormTypeInterface) {
// An unresolved type instance was passed. Type extensions
// are not supported for these. If you want to use type
// extensions, you should create form extensions or register
// your type in the Dependency Injection configuration instead.
$parentType = $type->getParent();
$type = $this->resolvedTypeFactory->createResolvedType(
$type,
array(),
$parentType ? $this->registry->getType($parentType) : null
);
$type = $this->resolveType($type);
} elseif (is_string($type)) {
$type = $this->registry->getType($type);
} elseif (!$type instanceof ResolvedFormTypeInterface) {
Expand Down Expand Up @@ -196,4 +187,32 @@ public function getType($name)
{
return $this->registry->getType($name)->getInnerType();
}

/**
* Wraps a type into a ResolvedFormTypeInterface implementation and connects
* it with its parent type.
*
* @param FormTypeInterface $type The type to resolve.
*
* @return ResolvedFormTypeInterface The resolved type.
*/
private function resolveType(FormTypeInterface $type)
{
$parentType = $type->getParent();

if ($parentType instanceof FormTypeInterface) {
$parentType = $this->resolveType($parentType);
} elseif (null !== $parentType) {
$parentType = $this->registry->getType($parentType);
}

return $this->resolvedTypeFactory->createResolvedType(
$type,
// Type extensions are not supported for unregistered type instances,
// i.e. type instances that are passed to the FormFactory directly,
// nor for their parents, if getParent() also returns a type instance.
array(),
$parentType
);
}
}
47 changes: 33 additions & 14 deletions src/Symfony/Component/Form/FormRegistry.php
Expand Up @@ -95,25 +95,44 @@ public function getType($name)
throw new FormException(sprintf('Could not load type "%s"', $name));
}

$parentType = $type->getParent();
$typeExtensions = array();
$this->resolveAndAddType($type);
}

foreach ($this->extensions as $extension) {
/* @var FormExtensionInterface $extension */
$typeExtensions = array_merge(
$typeExtensions,
$extension->getTypeExtensions($name)
);
}
return $this->types[$name];
}

/**
* Wraps a type into a ResolvedFormTypeInterface implementation and connects
* it with its parent type.
*
* @param FormTypeInterface $type The type to resolve.
*
* @return ResolvedFormTypeInterface The resolved type.
*/
private function resolveAndAddType(FormTypeInterface $type)
{
$parentType = $type->getParent();

$this->addType($this->resolvedTypeFactory->createResolvedType(
$type,
if ($parentType instanceof FormTypeInterface) {
$this->resolveAndAddType($parentType);
$parentType = $parentType->getName();
}

$typeExtensions = array();

foreach ($this->extensions as $extension) {
/* @var FormExtensionInterface $extension */
$typeExtensions = array_merge(
$typeExtensions,
$parentType ? $this->getType($parentType) : null
));
$extension->getTypeExtensions($type->getName())
);
}

return $this->types[$name];
$this->addType($this->resolvedTypeFactory->createResolvedType(
$type,
$typeExtensions,
$parentType ? $this->getType($parentType) : null
));
}

/**
Expand Down
6 changes: 5 additions & 1 deletion src/Symfony/Component/Form/FormTypeInterface.php
Expand Up @@ -78,7 +78,11 @@ public function setDefaultOptions(OptionsResolverInterface $resolver);
/**
* Returns the name of the parent type.
*
* @return string|null The name of the parent type if any, null otherwise.
* You can also return a type instance from this method, although doing so
* is discouraged because it leads to a performance penalty. The support
* for returning type instances may be dropped from future releases.
*
* @return string|null|FormTypeInterface The name of the parent type if any, null otherwise.
*/
public function getParent();

Expand Down
@@ -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 FooSubTypeWithParentInstance extends AbstractType
{
public function getName()
{
return 'foo_sub_type_parent_instance';
}

public function getParent()
{
return new FooType();
}
}
54 changes: 53 additions & 1 deletion src/Symfony/Component/Form/Tests/FormFactoryTest.php
Expand Up @@ -21,6 +21,8 @@
use Symfony\Component\Form\Tests\Fixtures\AuthorType;
use Symfony\Component\Form\Tests\Fixtures\TestExtension;
use Symfony\Component\Form\Tests\Fixtures\FooType;
use Symfony\Component\Form\Tests\Fixtures\FooSubType;
use Symfony\Component\Form\Tests\Fixtures\FooSubTypeWithParentInstance;
use Symfony\Component\Form\Tests\Fixtures\FooTypeBarExtension;
use Symfony\Component\Form\Tests\Fixtures\FooTypeBazExtension;

Expand Down Expand Up @@ -139,7 +141,7 @@ public function testCreateNamedBuilderWithTypeName()
public function testCreateNamedBuilderWithTypeInstance()
{
$options = array('a' => '1', 'b' => '2');
$type = $this->getMockType();
$type = new FooType();
$resolvedType = $this->getMockResolvedType();

$this->resolvedTypeFactory->expects($this->once())
Expand All @@ -155,6 +157,56 @@ public function testCreateNamedBuilderWithTypeInstance()
$this->assertSame('BUILDER', $this->factory->createNamedBuilder('name', $type, null, $options));
}

public function testCreateNamedBuilderWithTypeInstanceWithParentType()
{
$options = array('a' => '1', 'b' => '2');
$type = new FooSubType();
$resolvedType = $this->getMockResolvedType();
$parentResolvedType = $this->getMockResolvedType();

$this->registry->expects($this->once())
->method('getType')
->with('foo')
->will($this->returnValue($parentResolvedType));

$this->resolvedTypeFactory->expects($this->once())
->method('createResolvedType')
->with($type, array(), $parentResolvedType)
->will($this->returnValue($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 testCreateNamedBuilderWithTypeInstanceWithParentTypeInstance()
{
$options = array('a' => '1', 'b' => '2');
$type = new FooSubTypeWithParentInstance();
$resolvedType = $this->getMockResolvedType();
$parentResolvedType = $this->getMockResolvedType();

$this->resolvedTypeFactory->expects($this->at(0))
->method('createResolvedType')
->with($type->getParent())
->will($this->returnValue($parentResolvedType));

$this->resolvedTypeFactory->expects($this->at(1))
->method('createResolvedType')
->with($type, array(), $parentResolvedType)
->will($this->returnValue($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 testCreateNamedBuilderWithResolvedTypeInstance()
{
$options = array('a' => '1', 'b' => '2');
Expand Down
30 changes: 30 additions & 0 deletions src/Symfony/Component/Form/Tests/FormRegistryTest.php
Expand Up @@ -12,6 +12,7 @@
namespace Symfony\Component\Form;

use Symfony\Component\Form\Tests\Fixtures\TestExtension;
use Symfony\Component\Form\Tests\Fixtures\FooSubTypeWithParentInstance;
use Symfony\Component\Form\Tests\Fixtures\FooSubType;
use Symfony\Component\Form\Tests\Fixtures\FooTypeBazExtension;
use Symfony\Component\Form\Tests\Fixtures\FooTypeBarExtension;
Expand Down Expand Up @@ -153,6 +154,35 @@ public function testGetTypeConnectsParent()
$this->assertSame($resolvedType, $this->registry->getType('foo_sub_type'));
}

public function testGetTypeConnectsParentIfGetParentReturnsInstance()
{
$type = new FooSubTypeWithParentInstance();
$parentResolvedType = $this->getMock('Symfony\Component\Form\ResolvedFormTypeInterface');
$resolvedType = $this->getMock('Symfony\Component\Form\ResolvedFormTypeInterface');

$this->extension1->addType($type);

$this->resolvedTypeFactory->expects($this->at(0))
->method('createResolvedType')
->with($this->isInstanceOf('Symfony\Component\Form\Tests\Fixtures\FooType'))
->will($this->returnValue($parentResolvedType));

$this->resolvedTypeFactory->expects($this->at(1))
->method('createResolvedType')
->with($type, array(), $parentResolvedType)
->will($this->returnValue($resolvedType));

$parentResolvedType->expects($this->any())
->method('getName')
->will($this->returnValue('foo'));

$resolvedType->expects($this->any())
->method('getName')
->will($this->returnValue('foo_sub_type_parent_instance'));

$this->assertSame($resolvedType, $this->registry->getType('foo_sub_type_parent_instance'));
}

/**
* @expectedException Symfony\Component\Form\Exception\FormException
*/
Expand Down

0 comments on commit a38232a

Please sign in to comment.