diff --git a/UPGRADE-4.2.md b/UPGRADE-4.2.md index 31a06c8844f9..f04124d9bc4c 100644 --- a/UPGRADE-4.2.md +++ b/UPGRADE-4.2.md @@ -55,9 +55,38 @@ Finder Form ---- + * The `getExtendedType()` method of the `FormTypeExtensionInterface` is deprecated and will be removed in 5.0. Type + extensions must implement the static `getExtendedTypes()` method instead and return an iterable of extended types. + + Before: + + ```php + class FooTypeExtension extends AbstractTypeExtension + { + public function getExtendedType() + { + return FormType::class; + } + + // ... + } + ``` + + After: + + ```php + class FooTypeExtension extends AbstractTypeExtension + { + public static function getExtendedTypes(): iterable + { + return array(FormType::class); + } + + // ... + } + ``` * The `scale` option of the `IntegerType` is deprecated. * The `$scale` argument of the `IntegerToLocalizedStringTransformer` is deprecated. - * Deprecated calling `FormRenderer::searchAndRenderBlock` for fields which were already rendered. Instead of expecting such calls to return empty strings, check if the field has already been rendered. diff --git a/UPGRADE-5.0.md b/UPGRADE-5.0.md index d3ca51c806c2..80403f038d62 100644 --- a/UPGRADE-5.0.md +++ b/UPGRADE-5.0.md @@ -72,6 +72,36 @@ Finder Form ---- + * The `getExtendedType()` method was removed from the `FormTypeExtensionInterface`. It is replaced by the the static + `getExtendedTypes()` method which must return an iterable of extended types. + + Before: + + ```php + class FooTypeExtension extends AbstractTypeExtension + { + public function getExtendedType() + { + return FormType::class; + } + + // ... + } + ``` + + After: + + ```php + class FooTypeExtension extends AbstractTypeExtension + { + public static function getExtendedTypes(): iterable + { + return array(FormType::class); + } + + // ... + } + ``` * The `scale` option was removed from the `IntegerType`. * The `$scale` argument of the `IntegerToLocalizedStringTransformer` was removed. diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index d09b0afb9d49..ebae75ec3f05 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -52,6 +52,7 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; use Symfony\Component\Finder\Finder; +use Symfony\Component\Form\FormTypeExtensionInterface; use Symfony\Component\Form\FormTypeGuesserInterface; use Symfony\Component\Form\FormTypeInterface; use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface; @@ -325,6 +326,8 @@ public function load(array $configs, ContainerBuilder $container) ->addTag('form.type'); $container->registerForAutoconfiguration(FormTypeGuesserInterface::class) ->addTag('form.type_guesser'); + $container->registerForAutoconfiguration(FormTypeExtensionInterface::class) + ->addTag('form.type_extension'); $container->registerForAutoconfiguration(CacheClearerInterface::class) ->addTag('kernel.cache_clearer'); $container->registerForAutoconfiguration(CacheWarmerInterface::class) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml index 504fe619bf4d..898ab56a2efa 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml @@ -74,7 +74,7 @@ - + @@ -92,13 +92,13 @@ - + - + %validator.translation_domain% diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml index 5acfce366d77..25b1cecc5a73 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml @@ -8,7 +8,7 @@ - + %form.type_extension.csrf.enabled% %form.type_extension.csrf.field_name% diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_debug.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_debug.xml index 525b18c57b60..c8d35db32aaf 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_debug.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_debug.xml @@ -16,7 +16,7 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index ed47758fa000..6be5e16fa62a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -39,7 +39,7 @@ "symfony/dom-crawler": "~3.4|~4.0", "symfony/polyfill-intl-icu": "~1.0", "symfony/security": "~3.4|~4.0", - "symfony/form": "^4.1", + "symfony/form": "^4.2", "symfony/expression-language": "~3.4|~4.0", "symfony/messenger": "^4.2", "symfony/process": "~3.4|~4.0", @@ -66,7 +66,7 @@ "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", "symfony/asset": "<3.4", "symfony/console": "<3.4", - "symfony/form": "<4.1", + "symfony/form": "<4.2", "symfony/messenger": "<4.2", "symfony/property-info": "<3.4", "symfony/serializer": "<4.1", diff --git a/src/Symfony/Component/Form/AbstractExtension.php b/src/Symfony/Component/Form/AbstractExtension.php index 61497aba6b62..83dd8236035a 100644 --- a/src/Symfony/Component/Form/AbstractExtension.php +++ b/src/Symfony/Component/Form/AbstractExtension.php @@ -175,9 +175,21 @@ private function initTypeExtensions() throw new UnexpectedTypeException($extension, 'Symfony\Component\Form\FormTypeExtensionInterface'); } - $type = $extension->getExtendedType(); + if (method_exists($extension, 'getExtendedTypes')) { + $extendedTypes = array(); - $this->typeExtensions[$type][] = $extension; + foreach ($extension::getExtendedTypes() as $extendedType) { + $extendedTypes[] = $extendedType; + } + } else { + @trigger_error(sprintf('Not implementing the static getExtendedTypes() method in %s when implementing the %s is deprecated since Symfony 4.2. The method will be added to the interface in 5.0.', \get_class($extension), FormTypeExtensionInterface::class), E_USER_DEPRECATED); + + $extendedTypes = array($extension->getExtendedType()); + } + + foreach ($extendedTypes as $extendedType) { + $this->typeExtensions[$extendedType][] = $extension; + } } } diff --git a/src/Symfony/Component/Form/AbstractTypeExtension.php b/src/Symfony/Component/Form/AbstractTypeExtension.php index 9d369bf294e9..2305db89ee44 100644 --- a/src/Symfony/Component/Form/AbstractTypeExtension.php +++ b/src/Symfony/Component/Form/AbstractTypeExtension.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Form; +use Symfony\Component\Form\Exception\LogicException; use Symfony\Component\OptionsResolver\OptionsResolver; /** @@ -45,4 +46,22 @@ public function finishView(FormView $view, FormInterface $form, array $options) public function configureOptions(OptionsResolver $resolver) { } + + /** + * {@inheritdoc} + * + * @deprecated since Symfony 4.2, use getExtendedTypes() instead. + */ + public function getExtendedType() + { + if (!method_exists($this, 'getExtendedTypes')) { + throw new LogicException(sprintf('You need to implement the static getExtendedTypes() method when implementing the %s in %s.', FormTypeExtensionInterface::class, static::class)); + } + + @trigger_error(sprintf('The %s::getExtendedType() method is deprecated since Symfony 4.2 and will be removed in 5.0. Use getExtendedTypes() instead.', \get_class($this)), E_USER_DEPRECATED); + + foreach (static::getExtendedTypes() as $extendedType) { + return $extendedType; + } + } } diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md index 388d11652994..dc2ddf78db3c 100644 --- a/src/Symfony/Component/Form/CHANGELOG.md +++ b/src/Symfony/Component/Form/CHANGELOG.md @@ -4,6 +4,36 @@ CHANGELOG 4.2.0 ----- + * The `getExtendedType()` method of the `FormTypeExtensionInterface` is deprecated and will be removed in 5.0. Type + extensions must implement the static `getExtendedTypes()` method instead and return an iterable of extended types. + + Before: + + ```php + class FooTypeExtension extends AbstractTypeExtension + { + public function getExtendedType() + { + return FormType::class; + } + + // ... + } + ``` + + After: + + ```php + class FooTypeExtension extends AbstractTypeExtension + { + public static function getExtendedTypes(): iterable + { + return array(FormType::class); + } + + // ... + } + ``` * deprecated the `$scale` argument of the `IntegerToLocalizedStringTransformer` * added `Symfony\Component\Form\ClearableErrorsInterface` * deprecated calling `FormRenderer::searchAndRenderBlock` for fields which were already rendered diff --git a/src/Symfony/Component/Form/DependencyInjection/FormPass.php b/src/Symfony/Component/Form/DependencyInjection/FormPass.php index 01a81469503b..8b604ccebc1b 100644 --- a/src/Symfony/Component/Form/DependencyInjection/FormPass.php +++ b/src/Symfony/Component/Form/DependencyInjection/FormPass.php @@ -18,6 +18,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Form\FormTypeExtensionInterface; /** * Adds all services with the tags "form.type", "form.type_extension" and @@ -92,13 +93,27 @@ private function processFormTypeExtensions(ContainerBuilder $container) $tag = $serviceDefinition->getTag($this->formTypeExtensionTag); if (isset($tag[0]['extended_type'])) { - $extendedType = $tag[0]['extended_type']; + if (!method_exists($serviceDefinition->getClass(), 'getExtendedTypes')) { + @trigger_error(sprintf('Not implementing the static getExtendedTypes() method in %s when implementing the %s is deprecated since Symfony 4.2. The method will be added to the interface in 5.0.', $serviceDefinition->getClass(), FormTypeExtensionInterface::class), E_USER_DEPRECATED); + } + + $typeExtensions[$tag[0]['extended_type']][] = new Reference($serviceId); + $typeExtensionsClasses[] = $serviceDefinition->getClass(); + } elseif (method_exists($serviceDefinition->getClass(), 'getExtendedTypes')) { + $extendsTypes = false; + + foreach ($serviceDefinition->getClass()::getExtendedTypes() as $extendedType) { + $typeExtensions[$extendedType][] = new Reference($serviceId); + $typeExtensionsClasses[] = $serviceDefinition->getClass(); + $extendsTypes = true; + } + + if (!$extendsTypes) { + throw new InvalidArgumentException(sprintf('The getExtendedTypes() method for service "%s" does not return any extended types.', $serviceId)); + } } else { - throw new InvalidArgumentException(sprintf('"%s" tagged services must have the extended type configured using the extended_type/extended-type attribute, none was configured for the "%s" service.', $this->formTypeExtensionTag, $serviceId)); + throw new InvalidArgumentException(sprintf('"%s" tagged services have to implement the static getExtendedTypes() method. The class for service "%s" does not implement it.', $this->formTypeExtensionTag, $serviceId)); } - - $typeExtensions[$extendedType][] = new Reference($serviceId); - $typeExtensionsClasses[] = $serviceDefinition->getClass(); } foreach ($typeExtensions as $extendedType => $extensions) { diff --git a/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php b/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php index 7e16f6dc375f..4a39b1d999a9 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php +++ b/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Form\Extension\Csrf\Type; use Symfony\Component\Form\AbstractTypeExtension; +use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\Csrf\EventListener\CsrfValidationListener; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormInterface; @@ -114,8 +115,8 @@ public function configureOptions(OptionsResolver $resolver) /** * {@inheritdoc} */ - public function getExtendedType() + public static function getExtendedTypes(): iterable { - return 'Symfony\Component\Form\Extension\Core\Type\FormType'; + return array(FormType::class); } } diff --git a/src/Symfony/Component/Form/Extension/DataCollector/Type/DataCollectorTypeExtension.php b/src/Symfony/Component/Form/Extension/DataCollector/Type/DataCollectorTypeExtension.php index 5c4076d06095..5dffe481cc8b 100644 --- a/src/Symfony/Component/Form/Extension/DataCollector/Type/DataCollectorTypeExtension.php +++ b/src/Symfony/Component/Form/Extension/DataCollector/Type/DataCollectorTypeExtension.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Form\Extension\DataCollector\Type; use Symfony\Component\Form\AbstractTypeExtension; +use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\DataCollector\EventListener\DataCollectorListener; use Symfony\Component\Form\Extension\DataCollector\FormDataCollectorInterface; use Symfony\Component\Form\FormBuilderInterface; @@ -45,8 +46,8 @@ public function buildForm(FormBuilderInterface $builder, array $options) /** * {@inheritdoc} */ - public function getExtendedType() + public static function getExtendedTypes(): iterable { - return 'Symfony\Component\Form\Extension\Core\Type\FormType'; + return array(FormType::class); } } diff --git a/src/Symfony/Component/Form/Extension/DependencyInjection/DependencyInjectionExtension.php b/src/Symfony/Component/Form/Extension/DependencyInjection/DependencyInjectionExtension.php index a97f6af1a2ef..7afccddf0f81 100644 --- a/src/Symfony/Component/Form/Extension/DependencyInjection/DependencyInjectionExtension.php +++ b/src/Symfony/Component/Form/Extension/DependencyInjection/DependencyInjectionExtension.php @@ -58,9 +58,19 @@ public function getTypeExtensions($name) foreach ($this->typeExtensionServices[$name] as $serviceId => $extension) { $extensions[] = $extension; - // 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())); + if (method_exists($extension, 'getExtendedTypes')) { + $extendedTypes = array(); + + foreach ($extension::getExtendedTypes() as $extendedType) { + $extendedTypes[] = $extendedType; + } + } else { + $extendedTypes = array($extension->getExtendedType()); + } + + // validate the result of getExtendedTypes()/getExtendedType() to ensure it is consistent with the service definition + if (!\in_array($name, $extendedTypes, true)) { + 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, implode(', ', $extendedTypes))); } } } diff --git a/src/Symfony/Component/Form/Extension/HttpFoundation/Type/FormTypeHttpFoundationExtension.php b/src/Symfony/Component/Form/Extension/HttpFoundation/Type/FormTypeHttpFoundationExtension.php index 0216b41c1271..c3551d55e02b 100644 --- a/src/Symfony/Component/Form/Extension/HttpFoundation/Type/FormTypeHttpFoundationExtension.php +++ b/src/Symfony/Component/Form/Extension/HttpFoundation/Type/FormTypeHttpFoundationExtension.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Form\Extension\HttpFoundation\Type; use Symfony\Component\Form\AbstractTypeExtension; +use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationRequestHandler; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\RequestHandlerInterface; @@ -39,8 +40,8 @@ public function buildForm(FormBuilderInterface $builder, array $options) /** * {@inheritdoc} */ - public function getExtendedType() + public static function getExtendedTypes(): iterable { - return 'Symfony\Component\Form\Extension\Core\Type\FormType'; + return array(FormType::class); } } diff --git a/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php b/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php index b83c2030c0f0..418950aa7d94 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php +++ b/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Form\Extension\Validator\Type; +use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\Validator\EventListener\ValidationListener; use Symfony\Component\Form\Extension\Validator\ViolationMapper\ViolationMapper; use Symfony\Component\Form\FormBuilderInterface; @@ -67,8 +68,8 @@ public function configureOptions(OptionsResolver $resolver) /** * {@inheritdoc} */ - public function getExtendedType() + public static function getExtendedTypes(): iterable { - return 'Symfony\Component\Form\Extension\Core\Type\FormType'; + return array(FormType::class); } } diff --git a/src/Symfony/Component/Form/Extension/Validator/Type/RepeatedTypeValidatorExtension.php b/src/Symfony/Component/Form/Extension/Validator/Type/RepeatedTypeValidatorExtension.php index ff27fbdb474f..3ca6c5308a41 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Type/RepeatedTypeValidatorExtension.php +++ b/src/Symfony/Component/Form/Extension/Validator/Type/RepeatedTypeValidatorExtension.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Form\Extension\Validator\Type; use Symfony\Component\Form\AbstractTypeExtension; +use Symfony\Component\Form\Extension\Core\Type\RepeatedType; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -38,8 +39,8 @@ public function configureOptions(OptionsResolver $resolver) /** * {@inheritdoc} */ - public function getExtendedType() + public static function getExtendedTypes(): iterable { - return 'Symfony\Component\Form\Extension\Core\Type\RepeatedType'; + return array(RepeatedType::class); } } diff --git a/src/Symfony/Component/Form/Extension/Validator/Type/SubmitTypeValidatorExtension.php b/src/Symfony/Component/Form/Extension/Validator/Type/SubmitTypeValidatorExtension.php index fa844eb1331a..ef14faa99b30 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Type/SubmitTypeValidatorExtension.php +++ b/src/Symfony/Component/Form/Extension/Validator/Type/SubmitTypeValidatorExtension.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Form\Extension\Validator\Type; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; + /** * @author Bernhard Schussek */ @@ -19,8 +21,8 @@ class SubmitTypeValidatorExtension extends BaseValidatorExtension /** * {@inheritdoc} */ - public function getExtendedType() + public static function getExtendedTypes(): iterable { - return 'Symfony\Component\Form\Extension\Core\Type\SubmitType'; + return array(SubmitType::class); } } diff --git a/src/Symfony/Component/Form/Extension/Validator/Type/UploadValidatorExtension.php b/src/Symfony/Component/Form/Extension/Validator/Type/UploadValidatorExtension.php index 9e5eec80014f..54f7db095db7 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Type/UploadValidatorExtension.php +++ b/src/Symfony/Component/Form/Extension/Validator/Type/UploadValidatorExtension.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Form\Extension\Validator\Type; use Symfony\Component\Form\AbstractTypeExtension; +use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface; @@ -55,8 +56,8 @@ public function configureOptions(OptionsResolver $resolver) /** * {@inheritdoc} */ - public function getExtendedType() + public static function getExtendedTypes(): iterable { - return 'Symfony\Component\Form\Extension\Core\Type\FormType'; + return array(FormType::class); } } diff --git a/src/Symfony/Component/Form/FormFactoryBuilder.php b/src/Symfony/Component/Form/FormFactoryBuilder.php index 3afe1f535960..59f5f4910d73 100644 --- a/src/Symfony/Component/Form/FormFactoryBuilder.php +++ b/src/Symfony/Component/Form/FormFactoryBuilder.php @@ -100,7 +100,13 @@ public function addTypes(array $types) */ public function addTypeExtension(FormTypeExtensionInterface $typeExtension) { - $this->typeExtensions[$typeExtension->getExtendedType()][] = $typeExtension; + if (method_exists($typeExtension, 'getExtendedTypes')) { + foreach ($typeExtension::getExtendedTypes() as $extendedType) { + $this->typeExtensions[$extendedType][] = $typeExtension; + } + } else { + $this->typeExtensions[$typeExtension->getExtendedType()][] = $typeExtension; + } return $this; } @@ -111,7 +117,7 @@ public function addTypeExtension(FormTypeExtensionInterface $typeExtension) public function addTypeExtensions(array $typeExtensions) { foreach ($typeExtensions as $typeExtension) { - $this->typeExtensions[$typeExtension->getExtendedType()][] = $typeExtension; + $this->addTypeExtension($typeExtension); } return $this; diff --git a/src/Symfony/Component/Form/FormTypeExtensionInterface.php b/src/Symfony/Component/Form/FormTypeExtensionInterface.php index f7394a2999a1..c80d8a837160 100644 --- a/src/Symfony/Component/Form/FormTypeExtensionInterface.php +++ b/src/Symfony/Component/Form/FormTypeExtensionInterface.php @@ -15,6 +15,8 @@ /** * @author Bernhard Schussek + * + * @method getExtendedTypes(): iterable */ interface FormTypeExtensionInterface { @@ -59,6 +61,8 @@ public function configureOptions(OptionsResolver $resolver); * Returns the name of the type being extended. * * @return string The name of the type being extended + * + * @deprecated since Symfony 4.2, use getExtendedTypes() instead. */ public function getExtendedType(); } diff --git a/src/Symfony/Component/Form/PreloadedExtension.php b/src/Symfony/Component/Form/PreloadedExtension.php index b26dbe638d8c..4b1f3e521370 100644 --- a/src/Symfony/Component/Form/PreloadedExtension.php +++ b/src/Symfony/Component/Form/PreloadedExtension.php @@ -33,6 +33,14 @@ class PreloadedExtension implements FormExtensionInterface */ public function __construct(array $types, array $typeExtensions, FormTypeGuesserInterface $typeGuesser = null) { + foreach ($typeExtensions as $extensions) { + foreach ($extensions as $typeExtension) { + if (!method_exists($typeExtension, 'getExtendedTypes')) { + @trigger_error(sprintf('Not implementing the static getExtendedTypes() method in %s when implementing the %s is deprecated since Symfony 4.2. The method will be added to the interface in 5.0.', \get_class($typeExtension), FormTypeExtensionInterface::class), E_USER_DEPRECATED); + } + } + } + $this->typeExtensions = $typeExtensions; $this->typeGuesser = $typeGuesser; diff --git a/src/Symfony/Component/Form/Tests/AbstractTypeExtensionTest.php b/src/Symfony/Component/Form/Tests/AbstractTypeExtensionTest.php new file mode 100644 index 000000000000..73b066a2076c --- /dev/null +++ b/src/Symfony/Component/Form/Tests/AbstractTypeExtensionTest.php @@ -0,0 +1,53 @@ + + * + * 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 PHPUnit\Framework\TestCase; +use Symfony\Component\Form\AbstractTypeExtension; +use Symfony\Component\Form\Extension\Core\Type\DateTimeType; +use Symfony\Component\Form\Extension\Core\Type\DateType; + +class AbstractTypeExtensionTest extends TestCase +{ + /** + * @expectedException \Symfony\Component\Form\Exception\LogicException + * @expectedExceptionMessage You need to implement the static getExtendedTypes() method when implementing the Symfony\Component\Form\FormTypeExtensionInterface in Symfony\Component\Form\Tests\TypeExtensionWithoutExtendedTypes. + */ + public function testImplementingNeitherGetExtendedTypeNorExtendsTypeThrowsException() + { + $extension = new TypeExtensionWithoutExtendedTypes(); + $extension->getExtendedType(); + } + + /** + * @group legacy + */ + public function testGetExtendedTypeReturnsFirstConfiguredExtension() + { + $extension = new MultipleTypesExtension(); + + $this->assertSame(DateTimeType::class, $extension->getExtendedType()); + } +} + +class MultipleTypesExtension extends AbstractTypeExtension +{ + public static function getExtendedTypes(): iterable + { + yield DateTimeType::class; + yield DateType::class; + } +} + +class TypeExtensionWithoutExtendedTypes extends AbstractTypeExtension +{ +} diff --git a/src/Symfony/Component/Form/Tests/DependencyInjection/FormPassTest.php b/src/Symfony/Component/Form/Tests/DependencyInjection/FormPassTest.php index f5939b40e09c..debbddfdb3f1 100644 --- a/src/Symfony/Component/Form/Tests/DependencyInjection/FormPassTest.php +++ b/src/Symfony/Component/Form/Tests/DependencyInjection/FormPassTest.php @@ -19,6 +19,7 @@ use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\AbstractTypeExtension; use Symfony\Component\Form\DependencyInjection\FormPass; use Symfony\Component\Form\FormRegistryInterface; @@ -97,6 +98,107 @@ public function testAddTaggedTypeExtensions(array $extensions, array $expectedRe $container->setDefinition('form.extension', $this->createExtensionDefinition()); + foreach ($extensions as $serviceId => $config) { + $container->register($serviceId, $config['class'])->addTag('form.type_extension', $config['tag']); + } + + $container->compile(); + + $extDefinition = $container->getDefinition('form.extension'); + $this->assertEquals($expectedRegisteredExtensions, $extDefinition->getArgument(1)); + } + + public function addTaggedTypeExtensionsDataProvider() + { + return array( + array( + array( + Type1TypeExtension::class => array( + 'class' => Type1TypeExtension::class, + 'tag' => array('extended_type' => 'type1'), + ), + Type1Type2TypeExtension::class => array( + 'class' => Type1Type2TypeExtension::class, + 'tag' => array('extended_type' => 'type2'), + ), + ), + array( + 'type1' => new IteratorArgument(array(new Reference(Type1TypeExtension::class))), + 'type2' => new IteratorArgument(array(new Reference(Type1Type2TypeExtension::class))), + ), + ), + array( + array( + Type1TypeExtension::class => array( + 'class' => Type1TypeExtension::class, + 'tag' => array(), + ), + Type1Type2TypeExtension::class => array( + 'class' => Type1Type2TypeExtension::class, + 'tag' => array(), + ), + ), + array( + 'type1' => new IteratorArgument(array( + new Reference(Type1TypeExtension::class), + new Reference(Type1Type2TypeExtension::class), + )), + 'type2' => new IteratorArgument(array(new Reference(Type1Type2TypeExtension::class))), + ), + ), + array( + array( + 'my.type_extension1' => array( + 'class' => Type1TypeExtension::class, + 'tag' => array('extended_type' => 'type1', 'priority' => 1), + ), + 'my.type_extension2' => array( + 'class' => Type1TypeExtension::class, + 'tag' => array('extended_type' => 'type1', 'priority' => 2), + ), + 'my.type_extension3' => array( + 'class' => Type1TypeExtension::class, + 'tag' => array('extended_type' => 'type1', 'priority' => -1), + ), + 'my.type_extension4' => array( + 'class' => Type2TypeExtension::class, + 'tag' => array('extended_type' => 'type2', 'priority' => 2), + ), + 'my.type_extension5' => array( + 'class' => Type2TypeExtension::class, + 'tag' => array('extended_type' => 'type2', 'priority' => 1), + ), + 'my.type_extension6' => array( + 'class' => Type2TypeExtension::class, + 'tag' => array('extended_type' => 'type2', 'priority' => 1), + ), + ), + array( + 'type1' => new IteratorArgument(array( + new Reference('my.type_extension2'), + new Reference('my.type_extension1'), + new Reference('my.type_extension3'), + )), + 'type2' => new IteratorArgument(array( + new Reference('my.type_extension4'), + new Reference('my.type_extension5'), + new Reference('my.type_extension6'), + )), + ), + ), + ); + } + + /** + * @group legacy + * @dataProvider addLegacyTaggedTypeExtensionsDataProvider + */ + public function testAddLegacyTaggedTypeExtensions(array $extensions, array $expectedRegisteredExtensions) + { + $container = $this->createContainerBuilder(); + + $container->setDefinition('form.extension', $this->createExtensionDefinition()); + foreach ($extensions as $serviceId => $tag) { $container->register($serviceId, 'stdClass')->addTag('form.type_extension', $tag); } @@ -110,7 +212,7 @@ public function testAddTaggedTypeExtensions(array $extensions, array $expectedRe /** * @return array */ - public function addTaggedTypeExtensionsDataProvider() + public function addLegacyTaggedTypeExtensionsDataProvider() { return array( array( @@ -154,9 +256,9 @@ public function addTaggedTypeExtensionsDataProvider() /** * @expectedException \InvalidArgumentException - * @expectedExceptionMessage extended-type attribute, none was configured for the "my.type_extension" service + * @expectedExceptionMessage "form.type_extension" tagged services have to implement the static getExtendedTypes() method. The class for service "my.type_extension" does not implement it. */ - public function testAddTaggedFormTypeExtensionWithoutExtendedTypeAttribute() + public function testAddTaggedFormTypeExtensionWithoutExtendedTypeAttributeNorImplementingGetExtendedTypes() { $container = $this->createContainerBuilder(); @@ -168,6 +270,22 @@ public function testAddTaggedFormTypeExtensionWithoutExtendedTypeAttribute() $container->compile(); } + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The getExtendedTypes() method for service "my.type_extension" does not return any extended types. + */ + public function testAddTaggedFormTypeExtensionWithoutExtendingAnyType() + { + $container = $this->createContainerBuilder(); + + $container->setDefinition('form.extension', $this->createExtensionDefinition()); + $container->register('my.type_extension', WithoutExtendedTypesTypeExtension::class) + ->setPublic(true) + ->addTag('form.type_extension'); + + $container->compile(); + } + public function testAddTaggedGuessers() { $container = $this->createContainerBuilder(); @@ -197,13 +315,13 @@ public function testAddTaggedGuessers() /** * @dataProvider privateTaggedServicesProvider */ - public function testPrivateTaggedServices($id, $tagName, callable $assertion, array $tagAttributes = array()) + public function testPrivateTaggedServices($id, $class, $tagName, callable $assertion, array $tagAttributes = array()) { $formPass = new FormPass(); $container = new ContainerBuilder(); $container->setDefinition('form.extension', $this->createExtensionDefinition()); - $container->register($id, 'stdClass')->setPublic(false)->addTag($tagName, $tagAttributes); + $container->register($id, $class)->setPublic(false)->addTag($tagName, $tagAttributes); $formPass->process($container); $assertion($container); @@ -214,6 +332,7 @@ public function privateTaggedServicesProvider() return array( array( 'my.type', + 'stdClass', 'form.type', function (ContainerBuilder $container) { $formTypes = $container->getDefinition('form.extension')->getArgument(0); @@ -231,6 +350,7 @@ function (ContainerBuilder $container) { ), array( 'my.type_extension', + Type1TypeExtension::class, 'form.type_extension', function (ContainerBuilder $container) { $this->assertEquals( @@ -240,7 +360,7 @@ function (ContainerBuilder $container) { }, array('extended_type' => 'Symfony\Component\Form\Extension\Core\Type\FormType'), ), - array('my.guesser', 'form.type_guesser', function (ContainerBuilder $container) { + array('my.guesser', 'stdClass', 'form.type_guesser', function (ContainerBuilder $container) { $this->assertEquals(new IteratorArgument(array(new Reference('my.guesser'))), $container->getDefinition('form.extension')->getArgument(2)); }), ); @@ -288,3 +408,36 @@ class FormPassTest_Type1 extends AbstractType class FormPassTest_Type2 extends AbstractType { } + +class Type1TypeExtension extends AbstractTypeExtension +{ + public static function getExtendedTypes(): iterable + { + return array('type1'); + } +} + +class Type2TypeExtension extends AbstractTypeExtension +{ + public static function getExtendedTypes(): iterable + { + return array('type2'); + } +} + +class Type1Type2TypeExtension extends AbstractTypeExtension +{ + public static function getExtendedTypes(): iterable + { + yield 'type1'; + yield 'type2'; + } +} + +class WithoutExtendedTypesTypeExtension extends AbstractTypeExtension +{ + public static function getExtendedTypes(): iterable + { + return array(); + } +} diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ExtendedChoiceTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ExtendedChoiceTypeTest.php index 0475254d970a..7d33e7e3903d 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ExtendedChoiceTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ExtendedChoiceTypeTest.php @@ -23,8 +23,9 @@ class ExtendedChoiceTypeTest extends TestCase */ public function testChoicesAreOverridden($type) { + ChoiceTypeExtension::$extendedType = $type; $factory = Forms::createFormFactoryBuilder() - ->addTypeExtension(new ChoiceTypeExtension($type)) + ->addTypeExtension(new ChoiceTypeExtension()) ->getFormFactory() ; @@ -42,8 +43,9 @@ public function testChoicesAreOverridden($type) */ public function testChoiceLoaderIsOverridden($type) { + LazyChoiceTypeExtension::$extendedType = $type; $factory = Forms::createFormFactoryBuilder() - ->addTypeExtension(new LazyChoiceTypeExtension($type)) + ->addTypeExtension(new LazyChoiceTypeExtension()) ->getFormFactory() ; diff --git a/src/Symfony/Component/Form/Tests/Extension/DataCollector/Type/DataCollectorTypeExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/DataCollector/Type/DataCollectorTypeExtensionTest.php index de635f64a3f4..88561b3bb59e 100644 --- a/src/Symfony/Component/Form/Tests/Extension/DataCollector/Type/DataCollectorTypeExtensionTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/DataCollector/Type/DataCollectorTypeExtensionTest.php @@ -32,6 +32,9 @@ protected function setUp() $this->extension = new DataCollectorTypeExtension($this->dataCollector); } + /** + * @group legacy + */ public function testGetExtendedType() { $this->assertEquals('Symfony\Component\Form\Extension\Core\Type\FormType', $this->extension->getExtendedType()); diff --git a/src/Symfony/Component/Form/Tests/Extension/DependencyInjection/DependencyInjectionExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/DependencyInjection/DependencyInjectionExtensionTest.php index a94d57ec475a..9d8020b07836 100644 --- a/src/Symfony/Component/Form/Tests/Extension/DependencyInjection/DependencyInjectionExtensionTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/DependencyInjection/DependencyInjectionExtensionTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Form\Tests\Extension\DependencyInjection; use PHPUnit\Framework\TestCase; +use Symfony\Component\Form\AbstractTypeExtension; use Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension; use Symfony\Component\Form\FormTypeGuesserChain; use Symfony\Component\Form\FormTypeGuesserInterface; @@ -23,13 +24,14 @@ public function testGetTypeExtensions() $container = $this->createContainerMock(); $container->expects($this->never())->method('get'); - $typeExtension1 = $this->createFormTypeExtensionMock('test'); - $typeExtension2 = $this->createFormTypeExtensionMock('test'); - $typeExtension3 = $this->createFormTypeExtensionMock('other'); + $typeExtension1 = new TestTypeExtension(); + $typeExtension2 = new TestTypeExtension(); + $typeExtension3 = new OtherTypeExtension(); + $typeExtension4 = new MultipleTypesTypeExtension(); $extensions = array( - 'test' => new \ArrayIterator(array($typeExtension1, $typeExtension2)), - 'other' => new \ArrayIterator(array($typeExtension3)), + 'test' => new \ArrayIterator(array($typeExtension1, $typeExtension2, $typeExtension4)), + 'other' => new \ArrayIterator(array($typeExtension3, $typeExtension4)), ); $extension = new DependencyInjectionExtension($container, $extensions, array()); @@ -37,8 +39,8 @@ public function testGetTypeExtensions() $this->assertTrue($extension->hasTypeExtensions('test')); $this->assertTrue($extension->hasTypeExtensions('other')); $this->assertFalse($extension->hasTypeExtensions('unknown')); - $this->assertSame(array($typeExtension1, $typeExtension2), $extension->getTypeExtensions('test')); - $this->assertSame(array($typeExtension3), $extension->getTypeExtensions('other')); + $this->assertSame(array($typeExtension1, $typeExtension2, $typeExtension4), $extension->getTypeExtensions('test')); + $this->assertSame(array($typeExtension3, $typeExtension4), $extension->getTypeExtensions('other')); } /** @@ -50,12 +52,12 @@ public function testThrowExceptionForInvalidExtendedType() $container->expects($this->never())->method('get'); $extensions = array( - 'test' => new \ArrayIterator(array($this->createFormTypeExtensionMock('unmatched'))), + 'unmatched' => new \ArrayIterator(array(new TestTypeExtension())), ); $extension = new DependencyInjectionExtension($container, $extensions, array()); - $extension->getTypeExtensions('test'); + $extension->getTypeExtensions('unmatched'); } public function testGetTypeGuesser() @@ -80,12 +82,29 @@ private function createContainerMock() ->setMethods(array('get', 'has')) ->getMock(); } +} - private function createFormTypeExtensionMock($extendedType) +class TestTypeExtension extends AbstractTypeExtension +{ + public static function getExtendedTypes(): iterable { - $extension = $this->getMockBuilder('Symfony\Component\Form\FormTypeExtensionInterface')->getMock(); - $extension->expects($this->any())->method('getExtendedType')->willReturn($extendedType); + return array('test'); + } +} - return $extension; +class OtherTypeExtension extends AbstractTypeExtension +{ + public static function getExtendedTypes(): iterable + { + return array('other'); + } +} + +class MultipleTypesTypeExtension extends AbstractTypeExtension +{ + public static function getExtendedTypes(): iterable + { + yield 'test'; + yield 'other'; } } diff --git a/src/Symfony/Component/Form/Tests/Fixtures/ChoiceTypeExtension.php b/src/Symfony/Component/Form/Tests/Fixtures/ChoiceTypeExtension.php index db4166ff3092..55e394a2469e 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/ChoiceTypeExtension.php +++ b/src/Symfony/Component/Form/Tests/Fixtures/ChoiceTypeExtension.php @@ -12,17 +12,11 @@ namespace Symfony\Component\Form\Tests\Fixtures; use Symfony\Component\Form\AbstractTypeExtension; -use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\OptionsResolver\OptionsResolver; class ChoiceTypeExtension extends AbstractTypeExtension { - private $extendedType; - - public function __construct($extendedType = ChoiceType::class) - { - $this->extendedType = $extendedType; - } + public static $extendedType; /** * {@inheritdoc} @@ -38,8 +32,8 @@ public function configureOptions(OptionsResolver $resolver) /** * {@inheritdoc} */ - public function getExtendedType() + public static function getExtendedTypes(): iterable { - return $this->extendedType; + return array(self::$extendedType); } } diff --git a/src/Symfony/Component/Form/Tests/Fixtures/FooTypeBarExtension.php b/src/Symfony/Component/Form/Tests/Fixtures/FooTypeBarExtension.php index 8d72132f2f1f..5ed877fdf99c 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/FooTypeBarExtension.php +++ b/src/Symfony/Component/Form/Tests/Fixtures/FooTypeBarExtension.php @@ -28,8 +28,8 @@ public function getAllowedOptionValues() ); } - public function getExtendedType() + public static function getExtendedTypes(): iterable { - return __NAMESPACE__.'\FooType'; + return array(__NAMESPACE__.'\FooType'); } } diff --git a/src/Symfony/Component/Form/Tests/Fixtures/FooTypeBazExtension.php b/src/Symfony/Component/Form/Tests/Fixtures/FooTypeBazExtension.php index 646d41f7ffe0..1f722776e02f 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/FooTypeBazExtension.php +++ b/src/Symfony/Component/Form/Tests/Fixtures/FooTypeBazExtension.php @@ -21,8 +21,8 @@ public function buildForm(FormBuilderInterface $builder, array $options) $builder->setAttribute('baz', 'x'); } - public function getExtendedType() + public static function getExtendedTypes(): iterable { - return __NAMESPACE__.'\FooType'; + return array(__NAMESPACE__.'\FooType'); } } diff --git a/src/Symfony/Component/Form/Tests/Fixtures/LazyChoiceTypeExtension.php b/src/Symfony/Component/Form/Tests/Fixtures/LazyChoiceTypeExtension.php index 133b8829e0df..66af47aa17ce 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/LazyChoiceTypeExtension.php +++ b/src/Symfony/Component/Form/Tests/Fixtures/LazyChoiceTypeExtension.php @@ -13,17 +13,11 @@ use Symfony\Component\Form\AbstractTypeExtension; use Symfony\Component\Form\ChoiceList\Loader\CallbackChoiceLoader; -use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\OptionsResolver\OptionsResolver; class LazyChoiceTypeExtension extends AbstractTypeExtension { - private $extendedType; - - public function __construct($extendedType = ChoiceType::class) - { - $this->extendedType = $extendedType; - } + public static $extendedType; /** * {@inheritdoc} @@ -41,8 +35,8 @@ public function configureOptions(OptionsResolver $resolver) /** * {@inheritdoc} */ - public function getExtendedType() + public static function getExtendedTypes(): iterable { - return $this->extendedType; + return array(self::$extendedType); } } diff --git a/src/Symfony/Component/Form/Tests/Fixtures/TestExtension.php b/src/Symfony/Component/Form/Tests/Fixtures/TestExtension.php index bdbc5a9c1cfb..0173bae6f9dc 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/TestExtension.php +++ b/src/Symfony/Component/Form/Tests/Fixtures/TestExtension.php @@ -46,13 +46,13 @@ public function hasType($name) public function addTypeExtension(FormTypeExtensionInterface $extension) { - $type = $extension->getExtendedType(); + foreach ($extension::getExtendedTypes() as $type) { + if (!isset($this->extensions[$type])) { + $this->extensions[$type] = array(); + } - if (!isset($this->extensions[$type])) { - $this->extensions[$type] = array(); + $this->extensions[$type][] = $extension; } - - $this->extensions[$type][] = $extension; } public function getTypeExtensions($name)