Merge branch '3.4' into 4.0

* 3.4:
  [DI] Fix circular reference when using setters
  [DI] Clear service reference graph
This commit is contained in:
Nicolas Grekas 2017-11-29 17:55:50 +01:00
commit ff87b4c939
14 changed files with 431 additions and 263 deletions

View File

@ -113,6 +113,8 @@ class Compiler
}
throw $e;
} finally {
$this->getServiceReferenceGraph()->clear();
}
}
}

View File

@ -64,6 +64,9 @@ class ServiceReferenceGraph
*/
public function clear()
{
foreach ($this->nodes as $node) {
$node->clear();
}
$this->nodes = array();
}

View File

@ -107,4 +107,12 @@ class ServiceReferenceGraphNode
{
return $this->value;
}
/**
* Clears all edges.
*/
public function clear()
{
$this->inEdges = $this->outEdges = array();
}
}

View File

@ -519,6 +519,14 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
*/
public function get($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE)
{
return $this->doGet($id, $invalidBehavior);
}
private function doGet($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, array &$inlineServices = array())
{
if (isset($inlineServices[$id])) {
return $inlineServices[$id];
}
if (ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $invalidBehavior) {
return parent::get($id, $invalidBehavior);
}
@ -527,7 +535,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
}
if (!isset($this->definitions[$id]) && isset($this->aliasDefinitions[$id])) {
return $this->get((string) $this->aliasDefinitions[$id], $invalidBehavior);
return $this->doGet((string) $this->aliasDefinitions[$id], $invalidBehavior, $inlineServices);
}
try {
@ -544,7 +552,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]);
}
@ -978,10 +986,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) {
@ -1002,11 +1010,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;
}
@ -1017,7 +1025,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];
@ -1025,7 +1033,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));
}
@ -1051,16 +1059,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()) {
@ -1068,9 +1076,9 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
$callable[0] = $parameterBag->resolveValue($callable[0]);
if ($callable[0] instanceof Reference) {
$callable[0] = $this->get((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);
}
}
@ -1094,14 +1102,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];
@ -1117,7 +1125,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
}
}
foreach (self::getInitializedConditionals($v) as $s) {
if (!$this->get($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE)) {
if (!$this->doGet($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE)) {
continue 2;
}
}
@ -1133,7 +1141,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
}
}
foreach (self::getInitializedConditionals($v) as $s) {
if (!$this->get($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE)) {
if (!$this->doGet($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE)) {
continue 2;
}
}
@ -1144,9 +1152,9 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
return $count;
});
} elseif ($value instanceof Reference) {
$value = $this->get((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));
}
@ -1443,7 +1451,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)) {
@ -1451,12 +1459,12 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
}
}
foreach (self::getInitializedConditionals($call[1]) as $s) {
if (!$this->get($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));
}
/**
@ -1466,14 +1474,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

@ -52,7 +52,6 @@ class PhpDumper extends Dumper
*/
const NON_FIRST_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789_';
private $inlinedDefinitions;
private $definitionVariables;
private $referenceVariables;
private $variableCount;
@ -85,8 +84,6 @@ class PhpDumper extends Dumper
}
parent::__construct($container);
$this->inlinedDefinitions = new \SplObjectStorage();
}
/**
@ -143,6 +140,7 @@ class PhpDumper extends Dumper
$currentPath = array($id => $id);
$this->analyzeCircularReferences($node->getOutEdges(), $checkedNodes, $currentPath);
}
$this->container->getCompiler()->getServiceReferenceGraph()->clear();
$this->docStar = $options['debug'] ? '*' : '';
@ -272,54 +270,46 @@ EOF;
return $this->proxyDumper;
}
private function addServiceLocalTempVariables(string $cId, Definition $definition, array $inlinedDefinitions): string
private function addServiceLocalTempVariables(string $cId, Definition $definition, \SplObjectStorage $inlinedDefinitions, \SplObjectStorage $allInlinedDefinitions): string
{
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 .= sprintf(<<<'EOTXT'
if (isset($this->%s['%s'])) {
@ -333,14 +323,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;
}
@ -350,11 +332,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])) {
@ -362,10 +339,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]);
}
}
}
@ -420,18 +398,39 @@ EOTXT
}
}
private function addServiceInclude(Definition $definition, array $inlinedDefinitions): string
private function addServiceInclude(string $cId, Definition $definition, \SplObjectStorage $inlinedDefinitions): string
{
$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));
}
}
@ -448,54 +447,46 @@ EOTXT
* @throws RuntimeException When the factory definition is incomplete
* @throws ServiceCircularReferenceException When a circular reference is detected
*/
private function addServiceInlinedDefinitions(string $id, array $inlinedDefinitions): string
private function addServiceInlinedDefinitions(string $id, Definition $definition, \SplObjectStorage $inlinedDefinitions, bool &$isSimpleInstance): string
{
$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;
@ -538,21 +529,6 @@ EOTXT
return $code;
}
private function isSimpleInstance(string $id, Definition $definition, array $inlinedDefinitions): bool
{
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;
}
private function isTrivialInstance(Definition $definition): bool
{
if ($definition->isSynthetic() || $definition->getFile() || $definition->getMethodCalls() || $definition->getProperties() || $definition->getConfigurator()) {
@ -623,19 +599,13 @@ EOTXT
/**
* @throws ServiceCircularReferenceException when the container contains a circular reference
*/
private function addServiceInlinedDefinitionsSetup(string $id, array $inlinedDefinitions, bool $isSimpleInstance): string
private function addServiceInlinedDefinitionsSetup(string $id, Definition $definition, \SplObjectStorage $inlinedDefinitions, bool $isSimpleInstance): string
{
$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;
}
@ -645,13 +615,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";
}
@ -759,15 +729,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).
@ -1085,11 +1068,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);
}
}
@ -1298,16 +1280,16 @@ EOF;
return implode(' && ', $conditions);
}
private function getServiceCallsFromArguments(array $arguments, array &$calls, array &$behavior, bool $isPreInstantiation, string $callerId)
private function getServiceCallsFromArguments(array $arguments, array &$calls, bool $isPreInstance, string $callerId, array &$behavior = array(), int $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();
@ -1315,42 +1297,35 @@ EOF;
$behavior[$id] = min($behavior[$id], $argument->getInvalidBehavior());
}
++$calls[$id];
$calls[$id] += $step;
}
}
}
private function getInlinedDefinitions(Definition $definition): array
private function getDefinitionsFromArguments(array $arguments, \SplObjectStorage $definitions = null): \SplObjectStorage
{
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);
}
private function getDefinitionsFromArguments(array $arguments): array
{
$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;
}
}
@ -1395,9 +1370,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;
}
}
@ -1473,7 +1446,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

