feature #15096 [DependencyInjection] Allow anonymous DefinitionDecorator resolving (nicolas-grekas)
This PR was merged into the 2.8 branch.
Discussion
----------
[DependencyInjection] Allow anonymous DefinitionDecorator resolving
| Q | A
| ------------- | ---
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | -
| License | MIT
| Doc PR | -
This PR allows injecting anonymous DefinitionDecorator into services' arguments/properties, such as:
```php
$container->register('foo_service_name', 'FooClass')
->setProperty('bar', new DefinitionDecorator('definition_decorated_service'))
;
```
Commits
-------
e5763ce
[DependencyInjection] Allow anonymous DefinitionDecorator resolving
This commit is contained in:
commit
32cbfd49d4
@ -48,27 +48,7 @@ class InlineServiceDefinitionsPass implements RepeatablePassInterface
|
|||||||
$this->formatter = $this->compiler->getLoggingFormatter();
|
$this->formatter = $this->compiler->getLoggingFormatter();
|
||||||
$this->graph = $this->compiler->getServiceReferenceGraph();
|
$this->graph = $this->compiler->getServiceReferenceGraph();
|
||||||
|
|
||||||
foreach ($container->getDefinitions() as $id => $definition) {
|
$container->setDefinitions($this->inlineArguments($container, $container->getDefinitions(), true));
|
||||||
$this->currentId = $id;
|
|
||||||
|
|
||||||
$definition->setArguments(
|
|
||||||
$this->inlineArguments($container, $definition->getArguments())
|
|
||||||
);
|
|
||||||
|
|
||||||
$definition->setMethodCalls(
|
|
||||||
$this->inlineArguments($container, $definition->getMethodCalls())
|
|
||||||
);
|
|
||||||
|
|
||||||
$definition->setProperties(
|
|
||||||
$this->inlineArguments($container, $definition->getProperties())
|
|
||||||
);
|
|
||||||
|
|
||||||
$configurator = $this->inlineArguments($container, array($definition->getConfigurator()));
|
|
||||||
$definition->setConfigurator($configurator[0]);
|
|
||||||
|
|
||||||
$factory = $this->inlineArguments($container, array($definition->getFactory()));
|
|
||||||
$definition->setFactory($factory[0]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -76,12 +56,16 @@ class InlineServiceDefinitionsPass implements RepeatablePassInterface
|
|||||||
*
|
*
|
||||||
* @param ContainerBuilder $container The ContainerBuilder
|
* @param ContainerBuilder $container The ContainerBuilder
|
||||||
* @param array $arguments An array of arguments
|
* @param array $arguments An array of arguments
|
||||||
|
* @param bool $isRoot If we are processing the root definitions or not
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
private function inlineArguments(ContainerBuilder $container, array $arguments)
|
private function inlineArguments(ContainerBuilder $container, array $arguments, $isRoot = false)
|
||||||
{
|
{
|
||||||
foreach ($arguments as $k => $argument) {
|
foreach ($arguments as $k => $argument) {
|
||||||
|
if ($isRoot) {
|
||||||
|
$this->currentId = $k;
|
||||||
|
}
|
||||||
if (is_array($argument)) {
|
if (is_array($argument)) {
|
||||||
$arguments[$k] = $this->inlineArguments($container, $argument);
|
$arguments[$k] = $this->inlineArguments($container, $argument);
|
||||||
} elseif ($argument instanceof Reference) {
|
} elseif ($argument instanceof Reference) {
|
||||||
@ -102,6 +86,12 @@ class InlineServiceDefinitionsPass implements RepeatablePassInterface
|
|||||||
$argument->setArguments($this->inlineArguments($container, $argument->getArguments()));
|
$argument->setArguments($this->inlineArguments($container, $argument->getArguments()));
|
||||||
$argument->setMethodCalls($this->inlineArguments($container, $argument->getMethodCalls()));
|
$argument->setMethodCalls($this->inlineArguments($container, $argument->getMethodCalls()));
|
||||||
$argument->setProperties($this->inlineArguments($container, $argument->getProperties()));
|
$argument->setProperties($this->inlineArguments($container, $argument->getProperties()));
|
||||||
|
|
||||||
|
$configurator = $this->inlineArguments($container, array($argument->getConfigurator()));
|
||||||
|
$argument->setConfigurator($configurator[0]);
|
||||||
|
|
||||||
|
$factory = $this->inlineArguments($container, array($argument->getFactory()));
|
||||||
|
$argument->setFactory($factory[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,12 +21,13 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException;
|
|||||||
* merged Definition instance.
|
* merged Definition instance.
|
||||||
*
|
*
|
||||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||||
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
*/
|
*/
|
||||||
class ResolveDefinitionTemplatesPass implements CompilerPassInterface
|
class ResolveDefinitionTemplatesPass implements CompilerPassInterface
|
||||||
{
|
{
|
||||||
private $container;
|
|
||||||
private $compiler;
|
private $compiler;
|
||||||
private $formatter;
|
private $formatter;
|
||||||
|
private $currentId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process the ContainerBuilder to replace DefinitionDecorator instances with their real Definition instances.
|
* Process the ContainerBuilder to replace DefinitionDecorator instances with their real Definition instances.
|
||||||
@ -35,44 +36,80 @@ class ResolveDefinitionTemplatesPass implements CompilerPassInterface
|
|||||||
*/
|
*/
|
||||||
public function process(ContainerBuilder $container)
|
public function process(ContainerBuilder $container)
|
||||||
{
|
{
|
||||||
$this->container = $container;
|
|
||||||
$this->compiler = $container->getCompiler();
|
$this->compiler = $container->getCompiler();
|
||||||
$this->formatter = $this->compiler->getLoggingFormatter();
|
$this->formatter = $this->compiler->getLoggingFormatter();
|
||||||
|
|
||||||
foreach ($container->getDefinitions() as $id => $definition) {
|
$container->setDefinitions($this->resolveArguments($container, $container->getDefinitions(), true));
|
||||||
// yes, we are specifically fetching the definition from the
|
|
||||||
// container to ensure we are not operating on stale data
|
|
||||||
$definition = $container->getDefinition($id);
|
|
||||||
if (!$definition instanceof DefinitionDecorator || $definition->isAbstract()) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->resolveDefinition($id, $definition);
|
/**
|
||||||
|
* Resolves definition decorator arguments.
|
||||||
|
*
|
||||||
|
* @param ContainerBuilder $container The ContainerBuilder
|
||||||
|
* @param array $arguments An array of arguments
|
||||||
|
* @param bool $isRoot If we are processing the root definitions or not
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function resolveArguments(ContainerBuilder $container, array $arguments, $isRoot = false)
|
||||||
|
{
|
||||||
|
foreach ($arguments as $k => $argument) {
|
||||||
|
if ($isRoot) {
|
||||||
|
// yes, we are specifically fetching the definition from the
|
||||||
|
// container to ensure we are not operating on stale data
|
||||||
|
$arguments[$k] = $argument = $container->getDefinition($k);
|
||||||
|
$this->currentId = $k;
|
||||||
}
|
}
|
||||||
|
if (is_array($argument)) {
|
||||||
|
$arguments[$k] = $this->resolveArguments($container, $argument);
|
||||||
|
} elseif ($argument instanceof Definition) {
|
||||||
|
if ($argument instanceof DefinitionDecorator) {
|
||||||
|
$arguments[$k] = $argument = $this->resolveDefinition($container, $argument);
|
||||||
|
if ($isRoot) {
|
||||||
|
$container->setDefinition($k, $argument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$argument->setArguments($this->resolveArguments($container, $argument->getArguments()));
|
||||||
|
$argument->setMethodCalls($this->resolveArguments($container, $argument->getMethodCalls()));
|
||||||
|
$argument->setProperties($this->resolveArguments($container, $argument->getProperties()));
|
||||||
|
|
||||||
|
$configurator = $this->resolveArguments($container, array($argument->getConfigurator()));
|
||||||
|
$argument->setConfigurator($configurator[0]);
|
||||||
|
|
||||||
|
$factory = $this->resolveArguments($container, array($argument->getFactory()));
|
||||||
|
$argument->setFactory($factory[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $arguments;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves the definition.
|
* Resolves the definition.
|
||||||
*
|
*
|
||||||
* @param string $id The definition identifier
|
* @param ContainerBuilder $container The ContainerBuilder
|
||||||
* @param DefinitionDecorator $definition
|
* @param DefinitionDecorator $definition
|
||||||
*
|
*
|
||||||
* @return Definition
|
* @return Definition
|
||||||
*
|
*
|
||||||
* @throws \RuntimeException When the definition is invalid
|
* @throws \RuntimeException When the definition is invalid
|
||||||
*/
|
*/
|
||||||
private function resolveDefinition($id, DefinitionDecorator $definition)
|
private function resolveDefinition(ContainerBuilder $container, DefinitionDecorator $definition)
|
||||||
{
|
{
|
||||||
if (!$this->container->hasDefinition($parent = $definition->getParent())) {
|
if (!$container->hasDefinition($parent = $definition->getParent())) {
|
||||||
throw new RuntimeException(sprintf('The parent definition "%s" defined for definition "%s" does not exist.', $parent, $id));
|
throw new RuntimeException(sprintf('The parent definition "%s" defined for definition "%s" does not exist.', $parent, $this->currentId));
|
||||||
}
|
}
|
||||||
|
|
||||||
$parentDef = $this->container->getDefinition($parent);
|
$parentDef = $container->getDefinition($parent);
|
||||||
if ($parentDef instanceof DefinitionDecorator) {
|
if ($parentDef instanceof DefinitionDecorator) {
|
||||||
$parentDef = $this->resolveDefinition($parent, $parentDef);
|
$id = $this->currentId;
|
||||||
|
$this->currentId = $parent;
|
||||||
|
$parentDef = $this->resolveDefinition($container, $parentDef);
|
||||||
|
$container->setDefinition($parent, $parentDef);
|
||||||
|
$this->currentId = $id;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->compiler->addLogMessage($this->formatter->formatResolveInheritance($this, $id, $parent));
|
$this->compiler->addLogMessage($this->formatter->formatResolveInheritance($this, $this->currentId, $parent));
|
||||||
$def = new Definition();
|
$def = new Definition();
|
||||||
|
|
||||||
// merge in parent definition
|
// merge in parent definition
|
||||||
@ -156,9 +193,6 @@ class ResolveDefinitionTemplatesPass implements CompilerPassInterface
|
|||||||
$def->setScope($definition->getScope(false), false);
|
$def->setScope($definition->getScope(false), false);
|
||||||
$def->setTags($definition->getTags());
|
$def->setTags($definition->getTags());
|
||||||
|
|
||||||
// set new definition on container
|
|
||||||
$this->container->setDefinition($id, $def);
|
|
||||||
|
|
||||||
return $def;
|
return $def;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -176,6 +176,42 @@ class ResolveDefinitionTemplatesPassTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertTrue($container->getDefinition('child1')->isLazy());
|
$this->assertTrue($container->getDefinition('child1')->isLazy());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testDeepDefinitionsResolving()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
|
||||||
|
$container->register('parent', 'parentClass');
|
||||||
|
$container->register('sibling', 'siblingClass')
|
||||||
|
->setConfigurator(new DefinitionDecorator('parent'), 'foo')
|
||||||
|
->setFactory(array(new DefinitionDecorator('parent'), 'foo'))
|
||||||
|
->addArgument(new DefinitionDecorator('parent'))
|
||||||
|
->setProperty('prop', new DefinitionDecorator('parent'))
|
||||||
|
->addMethodCall('meth', array(new DefinitionDecorator('parent')))
|
||||||
|
;
|
||||||
|
|
||||||
|
$this->process($container);
|
||||||
|
|
||||||
|
$configurator = $container->getDefinition('sibling')->getConfigurator();
|
||||||
|
$this->assertSame('Symfony\Component\DependencyInjection\Definition', get_class($configurator));
|
||||||
|
$this->assertSame('parentClass', $configurator->getClass());
|
||||||
|
|
||||||
|
$factory = $container->getDefinition('sibling')->getFactory();
|
||||||
|
$this->assertSame('Symfony\Component\DependencyInjection\Definition', get_class($factory[0]));
|
||||||
|
$this->assertSame('parentClass', $factory[0]->getClass());
|
||||||
|
|
||||||
|
$argument = $container->getDefinition('sibling')->getArgument(0);
|
||||||
|
$this->assertSame('Symfony\Component\DependencyInjection\Definition', get_class($argument));
|
||||||
|
$this->assertSame('parentClass', $argument->getClass());
|
||||||
|
|
||||||
|
$properties = $container->getDefinition('sibling')->getProperties();
|
||||||
|
$this->assertSame('Symfony\Component\DependencyInjection\Definition', get_class($properties['prop']));
|
||||||
|
$this->assertSame('parentClass', $properties['prop']->getClass());
|
||||||
|
|
||||||
|
$methodCalls = $container->getDefinition('sibling')->getMethodCalls();
|
||||||
|
$this->assertSame('Symfony\Component\DependencyInjection\Definition', get_class($methodCalls[0][1][0]));
|
||||||
|
$this->assertSame('parentClass', $methodCalls[0][1][0]->getClass());
|
||||||
|
}
|
||||||
|
|
||||||
protected function process(ContainerBuilder $container)
|
protected function process(ContainerBuilder $container)
|
||||||
{
|
{
|
||||||
$pass = new ResolveDefinitionTemplatesPass();
|
$pass = new ResolveDefinitionTemplatesPass();
|
||||||
|
Reference in New Issue
Block a user