Skip to content

Commit

Permalink
feature #15096 [DependencyInjection] Allow anonymous DefinitionDecora…
Browse files Browse the repository at this point in the history
…tor 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
  • Loading branch information
fabpot committed Jun 30, 2015
2 parents cad16ac + e5763ce commit 32cbfd4
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 42 deletions.
Expand Up @@ -48,40 +48,24 @@ public function process(ContainerBuilder $container)
$this->formatter = $this->compiler->getLoggingFormatter();
$this->graph = $this->compiler->getServiceReferenceGraph();

foreach ($container->getDefinitions() as $id => $definition) {
$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]);
}
$container->setDefinitions($this->inlineArguments($container, $container->getDefinitions(), true));
}

/**
* Processes inline 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 inlineArguments(ContainerBuilder $container, array $arguments)
private function inlineArguments(ContainerBuilder $container, array $arguments, $isRoot = false)
{
foreach ($arguments as $k => $argument) {
if ($isRoot) {
$this->currentId = $k;
}
if (is_array($argument)) {
$arguments[$k] = $this->inlineArguments($container, $argument);
} elseif ($argument instanceof Reference) {
Expand All @@ -102,6 +86,12 @@ private function inlineArguments(ContainerBuilder $container, array $arguments)
$argument->setArguments($this->inlineArguments($container, $argument->getArguments()));
$argument->setMethodCalls($this->inlineArguments($container, $argument->getMethodCalls()));
$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]);
}
}

Expand Down
Expand Up @@ -21,12 +21,13 @@
* merged Definition instance.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class ResolveDefinitionTemplatesPass implements CompilerPassInterface
{
private $container;
private $compiler;
private $formatter;
private $currentId;

/**
* Process the ContainerBuilder to replace DefinitionDecorator instances with their real Definition instances.
Expand All @@ -35,44 +36,80 @@ class ResolveDefinitionTemplatesPass implements CompilerPassInterface
*/
public function process(ContainerBuilder $container)
{
$this->container = $container;
$this->compiler = $container->getCompiler();
$this->formatter = $this->compiler->getLoggingFormatter();

foreach ($container->getDefinitions() as $id => $definition) {
// 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;
}
$container->setDefinitions($this->resolveArguments($container, $container->getDefinitions(), true));
}

$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.
*
* @param string $id The definition identifier
* @param ContainerBuilder $container The ContainerBuilder
* @param DefinitionDecorator $definition
*
* @return Definition
*
* @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())) {
throw new RuntimeException(sprintf('The parent definition "%s" defined for definition "%s" does not exist.', $parent, $id));
if (!$container->hasDefinition($parent = $definition->getParent())) {
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) {
$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();

// merge in parent definition
Expand Down Expand Up @@ -156,9 +193,6 @@ private function resolveDefinition($id, DefinitionDecorator $definition)
$def->setScope($definition->getScope(false), false);
$def->setTags($definition->getTags());

// set new definition on container
$this->container->setDefinition($id, $def);

return $def;
}
}
Expand Up @@ -176,6 +176,42 @@ public function testSetLazyOnServiceIsParent()
$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)
{
$pass = new ResolveDefinitionTemplatesPass();
Expand Down

0 comments on commit 32cbfd4

Please sign in to comment.