@ -1223,24 +1223,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

@ -301,21 +301,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';
@ -773,38 +758,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()
@ -814,12 +796,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

@ -66,11 +66,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(($this->services['baz'] ?? $this->load(__DIR__.'/getBazService.php')));
$this->services['configured_service'] = $instance = new \stdClass();
$a->configureStdClass($instance);
return $instance;
@ -186,10 +186,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(($this->services['baz'] ?? $this->load(__DIR__.'/getBazService.php')));

View File

@ -162,11 +162,11 @@ class ProjectServiceContainer extends Container
*/
protected function getConfiguredServiceService()
{
$this->services['configured_service'] = $instance = new \stdClass();
$a = new \ConfClass();
$a->setFoo(($this->services['baz'] ?? $this->getBazService()));
$this->services['configured_service'] = $instance = new \stdClass();
$a->configureStdClass($instance);
return $instance;
@ -292,10 +292,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(($this->services['baz'] ?? $this->getBazService()));

View File

@ -24,7 +24,11 @@ class Symfony_DI_PhpDumper_Test_Almost_Circular_Private extends Container
{
$this->services = $this->privates = array();
$this->methodMap = array(
'bar2' => 'getBar2Service',
'bar3' => 'getBar3Service',
'foo' => 'getFooService',
'foo2' => 'getFoo2Service',
'foobar4' => 'getFoobar4Service',
);
$this->aliases = array();
@ -52,10 +56,43 @@ 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,
);
}
/**
* Gets the public 'bar2' shared service.
*
* @return \BarCircular
*/
protected function getBar2Service()
{
$this->services['bar2'] = $instance = new \BarCircular();
$instance->addFoobar(new \FoobarCircular(($this->services['foo2'] ?? $this->getFoo2Service())));
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 = ($this->services['bar2'] ?? $this->getBar2Service());
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

@ -25,8 +25,14 @@ class Symfony_DI_PhpDumper_Test_Almost_Circular_Public extends Container
$this->services = $this->privates = 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();
@ -53,6 +59,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 = ($this->services['foobar3'] ?? $this->services['foobar3'] = new \FoobarCircular());
$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(($this->services['foobar2'] ?? $this->getFoobar2Service()));
return $instance;
}
/**
* Gets the public 'foo4' service.
*
* @return \stdClass
*/
protected function getFoo4Service()
{
$instance = new \stdClass();
$instance->foobar = ($this->services['foobar4'] ?? $this->getFoobar4Service());
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 = ($this->services['foo2'] ?? $this->getFoo2Service());
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

@ -95,8 +95,8 @@ class ProjectServiceContainer extends Container
*/
protected function getC2Service()
{
require_once $this->targetDirs[1].'/includes/HotPath/C2.php';
require_once $this->targetDirs[1].'/includes/HotPath/C3.php';
require_once $this->targetDirs[1].'/includes/HotPath/C2.php';
return $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C2'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C2(new \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3());
}

View File

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