diff --git a/UPGRADE-2.8.md b/UPGRADE-2.8.md index 24861929c8ae..6a7282172db1 100644 --- a/UPGRADE-2.8.md +++ b/UPGRADE-2.8.md @@ -21,7 +21,7 @@ Form ```php use Symfony\Component\Validator\Constraints\Valid; - + $form = $this->createFormBuilder($article) ->add('author', new AuthorType(), array( 'constraints' => new Valid(), @@ -42,42 +42,42 @@ Form private $author; } ``` - + * Type names were deprecated and will be removed in Symfony 3.0. Instead of referencing types by name, you should reference them by their fully-qualified class name (FQCN) instead. With PHP 5.5 or later, you can use the "class" constant for that: - + Before: - + ```php $form = $this->createFormBuilder() ->add('name', 'text') ->add('age', 'integer') ->getForm(); ``` - + After: - + ```php use Symfony\Component\Form\Extension\Core\Type\IntegerType; use Symfony\Component\Form\Extension\Core\Type\TextType; - + $form = $this->createFormBuilder() ->add('name', TextType::class) ->add('age', IntegerType::class) ->getForm(); ``` - + As a further consequence, the method `FormTypeInterface::getName()` was deprecated and will be removed in Symfony 3.0. You should remove this method from your form types. - + If you want to customize the block prefix of a type in Twig, you should now implement `FormTypeInterface::getBlockPrefix()` instead: - + Before: - + ```php class UserProfileType extends AbstractType { @@ -87,9 +87,9 @@ Form } } ``` - + After: - + ```php class UserProfileType extends AbstractType { @@ -99,14 +99,14 @@ Form } } ``` - + If you don't customize `getBlockPrefix()`, it defaults to the class name without "Type" suffix in underscore notation (here: "user_profile"). - + If you want to create types that are compatible with Symfony 2.3 up to 2.8 and don't trigger deprecation errors, implement *both* `getName()` and `getBlockPrefix()`: - + ```php class ProfileType extends AbstractType { @@ -114,38 +114,38 @@ Form { return $this->getBlockPrefix(); } - + public function getBlockPrefix() { return 'profile'; } } ``` - + If you define your form types in the Dependency Injection configuration, you should further remove the "alias" attribute: - + Before: - + ```xml ``` - + After: - + ```xml ``` - + Type extension should return the fully-qualified class name of the extended type from `FormTypeExtensionInterface::getExtendedType()` now. - + Before: - + ```php class MyTypeExtension extends AbstractTypeExtension { @@ -155,12 +155,12 @@ Form } } ``` - + After: - + ```php use Symfony\Component\Form\Extension\Core\Type\FormType; - + class MyTypeExtension extends AbstractTypeExtension { public function getExtendedType() @@ -169,14 +169,14 @@ Form } } ``` - + If your extension has to be compatible with Symfony 2.3-2.8, use the following statement: - + ```php use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\FormType; - + class MyTypeExtension extends AbstractTypeExtension { public function getExtendedType() @@ -185,13 +185,13 @@ Form } } ``` - + * Returning type instances from `FormTypeInterface::getParent()` is deprecated and will not be supported anymore in Symfony 3.0. Return the fully-qualified class name of the parent type class instead. - + Before: - + ```php class MyType { @@ -201,9 +201,9 @@ Form } } ``` - + After: - + ```php class MyType { @@ -213,24 +213,28 @@ Form } } ``` - + * Passing type instances to `Form::add()`, `FormBuilder::add()` and the `FormFactory::create*()` methods is deprecated and will not be supported anymore in Symfony 3.0. Pass the fully-qualified class name of the type instead. - + Before: - + ```php $form = $this->createForm(new MyType()); ``` - + After: - + ```php $form = $this->createForm(MyType::class); ``` + * Registering type extensions as a service with an alias which does not + match the type returned by `getExtendedType` is now forbidden. Fix your + implementation to define the right type. + Translator ---------- diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md index 76b2da1da319..cebd4b87ac47 100644 --- a/src/Symfony/Component/Form/CHANGELOG.md +++ b/src/Symfony/Component/Form/CHANGELOG.md @@ -9,6 +9,7 @@ CHANGELOG * deprecated the "cascade_validation" option in favor of setting "constraints" with the Valid constraint * moved data trimming logic of TrimListener into StringUtil + * [BC BREAK] When registering a type extension through the DI extension, the tag alias has to match the actual extended type. 2.7.0 ----- diff --git a/src/Symfony/Component/Form/Extension/DependencyInjection/DependencyInjectionExtension.php b/src/Symfony/Component/Form/Extension/DependencyInjection/DependencyInjectionExtension.php index 487a9aa8b75e..3a8a6b254523 100644 --- a/src/Symfony/Component/Form/Extension/DependencyInjection/DependencyInjectionExtension.php +++ b/src/Symfony/Component/Form/Extension/DependencyInjection/DependencyInjectionExtension.php @@ -68,7 +68,18 @@ public function getTypeExtensions($name) if (isset($this->typeExtensionServiceIds[$name])) { foreach ($this->typeExtensionServiceIds[$name] as $serviceId) { - $extensions[] = $this->container->get($serviceId); + $extensions[] = $extension = $this->container->get($serviceId); + + // validate result of getExtendedType() to ensure it is consistent with the service definition + if ($extension->getExtendedType() !== $name) { + throw new InvalidArgumentException( + sprintf('The extended type specified for the service "%s" does not match the actual extended type. Expected "%s", given "%s".', + $serviceId, + $name, + $extension->getExtendedType() + ) + ); + } } } diff --git a/src/Symfony/Component/Form/Tests/Extension/DependencyInjection/DependencyInjectionExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/DependencyInjection/DependencyInjectionExtensionTest.php new file mode 100644 index 000000000000..77fb370d73e0 --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Extension/DependencyInjection/DependencyInjectionExtensionTest.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\Extension\DependencyInjection; + +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; +use Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension; + +class DependencyInjectionExtensionTest extends \PHPUnit_Framework_TestCase +{ + public function testGetTypeExtensions() + { + $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); + + $typeExtension1 = $this->getMock('Symfony\Component\Form\FormTypeExtensionInterface'); + $typeExtension1->expects($this->any()) + ->method('getExtendedType') + ->willReturn('test'); + $typeExtension2 = $this->getMock('Symfony\Component\Form\FormTypeExtensionInterface'); + $typeExtension2->expects($this->any()) + ->method('getExtendedType') + ->willReturn('test'); + $typeExtension3 = $this->getMock('Symfony\Component\Form\FormTypeExtensionInterface'); + $typeExtension3->expects($this->any()) + ->method('getExtendedType') + ->willReturn('other'); + + $services = array( + 'extension1' => $typeExtension1, + 'extension2' => $typeExtension2, + 'extension3' => $typeExtension3, + ); + + $container->expects($this->any()) + ->method('get') + ->willReturnCallback(function ($id) use ($services) { + if (isset($services[$id])) { + return $services[$id]; + } + + throw new ServiceNotFoundException($id); + }); + + $extension = new DependencyInjectionExtension($container, array(), array('test' => array('extension1', 'extension2'), 'other' => array('extension3')), array()); + + $this->assertTrue($extension->hasTypeExtensions('test')); + $this->assertFalse($extension->hasTypeExtensions('unknown')); + $this->assertSame(array($typeExtension1, $typeExtension2), $extension->getTypeExtensions('test')); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\InvalidArgumentException + */ + public function testThrowExceptionForInvalidExtendedType() + { + $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); + + $typeExtension = $this->getMock('Symfony\Component\Form\FormTypeExtensionInterface'); + $typeExtension->expects($this->any()) + ->method('getExtendedType') + ->willReturn('unmatched'); + + $container->expects($this->any()) + ->method('get') + ->with('extension') + ->willReturn($typeExtension); + + $extension = new DependencyInjectionExtension($container, array(), array('test' => array('extension')), array()); + + $extension->getTypeExtensions('test'); + } +} diff --git a/src/Symfony/Component/Form/composer.json b/src/Symfony/Component/Form/composer.json index 66a8674ab70f..b777792e1f32 100644 --- a/src/Symfony/Component/Form/composer.json +++ b/src/Symfony/Component/Form/composer.json @@ -26,6 +26,7 @@ "symfony/phpunit-bridge": "~2.7|~3.0.0", "doctrine/collections": "~1.0", "symfony/validator": "~2.8|~3.0.0", + "symfony/dependency-injection": "~2.3|~3.0.0", "symfony/http-foundation": "~2.2|~3.0.0", "symfony/http-kernel": "~2.4|~3.0.0", "symfony/security-csrf": "~2.4|~3.0.0",