bug #25312 [DI] Fix deep-inlining of non-shared refs (nicolas-grekas)

This PR was merged into the 3.3 branch.

Discussion
----------

[DI] Fix deep-inlining of non-shared refs

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

Non-shared definitions should deep-clone their inlined non-shared definitions.

Commits
-------

eb2a15229a [DI] Fix deep-inlining of non-shared refs
This commit is contained in:
Fabien Potencier 2017-12-04 10:00:31 -08:00
commit 73ff764bd9
2 changed files with 90 additions and 8 deletions

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
use Symfony\Component\DependencyInjection\Reference;
/**
@ -23,6 +24,7 @@ use Symfony\Component\DependencyInjection\Reference;
class InlineServiceDefinitionsPass extends AbstractRecursivePass implements RepeatablePassInterface
{
private $repeatedPass;
private $cloningIds = array();
private $inlinedServiceIds = array();
/**
@ -54,18 +56,44 @@ class InlineServiceDefinitionsPass extends AbstractRecursivePass implements Repe
// Reference found in ArgumentInterface::getValues() are not inlineable
return $value;
}
if ($value instanceof Reference && $this->container->hasDefinition($id = (string) $value)) {
$definition = $this->container->getDefinition($id);
if ($this->isInlineableDefinition($id, $definition, $this->container->getCompiler()->getServiceReferenceGraph())) {
$this->container->log($this, sprintf('Inlined service "%s" to "%s".', $id, $this->currentId));
$this->inlinedServiceIds[$id][] = $this->currentId;
return $definition->isShared() ? $definition : clone $definition;
if ($value instanceof Definition && $this->cloningIds) {
if ($value->isShared()) {
return $value;
}
$value = clone $value;
}
return parent::processValue($value, $isRoot);
if (!$value instanceof Reference || !$this->container->hasDefinition($id = (string) $value)) {
return parent::processValue($value, $isRoot);
}
$definition = $this->container->getDefinition($id);
if (!$this->isInlineableDefinition($id, $definition, $this->container->getCompiler()->getServiceReferenceGraph())) {
return $value;
}
$this->container->log($this, sprintf('Inlined service "%s" to "%s".', $id, $this->currentId));
$this->inlinedServiceIds[$id][] = $this->currentId;
if ($definition->isShared()) {
return $definition;
}
if (isset($this->cloningIds[$id])) {
$ids = array_keys($this->cloningIds);
$ids[] = $id;
throw new ServiceCircularReferenceException($id, array_slice($ids, array_search($id, $ids)));
}
$this->cloningIds[$id] = true;
try {
return $this->processValue($definition);
} finally {
unset($this->cloningIds[$id]);
}
}
/**

View File

@ -111,6 +111,60 @@ class InlineServiceDefinitionsPassTest extends TestCase
$this->assertEquals($container->getDefinition('foo')->getArgument(0), $container->getDefinition('bar'));
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException
* @expectedExceptionMessage Circular reference detected for service "bar", path: "bar -> foo -> bar".
*/
public function testProcessThrowsOnNonSharedLoops()
{
$container = new ContainerBuilder();
$container
->register('foo')
->addArgument(new Reference('bar'))
->setShared(false)
;
$container
->register('bar')
->setShared(false)
->addMethodCall('setFoo', array(new Reference('foo')))
;
$this->process($container);
}
public function testProcessNestedNonSharedServices()
{
$container = new ContainerBuilder();
$container
->register('foo')
->addArgument(new Reference('bar1'))
->addArgument(new Reference('bar2'))
;
$container
->register('bar1')
->setShared(false)
->addArgument(new Reference('baz'))
;
$container
->register('bar2')
->setShared(false)
->addArgument(new Reference('baz'))
;
$container
->register('baz')
->setShared(false)
;
$this->process($container);
$baz1 = $container->getDefinition('foo')->getArgument(0)->getArgument(0);
$baz2 = $container->getDefinition('foo')->getArgument(1)->getArgument(0);
$this->assertEquals($container->getDefinition('baz'), $baz1);
$this->assertEquals($container->getDefinition('baz'), $baz2);
$this->assertNotSame($baz1, $baz2);
}
public function testProcessInlinesIfMultipleReferencesButAllFromTheSameDefinition()
{
$container = new ContainerBuilder();