diff --git a/UPGRADE-3.3.md b/UPGRADE-3.3.md index afd31e75ec..212cb27dfd 100644 --- a/UPGRADE-3.3.md +++ b/UPGRADE-3.3.md @@ -35,6 +35,10 @@ FrameworkBundle * The `Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConsoleCommandPass` has been deprecated. Use `Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass` instead. + * The `Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\FormPass` class has been + deprecated and will be removed in 4.0. Use the `Symfony\Component\Form\DependencyInjection\FormPass` + class instead. + HttpKernel ----------- diff --git a/UPGRADE-4.0.md b/UPGRADE-4.0.md index d88fc607d7..752e8d40a1 100644 --- a/UPGRADE-4.0.md +++ b/UPGRADE-4.0.md @@ -156,6 +156,9 @@ FrameworkBundle * The `Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConsoleCommandPass` has been removed. Use `Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass` instead. + * The `Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\FormPass` class has been + removed. Use the `Symfony\Component\Form\DependencyInjection\FormPass` class instead. + SecurityBundle -------------- diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 735f116abe..ba889779e7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -12,7 +12,7 @@ CHANGELOG is disabled. * Added `GlobalVariables::getToken()` * Deprecated `Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConsoleCommandPass`. Use `Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass` instead. - * Added configurable paths for validation files + * Deprecated `FormPass`, use `Symfony\Component\Form\DependencyInjection\FormPass` instead. 3.2.0 ----- diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/FormPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/FormPass.php index c248a079df..252b00f069 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/FormPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/FormPass.php @@ -11,74 +11,18 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; -use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +@trigger_error(sprintf('The %s class is deprecated since version 3.3 and will be removed in 4.0. Use Symfony\Component\Form\DependencyInjection\FormPass instead.', FormPass::class), E_USER_DEPRECATED); + +use Symfony\Component\Form\DependencyInjection\FormPass as BaseFormPass; /** * Adds all services with the tags "form.type" and "form.type_guesser" as * arguments of the "form.extension" service. * + * @deprecated since version 3.3, to be removed in 4.0. Use {@link BaseFormPass} instead. + * * @author Bernhard Schussek */ -class FormPass implements CompilerPassInterface +class FormPass extends BaseFormPass { - use PriorityTaggedServiceTrait; - - public function process(ContainerBuilder $container) - { - if (!$container->hasDefinition('form.extension')) { - return; - } - - $definition = $container->getDefinition('form.extension'); - - // Builds an array with fully-qualified type class names as keys and service IDs as values - $types = array(); - - foreach ($container->findTaggedServiceIds('form.type') as $serviceId => $tag) { - $serviceDefinition = $container->getDefinition($serviceId); - if (!$serviceDefinition->isPublic()) { - throw new InvalidArgumentException(sprintf('The service "%s" must be public as form types are lazy-loaded.', $serviceId)); - } - - // Support type access by FQCN - $types[$serviceDefinition->getClass()] = $serviceId; - } - - $definition->replaceArgument(1, $types); - - $typeExtensions = array(); - - foreach ($this->findAndSortTaggedServices('form.type_extension', $container) as $reference) { - $serviceId = (string) $reference; - $serviceDefinition = $container->getDefinition($serviceId); - if (!$serviceDefinition->isPublic()) { - throw new InvalidArgumentException(sprintf('The service "%s" must be public as form type extensions are lazy-loaded.', $serviceId)); - } - - $tag = $serviceDefinition->getTag('form.type_extension'); - if (isset($tag[0]['extended_type'])) { - $extendedType = $tag[0]['extended_type']; - } else { - throw new InvalidArgumentException(sprintf('Tagged form type extension must have the extended type configured using the extended_type/extended-type attribute, none was configured for the "%s" service.', $serviceId)); - } - - $typeExtensions[$extendedType][] = $serviceId; - } - - $definition->replaceArgument(2, $typeExtensions); - - // Find all services annotated with "form.type_guesser" - $guessers = array_keys($container->findTaggedServiceIds('form.type_guesser')); - foreach ($guessers as $serviceId) { - $serviceDefinition = $container->getDefinition($serviceId); - if (!$serviceDefinition->isPublic()) { - throw new InvalidArgumentException(sprintf('The service "%s" must be public as form type guessers are lazy-loaded.', $serviceId)); - } - } - - $definition->replaceArgument(3, $guessers); - } } diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index 535fa0a5b7..1a81d17583 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -18,7 +18,6 @@ use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CacheCollectorPa use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CachePoolPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CachePoolClearerPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ControllerArgumentValueResolverPass; -use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\FormPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\PropertyInfoPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TemplatingPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\RoutingResolverPass; @@ -42,8 +41,10 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass; use Symfony\Component\HttpKernel\DependencyInjection\FragmentRendererPass; +use Symfony\Component\Form\DependencyInjection\FormPass; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Bundle\Bundle; +use Symfony\Component\Config\Resource\ClassExistenceResource; /** * Bundle. @@ -81,10 +82,6 @@ class FrameworkBundle extends Bundle $container->addCompilerPass(new TemplatingPass()); $container->addCompilerPass(new AddConstraintValidatorsPass(), PassConfig::TYPE_BEFORE_REMOVING); $container->addCompilerPass(new AddValidatorInitializersPass()); - if (class_exists(AddConsoleCommandPass::class)) { - $container->addCompilerPass(new AddConsoleCommandPass()); - } - $container->addCompilerPass(new FormPass()); $container->addCompilerPass(new TranslatorPass()); $container->addCompilerPass(new LoggingTranslatorPass()); $container->addCompilerPass(new AddCacheWarmerPass()); @@ -99,6 +96,8 @@ class FrameworkBundle extends Bundle $container->addCompilerPass(new CachePoolPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 32); $container->addCompilerPass(new ValidateWorkflowsPass()); $container->addCompilerPass(new CachePoolClearerPass(), PassConfig::TYPE_AFTER_REMOVING); + $this->addCompilerPassIfExists($container, AddConsoleCommandPass::class); + $this->addCompilerPassIfExists($container, FormPass::class); if ($container->getParameter('kernel.debug')) { $container->addCompilerPass(new AddDebugLogProcessorPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32); @@ -109,4 +108,13 @@ class FrameworkBundle extends Bundle $container->addCompilerPass(new CacheCollectorPass()); } } + + private function addCompilerPassIfExists(ContainerBuilder $container, $class, $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, $priority = 0) + { + $container->addResource(new ClassExistenceResource($class)); + + if (class_exists($class)) { + $container->addCompilerPass(new $class(), $type, $priority); + } + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/FormPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/FormPassTest.php index ec80b95510..b44a3eb5b9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/FormPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/FormPassTest.php @@ -18,6 +18,8 @@ use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\Form\AbstractType; /** + * @group legacy + * * @author Bernhard Schussek */ class FormPassTest extends \PHPUnit_Framework_TestCase diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index add66921d3..3a515412b2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -39,7 +39,7 @@ "symfony/dom-crawler": "~2.8|~3.0", "symfony/polyfill-intl-icu": "~1.0", "symfony/security": "~2.8|~3.0", - "symfony/form": "~2.8.16|~3.1.9|^3.2.2", + "symfony/form": "~3.3", "symfony/expression-language": "~2.8|~3.0", "symfony/process": "~2.8|~3.0", "symfony/security-core": "~3.2", @@ -57,7 +57,8 @@ "conflict": { "phpdocumentor/reflection-docblock": "<3.0", "phpdocumentor/type-resolver": "<0.2.0", - "symfony/console": "<3.3" + "symfony/console": "<3.3", + "symfony/form": "<3.3" }, "suggest": { "ext-apcu": "For best performance of the system caches", diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md index 5a618f2a35..effbfcf596 100644 --- a/src/Symfony/Component/Form/CHANGELOG.md +++ b/src/Symfony/Component/Form/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +3.3.0 +----- + + * added `FormPass` + 3.2.0 ----- diff --git a/src/Symfony/Component/Form/DependencyInjection/FormPass.php b/src/Symfony/Component/Form/DependencyInjection/FormPass.php new file mode 100644 index 0000000000..19d7d22e44 --- /dev/null +++ b/src/Symfony/Component/Form/DependencyInjection/FormPass.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; + +/** + * Adds all services with the tags "form.type" and "form.type_guesser" as + * arguments of the "form.extension" service. + * + * @author Bernhard Schussek + */ +class FormPass implements CompilerPassInterface +{ + use PriorityTaggedServiceTrait; + + private $formExtensionService; + private $formTypeTag; + private $formTypeExtensionTag; + private $formTypeGuesserTag; + + public function __construct($formExtensionService = 'form.extension', $formTypeTag = 'form.type', $formTypeExtensionTag = 'form.type_extension', $formTypeGuesserTag = 'form.type_guesser') + { + $this->formExtensionService = $formExtensionService; + $this->formTypeTag = $formTypeTag; + $this->formTypeExtensionTag = $formTypeExtensionTag; + $this->formTypeGuesserTag = $formTypeGuesserTag; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->formExtensionService)) { + return; + } + + $definition = $container->getDefinition($this->formExtensionService); + + // Builds an array with fully-qualified type class names as keys and service IDs as values + $types = array(); + foreach ($container->findTaggedServiceIds($this->formTypeTag) as $serviceId => $tag) { + $serviceDefinition = $container->getDefinition($serviceId); + if (!$serviceDefinition->isPublic()) { + throw new InvalidArgumentException(sprintf('The service "%s" must be public as form types are lazy-loaded.', $serviceId)); + } + + // Support type access by FQCN + $types[$serviceDefinition->getClass()] = $serviceId; + } + + $definition->replaceArgument(1, $types); + + $typeExtensions = array(); + + foreach ($this->findAndSortTaggedServices($this->formTypeExtensionTag, $container) as $reference) { + $serviceId = (string) $reference; + $serviceDefinition = $container->getDefinition($serviceId); + if (!$serviceDefinition->isPublic()) { + throw new InvalidArgumentException(sprintf('The service "%s" must be public as form type extensions are lazy-loaded.', $serviceId)); + } + + $tag = $serviceDefinition->getTag($this->formTypeExtensionTag); + if (isset($tag[0]['extended_type'])) { + $extendedType = $tag[0]['extended_type']; + } 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)); + } + + $typeExtensions[$extendedType][] = $serviceId; + } + + $definition->replaceArgument(2, $typeExtensions); + + $guessers = array_keys($container->findTaggedServiceIds($this->formTypeGuesserTag)); + foreach ($guessers as $serviceId) { + $serviceDefinition = $container->getDefinition($serviceId); + if (!$serviceDefinition->isPublic()) { + throw new InvalidArgumentException(sprintf('The service "%s" must be public as form type guessers are lazy-loaded.', $serviceId)); + } + } + + $definition->replaceArgument(3, $guessers); + } +} diff --git a/src/Symfony/Component/Form/Tests/DependencyInjection/FormPassTest.php b/src/Symfony/Component/Form/Tests/DependencyInjection/FormPassTest.php new file mode 100644 index 0000000000..8f0b06da51 --- /dev/null +++ b/src/Symfony/Component/Form/Tests/DependencyInjection/FormPassTest.php @@ -0,0 +1,218 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\DependencyInjection; + +use Symfony\Component\Form\DependencyInjection\FormPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Form\AbstractType; + +/** + * @author Bernhard Schussek + */ +class FormPassTest extends \PHPUnit_Framework_TestCase +{ + public function testDoNothingIfFormExtensionNotLoaded() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new FormPass()); + + $container->compile(); + + $this->assertFalse($container->hasDefinition('form.extension')); + } + + public function testAddTaggedTypes() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new FormPass()); + + $extDefinition = new Definition('Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension'); + $extDefinition->setArguments(array( + new Reference('service_container'), + array(), + array(), + array(), + )); + + $container->setDefinition('form.extension', $extDefinition); + $container->register('my.type1', __CLASS__.'_Type1')->addTag('form.type'); + $container->register('my.type2', __CLASS__.'_Type2')->addTag('form.type'); + + $container->compile(); + + $extDefinition = $container->getDefinition('form.extension'); + + $this->assertEquals(array( + __CLASS__.'_Type1' => 'my.type1', + __CLASS__.'_Type2' => 'my.type2', + ), $extDefinition->getArgument(1)); + } + + /** + * @dataProvider addTaggedTypeExtensionsDataProvider + */ + public function testAddTaggedTypeExtensions(array $extensions, array $expectedRegisteredExtensions) + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new FormPass()); + + $extDefinition = new Definition('Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension', array( + new Reference('service_container'), + array(), + array(), + array(), + )); + + $container->setDefinition('form.extension', $extDefinition); + + foreach ($extensions as $serviceId => $tag) { + $container->register($serviceId, 'stdClass')->addTag('form.type_extension', $tag); + } + + $container->compile(); + + $extDefinition = $container->getDefinition('form.extension'); + $this->assertSame($expectedRegisteredExtensions, $extDefinition->getArgument(2)); + } + + /** + * @return array + */ + public function addTaggedTypeExtensionsDataProvider() + { + return array( + array( + array( + 'my.type_extension1' => array('extended_type' => 'type1'), + 'my.type_extension2' => array('extended_type' => 'type1'), + 'my.type_extension3' => array('extended_type' => 'type2'), + ), + array( + 'type1' => array('my.type_extension1', 'my.type_extension2'), + 'type2' => array('my.type_extension3'), + ), + ), + array( + array( + 'my.type_extension1' => array('extended_type' => 'type1', 'priority' => 1), + 'my.type_extension2' => array('extended_type' => 'type1', 'priority' => 2), + 'my.type_extension3' => array('extended_type' => 'type1', 'priority' => -1), + 'my.type_extension4' => array('extended_type' => 'type2', 'priority' => 2), + 'my.type_extension5' => array('extended_type' => 'type2', 'priority' => 1), + 'my.type_extension6' => array('extended_type' => 'type2', 'priority' => 1), + ), + array( + 'type1' => array('my.type_extension2', 'my.type_extension1', 'my.type_extension3'), + 'type2' => array('my.type_extension4', 'my.type_extension5', 'my.type_extension6'), + ), + ), + ); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage extended-type attribute, none was configured for the "my.type_extension" service + */ + public function testAddTaggedFormTypeExtensionWithoutExtendedTypeAttribute() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new FormPass()); + + $extDefinition = new Definition('Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension', array( + new Reference('service_container'), + array(), + array(), + array(), + )); + + $container->setDefinition('form.extension', $extDefinition); + $container->register('my.type_extension', 'stdClass') + ->addTag('form.type_extension'); + + $container->compile(); + } + + public function testAddTaggedGuessers() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new FormPass()); + + $extDefinition = new Definition('Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension'); + $extDefinition->setArguments(array( + new Reference('service_container'), + array(), + array(), + array(), + )); + + $definition1 = new Definition('stdClass'); + $definition1->addTag('form.type_guesser'); + $definition2 = new Definition('stdClass'); + $definition2->addTag('form.type_guesser'); + + $container->setDefinition('form.extension', $extDefinition); + $container->setDefinition('my.guesser1', $definition1); + $container->setDefinition('my.guesser2', $definition2); + + $container->compile(); + + $extDefinition = $container->getDefinition('form.extension'); + + $this->assertSame(array( + 'my.guesser1', + 'my.guesser2', + ), $extDefinition->getArgument(3)); + } + + /** + * @dataProvider privateTaggedServicesProvider + */ + public function testPrivateTaggedServices($id, $tagName, $expectedExceptionMessage) + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new FormPass()); + + $extDefinition = new Definition('Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension'); + $extDefinition->setArguments(array( + new Reference('service_container'), + array(), + array(), + array(), + )); + + $container->setDefinition('form.extension', $extDefinition); + $container->register($id, 'stdClass')->setPublic(false)->addTag($tagName); + + $this->setExpectedException('\InvalidArgumentException', $expectedExceptionMessage); + + $container->compile(); + } + + public function privateTaggedServicesProvider() + { + return array( + array('my.type', 'form.type', 'The service "my.type" must be public as form types are lazy-loaded'), + array('my.type_extension', 'form.type_extension', 'The service "my.type_extension" must be public as form type extensions are lazy-loaded'), + array('my.guesser', 'form.type_guesser', 'The service "my.guesser" must be public as form type guessers are lazy-loaded'), + ); + } +} + +class FormPassTest_Type1 extends AbstractType +{ +} + +class FormPassTest_Type2 extends AbstractType +{ +} diff --git a/src/Symfony/Component/Form/composer.json b/src/Symfony/Component/Form/composer.json index 8a01fc8eeb..1f645266d1 100644 --- a/src/Symfony/Component/Form/composer.json +++ b/src/Symfony/Component/Form/composer.json @@ -26,7 +26,8 @@ "require-dev": { "doctrine/collections": "~1.0", "symfony/validator": "~2.8|~3.0", - "symfony/dependency-injection": "~2.8|~3.0", + "symfony/dependency-injection": "~3.2", + "symfony/config": "~2.7|~3.0", "symfony/http-foundation": "~2.8|~3.0", "symfony/http-kernel": "~2.8|~3.0", "symfony/security-csrf": "~2.8|~3.0",