[DI] Fix circular reference when using setters

This commit is contained in:
Nicolas Grekas 2017-11-27 21:00:44 +01:00
parent f74ecedd0b
commit de5eecc5fa
11 changed files with 410 additions and 290 deletions

View File

@ -570,10 +570,13 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
return $this->doGet($id, $invalidBehavior);
}
private function doGet($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE)
private function doGet($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, array &$inlineServices = array())
{
$id = $this->normalizeId($id);
if (isset($inlineServices[$id])) {
return $inlineServices[$id];
}
if (ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $invalidBehavior) {
return parent::get($id, $invalidBehavior);
}
@ -582,7 +585,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
}
if (!isset($this->definitions[$id]) && isset($this->aliasDefinitions[$id])) {
return $this->doGet((string) $this->aliasDefinitions[$id], $invalidBehavior);
return $this->doGet((string) $this->aliasDefinitions[$id], $invalidBehavior, $inlineServices);
}
try {
@ -599,7 +602,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
$this->{$loading}[$id] = true;
try {
$service = $this->createService($definition, new \SplObjectStorage(), $id);
$service = $this->createService($definition, $inlineServices, $id);
} finally {
unset($this->{$loading}[$id]);
}
@ -1054,10 +1057,10 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
* @throws RuntimeException When the service is a synthetic service
* @throws InvalidArgumentException When configure callable is not callable
*/
private function createService(Definition $definition, \SplObjectStorage $inlinedDefinitions, $id = null, $tryProxy = true)
private function createService(Definition $definition, array &$inlineServices, $id = null, $tryProxy = true)
{
if (null === $id && isset($inlinedDefinitions[$definition])) {
return $inlinedDefinitions[$definition];
if (null === $id && isset($inlineServices[$h = spl_object_hash($definition)])) {
return $inlineServices[$h];
}
if ($definition instanceof ChildDefinition) {
@ -1078,11 +1081,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
->instantiateProxy(
$this,
$definition,
$id, function () use ($definition, $inlinedDefinitions, $id) {
return $this->createService($definition, $inlinedDefinitions, $id, false);
$id, function () use ($definition, &$inlineServices, $id) {
return $this->createService($definition, $inlineServices, $id, false);
}
);
$this->shareService($definition, $proxy, $id, $inlinedDefinitions);
$this->shareService($definition, $proxy, $id, $inlineServices);
return $proxy;
}
@ -1093,7 +1096,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
require_once $parameterBag->resolveValue($definition->getFile());
}
$arguments = $this->doResolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getArguments())), $inlinedDefinitions);
$arguments = $this->doResolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getArguments())), $inlineServices);
if (null !== $id && $definition->isShared() && isset($this->services[$id]) && ($tryProxy || !$definition->isLazy())) {
return $this->services[$id];
@ -1101,7 +1104,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
if (null !== $factory = $definition->getFactory()) {
if (is_array($factory)) {
$factory = array($this->doResolveServices($parameterBag->resolveValue($factory[0]), $inlinedDefinitions), $factory[1]);
$factory = array($this->doResolveServices($parameterBag->resolveValue($factory[0]), $inlineServices), $factory[1]);
} elseif (!is_string($factory)) {
throw new RuntimeException(sprintf('Cannot create service "%s" because of invalid factory', $id));
}
@ -1130,16 +1133,16 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
if ($tryProxy || !$definition->isLazy()) {
// share only if proxying failed, or if not a proxy
$this->shareService($definition, $service, $id, $inlinedDefinitions);
$this->shareService($definition, $service, $id, $inlineServices);
}
$properties = $this->doResolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getProperties())), $inlinedDefinitions);
$properties = $this->doResolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getProperties())), $inlineServices);
foreach ($properties as $name => $value) {
$service->$name = $value;
}
foreach ($definition->getMethodCalls() as $call) {
$this->callMethod($service, $call, $inlinedDefinitions);
$this->callMethod($service, $call, $inlineServices);
}
if ($callable = $definition->getConfigurator()) {
@ -1147,9 +1150,9 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
$callable[0] = $parameterBag->resolveValue($callable[0]);
if ($callable[0] instanceof Reference) {
$callable[0] = $this->doGet((string) $callable[0], $callable[0]->getInvalidBehavior());
$callable[0] = $this->doGet((string) $callable[0], $callable[0]->getInvalidBehavior(), $inlineServices);
} elseif ($callable[0] instanceof Definition) {
$callable[0] = $this->createService($callable[0], $inlinedDefinitions);
$callable[0] = $this->createService($callable[0], $inlineServices);
}
}
@ -1173,14 +1176,14 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
*/
public function resolveServices($value)
{
return $this->doResolveServices($value, new \SplObjectStorage());
return $this->doResolveServices($value);
}
private function doResolveServices($value, \SplObjectStorage $inlinedDefinitions)
private function doResolveServices($value, array &$inlineServices = array())
{
if (is_array($value)) {
foreach ($value as $k => $v) {
$value[$k] = $this->doResolveServices($v, $inlinedDefinitions);
$value[$k] = $this->doResolveServices($v, $inlineServices);
}
} elseif ($value instanceof ServiceClosureArgument) {
$reference = $value->getValues()[0];
@ -1223,9 +1226,9 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
return $count;
});
} elseif ($value instanceof Reference) {
$value = $this->doGet((string) $value, $value->getInvalidBehavior());
$value = $this->doGet((string) $value, $value->getInvalidBehavior(), $inlineServices);
} elseif ($value instanceof Definition) {
$value = $this->createService($value, $inlinedDefinitions);
$value = $this->createService($value, $inlineServices);
} elseif ($value instanceof Expression) {
$value = $this->getExpressionLanguage()->evaluate($value, array('container' => $this));
}
@ -1540,7 +1543,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
return $this->proxyInstantiator;
}
private function callMethod($service, $call, \SplObjectStorage $inlinedDefinitions)
private function callMethod($service, $call, array &$inlineServices)
{
foreach (self::getServiceConditionals($call[1]) as $s) {
if (!$this->has($s)) {
@ -1548,12 +1551,12 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
}
}
foreach (self::getInitializedConditionals($call[1]) as $s) {
if (!$this->doGet($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE)) {
if (!$this->doGet($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE, $inlineServices)) {
return;
}
}
call_user_func_array(array($service, $call[0]), $this->doResolveServices($this->getParameterBag()->unescapeValue($this->getParameterBag()->resolveValue($call[1])), $inlinedDefinitions));
call_user_func_array(array($service, $call[0]), $this->doResolveServices($this->getParameterBag()->unescapeValue($this->getParameterBag()->resolveValue($call[1])), $inlineServices));
}
/**
@ -1563,14 +1566,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
* @param object $service
* @param string|null $id
*/
private function shareService(Definition $definition, $service, $id, \SplObjectStorage $inlinedDefinitions)
private function shareService(Definition $definition, $service, $id, array &$inlineServices)
{
if (!$definition->isShared()) {
return;
}
if (null === $id) {
$inlinedDefinitions[$definition] = $service;
} else {
$inlineServices[null !== $id ? $id : spl_object_hash($definition)] = $service;
if (null !== $id && $definition->isShared()) {
$this->services[$id] = $service;
unset($this->loading[$id], $this->alreadyLoading[$id]);
}

View File

@ -51,7 +51,6 @@ class PhpDumper extends Dumper
*/
const NON_FIRST_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789_';
private $inlinedDefinitions;
private $definitionVariables;
private $referenceVariables;
private $variableCount;
@ -84,8 +83,6 @@ class PhpDumper extends Dumper
}
parent::__construct($container);
$this->inlinedDefinitions = new \SplObjectStorage();
}
/**
@ -270,60 +267,48 @@ EOF;
/**
* Generates Service local temp variables.
*
* @param string $cId
* @param Definition $definition
* @param array $inlinedDefinitions
*
* @return string
*/
private function addServiceLocalTempVariables($cId, Definition $definition, array $inlinedDefinitions)
private function addServiceLocalTempVariables($cId, Definition $definition, \SplObjectStorage $inlinedDefinitions, \SplObjectStorage $allInlinedDefinitions)
{
static $template = " \$%s = %s;\n";
$allCalls = $calls = $behavior = array();
array_unshift($inlinedDefinitions, $definition);
foreach ($allInlinedDefinitions as $def) {
$arguments = array($def->getArguments(), $def->getFactory(), $def->getProperties(), $def->getMethodCalls(), $def->getConfigurator());
$this->getServiceCallsFromArguments($arguments, $allCalls, false, $cId, $behavior, $allInlinedDefinitions[$def]);
}
$collectLineage = $this->inlineRequires && !$this->isHotPath($definition);
$isNonLazyShared = isset($this->circularReferences[$cId]) && !$this->getProxyDumper()->isProxyCandidate($definition) && $definition->isShared();
$lineage = $calls = $behavior = array();
foreach ($inlinedDefinitions as $iDefinition) {
if ($collectLineage && !$iDefinition->isDeprecated() && $class = is_array($factory = $iDefinition->getFactory()) && is_string($factory[0]) ? $factory[0] : $iDefinition->getClass()) {
$this->collectLineage($class, $lineage);
$isPreInstance = isset($inlinedDefinitions[$definition]) && isset($this->circularReferences[$cId]) && !$this->getProxyDumper()->isProxyCandidate($definition) && $definition->isShared();
foreach ($inlinedDefinitions as $def) {
$this->getServiceCallsFromArguments(array($def->getArguments(), $def->getFactory()), $calls, $isPreInstance, $cId);
if ($def !== $definition) {
$arguments = array($def->getProperties(), $def->getMethodCalls(), $def->getConfigurator());
$this->getServiceCallsFromArguments($arguments, $calls, $isPreInstance && !$this->hasReference($cId, $arguments, true), $cId);
}
$this->getServiceCallsFromArguments($iDefinition->getArguments(), $calls, $behavior, $isNonLazyShared, $cId);
$isPreInstantiation = $isNonLazyShared && $iDefinition !== $definition && !$this->hasReference($cId, $iDefinition->getMethodCalls(), true) && !$this->hasReference($cId, $iDefinition->getProperties(), true);
$this->getServiceCallsFromArguments($iDefinition->getMethodCalls(), $calls, $behavior, $isPreInstantiation, $cId);
$this->getServiceCallsFromArguments($iDefinition->getProperties(), $calls, $behavior, $isPreInstantiation, $cId);
$this->getServiceCallsFromArguments(array($iDefinition->getConfigurator()), $calls, $behavior, $isPreInstantiation, $cId);
$this->getServiceCallsFromArguments(array($iDefinition->getFactory()), $calls, $behavior, $isNonLazyShared, $cId);
}
if (!isset($inlinedDefinitions[$definition])) {
$arguments = array($definition->getProperties(), $definition->getMethodCalls(), $definition->getConfigurator());
$this->getServiceCallsFromArguments($arguments, $calls, false, $cId);
}
$code = '';
foreach ($calls as $id => $callCount) {
if ('service_container' === $id || $id === $cId) {
if ('service_container' === $id || $id === $cId || isset($this->referenceVariables[$id])) {
continue;
}
if ($callCount <= 1 && $allCalls[$id] <= 1) {
continue;
}
if ($collectLineage && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE !== $behavior[$id] && $this->container->has($id)
&& $this->isTrivialInstance($iDefinition = $this->container->findDefinition($id))
&& $class = is_array($factory = $iDefinition->getFactory()) && is_string($factory[0]) ? $factory[0] : $iDefinition->getClass()
) {
$this->collectLineage($class, $lineage);
}
$name = $this->getNextVariableName();
$this->referenceVariables[$id] = new Variable($name);
if ($callCount > 1) {
$name = $this->getNextVariableName();
$this->referenceVariables[$id] = new Variable($name);
if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $behavior[$id]) {
$code .= sprintf($template, $name, $this->getServiceCall($id));
} else {
$code .= sprintf($template, $name, $this->getServiceCall($id, new Reference($id, $behavior[$id])));
}
}
$reference = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $behavior[$id] ? new Reference($id, $behavior[$id]) : null;
$code .= sprintf(" \$%s = %s;\n", $name, $this->getServiceCall($id, $reference));
}
if ('' !== $code) {
if ($isNonLazyShared) {
if ($isPreInstance) {
$code .= <<<EOTXT
if (isset(\$this->services['$cId'])) {
@ -336,14 +321,6 @@ EOTXT;
$code .= "\n";
}
if ($lineage && $lineage = array_diff_key(array_flip($lineage), $this->inlinedRequires)) {
$code = "\n".$code;
foreach (array_reverse($lineage) as $file => $class) {
$code = sprintf(" require_once %s;\n", $file).$code;
}
}
return $code;
}
@ -353,11 +330,6 @@ EOTXT;
$node = $edge->getDestNode();
$id = $node->getId();
if (isset($checkedNodes[$id])) {
continue;
}
$checkedNodes[$id] = true;
if ($node->getValue() && ($edge->isLazy() || $edge->isWeak())) {
// no-op
} elseif (isset($currentPath[$id])) {
@ -365,10 +337,11 @@ EOTXT;
$this->circularReferences[$parentId][$id] = $id;
$id = $parentId;
}
} else {
} elseif (!isset($checkedNodes[$id])) {
$checkedNodes[$id] = true;
$currentPath[$id] = $id;
$this->analyzeCircularReferences($node->getOutEdges(), $checkedNodes, $currentPath);
array_pop($currentPath);
unset($currentPath[$id]);
}
}
}
@ -428,18 +401,39 @@ EOTXT;
*
* @return string
*/
private function addServiceInclude(Definition $definition, array $inlinedDefinitions)
private function addServiceInclude($cId, Definition $definition, \SplObjectStorage $inlinedDefinitions)
{
$template = " require_once %s;\n";
$code = '';
if (null !== $file = $definition->getFile()) {
$code .= sprintf($template, $this->dumpValue($file));
if ($this->inlineRequires && !$this->isHotPath($definition)) {
$lineage = $calls = $behavior = array();
foreach ($inlinedDefinitions as $def) {
if (!$def->isDeprecated() && $class = is_array($factory = $def->getFactory()) && is_string($factory[0]) ? $factory[0] : $def->getClass()) {
$this->collectLineage($class, $lineage);
}
$arguments = array($def->getArguments(), $def->getFactory(), $def->getProperties(), $def->getMethodCalls(), $def->getConfigurator());
$this->getServiceCallsFromArguments($arguments, $calls, false, $cId, $behavior, $inlinedDefinitions[$def]);
}
foreach ($calls as $id => $callCount) {
if ('service_container' !== $id && $id !== $cId
&& ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE !== $behavior[$id]
&& $this->container->has($id)
&& $this->isTrivialInstance($def = $this->container->findDefinition($id))
&& $class = is_array($factory = $def->getFactory()) && is_string($factory[0]) ? $factory[0] : $def->getClass()
) {
$this->collectLineage($class, $lineage);
}
}
foreach (array_diff_key(array_flip($lineage), $this->inlinedRequires) as $file => $class) {
$code .= sprintf(" require_once %s;\n", $file);
}
}
foreach ($inlinedDefinitions as $definition) {
if (null !== $file = $definition->getFile()) {
$code .= sprintf($template, $this->dumpValue($file));
foreach ($inlinedDefinitions as $def) {
if ($file = $def->getFile()) {
$code .= sprintf(" require_once %s;\n", $this->dumpValue($file));
}
}
@ -453,62 +447,51 @@ EOTXT;
/**
* Generates the inline definition of a service.
*
* @param string $id
* @param array $inlinedDefinitions
*
* @return string
*
* @throws RuntimeException When the factory definition is incomplete
* @throws ServiceCircularReferenceException When a circular reference is detected
*/
private function addServiceInlinedDefinitions($id, array $inlinedDefinitions)
private function addServiceInlinedDefinitions($id, Definition $definition, \SplObjectStorage $inlinedDefinitions, &$isSimpleInstance)
{
$code = '';
$variableMap = $this->definitionVariables;
$nbOccurrences = new \SplObjectStorage();
$processed = new \SplObjectStorage();
foreach ($inlinedDefinitions as $definition) {
if (false === $nbOccurrences->contains($definition)) {
$nbOccurrences->offsetSet($definition, 1);
} else {
$i = $nbOccurrences->offsetGet($definition);
$nbOccurrences->offsetSet($definition, $i + 1);
}
}
foreach ($inlinedDefinitions as $sDefinition) {
if ($processed->contains($sDefinition)) {
foreach ($inlinedDefinitions as $def) {
if ($definition === $def) {
continue;
}
$processed->offsetSet($sDefinition);
$class = $this->dumpValue($sDefinition->getClass());
if ($nbOccurrences->offsetGet($sDefinition) > 1 || $sDefinition->getMethodCalls() || $sDefinition->getProperties() || null !== $sDefinition->getConfigurator() || false !== strpos($class, '$')) {
$name = $this->getNextVariableName();
$variableMap->offsetSet($sDefinition, new Variable($name));
// a construct like:
// $a = new ServiceA(ServiceB $b); $b = new ServiceB(ServiceA $a);
// this is an indication for a wrong implementation, you can circumvent this problem
// by setting up your service structure like this:
// $b = new ServiceB();
// $a = new ServiceA(ServiceB $b);
// $b->setServiceA(ServiceA $a);
if ($this->hasReference($id, $sDefinition->getArguments())) {
throw new ServiceCircularReferenceException($id, array($id));
}
$code .= $this->addNewInstance($sDefinition, '$'.$name, ' = ', $id);
if (!$this->hasReference($id, $sDefinition->getMethodCalls(), true) && !$this->hasReference($id, $sDefinition->getProperties(), true)) {
$code .= $this->addServiceProperties($sDefinition, $name);
$code .= $this->addServiceMethodCalls($sDefinition, $name);
$code .= $this->addServiceConfigurator($sDefinition, $name);
}
$code .= "\n";
if ($inlinedDefinitions[$def] <= 1 && !$def->getMethodCalls() && !$def->getProperties() && !$def->getConfigurator() && false === strpos($this->dumpValue($def->getClass()), '$')) {
continue;
}
if (isset($this->definitionVariables[$def])) {
$name = $this->definitionVariables[$def];
} else {
$name = $this->getNextVariableName();
$this->definitionVariables[$def] = new Variable($name);
}
// a construct like:
// $a = new ServiceA(ServiceB $b); $b = new ServiceB(ServiceA $a);
// this is an indication for a wrong implementation, you can circumvent this problem
// by setting up your service structure like this:
// $b = new ServiceB();
// $a = new ServiceA(ServiceB $b);
// $b->setServiceA(ServiceA $a);
if ($this->hasReference($id, array($def->getArguments(), $def->getFactory()))) {
throw new ServiceCircularReferenceException($id, array($id));
}
$code .= $this->addNewInstance($def, '$'.$name, ' = ', $id);
if (!$this->hasReference($id, array($def->getProperties(), $def->getMethodCalls(), $def->getConfigurator()), true)) {
$code .= $this->addServiceProperties($def, $name);
$code .= $this->addServiceMethodCalls($def, $name);
$code .= $this->addServiceConfigurator($def, $name);
} else {
$isSimpleInstance = false;
}
$code .= "\n";
}
return $code;
@ -559,29 +542,6 @@ EOTXT;
return $code;
}
/**
* Checks if the definition is a simple instance.
*
* @param string $id
* @param Definition $definition
*
* @return bool
*/
private function isSimpleInstance($id, Definition $definition, array $inlinedDefinitions)
{
foreach (array_merge(array($definition), $inlinedDefinitions) as $sDefinition) {
if ($definition !== $sDefinition && !$this->hasReference($id, $sDefinition->getMethodCalls())) {
continue;
}
if ($sDefinition->getMethodCalls() || $sDefinition->getProperties() || $sDefinition->getConfigurator()) {
return false;
}
}
return true;
}
/**
* Checks if the definition is a trivial instance.
*
@ -667,27 +627,17 @@ EOTXT;
/**
* Generates the inline definition setup.
*
* @param string $id
* @param array $inlinedDefinitions
* @param bool $isSimpleInstance
*
* @return string
*
* @throws ServiceCircularReferenceException when the container contains a circular reference
*/
private function addServiceInlinedDefinitionsSetup($id, array $inlinedDefinitions, $isSimpleInstance)
private function addServiceInlinedDefinitionsSetup($id, Definition $definition, \SplObjectStorage $inlinedDefinitions, $isSimpleInstance)
{
$this->referenceVariables[$id] = new Variable('instance');
$code = '';
$processed = new \SplObjectStorage();
foreach ($inlinedDefinitions as $iDefinition) {
if ($processed->contains($iDefinition)) {
continue;
}
$processed->offsetSet($iDefinition);
if (!$this->hasReference($id, $iDefinition->getMethodCalls(), true) && !$this->hasReference($id, $iDefinition->getProperties(), true)) {
foreach ($inlinedDefinitions as $def) {
if ($definition === $def || !$this->hasReference($id, array($def->getProperties(), $def->getMethodCalls(), $def->getConfigurator()), true)) {
continue;
}
@ -697,13 +647,13 @@ EOTXT;
throw new ServiceCircularReferenceException($id, array($id));
}
$name = (string) $this->definitionVariables->offsetGet($iDefinition);
$code .= $this->addServiceProperties($iDefinition, $name);
$code .= $this->addServiceMethodCalls($iDefinition, $name);
$code .= $this->addServiceConfigurator($iDefinition, $name);
$name = (string) $this->definitionVariables[$def];
$code .= $this->addServiceProperties($def, $name);
$code .= $this->addServiceMethodCalls($def, $name);
$code .= $this->addServiceConfigurator($def, $name);
}
if ('' !== $code) {
if ('' !== $code && ($definition->getProperties() || $definition->getMethodCalls() || $definition->getConfigurator())) {
$code .= "\n";
}
@ -828,15 +778,28 @@ EOF;
$code .= sprintf(" @trigger_error(%s, E_USER_DEPRECATED);\n\n", $this->export($definition->getDeprecationMessage($id)));
}
$inlinedDefinitions = $this->getInlinedDefinitions($definition);
$isSimpleInstance = $this->isSimpleInstance($id, $definition, $inlinedDefinitions);
$inlinedDefinitions = $this->getDefinitionsFromArguments(array($definition));
$constructorDefinitions = $this->getDefinitionsFromArguments(array($definition->getArguments(), $definition->getFactory()));
$otherDefinitions = new \SplObjectStorage();
foreach ($inlinedDefinitions as $def) {
if ($def === $definition || isset($constructorDefinitions[$def])) {
$constructorDefinitions[$def] = $inlinedDefinitions[$def];
} else {
$otherDefinitions[$def] = $inlinedDefinitions[$def];
}
}
$isSimpleInstance = !$definition->getProperties() && !$definition->getMethodCalls() && !$definition->getConfigurator();
$code .=
$this->addServiceInclude($definition, $inlinedDefinitions).
$this->addServiceLocalTempVariables($id, $definition, $inlinedDefinitions).
$this->addServiceInlinedDefinitions($id, $inlinedDefinitions).
$this->addServiceInclude($id, $definition, $inlinedDefinitions).
$this->addServiceLocalTempVariables($id, $definition, $constructorDefinitions, $inlinedDefinitions).
$this->addServiceInlinedDefinitions($id, $definition, $constructorDefinitions, $isSimpleInstance).
$this->addServiceInstance($id, $definition, $isSimpleInstance).
$this->addServiceInlinedDefinitionsSetup($id, $inlinedDefinitions, $isSimpleInstance).
$this->addServiceLocalTempVariables($id, $definition, $otherDefinitions, $inlinedDefinitions).
$this->addServiceInlinedDefinitions($id, $definition, $otherDefinitions, $isSimpleInstance).
$this->addServiceInlinedDefinitionsSetup($id, $definition, $inlinedDefinitions, $isSimpleInstance).
$this->addServiceProperties($definition).
$this->addServiceMethodCalls($definition).
$this->addServiceConfigurator($definition).
@ -1255,11 +1218,10 @@ EOF;
foreach ($this->container->findTaggedServiceIds($this->hotPathTag) as $id => $tags) {
$definition = $this->container->getDefinition($id);
$inlinedDefinitions = $this->getInlinedDefinitions($definition);
array_unshift($inlinedDefinitions, $definition);
$inlinedDefinitions = $this->getDefinitionsFromArguments(array($definition));
foreach ($inlinedDefinitions as $iDefinition) {
if ($class = is_array($factory = $iDefinition->getFactory()) && is_string($factory[0]) ? $factory[0] : $iDefinition->getClass()) {
foreach ($inlinedDefinitions as $def) {
if ($class = is_array($factory = $def->getFactory()) && is_string($factory[0]) ? $factory[0] : $def->getClass()) {
$this->collectLineage($class, $lineage);
}
}
@ -1539,16 +1501,16 @@ EOF;
/**
* Builds service calls from arguments.
*/
private function getServiceCallsFromArguments(array $arguments, array &$calls, array &$behavior, $isPreInstantiation, $callerId)
private function getServiceCallsFromArguments(array $arguments, array &$calls, $isPreInstance, $callerId, array &$behavior = array(), $step = 1)
{
foreach ($arguments as $argument) {
if (is_array($argument)) {
$this->getServiceCallsFromArguments($argument, $calls, $behavior, $isPreInstantiation, $callerId);
$this->getServiceCallsFromArguments($argument, $calls, $isPreInstance, $callerId, $behavior, $step);
} elseif ($argument instanceof Reference) {
$id = (string) $argument;
if (!isset($calls[$id])) {
$calls[$id] = (int) ($isPreInstantiation && isset($this->circularReferences[$callerId][$id]));
$calls[$id] = (int) ($isPreInstance && isset($this->circularReferences[$callerId][$id]));
}
if (!isset($behavior[$id])) {
$behavior[$id] = $argument->getInvalidBehavior();
@ -1556,52 +1518,35 @@ EOF;
$behavior[$id] = min($behavior[$id], $argument->getInvalidBehavior());
}
++$calls[$id];
$calls[$id] += $step;
}
}
}
/**
* Returns the inline definition.
*
* @return array
*/
private function getInlinedDefinitions(Definition $definition)
private function getDefinitionsFromArguments(array $arguments, \SplObjectStorage $definitions = null)
{
if (false === $this->inlinedDefinitions->contains($definition)) {
$definitions = array_merge(
$this->getDefinitionsFromArguments($definition->getArguments()),
$this->getDefinitionsFromArguments($definition->getMethodCalls()),
$this->getDefinitionsFromArguments($definition->getProperties()),
$this->getDefinitionsFromArguments(array($definition->getConfigurator())),
$this->getDefinitionsFromArguments(array($definition->getFactory()))
);
$this->inlinedDefinitions->offsetSet($definition, $definitions);
return $definitions;
if (null === $definitions) {
$definitions = new \SplObjectStorage();
}
return $this->inlinedDefinitions->offsetGet($definition);
}
/**
* Gets the definition from arguments.
*
* @return array
*/
private function getDefinitionsFromArguments(array $arguments)
{
$definitions = array();
foreach ($arguments as $argument) {
if (is_array($argument)) {
$definitions = array_merge($definitions, $this->getDefinitionsFromArguments($argument));
} elseif ($argument instanceof Definition) {
$definitions = array_merge(
$definitions,
$this->getInlinedDefinitions($argument),
array($argument)
);
$this->getDefinitionsFromArguments($argument, $definitions);
} elseif (!$argument instanceof Definition) {
// no-op
} elseif (isset($definitions[$argument])) {
$definitions[$argument] = 1 + $definitions[$argument];
} else {
$definitions[$argument] = 1;
$this->getDefinitionsFromArguments($argument->getArguments(), $definitions);
$this->getDefinitionsFromArguments(array($argument->getFactory()), $definitions);
$this->getDefinitionsFromArguments($argument->getProperties(), $definitions);
$this->getDefinitionsFromArguments($argument->getMethodCalls(), $definitions);
$this->getDefinitionsFromArguments(array($argument->getConfigurator()), $definitions);
// move current definition last in the list
$nbOccurences = $definitions[$argument];
unset($definitions[$argument]);
$definitions[$argument] = $nbOccurences;
}
}
@ -1656,9 +1601,7 @@ EOF;
continue;
}
$arguments = array_merge($service->getMethodCalls(), $service->getArguments(), $service->getProperties());
if ($this->hasReference($id, $arguments, $deep, $visited)) {
if ($this->hasReference($id, array($service->getArguments(), $service->getFactory(), $service->getProperties(), $service->getMethodCalls(), $service->getConfigurator()), $deep, $visited)) {
return true;
}
}
@ -1740,7 +1683,7 @@ EOF;
}
} elseif ($value instanceof Definition) {
if (null !== $this->definitionVariables && $this->definitionVariables->contains($value)) {
return $this->dumpValue($this->definitionVariables->offsetGet($value), $interpolate);
return $this->dumpValue($this->definitionVariables[$value], $interpolate);
}
if ($value->getMethodCalls()) {
throw new RuntimeException('Cannot dump definitions which have method calls.');

View File

@ -1249,24 +1249,26 @@ class ContainerBuilderTest extends TestCase
$this->assertEquals(array('foo1' => new \stdClass(), 'foo3' => new \stdClass()), iterator_to_array($bar->iter));
}
public function testAlmostCircularPrivate()
/**
* @dataProvider provideAlmostCircular
*/
public function testAlmostCircular($visibility)
{
$public = false;
$container = include __DIR__.'/Fixtures/containers/container_almost_circular.php';
$foo = $container->get('foo');
$this->assertSame($foo, $foo->bar->foobar->foo);
$foo2 = $container->get('foo2');
$this->assertSame($foo2, $foo2->bar->foobar->foo);
$this->assertSame(array(), (array) $container->get('foobar4'));
}
public function testAlmostCircularPublic()
public function provideAlmostCircular()
{
$public = true;
$container = include __DIR__.'/Fixtures/containers/container_almost_circular.php';
$foo = $container->get('foo');
$this->assertSame($foo, $foo->bar->foobar->foo);
yield array('public');
yield array('private');
}
public function testRegisterForAutoconfiguration()

View File

@ -302,21 +302,6 @@ class PhpDumperTest extends TestCase
$this->assertSame($decorator, $container->get('decorator_service'), '->set() overrides an already defined service');
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException
*/
public function testCircularReference()
{
$container = new ContainerBuilder();
$container->register('foo', 'stdClass')->addArgument(new Reference('bar'))->setPublic(true);
$container->register('bar', 'stdClass')->setPublic(false)->addMethodCall('setA', array(new Reference('baz')));
$container->register('baz', 'stdClass')->addMethodCall('setA', array(new Reference('foo')))->setPublic(true);
$container->compile();
$dumper = new PhpDumper($container);
$dumper->dump();
}
public function testDumpAutowireData()
{
$container = include self::$fixturesPath.'/containers/container24.php';
@ -774,38 +759,35 @@ class PhpDumperTest extends TestCase
$this->assertEquals(array('foo1' => new \stdClass(), 'foo3' => new \stdClass()), iterator_to_array($bar->iter));
}
public function testAlmostCircularPrivate()
/**
* @dataProvider provideAlmostCircular
*/
public function testAlmostCircular($visibility)
{
$public = false;
$container = include self::$fixturesPath.'/containers/container_almost_circular.php';
$container->compile();
$dumper = new PhpDumper($container);
$this->assertStringEqualsFile(self::$fixturesPath.'/php/container_almost_circular_private.php', $dumper->dump(array('class' => 'Symfony_DI_PhpDumper_Test_Almost_Circular_Private')));
$container = 'Symfony_DI_PhpDumper_Test_Almost_Circular_'.ucfirst($visibility);
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services_almost_circular_'.$visibility.'.php', $dumper->dump(array('class' => $container)));
require self::$fixturesPath.'/php/container_almost_circular_private.php';
require self::$fixturesPath.'/php/services_almost_circular_'.$visibility.'.php';
$container = new $container();
$container = new \Symfony_DI_PhpDumper_Test_Almost_Circular_Private();
$foo = $container->get('foo');
$this->assertSame($foo, $foo->bar->foobar->foo);
$foo2 = $container->get('foo2');
$this->assertSame($foo2, $foo2->bar->foobar->foo);
$this->assertSame(array(), (array) $container->get('foobar4'));
}
public function testAlmostCircularPublic()
public function provideAlmostCircular()
{
$public = true;
$container = include self::$fixturesPath.'/containers/container_almost_circular.php';
$container->compile();
$dumper = new PhpDumper($container);
$this->assertStringEqualsFile(self::$fixturesPath.'/php/container_almost_circular_public.php', $dumper->dump(array('class' => 'Symfony_DI_PhpDumper_Test_Almost_Circular_Public')));
require self::$fixturesPath.'/php/container_almost_circular_public.php';
$container = new \Symfony_DI_PhpDumper_Test_Almost_Circular_Public();
$foo = $container->get('foo');
$this->assertSame($foo, $foo->bar->foobar->foo);
yield array('public');
yield array('private');
}
public function testHotPathOptimizations()
@ -815,12 +797,12 @@ class PhpDumperTest extends TestCase
$container->compile();
$dumper = new PhpDumper($container);
$dump = $dumper->dump(array('hot_path_tag' => 'container.hot_path', 'inline_class_loader_parameter' => 'inline_requires', 'file' => self::$fixturesPath.'/php/container_inline_requires.php'));
$dump = $dumper->dump(array('hot_path_tag' => 'container.hot_path', 'inline_class_loader_parameter' => 'inline_requires', 'file' => self::$fixturesPath.'/php/services_inline_requires.php'));
if ('\\' === DIRECTORY_SEPARATOR) {
$dump = str_replace("'\\\\includes\\\\HotPath\\\\", "'/includes/HotPath/", $dump);
}
$this->assertStringEqualsFile(self::$fixturesPath.'/php/container_inline_requires.php', $dump);
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services_inline_requires.php', $dump);
}
public function testDumpHandlesLiteralClassWithRootNamespace()

View File

@ -5,8 +5,11 @@ use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
use Symfony\Component\DependencyInjection\Reference;
$public = 'public' === $visibility;
$container = new ContainerBuilder();
// same visibility for deps
$container->register('foo', FooCircular::class)->setPublic(true)
->addArgument(new Reference('bar'));
@ -16,4 +19,31 @@ $container->register('bar', BarCircular::class)->setPublic($public)
$container->register('foobar', FoobarCircular::class)->setPublic($public)
->addArgument(new Reference('foo'));
// mixed visibility for deps
$container->register('foo2', FooCircular::class)->setPublic(true)
->addArgument(new Reference('bar2'));
$container->register('bar2', BarCircular::class)->setPublic(!$public)
->addMethodCall('addFoobar', array(new Reference('foobar2')));
$container->register('foobar2', FoobarCircular::class)->setPublic($public)
->addArgument(new Reference('foo2'));
// simple inline setter with internal reference
$container->register('bar3', BarCircular::class)->setPublic(true)
->addMethodCall('addFoobar', array(new Reference('foobar3'), new Reference('foobar3')));
$container->register('foobar3', FoobarCircular::class)->setPublic($public);
// loop with non-shared dep
$container->register('foo4', 'stdClass')->setPublic($public)
->setShared(false)
->setProperty('foobar', new Reference('foobar4'));
$container->register('foobar4', 'stdClass')->setPublic(true)
->addArgument(new Reference('foo4'));
return $container;

View File

@ -33,11 +33,11 @@ use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
// This file has been auto-generated by the Symfony Dependency Injection Component for internal use.
// Returns the public 'configured_service' shared service.
$this->services['configured_service'] = $instance = new \stdClass();
$a = new \ConfClass();
$a->setFoo(${($_ = isset($this->services['baz']) ? $this->services['baz'] : $this->load(__DIR__.'/getBazService.php')) && false ?: '_'});
$this->services['configured_service'] = $instance = new \stdClass();
$a->configureStdClass($instance);
return $instance;
@ -153,10 +153,10 @@ use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
// This file has been auto-generated by the Symfony Dependency Injection Component for internal use.
// Returns the public 'foo_with_inline' shared service.
$a = new \Bar();
$this->services['foo_with_inline'] = $instance = new \Foo();
$a = new \Bar();
$a->pub = 'pub';
$a->setBaz(${($_ = isset($this->services['baz']) ? $this->services['baz'] : $this->load(__DIR__.'/getBazService.php')) && false ?: '_'});

View File

@ -129,11 +129,11 @@ class ProjectServiceContainer extends Container
*/
protected function getConfiguredServiceService()
{
$this->services['configured_service'] = $instance = new \stdClass();
$a = new \ConfClass();
$a->setFoo(${($_ = isset($this->services['baz']) ? $this->services['baz'] : $this->getBazService()) && false ?: '_'});
$this->services['configured_service'] = $instance = new \stdClass();
$a->configureStdClass($instance);
return $instance;
@ -259,10 +259,10 @@ class ProjectServiceContainer extends Container
*/
protected function getFooWithInlineService()
{
$a = new \Bar();
$this->services['foo_with_inline'] = $instance = new \Foo();
$a = new \Bar();
$a->pub = 'pub';
$a->setBaz(${($_ = isset($this->services['baz']) ? $this->services['baz'] : $this->getBazService()) && false ?: '_'});

View File

@ -23,7 +23,11 @@ class Symfony_DI_PhpDumper_Test_Almost_Circular_Private extends Container
{
$this->services = array();
$this->methodMap = array(
'bar2' => 'getBar2Service',
'bar3' => 'getBar3Service',
'foo' => 'getFooService',
'foo2' => 'getFoo2Service',
'foobar4' => 'getFoobar4Service',
);
$this->aliases = array();
@ -35,7 +39,10 @@ class Symfony_DI_PhpDumper_Test_Almost_Circular_Private extends Container
'Psr\\Container\\ContainerInterface' => true,
'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true,
'bar' => true,
'foo4' => true,
'foobar' => true,
'foobar2' => true,
'foobar3' => true,
);
}
@ -56,6 +63,36 @@ class Symfony_DI_PhpDumper_Test_Almost_Circular_Private extends Container
return true;
}
/**
* Gets the public 'bar2' shared service.
*
* @return \BarCircular
*/
protected function getBar2Service()
{
$this->services['bar2'] = $instance = new \BarCircular();
$instance->addFoobar(new \FoobarCircular(${($_ = isset($this->services['foo2']) ? $this->services['foo2'] : $this->getFoo2Service()) && false ?: '_'}));
return $instance;
}
/**
* Gets the public 'bar3' shared service.
*
* @return \BarCircular
*/
protected function getBar3Service()
{
$this->services['bar3'] = $instance = new \BarCircular();
$a = new \FoobarCircular();
$instance->addFoobar($a, $a);
return $instance;
}
/**
* Gets the public 'foo' shared service.
*
@ -69,6 +106,37 @@ class Symfony_DI_PhpDumper_Test_Almost_Circular_Private extends Container
$a->addFoobar(new \FoobarCircular($instance));
return $instance;
}
/**
* Gets the public 'foo2' shared service.
*
* @return \FooCircular
*/
protected function getFoo2Service()
{
$a = ${($_ = isset($this->services['bar2']) ? $this->services['bar2'] : $this->getBar2Service()) && false ?: '_'};
if (isset($this->services['foo2'])) {
return $this->services['foo2'];
}
return $this->services['foo2'] = new \FooCircular($a);
}
/**
* Gets the public 'foobar4' shared service.
*
* @return \stdClass
*/
protected function getFoobar4Service()
{
$a = new \stdClass();
$this->services['foobar4'] = $instance = new \stdClass($a);
$a->foobar = $instance;
return $instance;
}

View File

@ -24,8 +24,14 @@ class Symfony_DI_PhpDumper_Test_Almost_Circular_Public extends Container
$this->services = array();
$this->methodMap = array(
'bar' => 'getBarService',
'bar3' => 'getBar3Service',
'foo' => 'getFooService',
'foo2' => 'getFoo2Service',
'foo4' => 'getFoo4Service',
'foobar' => 'getFoobarService',
'foobar2' => 'getFoobar2Service',
'foobar3' => 'getFoobar3Service',
'foobar4' => 'getFoobar4Service',
);
$this->aliases = array();
@ -36,6 +42,7 @@ class Symfony_DI_PhpDumper_Test_Almost_Circular_Public extends Container
return array(
'Psr\\Container\\ContainerInterface' => true,
'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true,
'bar2' => true,
);
}
@ -70,6 +77,22 @@ class Symfony_DI_PhpDumper_Test_Almost_Circular_Public extends Container
return $instance;
}
/**
* Gets the public 'bar3' shared service.
*
* @return \BarCircular
*/
protected function getBar3Service()
{
$this->services['bar3'] = $instance = new \BarCircular();
$a = ${($_ = isset($this->services['foobar3']) ? $this->services['foobar3'] : $this->services['foobar3'] = new \FoobarCircular()) && false ?: '_'};
$instance->addFoobar($a, $a);
return $instance;
}
/**
* Gets the public 'foo' shared service.
*
@ -86,6 +109,36 @@ class Symfony_DI_PhpDumper_Test_Almost_Circular_Public extends Container
return $this->services['foo'] = new \FooCircular($a);
}
/**
* Gets the public 'foo2' shared service.
*
* @return \FooCircular
*/
protected function getFoo2Service()
{
$a = new \BarCircular();
$this->services['foo2'] = $instance = new \FooCircular($a);
$a->addFoobar(${($_ = isset($this->services['foobar2']) ? $this->services['foobar2'] : $this->getFoobar2Service()) && false ?: '_'});
return $instance;
}
/**
* Gets the public 'foo4' service.
*
* @return \stdClass
*/
protected function getFoo4Service()
{
$instance = new \stdClass();
$instance->foobar = ${($_ = isset($this->services['foobar4']) ? $this->services['foobar4'] : $this->getFoobar4Service()) && false ?: '_'};
return $instance;
}
/**
* Gets the public 'foobar' shared service.
*
@ -101,4 +154,46 @@ class Symfony_DI_PhpDumper_Test_Almost_Circular_Public extends Container
return $this->services['foobar'] = new \FoobarCircular($a);
}
/**
* Gets the public 'foobar2' shared service.
*
* @return \FoobarCircular
*/
protected function getFoobar2Service()
{
$a = ${($_ = isset($this->services['foo2']) ? $this->services['foo2'] : $this->getFoo2Service()) && false ?: '_'};
if (isset($this->services['foobar2'])) {
return $this->services['foobar2'];
}
return $this->services['foobar2'] = new \FoobarCircular($a);
}
/**
* Gets the public 'foobar3' shared service.
*
* @return \FoobarCircular
*/
protected function getFoobar3Service()
{
return $this->services['foobar3'] = new \FoobarCircular();
}
/**
* Gets the public 'foobar4' shared service.
*
* @return \stdClass
*/
protected function getFoobar4Service()
{
$a = new \stdClass();
$this->services['foobar4'] = $instance = new \stdClass($a);
$a->foobar = $instance;
return $instance;
}
}

View File

@ -159,12 +159,12 @@ class ProjectServiceContainer extends Container
*/
protected function getTranslator3Service()
{
$a = ${($_ = isset($this->services['translator.loader_3']) ? $this->services['translator.loader_3'] : $this->services['translator.loader_3'] = new \stdClass()) && false ?: '_'};
$this->services['translator_3'] = $instance = new \Symfony\Component\DependencyInjection\Tests\Fixtures\StubbedTranslator(new \Symfony\Component\DependencyInjection\ServiceLocator(array('translator.loader_3' => function () {
return ${($_ = isset($this->services['translator.loader_3']) ? $this->services['translator.loader_3'] : $this->services['translator.loader_3'] = new \stdClass()) && false ?: '_'};
})));
$a = ${($_ = isset($this->services['translator.loader_3']) ? $this->services['translator.loader_3'] : $this->services['translator.loader_3'] = new \stdClass()) && false ?: '_'};
$instance->addResource('db', $a, 'nl');
$instance->addResource('db', $a, 'en');