[DI] Fix missing "id" normalization when dumping the container

This commit is contained in:
Nicolas Grekas 2018-03-01 14:42:47 +01:00
parent c572e6c570
commit 4a5e43eae8
17 changed files with 76 additions and 43 deletions

View File

@ -64,7 +64,8 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements Repe
$this->lazy = false; $this->lazy = false;
foreach ($container->getAliases() as $id => $alias) { foreach ($container->getAliases() as $id => $alias) {
$this->graph->connect($id, $alias, (string) $alias, $this->getDefinition((string) $alias), null); $targetId = $this->getDefinitionId((string) $alias);
$this->graph->connect($id, $alias, $targetId, $this->getDefinition($targetId), null);
} }
parent::process($container); parent::process($container);
@ -87,12 +88,13 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements Repe
return $value; return $value;
} }
if ($value instanceof Reference) { if ($value instanceof Reference) {
$targetDefinition = $this->getDefinition((string) $value); $targetId = $this->getDefinitionId((string) $value);
$targetDefinition = $this->getDefinition($targetId);
$this->graph->connect( $this->graph->connect(
$this->currentId, $this->currentId,
$this->currentDefinition, $this->currentDefinition,
$this->getDefinitionId((string) $value), $targetId,
$targetDefinition, $targetDefinition,
$value, $value,
$this->lazy || ($targetDefinition && $targetDefinition->isLazy()), $this->lazy || ($targetDefinition && $targetDefinition->isLazy()),
@ -134,8 +136,6 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements Repe
*/ */
private function getDefinition($id) private function getDefinition($id)
{ {
$id = $this->getDefinitionId($id);
return null === $id ? null : $this->container->getDefinition($id); return null === $id ? null : $this->container->getDefinition($id);
} }
@ -149,7 +149,7 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements Repe
return; return;
} }
return $id; return $this->container->normalizeId($id);
} }
private function getExpressionLanguage() private function getExpressionLanguage()
@ -163,11 +163,12 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements Repe
$this->expressionLanguage = new ExpressionLanguage(null, $providers, function ($arg) { $this->expressionLanguage = new ExpressionLanguage(null, $providers, function ($arg) {
if ('""' === substr_replace($arg, '', 1, -1)) { if ('""' === substr_replace($arg, '', 1, -1)) {
$id = stripcslashes(substr($arg, 1, -1)); $id = stripcslashes(substr($arg, 1, -1));
$id = $this->getDefinitionId($id);
$this->graph->connect( $this->graph->connect(
$this->currentId, $this->currentId,
$this->currentDefinition, $this->currentDefinition,
$this->getDefinitionId($id), $id,
$this->getDefinition($id) $this->getDefinition($id)
); );
} }

View File

@ -281,7 +281,7 @@ class AutowirePass extends AbstractRecursivePass
$this->lastFailure = null; $this->lastFailure = null;
$type = $reference->getType(); $type = $reference->getType();
if ($type !== (string) $reference || ($this->container->has($type) && !$this->container->findDefinition($type)->isAbstract())) { if ($type !== $this->container->normalizeId($reference) || ($this->container->has($type) && !$this->container->findDefinition($type)->isAbstract())) {
return $reference; return $reference;
} }

View File

@ -50,7 +50,7 @@ class DecoratorServicePass implements CompilerPassInterface
$alias = $container->getAlias($inner); $alias = $container->getAlias($inner);
$public = $alias->isPublic(); $public = $alias->isPublic();
$private = $alias->isPrivate(); $private = $alias->isPrivate();
$container->setAlias($renamedId, new Alias((string) $alias, false)); $container->setAlias($renamedId, new Alias($container->normalizeId($alias), false));
} else { } else {
$decoratedDefinition = $container->getDefinition($inner); $decoratedDefinition = $container->getDefinition($inner);
$definition->setTags(array_merge($decoratedDefinition->getTags(), $definition->getTags())); $definition->setTags(array_merge($decoratedDefinition->getTags(), $definition->getTags()));

View File

@ -51,13 +51,12 @@ class FactoryReturnTypePass implements CompilerPassInterface
private function updateDefinition(ContainerBuilder $container, $id, Definition $definition, array $resolveClassPassChanges, array $previous = array()) private function updateDefinition(ContainerBuilder $container, $id, Definition $definition, array $resolveClassPassChanges, array $previous = array())
{ {
// circular reference // circular reference
$lcId = strtolower($id); if (isset($previous[$id])) {
if (isset($previous[$lcId])) {
return; return;
} }
$factory = $definition->getFactory(); $factory = $definition->getFactory();
if (null === $factory || (!isset($resolveClassPassChanges[$lcId]) && null !== $definition->getClass())) { if (null === $factory || (!isset($resolveClassPassChanges[$id]) && null !== $definition->getClass())) {
return; return;
} }
@ -73,9 +72,10 @@ class FactoryReturnTypePass implements CompilerPassInterface
} }
} else { } else {
if ($factory[0] instanceof Reference) { if ($factory[0] instanceof Reference) {
$previous[$lcId] = true; $previous[$id] = true;
$factoryDefinition = $container->findDefinition((string) $factory[0]); $factoryId = $container->normalizeId($factory[0]);
$this->updateDefinition($container, $factory[0], $factoryDefinition, $resolveClassPassChanges, $previous); $factoryDefinition = $container->findDefinition($factoryId);
$this->updateDefinition($container, $factoryId, $factoryDefinition, $resolveClassPassChanges, $previous);
$class = $factoryDefinition->getClass(); $class = $factoryDefinition->getClass();
} else { } else {
$class = $factory[0]; $class = $factory[0];
@ -103,7 +103,7 @@ class FactoryReturnTypePass implements CompilerPassInterface
} }
} }
if (null !== $returnType && (!isset($resolveClassPassChanges[$lcId]) || $returnType !== $resolveClassPassChanges[$lcId])) { if (null !== $returnType && (!isset($resolveClassPassChanges[$id]) || $returnType !== $resolveClassPassChanges[$id])) {
@trigger_error(sprintf('Relying on its factory\'s return-type to define the class of service "%s" is deprecated since Symfony 3.3 and won\'t work in 4.0. Set the "class" attribute to "%s" on the service definition instead.', $id, $returnType), E_USER_DEPRECATED); @trigger_error(sprintf('Relying on its factory\'s return-type to define the class of service "%s" is deprecated since Symfony 3.3 and won\'t work in 4.0. Set the "class" attribute to "%s" on the service definition instead.', $id, $returnType), E_USER_DEPRECATED);
} }
$definition->setClass($returnType); $definition->setClass($returnType);

View File

@ -67,7 +67,7 @@ class InlineServiceDefinitionsPass extends AbstractRecursivePass implements Repe
$value = clone $value; $value = clone $value;
} }
if (!$value instanceof Reference || !$this->container->hasDefinition($id = (string) $value)) { if (!$value instanceof Reference || !$this->container->hasDefinition($id = $this->container->normalizeId($value))) {
return parent::processValue($value, $isRoot); return parent::processValue($value, $isRoot);
} }

