diff --git a/src/Symfony/Component/Form/FormFactory.php b/src/Symfony/Component/Form/FormFactory.php index 02be3017a2..0cea611d5a 100644 --- a/src/Symfony/Component/Form/FormFactory.php +++ b/src/Symfony/Component/Form/FormFactory.php @@ -80,16 +80,7 @@ class FormFactory implements FormFactoryInterface } 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) { @@ -196,4 +187,32 @@ class FormFactory implements FormFactoryInterface { 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 + ); + } } diff --git a/src/Symfony/Component/Form/FormRegistry.php b/src/Symfony/Component/Form/FormRegistry.php index 14f5cb3d8c..a4146dbff3 100644 --- a/src/Symfony/Component/Form/FormRegistry.php +++ b/src/Symfony/Component/Form/FormRegistry.php @@ -95,27 +95,46 @@ class FormRegistry implements FormRegistryInterface throw new FormException(sprintf('Could not load type "%s"', $name)); } - $parentType = $type->getParent(); - $typeExtensions = array(); - - foreach ($this->extensions as $extension) { - /* @var FormExtensionInterface $extension */ - $typeExtensions = array_merge( - $typeExtensions, - $extension->getTypeExtensions($name) - ); - } - - $this->addType($this->resolvedTypeFactory->createResolvedType( - $type, - $typeExtensions, - $parentType ? $this->getType($parentType) : null - )); + $this->resolveAndAddType($type); } 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(); + + if ($parentType instanceof FormTypeInterface) { + $this->resolveAndAddType($parentType); + $parentType = $parentType->getName(); + } + + $typeExtensions = array(); + + foreach ($this->extensions as $extension) { + /* @var FormExtensionInterface $extension */ + $typeExtensions = array_merge( + $typeExtensions, + $extension->getTypeExtensions($type->getName()) + ); + } + + $this->addType($this->resolvedTypeFactory->createResolvedType( + $type, + $typeExtensions, + $parentType ? $this->getType($parentType) : null + )); + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Form/FormTypeInterface.php b/src/Symfony/Component/Form/FormTypeInterface.php index ef2846a83a..bcef73c6d7 100644 --- a/src/Symfony/Component/Form/FormTypeInterface.php +++ b/src/Symfony/Component/Form/FormTypeInterface.php @@ -78,7 +78,11 @@ interface FormTypeInterface /** * 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(); diff --git a/src/Symfony/Component/Form/Tests/Fixtures/FooSubTypeWithParentInstance.php b/src/Symfony/Component/Form/Tests/Fixtures/FooSubTypeWithParentInstance.php new file mode 100644 index 0000000000..468b5a32a7 --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Fixtures/FooSubTypeWithParentInstance.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\Fixtures; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormBuilder; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormFactoryInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\OptionsResolver\OptionsResolverInterface; + +class FooSubTypeWithParentInstance extends AbstractType +{ + public function getName() + { + return 'foo_sub_type_parent_instance'; + } + + public function getParent() + { + return new FooType(); + } +} diff --git a/src/Symfony/Component/Form/Tests/FormFactoryTest.php b/src/Symfony/Component/Form/Tests/FormFactoryTest.php index 0cbf54c899..a36d0f5f38 100644 --- a/src/Symfony/Component/Form/Tests/FormFactoryTest.php +++ b/src/Symfony/Component/Form/Tests/FormFactoryTest.php @@ -21,6 +21,8 @@ use Symfony\Component\Form\Tests\Fixtures\Author; 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; @@ -139,7 +141,7 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase public function testCreateNamedBuilderWithTypeInstance() { $options = array('a' => '1', 'b' => '2'); - $type = $this->getMockType(); + $type = new FooType(); $resolvedType = $this->getMockResolvedType(); $this->resolvedTypeFactory->expects($this->once()) @@ -155,6 +157,56 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase $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'); diff --git a/src/Symfony/Component/Form/Tests/FormRegistryTest.php b/src/Symfony/Component/Form/Tests/FormRegistryTest.php index e0a22da193..f2d4009048 100644 --- a/src/Symfony/Component/Form/Tests/FormRegistryTest.php +++ b/src/Symfony/Component/Form/Tests/FormRegistryTest.php @@ -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; @@ -153,6 +154,35 @@ class FormRegistryTest extends \PHPUnit_Framework_TestCase $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 */