From 8ff764be824591c7e2cce1d4633f0553aea69287 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 27 Mar 2017 13:10:12 +0200 Subject: [PATCH] [DI] add ServiceLocatorTagPass::register() to share service locators --- .../Compiler/TranslatorPass.php | 5 +- .../AddConstraintValidatorsPassTest.php | 5 +- .../Compiler/FormPassTest.php | 2 +- .../Compiler/TranslatorPassTest.php | 54 +++++++++---------- .../DependencyInjection/SecurityExtension.php | 7 ++- .../Compiler/RuntimeLoaderPass.php | 8 ++- .../DependencyInjection/TwigExtensionTest.php | 2 +- .../Argument/ServiceClosureArgument.php | 8 ++- .../RegisterServiceSubscribersPass.php | 11 +--- .../Compiler/ServiceLocatorTagPass.php | 51 +++++++++++++++++- .../Fixtures/php/services_subscriber.php | 4 +- .../Form/DependencyInjection/FormPass.php | 8 ++- .../DependencyInjection/FormPassTest.php | 2 +- .../FragmentRendererPass.php | 8 ++- ...RegisterControllerArgumentLocatorsPass.php | 21 ++------ ...oveEmptyControllerArgumentLocatorsPass.php | 28 +++++----- .../FragmentRendererPassTest.php | 5 +- ...sterControllerArgumentLocatorsPassTest.php | 42 +++++---------- ...mptyControllerArgumentLocatorsPassTest.php | 48 +++++++++++------ .../AddConstraintValidatorsPass.php | 10 ++-- .../AddConstraintValidatorsPassTest.php | 5 +- 21 files changed, 176 insertions(+), 158 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TranslatorPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TranslatorPass.php index 2bdd9d8055..3e2d0e24c4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TranslatorPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TranslatorPass.php @@ -11,11 +11,10 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; -use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; class TranslatorPass implements CompilerPassInterface { @@ -46,7 +45,7 @@ class TranslatorPass implements CompilerPassInterface $container ->findDefinition('translator.default') - ->replaceArgument(0, (new Definition(ServiceLocator::class, array($loaderRefs)))->addTag('container.service_locator')) + ->replaceArgument(0, ServiceLocatorTagPass::register($container, $loaderRefs)) ->replaceArgument(3, $loaders) ; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/AddConstraintValidatorsPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/AddConstraintValidatorsPassTest.php index ce12121bb0..ac0a4e0cab 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/AddConstraintValidatorsPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/AddConstraintValidatorsPassTest.php @@ -41,11 +41,12 @@ class AddConstraintValidatorsPassTest extends TestCase $addConstraintValidatorsPass = new AddConstraintValidatorsPass(); $addConstraintValidatorsPass->process($container); - $this->assertEquals((new Definition(ServiceLocator::class, array(array( + $expected = (new Definition(ServiceLocator::class, array(array( Validator1::class => new ServiceClosureArgument(new Reference('my_constraint_validator_service1')), 'my_constraint_validator_alias1' => new ServiceClosureArgument(new Reference('my_constraint_validator_service1')), Validator2::class => new ServiceClosureArgument(new Reference('my_constraint_validator_service2')), - ))))->addTag('container.service_locator'), $validatorFactory->getArgument(0)); + ))))->addTag('container.service_locator')->setPublic(false); + $this->assertEquals($expected, $container->getDefinition((string) $validatorFactory->getArgument(0))); } public function testThatCompilerPassIsIgnoredIfThereIsNoConstraintValidatorFactoryDefinition() diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/FormPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/FormPassTest.php index 0db1d1153a..c0be018e23 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/FormPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/FormPassTest.php @@ -53,7 +53,7 @@ class FormPassTest extends TestCase (new Definition(ServiceLocator::class, array(array( __CLASS__.'_Type1' => new ServiceClosureArgument(new Reference('my.type1')), __CLASS__.'_Type2' => new ServiceClosureArgument(new Reference('my.type2')), - ))))->addTag('container.service_locator'), + ))))->addTag('container.service_locator')->setPublic(false), $extDefinition->getArgument(0) ); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/TranslatorPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/TranslatorPassTest.php index cfb5beeb55..a92f734ecc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/TranslatorPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/TranslatorPassTest.php @@ -12,45 +12,39 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler; use PHPUnit\Framework\TestCase; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslatorPass; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslatorPass; -use Symfony\Component\DependencyInjection\ServiceLocator; class TranslatorPassTest extends TestCase { public function testValidCollector() { - $definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock(); - $definition->expects($this->at(0)) - ->method('addMethodCall') - ->with('addLoader', array('xliff', new Reference('xliff'))); - $definition->expects($this->at(1)) - ->method('addMethodCall') - ->with('addLoader', array('xlf', new Reference('xliff'))); + $loader = (new Definition()) + ->addTag('translation.loader', array('alias' => 'xliff', 'legacy-alias' => 'xlf')); + + $translator = (new Definition()) + ->setArguments(array(null, null, null, null)); + + $container = new ContainerBuilder(); + $container->setDefinition('translator.default', $translator); + $container->setDefinition('translation.loader', $loader); - $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'getDefinition', 'findTaggedServiceIds', 'findDefinition'))->getMock(); - $container->expects($this->any()) - ->method('hasDefinition') - ->will($this->returnValue(true)); - $container->expects($this->once()) - ->method('getDefinition') - ->will($this->returnValue($definition)); - $container->expects($this->once()) - ->method('findTaggedServiceIds') - ->will($this->returnValue(array('xliff' => array(array('alias' => 'xliff', 'legacy-alias' => 'xlf'))))); - $container->expects($this->once()) - ->method('findDefinition') - ->will($this->returnValue($translator = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock())); - $translator->expects($this->at(0)) - ->method('replaceArgument') - ->with(0, $this->equalTo((new Definition(ServiceLocator::class, array(array('xliff' => new Reference('xliff')))))->addTag('container.service_locator'))) - ->willReturn($translator); - $translator->expects($this->at(1)) - ->method('replaceArgument') - ->with(3, array('xliff' => array('xliff', 'xlf'))) - ->willReturn($translator); $pass = new TranslatorPass(); $pass->process($container); + + $expected = (new Definition()) + ->addTag('translation.loader', array('alias' => 'xliff', 'legacy-alias' => 'xlf')) + ->addMethodCall('addLoader', array('xliff', new Reference('translation.loader'))) + ->addMethodCall('addLoader', array('xlf', new Reference('translation.loader'))) + ; + $this->assertEquals($expected, $loader); + + $this->assertSame(array('translation.loader' => array('xliff', 'xlf')), $translator->getArgument(3)); + + $expected = array('translation.loader' => new ServiceClosureArgument(new Reference('translation.loader'))); + $this->assertEquals($expected, $container->getDefinition((string) $translator->getArgument(0))->getArgument(0)); } } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index 3fc032531d..520ccd6daf 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -17,14 +17,13 @@ use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\Console\Application; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; -use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\Config\FileLocator; use Symfony\Component\Security\Core\Authorization\ExpressionLanguage; @@ -262,10 +261,10 @@ class SecurityExtension extends Extension ->replaceArgument(2, new Reference($configId)) ; - $contextRefs[$contextId] = new ServiceClosureArgument(new Reference($contextId)); + $contextRefs[$contextId] = new Reference($contextId); $map[$contextId] = $matcher; } - $mapDef->replaceArgument(0, (new Definition(ServiceLocator::class, array($contextRefs)))->addTag('container.service_locator')); + $mapDef->replaceArgument(0, ServiceLocatorTagPass::register($container, $contextRefs)); $mapDef->replaceArgument(1, new IteratorArgument($map)); // add authentication providers to authentication manager diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/RuntimeLoaderPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/RuntimeLoaderPass.php index 7f4a87a945..20fda9e162 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/RuntimeLoaderPass.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/RuntimeLoaderPass.php @@ -11,12 +11,10 @@ namespace Symfony\Bundle\TwigBundle\DependencyInjection\Compiler; -use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\DependencyInjection\Definition; -use Symfony\Component\DependencyInjection\ServiceLocator; /** * Registers Twig runtime services. @@ -38,9 +36,9 @@ class RuntimeLoaderPass implements CompilerPassInterface continue; } - $mapping[$def->getClass()] = new ServiceClosureArgument(new Reference($id)); + $mapping[$def->getClass()] = new Reference($id); } - $definition->replaceArgument(0, (new Definition(ServiceLocator::class, array($mapping)))->addTag('container.service_locator')); + $definition->replaceArgument(0, ServiceLocatorTagPass::register($container, $mapping)); } } diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php index ef9445356a..4ff904dfe0 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php @@ -244,7 +244,7 @@ class TwigExtensionTest extends TestCase $container->compile(); $loader = $container->getDefinition('twig.runtime_loader'); - $args = $loader->getArgument(0)->getArgument(0); + $args = $container->getDefinition((string) $loader->getArgument(0))->getArgument(0); $this->assertArrayHasKey('Symfony\Bridge\Twig\Form\TwigRenderer', $args); $this->assertArrayHasKey('FooClass', $args); $this->assertEquals('twig.form.renderer', $args['Symfony\Bridge\Twig\Form\TwigRenderer']->getValues()[0]); diff --git a/src/Symfony/Component/DependencyInjection/Argument/ServiceClosureArgument.php b/src/Symfony/Component/DependencyInjection/Argument/ServiceClosureArgument.php index 466e63d215..2fec5d26d0 100644 --- a/src/Symfony/Component/DependencyInjection/Argument/ServiceClosureArgument.php +++ b/src/Symfony/Component/DependencyInjection/Argument/ServiceClosureArgument.php @@ -11,8 +11,8 @@ namespace Symfony\Component\DependencyInjection\Argument; -use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; /** * Represents a service wrapped in a memoizing closure. @@ -28,11 +28,17 @@ class ServiceClosureArgument implements ArgumentInterface $this->values = array($reference); } + /** + * {@inheritdoc} + */ public function getValues() { return $this->values; } + /** + * {@inheritdoc} + */ public function setValues(array $values) { if (array(0) !== array_keys($values) || !($values[0] instanceof Reference || null === $values[0])) { diff --git a/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php b/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php index 11c6f203b2..281410b77c 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php @@ -11,13 +11,11 @@ namespace Symfony\Component\DependencyInjection\Compiler; -use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ServiceSubscriberInterface; -use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\DependencyInjection\TypedReference; /** @@ -87,7 +85,7 @@ class RegisterServiceSubscribersPass extends AbstractRecursivePass $serviceMap[$key] = new Reference($type); } - $subscriberMap[$key] = new ServiceClosureArgument(new TypedReference((string) $serviceMap[$key], $type, $optionalBehavior ?: ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE)); + $subscriberMap[$key] = new TypedReference((string) $serviceMap[$key], $type, $optionalBehavior ?: ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE); unset($serviceMap[$key]); } @@ -97,12 +95,7 @@ class RegisterServiceSubscribersPass extends AbstractRecursivePass } $serviceLocator = $this->serviceLocator; - $this->serviceLocator = 'container.'.$this->currentId.'.'.md5(serialize($value)); - $this->container->register($this->serviceLocator, ServiceLocator::class) - ->addArgument($subscriberMap) - ->setPublic(false) - ->setAutowired($value->isAutowired()) - ->addTag('container.service_locator'); + $this->serviceLocator = (string) ServiceLocatorTagPass::register($this->container, $subscriberMap, $value->getAutowired()); try { return parent::processValue($value); diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ServiceLocatorTagPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ServiceLocatorTagPass.php index e8fd8379a4..8bac873e63 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ServiceLocatorTagPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ServiceLocatorTagPass.php @@ -11,7 +11,9 @@ namespace Symfony\Component\DependencyInjection\Compiler; +use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Reference; @@ -22,7 +24,7 @@ use Symfony\Component\DependencyInjection\ServiceLocator; * * @author Nicolas Grekas */ -class ServiceLocatorTagPass extends AbstractRecursivePass +final class ServiceLocatorTagPass extends AbstractRecursivePass { protected function processValue($value, $isRoot = false) { @@ -48,7 +50,52 @@ class ServiceLocatorTagPass extends AbstractRecursivePass } $arguments[0][$k] = new ServiceClosureArgument($v); } + ksort($arguments[0]); - return $value->setArguments($arguments); + $value->setArguments($arguments); + + if ($public = $value->isPublic()) { + $value->setPublic(false); + } + $id = 'service_locator.'.md5(serialize($value)); + + if ($isRoot) { + if ($id !== $this->currentId) { + $this->container->setAlias($id, new Alias($this->currentId, $public)); + } + + return $value; + } + + $this->container->setDefinition($id, $value); + + return new Reference($id); + } + + /** + * @param ContainerBuilder $container + * @param Reference[] $refMap + * @param int|bool $autowired + * + * @return Reference + */ + public static function register(ContainerBuilder $container, array $refMap, $autowired = false) + { + foreach ($refMap as $id => $ref) { + $refMap[$id] = new ServiceClosureArgument($ref); + } + ksort($refMap); + + $locator = (new Definition(ServiceLocator::class)) + ->addArgument($refMap) + ->setPublic(false) + ->setAutowired($autowired) + ->addTag('container.service_locator'); + + if (!$container->has($id = 'service_locator.'.md5(serialize($locator)))) { + $container->setDefinition($id, $locator); + } + + return new Reference($id); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php index c477203369..a29792c3e4 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php @@ -99,12 +99,12 @@ class ProjectServiceContainer extends Container { return $this->services['foo_service'] = new \TestServiceSubscriber(new \Symfony\Component\DependencyInjection\ServiceLocator(array('TestServiceSubscriber' => function () { $f = function (\TestServiceSubscriber $v) { return $v; }; return $f(${($_ = isset($this->services['TestServiceSubscriber']) ? $this->services['TestServiceSubscriber'] : $this->get('TestServiceSubscriber')) && false ?: '_'}); - }, 'stdClass' => function () { - $f = function (\stdClass $v = null) { return $v; }; return $f(${($_ = isset($this->services['autowired.stdClass']) ? $this->services['autowired.stdClass'] : $this->getAutowired_StdClassService()) && false ?: '_'}); }, 'bar' => function () { $f = function (\stdClass $v) { return $v; }; return $f(${($_ = isset($this->services['TestServiceSubscriber']) ? $this->services['TestServiceSubscriber'] : $this->get('TestServiceSubscriber')) && false ?: '_'}); }, 'baz' => function () { $f = function (\stdClass $v = null) { return $v; }; return $f(${($_ = isset($this->services['autowired.stdClass']) ? $this->services['autowired.stdClass'] : $this->getAutowired_StdClassService()) && false ?: '_'}); + }, 'stdClass' => function () { + $f = function (\stdClass $v = null) { return $v; }; return $f(${($_ = isset($this->services['autowired.stdClass']) ? $this->services['autowired.stdClass'] : $this->getAutowired_StdClassService()) && false ?: '_'}); }))); } diff --git a/src/Symfony/Component/Form/DependencyInjection/FormPass.php b/src/Symfony/Component/Form/DependencyInjection/FormPass.php index d905db5145..9952210ced 100644 --- a/src/Symfony/Component/Form/DependencyInjection/FormPass.php +++ b/src/Symfony/Component/Form/DependencyInjection/FormPass.php @@ -12,14 +12,13 @@ namespace Symfony\Component\Form\DependencyInjection; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; -use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\DependencyInjection\ServiceLocator; /** * Adds all services with the tags "form.type", "form.type_extension" and @@ -60,17 +59,16 @@ class FormPass implements CompilerPassInterface { // Get service locator argument $servicesMap = array(); - $locator = $definition->getArgument(0); // Builds an array with fully-qualified type class names as keys and service IDs as values foreach ($container->findTaggedServiceIds($this->formTypeTag) as $serviceId => $tag) { $serviceDefinition = $container->getDefinition($serviceId); // Add form type service to the service locator - $servicesMap[$serviceDefinition->getClass()] = new ServiceClosureArgument(new Reference($serviceId)); + $servicesMap[$serviceDefinition->getClass()] = new Reference($serviceId); } - return (new Definition(ServiceLocator::class, array($servicesMap)))->addTag('container.service_locator'); + return ServiceLocatorTagPass::register($container, $servicesMap); } private function processFormTypeExtensions(ContainerBuilder $container) diff --git a/src/Symfony/Component/Form/Tests/DependencyInjection/FormPassTest.php b/src/Symfony/Component/Form/Tests/DependencyInjection/FormPassTest.php index 9f454faa92..d2be0db74c 100644 --- a/src/Symfony/Component/Form/Tests/DependencyInjection/FormPassTest.php +++ b/src/Symfony/Component/Form/Tests/DependencyInjection/FormPassTest.php @@ -51,7 +51,7 @@ class FormPassTest extends TestCase (new Definition(ServiceLocator::class, array(array( __CLASS__.'_Type1' => new ServiceClosureArgument(new Reference('my.type1')), __CLASS__.'_Type2' => new ServiceClosureArgument(new Reference('my.type2')), - ))))->addTag('container.service_locator'), + ))))->addTag('container.service_locator')->setPublic(false), $extDefinition->getArgument(0) ); } diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/FragmentRendererPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/FragmentRendererPass.php index 5d4381b878..d3c8cb3092 100644 --- a/src/Symfony/Component/HttpKernel/DependencyInjection/FragmentRendererPass.php +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/FragmentRendererPass.php @@ -11,13 +11,11 @@ namespace Symfony\Component\HttpKernel\DependencyInjection; -use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface; /** @@ -64,10 +62,10 @@ class FragmentRendererPass implements CompilerPassInterface } foreach ($tags as $tag) { - $renderers[$tag['alias']] = new ServiceClosureArgument(new Reference($id)); + $renderers[$tag['alias']] = new Reference($id); } } - $definition->replaceArgument(0, (new Definition(ServiceLocator::class, array($renderers)))->addTag('container.service_locator')); + $definition->replaceArgument(0, ServiceLocatorTagPass::register($container, $renderers)); } } diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php index 7b86b5b8fc..24f64a6d31 100644 --- a/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php @@ -11,15 +11,13 @@ namespace Symfony\Component\HttpKernel\DependencyInjection; -use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\DependencyInjection\TypedReference; /** @@ -54,7 +52,7 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface continue; } $class = $def->getClass(); - $isAutowired = $def->isAutowired(); + $autowired = $def->getAutowired(); // resolve service class, taking parent definitions into account while (!$class && $def instanceof ChildDefinition) { @@ -127,25 +125,16 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface continue; } - $args[$p->name] = new ServiceClosureArgument($type ? new TypedReference($target, $type, $invalidBehavior, false) : new Reference($target, $invalidBehavior)); + $args[$p->name] = $type ? new TypedReference($target, $type, $invalidBehavior, false) : new Reference($target, $invalidBehavior); } // register the maps as a per-method service-locators if ($args) { - $argsId = sprintf('arguments.%s:%s', $id, $r->name); - $container->register($argsId, ServiceLocator::class) - ->addArgument($args) - ->setPublic(false) - ->setAutowired($isAutowired) - ->addTag('controller.arguments_locator', array($class, $id, $r->name)); - $controllers[$id.':'.$r->name] = new ServiceClosureArgument(new Reference($argsId)); - if ($id === $class) { - $controllers[$id.'::'.$r->name] = new ServiceClosureArgument(new Reference($argsId)); - } + $controllers[$id.':'.$r->name] = ServiceLocatorTagPass::register($container, $args, $autowired); } } } $container->getDefinition($this->resolverServiceId) - ->replaceArgument(0, (new Definition(ServiceLocator::class, array($controllers)))->addTag('container.service_locator')); + ->replaceArgument(0, ServiceLocatorTagPass::register($container, $controllers)); } } diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php index e42cf7b93a..50ad4c8be0 100644 --- a/src/Symfony/Component/HttpKernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php @@ -35,37 +35,39 @@ class RemoveEmptyControllerArgumentLocatorsPass implements CompilerPassInterface } $serviceResolver = $container->getDefinition($this->resolverServiceId); - $controllers = $serviceResolver->getArgument(0)->getArgument(0); + $controllerLocator = $container->getDefinition((string) $serviceResolver->getArgument(0)); + $controllers = $controllerLocator->getArgument(0); - foreach ($container->findTaggedServiceIds('controller.arguments_locator') as $id => $tags) { - $argumentLocator = $container->getDefinition($id)->clearTag('controller.arguments_locator'); - list($class, $service, $action) = $tags[0]; + foreach ($controllers as $controller => $argumentRef) { + $argumentLocator = $container->getDefinition((string) $argumentRef->getValues()[0]); if (!$argumentLocator->getArgument(0)) { // remove empty argument locators - $reason = sprintf('Removing service-argument-resolver for controller "%s:%s": no corresponding definitions were found for the referenced services/types.%s', $service, $action, !$argumentLocator->isAutowired() ? ' Did you forget to enable autowiring?' : ''); + $reason = sprintf('Removing service-argument-resolver for controller "%s": no corresponding definitions were found for the referenced services/types.%s', $controller, !$argumentLocator->isAutowired() ? ' Did you forget to enable autowiring?' : ''); } else { // any methods listed for call-at-instantiation cannot be actions $reason = false; - foreach ($container->getDefinition($service)->getMethodCalls() as list($method, $args)) { + $action = substr(strrchr($controller, ':'), 1); + $id = substr($controller, 0, -1 - strlen($action)); + $controllerDef = $container->getDefinition($id); + foreach ($controllerDef->getMethodCalls() as list($method, $args)) { if (0 === strcasecmp($action, $method)) { - $reason = sprintf('Removing method "%s" of service "%s" from controller candidates: the method is called at instantiation, thus cannot be an action.', $action, $service); + $reason = sprintf('Removing method "%s" of service "%s" from controller candidates: the method is called at instantiation, thus cannot be an action.', $action, $id); break; } } if (!$reason) { + if ($controllerDef->getClass() === $id) { + $controllers[$id.'::'.$action] = $argumentRef; + } continue; } } - $container->removeDefinition($id); - unset($controllers[$service.':'.$action]); - if ($service === $class) { - unset($controllers[$service.'::'.$action]); - } + unset($controllers[$controller]); $container->log($this, $reason); } - $serviceResolver->getArgument(0)->replaceArgument(0, $controllers); + $controllerLocator->replaceArgument(0, $controllers); } } diff --git a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/FragmentRendererPassTest.php b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/FragmentRendererPassTest.php index de12604f06..820ae81dfc 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/FragmentRendererPassTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/FragmentRendererPassTest.php @@ -12,10 +12,7 @@ namespace Symfony\Component\HttpKernel\Tests\DependencyInjection; use PHPUnit\Framework\TestCase; -use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; -use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\DependencyInjection\FragmentRendererPass; use Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface; @@ -65,7 +62,7 @@ class FragmentRendererPassTest extends TestCase $renderer ->expects($this->once()) ->method('replaceArgument') - ->with(0, $this->equalTo((new Definition(ServiceLocator::class, array(array('foo' => new ServiceClosureArgument(new Reference('my_content_renderer'))))))->addTag('container.service_locator'))); + ->with(0, $this->equalTo(new Reference('service_locator.5ae0a401097c64ca63ed976c71bc9642'))); $definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock(); $definition->expects($this->atLeastOnce()) diff --git a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php index 1e59351d73..86d1532b49 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php @@ -15,7 +15,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\DependencyInjection\TypedReference; @@ -128,7 +127,7 @@ class RegisterControllerArgumentLocatorsPassTest extends TestCase public function testAllActions() { $container = new ContainerBuilder(); - $container->register('argument_resolver.service')->addArgument(array()); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); $container->register('foo', RegisterTestController::class) ->setAutowired(true) @@ -138,12 +137,12 @@ class RegisterControllerArgumentLocatorsPassTest extends TestCase $pass = new RegisterControllerArgumentLocatorsPass(); $pass->process($container); - $expected = new Definition(ServiceLocator::class); - $expected->addArgument(array('foo:fooAction' => new ServiceClosureArgument(new Reference('arguments.foo:fooAction')))); - $expected->addTag('container.service_locator'); - $this->assertEquals($expected, $container->getDefinition('argument_resolver.service')->getArgument(0)); + $locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0); + + $this->assertEquals(array('foo:fooAction' => new ServiceClosureArgument(new Reference('service_locator.d964744f7278cba85dee823607f8c07f'))), $locator); + + $locator = $container->getDefinition((string) $locator['foo:fooAction']->getValues()[0]); - $locator = $container->getDefinition('arguments.foo:fooAction'); $this->assertSame(ServiceLocator::class, $locator->getClass()); $this->assertFalse($locator->isPublic()); $this->assertTrue($locator->isAutowired()); @@ -155,7 +154,7 @@ class RegisterControllerArgumentLocatorsPassTest extends TestCase public function testExplicitArgument() { $container = new ContainerBuilder(); - $container->register('argument_resolver.service')->addArgument(array()); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); $container->register('foo', RegisterTestController::class) ->addTag('controller.service_arguments', array('action' => 'fooAction', 'argument' => 'bar', 'id' => 'bar')) @@ -165,7 +164,8 @@ class RegisterControllerArgumentLocatorsPassTest extends TestCase $pass = new RegisterControllerArgumentLocatorsPass(); $pass->process($container); - $locator = $container->getDefinition('arguments.foo:fooAction'); + $locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0); + $locator = $container->getDefinition((string) $locator['foo:fooAction']->getValues()[0]); $this->assertFalse($locator->isAutowired()); $expected = array('bar' => new ServiceClosureArgument(new TypedReference('bar', 'stdClass', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, false))); @@ -175,7 +175,7 @@ class RegisterControllerArgumentLocatorsPassTest extends TestCase public function testOptionalArgument() { $container = new ContainerBuilder(); - $container->register('argument_resolver.service')->addArgument(array()); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); $container->register('foo', RegisterTestController::class) ->addTag('controller.service_arguments', array('action' => 'fooAction', 'argument' => 'bar', 'id' => '?bar')) @@ -184,30 +184,12 @@ class RegisterControllerArgumentLocatorsPassTest extends TestCase $pass = new RegisterControllerArgumentLocatorsPass(); $pass->process($container); - $locator = $container->getDefinition('arguments.foo:fooAction'); + $locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0); + $locator = $container->getDefinition((string) $locator['foo:fooAction']->getValues()[0]); $expected = array('bar' => new ServiceClosureArgument(new TypedReference('bar', 'stdClass', ContainerInterface::IGNORE_ON_INVALID_REFERENCE, false))); $this->assertEquals($expected, $locator->getArgument(0)); } - - public function testSameIdClass() - { - $container = new ContainerBuilder(); - $resolver = $container->register('argument_resolver.service')->addArgument(array()); - - $container->register(RegisterTestController::class, RegisterTestController::class) - ->addTag('controller.service_arguments') - ; - - $pass = new RegisterControllerArgumentLocatorsPass(); - $pass->process($container); - - $expected = array( - RegisterTestController::class.':fooAction' => new ServiceClosureArgument(new Reference('arguments.'.RegisterTestController::class.':fooAction')), - RegisterTestController::class.'::fooAction' => new ServiceClosureArgument(new Reference('arguments.'.RegisterTestController::class.':fooAction')), - ); - $this->assertEquals($expected, $resolver->getArgument(0)->getArgument(0)); - } } class RegisterTestController diff --git a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPassTest.php b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPassTest.php index 24684d37a8..f633fc3345 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPassTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPassTest.php @@ -24,7 +24,7 @@ class RemoveEmptyControllerArgumentLocatorsPassTest extends TestCase public function testProcess() { $container = new ContainerBuilder(); - $container->register('argument_resolver.service')->addArgument(array()); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); $container->register('stdClass', 'stdClass'); $container->register(parent::class, 'stdClass'); @@ -35,33 +35,49 @@ class RemoveEmptyControllerArgumentLocatorsPassTest extends TestCase $pass = new RegisterControllerArgumentLocatorsPass(); $pass->process($container); - $this->assertCount(2, $container->getDefinition('arguments.c1:fooAction')->getArgument(0)); - $this->assertCount(1, $container->getDefinition('arguments.c2:setTestCase')->getArgument(0)); - $this->assertCount(1, $container->getDefinition('arguments.c2:fooAction')->getArgument(0)); + $controllers = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0); - $pass = new ResolveInvalidReferencesPass(); - $pass->process($container); + $this->assertCount(2, $container->getDefinition((string) $controllers['c1:fooAction']->getValues()[0])->getArgument(0)); + $this->assertCount(1, $container->getDefinition((string) $controllers['c2:setTestCase']->getValues()[0])->getArgument(0)); + $this->assertCount(1, $container->getDefinition((string) $controllers['c2:fooAction']->getValues()[0])->getArgument(0)); - $this->assertCount(1, $container->getDefinition('arguments.c2:setTestCase')->getArgument(0)); - $this->assertSame(array(), $container->getDefinition('arguments.c2:fooAction')->getArgument(0)); + (new ResolveInvalidReferencesPass())->process($container); - $pass = new RemoveEmptyControllerArgumentLocatorsPass(); - $pass->process($container); + $this->assertCount(1, $container->getDefinition((string) $controllers['c2:setTestCase']->getValues()[0])->getArgument(0)); + $this->assertSame(array(), $container->getDefinition((string) $controllers['c2:fooAction']->getValues()[0])->getArgument(0)); - $this->assertFalse($container->hasDefinition('arguments.c2:setTestCase')); - $this->assertFalse($container->hasDefinition('arguments.c2:fooAction')); + (new RemoveEmptyControllerArgumentLocatorsPass())->process($container); - $this->assertCount(1, $container->getDefinition('arguments.c1:fooAction')->getArgument(0)); - $this->assertArrayHasKey('bar', $container->getDefinition('arguments.c1:fooAction')->getArgument(0)); + $controllers = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0); + + $this->assertSame(array('c1:fooAction'), array_keys($controllers)); + $this->assertSame(array('bar'), array_keys($container->getDefinition((string) $controllers['c1:fooAction']->getValues()[0])->getArgument(0))); $expectedLog = array( - 'Symfony\Component\HttpKernel\DependencyInjection\RemoveEmptyControllerArgumentLocatorsPass: Removing method "setTestCase" of service "c2" from controller candidates: the method is called at instantiation, thus cannot be an action.', 'Symfony\Component\HttpKernel\DependencyInjection\RemoveEmptyControllerArgumentLocatorsPass: Removing service-argument-resolver for controller "c2:fooAction": no corresponding definitions were found for the referenced services/types. Did you forget to enable autowiring?', + 'Symfony\Component\HttpKernel\DependencyInjection\RemoveEmptyControllerArgumentLocatorsPass: Removing method "setTestCase" of service "c2" from controller candidates: the method is called at instantiation, thus cannot be an action.', ); $this->assertSame($expectedLog, $container->getCompiler()->getLog()); + } - $this->assertEquals(array('c1:fooAction' => new ServiceClosureArgument(new Reference('arguments.c1:fooAction'))), $container->getDefinition('argument_resolver.service')->getArgument(0)->getArgument(0)); + public function testSameIdClass() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register(RegisterTestController::class, RegisterTestController::class) + ->addTag('controller.service_arguments') + ; + + (new RegisterControllerArgumentLocatorsPass())->process($container); + (new RemoveEmptyControllerArgumentLocatorsPass())->process($container); + + $expected = array( + RegisterTestController::class.':fooAction' => new ServiceClosureArgument(new Reference('service_locator.37b6201ea2e9e6ade00ab09ae7a6ceb3')), + RegisterTestController::class.'::fooAction' => new ServiceClosureArgument(new Reference('service_locator.37b6201ea2e9e6ade00ab09ae7a6ceb3')), + ); + $this->assertEquals($expected, $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0)); } } diff --git a/src/Symfony/Component/Validator/DependencyInjection/AddConstraintValidatorsPass.php b/src/Symfony/Component/Validator/DependencyInjection/AddConstraintValidatorsPass.php index eaff6effe0..d50368906e 100644 --- a/src/Symfony/Component/Validator/DependencyInjection/AddConstraintValidatorsPass.php +++ b/src/Symfony/Component/Validator/DependencyInjection/AddConstraintValidatorsPass.php @@ -11,12 +11,10 @@ namespace Symfony\Component\Validator\DependencyInjection; -use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\DependencyInjection\ServiceLocator; /** * @author Johannes M. Schmitt @@ -48,15 +46,15 @@ class AddConstraintValidatorsPass implements CompilerPassInterface } if (isset($attributes[0]['alias'])) { - $validators[$attributes[0]['alias']] = new ServiceClosureArgument(new Reference($id)); + $validators[$attributes[0]['alias']] = new Reference($id); } - $validators[$definition->getClass()] = new ServiceClosureArgument(new Reference($id)); + $validators[$definition->getClass()] = new Reference($id); } $container ->getDefinition('validator.validator_factory') - ->replaceArgument(0, (new Definition(ServiceLocator::class, array($validators)))->addTag('container.service_locator')) + ->replaceArgument(0, ServiceLocatorTagPass::register($container, $validators)) ; } } diff --git a/src/Symfony/Component/Validator/Tests/DependencyInjection/AddConstraintValidatorsPassTest.php b/src/Symfony/Component/Validator/Tests/DependencyInjection/AddConstraintValidatorsPassTest.php index ed2f59c529..00f6c275dc 100644 --- a/src/Symfony/Component/Validator/Tests/DependencyInjection/AddConstraintValidatorsPassTest.php +++ b/src/Symfony/Component/Validator/Tests/DependencyInjection/AddConstraintValidatorsPassTest.php @@ -38,11 +38,12 @@ class AddConstraintValidatorsPassTest extends TestCase $addConstraintValidatorsPass = new AddConstraintValidatorsPass(); $addConstraintValidatorsPass->process($container); - $this->assertEquals((new Definition(ServiceLocator::class, array(array( + $expected = (new Definition(ServiceLocator::class, array(array( Validator1::class => new ServiceClosureArgument(new Reference('my_constraint_validator_service1')), 'my_constraint_validator_alias1' => new ServiceClosureArgument(new Reference('my_constraint_validator_service1')), Validator2::class => new ServiceClosureArgument(new Reference('my_constraint_validator_service2')), - ))))->addTag('container.service_locator'), $validatorFactory->getArgument(0)); + ))))->addTag('container.service_locator')->setPublic(false); + $this->assertEquals($expected, $container->getDefinition((string) $validatorFactory->getArgument(0))); } public function testThatCompilerPassIsIgnoredIfThereIsNoConstraintValidatorFactoryDefinition()