View File

@ -85,7 +85,7 @@ class RegisterServiceSubscribersPass extends AbstractRecursivePass
$serviceMap[$key] = new Reference($type); $serviceMap[$key] = new Reference($type);
} }
$subscriberMap[$key] = new TypedReference((string) $serviceMap[$key], $type, $declaringClass, $optionalBehavior ?: ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE); $subscriberMap[$key] = new TypedReference($this->container->normalizeId($serviceMap[$key]), $type, $declaringClass, $optionalBehavior ?: ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE);
unset($serviceMap[$key]); unset($serviceMap[$key]);
} }

View File

@ -36,7 +36,7 @@ class ReplaceAliasByActualDefinitionPass extends AbstractRecursivePass
$seenAliasTargets = array(); $seenAliasTargets = array();
$replacements = array(); $replacements = array();
foreach ($container->getAliases() as $definitionId => $target) { foreach ($container->getAliases() as $definitionId => $target) {
$targetId = (string) $target; $targetId = $container->normalizeId($target);
// Special case: leave this target alone // Special case: leave this target alone
if ('service_container' === $targetId) { if ('service_container' === $targetId) {
continue; continue;
@ -77,7 +77,7 @@ class ReplaceAliasByActualDefinitionPass extends AbstractRecursivePass
*/ */
protected function processValue($value, $isRoot = false) protected function processValue($value, $isRoot = false)
{ {
if ($value instanceof Reference && isset($this->replacements[$referenceId = (string) $value])) { if ($value instanceof Reference && isset($this->replacements[$referenceId = $this->container->normalizeId($value)])) {
// Perform the replacement // Perform the replacement
$newId = $this->replacements[$referenceId]; $newId = $this->replacements[$referenceId];
$value = new Reference($newId, $value->getInvalidBehavior()); $value = new Reference($newId, $value->getInvalidBehavior());

View File

@ -49,7 +49,7 @@ class ResolveBindingsPass extends AbstractRecursivePass
*/ */
protected function processValue($value, $isRoot = false) protected function processValue($value, $isRoot = false)
{ {
if ($value instanceof TypedReference && $value->getType() === (string) $value) { if ($value instanceof TypedReference && $value->getType() === $this->container->normalizeId($value)) {
// Already checked // Already checked
$bindings = $this->container->getDefinition($this->currentId)->getBindings(); $bindings = $this->container->getDefinition($this->currentId)->getBindings();

View File

@ -55,7 +55,7 @@ class ResolveHotPathPass extends AbstractRecursivePass
if ($value instanceof Definition && $isRoot && (isset($this->resolvedIds[$this->currentId]) || !$value->hasTag($this->tagName) || $value->isDeprecated())) { if ($value instanceof Definition && $isRoot && (isset($this->resolvedIds[$this->currentId]) || !$value->hasTag($this->tagName) || $value->isDeprecated())) {
return $value->isDeprecated() ? $value->clearTag($this->tagName) : $value; return $value->isDeprecated() ? $value->clearTag($this->tagName) : $value;
} }
if ($value instanceof Reference && ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE !== $value->getInvalidBehavior() && $this->container->has($id = (string) $value)) { if ($value instanceof Reference && ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE !== $value->getInvalidBehavior() && $this->container->has($id = $this->container->normalizeId($value))) {
$definition = $this->container->findDefinition($id); $definition = $this->container->findDefinition($id);
if (!$definition->hasTag($this->tagName) && !$definition->isDeprecated()) { if (!$definition->hasTag($this->tagName) && !$definition->isDeprecated()) {
$this->resolvedIds[$id] = true; $this->resolvedIds[$id] = true;

View File

@ -90,9 +90,7 @@ class ResolveInvalidReferencesPass implements CompilerPassInterface
$value = array_values($value); $value = array_values($value);
} }
} elseif ($value instanceof Reference) { } elseif ($value instanceof Reference) {
$id = (string) $value; if ($this->container->has($value)) {
if ($this->container->has($id)) {
return $value; return $value;
} }
$invalidBehavior = $value->getInvalidBehavior(); $invalidBehavior = $value->getInvalidBehavior();

View File

@ -30,7 +30,7 @@ class ResolveReferencesToAliasesPass extends AbstractRecursivePass
parent::process($container); parent::process($container);
foreach ($container->getAliases() as $id => $alias) { foreach ($container->getAliases() as $id => $alias) {
$aliasId = (string) $alias; $aliasId = $container->normalizeId($alias);
if ($aliasId !== $defId = $this->getDefinitionId($aliasId, $container)) { if ($aliasId !== $defId = $this->getDefinitionId($aliasId, $container)) {
$container->setAlias($id, $defId)->setPublic($alias->isPublic())->setPrivate($alias->isPrivate()); $container->setAlias($id, $defId)->setPublic($alias->isPublic())->setPrivate($alias->isPrivate());
} }
@ -43,7 +43,7 @@ class ResolveReferencesToAliasesPass extends AbstractRecursivePass
protected function processValue($value, $isRoot = false) protected function processValue($value, $isRoot = false)
{ {
if ($value instanceof Reference) { if ($value instanceof Reference) {
$defId = $this->getDefinitionId($id = (string) $value, $this->container); $defId = $this->getDefinitionId($id = $this->container->normalizeId($value), $this->container);
if ($defId !== $id) { if ($defId !== $id) {
return new Reference($defId, $value->getInvalidBehavior()); return new Reference($defId, $value->getInvalidBehavior());
@ -69,7 +69,7 @@ class ResolveReferencesToAliasesPass extends AbstractRecursivePass
throw new ServiceCircularReferenceException($id, array_keys($seen)); throw new ServiceCircularReferenceException($id, array_keys($seen));
} }
$seen[$id] = true; $seen[$id] = true;
$id = (string) $container->getAlias($id); $id = $container->normalizeId($container->getAlias($id));
} }
return $id; return $id;

View File

@ -26,7 +26,7 @@ class ResolveServiceSubscribersPass extends AbstractRecursivePass
protected function processValue($value, $isRoot = false) protected function processValue($value, $isRoot = false)
{ {
if ($value instanceof Reference && $this->serviceLocator && ContainerInterface::class === (string) $value) { if ($value instanceof Reference && $this->serviceLocator && ContainerInterface::class === $this->container->normalizeId($value)) {
return new Reference($this->serviceLocator); return new Reference($this->serviceLocator);
} }

View File

@ -507,7 +507,7 @@ class Container implements ResettableContainerInterface
*/ */
public function normalizeId($id) public function normalizeId($id)
{ {
if (!is_string($id)) { if (!\is_string($id)) {
$id = (string) $id; $id = (string) $id;
} }
if (isset($this->normalizedIds[$normalizedId = strtolower($id)])) { if (isset($this->normalizedIds[$normalizedId = strtolower($id)])) {

View File

@ -627,7 +627,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
* *
* @throws BadMethodCallException When this ContainerBuilder is compiled * @throws BadMethodCallException When this ContainerBuilder is compiled
*/ */
public function merge(ContainerBuilder $container) public function merge(self $container)
{ {
if ($this->isCompiled()) { if ($this->isCompiled()) {
throw new BadMethodCallException('Cannot merge on a compiled container.'); throw new BadMethodCallException('Cannot merge on a compiled container.');
@ -1373,16 +1373,16 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
$value = $bag->resolveValue($value); $value = $bag->resolveValue($value);
} }
if (is_array($value)) { if (\is_array($value)) {
$result = array(); $result = array();
foreach ($value as $k => $v) { foreach ($value as $k => $v) {
$result[$this->resolveEnvPlaceholders($k, $format, $usedEnvs)] = $this->resolveEnvPlaceholders($v, $format, $usedEnvs); $result[\is_string($k) ? $this->resolveEnvPlaceholders($k, $format, $usedEnvs) : $k] = $this->resolveEnvPlaceholders($v, $format, $usedEnvs);
} }
return $result; return $result;
} }
if (!is_string($value)) { if (!\is_string($value) || 38 > \strlen($value)) {
return $value; return $value;
} }
$envPlaceholders = $bag instanceof EnvPlaceholderParameterBag ? $bag->getEnvPlaceholders() : $this->envPlaceholders; $envPlaceholders = $bag instanceof EnvPlaceholderParameterBag ? $bag->getEnvPlaceholders() : $this->envPlaceholders;
@ -1455,6 +1455,18 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
$this->getCompiler()->log($pass, $message); $this->getCompiler()->log($pass, $message);
} }
/**
* {@inheritdoc}
*/
public function normalizeId($id)
{
if (!\is_string($id)) {
$id = (string) $id;
}
return isset($this->definitions[$id]) || isset($this->aliasDefinitions[$id]) || isset($this->removedIds[$id]) ? $id : parent::normalizeId($id);
}
/** /**
* Returns the Service Conditionals. * Returns the Service Conditionals.
* *

View File

@ -1232,9 +1232,9 @@ EOF;
$code = " \$this->aliases = array(\n"; $code = " \$this->aliases = array(\n";
ksort($aliases); ksort($aliases);
foreach ($aliases as $alias => $id) { foreach ($aliases as $alias => $id) {
$id = (string) $id; $id = $this->container->normalizeId($id);
while (isset($aliases[$id])) { while (isset($aliases[$id])) {
$id = (string) $aliases[$id]; $id = $this->container->normalizeId($aliases[$id]);
} }
$code .= ' '.$this->doExport($alias).' => '.$this->doExport($id).",\n"; $code .= ' '.$this->doExport($alias).' => '.$this->doExport($id).",\n";
} }
@ -1555,7 +1555,7 @@ EOF;
if (is_array($argument)) { if (is_array($argument)) {
$this->getServiceCallsFromArguments($argument, $calls, $isPreInstance, $callerId, $behavior, $step); $this->getServiceCallsFromArguments($argument, $calls, $isPreInstance, $callerId, $behavior, $step);
} elseif ($argument instanceof Reference) { } elseif ($argument instanceof Reference) {
$id = (string) $argument; $id = $this->container->normalizeId($argument);
if (!isset($calls[$id])) { if (!isset($calls[$id])) {
$calls[$id] = (int) ($isPreInstance && isset($this->circularReferences[$callerId][$id])); $calls[$id] = (int) ($isPreInstance && isset($this->circularReferences[$callerId][$id]));
@ -1625,7 +1625,7 @@ EOF;
continue; continue;
} elseif ($argument instanceof Reference) { } elseif ($argument instanceof Reference) {
$argumentId = (string) $argument; $argumentId = $this->container->normalizeId($argument);
if ($id === $argumentId) { if ($id === $argumentId) {
return true; return true;
} }
@ -1790,11 +1790,12 @@ EOF;
} elseif ($value instanceof Variable) { } elseif ($value instanceof Variable) {
return '$'.$value; return '$'.$value;
} elseif ($value instanceof Reference) { } elseif ($value instanceof Reference) {
if (null !== $this->referenceVariables && isset($this->referenceVariables[$id = (string) $value])) { $id = $this->container->normalizeId($value);
if (null !== $this->referenceVariables && isset($this->referenceVariables[$id])) {
return $this->dumpValue($this->referenceVariables[$id], $interpolate); return $this->dumpValue($this->referenceVariables[$id], $interpolate);
} }
return $this->getServiceCall((string) $value, $value); return $this->getServiceCall($id, $value);
} elseif ($value instanceof Expression) { } elseif ($value instanceof Expression) {
return $this->getExpressionLanguage()->compile((string) $value, array('this' => 'container')); return $this->getExpressionLanguage()->compile((string) $value, array('this' => 'container'));
} elseif ($value instanceof Parameter) { } elseif ($value instanceof Parameter) {
@ -1881,6 +1882,7 @@ EOF;
while ($this->container->hasAlias($id)) { while ($this->container->hasAlias($id)) {
$id = (string) $this->container->getAlias($id); $id = (string) $this->container->getAlias($id);
} }
$id = $this->container->normalizeId($id);
if ('service_container' === $id) { if ('service_container' === $id) {
return '$this'; return '$this';

View File

@ -173,16 +173,16 @@ class ParameterBag implements ParameterBagInterface
*/ */
public function resolveValue($value, array $resolving = array()) public function resolveValue($value, array $resolving = array())
{ {
if (is_array($value)) { if (\is_array($value)) {
$args = array(); $args = array();
foreach ($value as $k => $v) { foreach ($value as $k => $v) {
$args[$this->resolveValue($k, $resolving)] = $this->resolveValue($v, $resolving); $args[\is_string($k) ? $this->resolveValue($k, $resolving) : $k] = $this->resolveValue($v, $resolving);
} }
return $args; return $args;
} }
if (!is_string($value)) { if (!\is_string($value) || 2 > \strlen($value)) {
return $value; return $value;
} }

View File

@ -1001,6 +1001,26 @@ class PhpDumperTest extends TestCase
$this->assertSame('bar', $container->getParameter('FOO')); $this->assertSame('bar', $container->getParameter('FOO'));
} }
/**
* @group legacy
* @expectedDeprecation Service identifiers will be made case sensitive in Symfony 4.0. Using "foo" instead of "Foo" is deprecated since Symfony 3.3.
* @expectedDeprecation The "Foo" service is deprecated. You should stop using it, as it will soon be removed.
*/
public function testReferenceWithLowerCaseId()
{
$container = new ContainerBuilder();
$container->register('Bar', 'stdClass')->setProperty('foo', new Reference('foo'))->setPublic(true);
$container->register('Foo', 'stdClass')->setDeprecated();
$container->compile();
$dumper = new PhpDumper($container);
eval('?>'.$dumper->dump(array('class' => 'Symfony_DI_PhpDumper_Test_Reference_With_Lower_Case_Id')));
$container = new \Symfony_DI_PhpDumper_Test_Reference_With_Lower_Case_Id();
$this->assertEquals((object) array('foo' => (object) array()), $container->get('Bar'));
}
} }
class Rot13EnvVarProcessor implements EnvVarProcessorInterface class Rot13EnvVarProcessor implements EnvVarProcessorInterface