feature #22175 [DI] add ServiceLocatorTagPass::register() to share service locators (nicolas-grekas)

This PR was merged into the 3.3-dev branch.

Discussion
----------

[DI] add ServiceLocatorTagPass::register() to share service locators

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | yes
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        | -

Right now, one service locator is created per controller / service subscriber. But since service locators are stateless, this is just wasting resources when several controllers have the exact same set of services managed by their locators (as would be the case when registering the new `AbstractController` as a service subscribers).

This PR fixes this issue, and a few related others found along the way.

Commits
-------

8ff764be82 [DI] add ServiceLocatorTagPass::register() to share service locators
This commit is contained in:
Fabien Potencier 2017-04-03 15:47:57 -07:00
commit 2450449605
21 changed files with 176 additions and 158 deletions

View File

@ -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)
;
}

View File

@ -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()

View File

@ -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)
);
}

View File

@ -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));
}
}

View File

@ -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

View File

@ -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));
}
}

View File

@ -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]);

View File

@ -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])) {

View File

@ -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);

View File

@ -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 <p@tchwork.com>
*/
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);
}
}

View File

@ -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 ?: '_'});
})));
}

View File

@ -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)

View File

@ -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)
);
}

View File

@ -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));
}
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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())

View File

@ -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

View File

@ -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));
}
}

View File

@ -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 <schmittjoh@gmail.com>
@ -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))
;
}
}

View File

@ -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()