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

This commit is contained in:
Nicolas Grekas 2017-12-04 16:10:11 +01:00
parent c9f72e2807
commit eb2a15229a
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();