Skip to content

[DI] Fix inlining conflict by restricting IteratorArgument to Reference[] #22496

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 23, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@

namespace Symfony\Component\DependencyInjection\Argument;

use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;

/**
* Represents a collection of values to lazily iterate over.
*
Expand All @@ -20,9 +23,12 @@ class IteratorArgument implements ArgumentInterface
{
private $values;

/**
* @param Reference[] $values
*/
public function __construct(array $values)
{
$this->values = $values;
$this->setValues($values);
}

/**
Expand All @@ -34,10 +40,16 @@ public function getValues()
}

/**
* @param array $values The values to lazily iterate over
* @param Reference[] $values The service references to lazily iterate over
*/
public function setValues(array $values)
{
foreach ($values as $k => $v) {
if (null !== $v && !$v instanceof Reference) {
throw new InvalidArgumentException(sprintf('An IteratorArgument must hold only Reference instances, "%s" given.', is_object($v) ? get_class($v) : gettype($v)));
}
}

$this->values = $values;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ public function setRepeatedPass(RepeatedPass $repeatedPass)
protected function processValue($value, $isRoot = false)
{
if ($value instanceof ArgumentInterface) {
$this->processValue($value->getValues());

// Reference found in ArgumentInterface::getValues() are not inlineable
return $value;
}
if ($value instanceof Reference && $this->container->hasDefinition($id = (string) $value)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1125,16 +1125,15 @@ public function resolveServices($value)
return $this->resolveServices($reference);
};
} elseif ($value instanceof IteratorArgument) {
$parameterBag = $this->getParameterBag();
$value = new RewindableGenerator(function () use ($value, $parameterBag) {
$value = new RewindableGenerator(function () use ($value) {
foreach ($value->getValues() as $k => $v) {
foreach (self::getServiceConditionals($v) as $s) {
if (!$this->has($s)) {
continue 2;
}
}

yield $k => $this->resolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($v)));
yield $k => $this->resolveServices($v);
}
}, function () use ($value) {
$count = 0;
Expand Down Expand Up @@ -1409,7 +1408,7 @@ private function callMethod($service, $call)
* Shares a given service in the container.
*
* @param Definition $definition
* @param mixed $service
* @param object $service
* @param string|null $id
*/
private function shareService(Definition $definition, $service, $id)
Expand Down
76 changes: 38 additions & 38 deletions src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
Original file line number Diff line number Diff line change
Expand Up @@ -202,22 +202,20 @@ private function getProxyDumper()
/**
* Generates Service local temp variables.
*
* @param string $cId
* @param string $definition
* @param string $cId
* @param Definition $definition
* @param array $inlinedDefinitions
*
* @return string
*/
private function addServiceLocalTempVariables($cId, $definition)
private function addServiceLocalTempVariables($cId, Definition $definition, array $inlinedDefinitions)
{
static $template = " \$%s = %s;\n";

$localDefinitions = array_merge(
array($definition),
$this->getInlinedDefinitions($definition)
);
array_unshift($inlinedDefinitions, $definition);

$calls = $behavior = array();
foreach ($localDefinitions as $iDefinition) {
foreach ($inlinedDefinitions as $iDefinition) {
$this->getServiceCallsFromArguments($iDefinition->getArguments(), $calls, $behavior);
$this->getServiceCallsFromArguments($iDefinition->getMethodCalls(), $calls, $behavior);
$this->getServiceCallsFromArguments($iDefinition->getProperties(), $calls, $behavior);
Expand Down Expand Up @@ -280,12 +278,13 @@ private function addProxyClasses()
/**
* Generates the require_once statement for service includes.
*
* @param string $id The service id
* @param string $id
* @param Definition $definition
* @param array $inlinedDefinitions
*
* @return string
*/
private function addServiceInclude($id, $definition)
private function addServiceInclude($id, Definition $definition, array $inlinedDefinitions)
{
$template = " require_once %s;\n";
$code = '';
Expand All @@ -294,7 +293,7 @@ private function addServiceInclude($id, $definition)
$code .= sprintf($template, $this->dumpValue($file));
}

foreach ($this->getInlinedDefinitions($definition) as $definition) {
foreach ($inlinedDefinitions as $definition) {
if (null !== $file = $definition->getFile()) {
$code .= sprintf($template, $this->dumpValue($file));
}
Expand All @@ -310,21 +309,20 @@ private function addServiceInclude($id, $definition)
/**
* Generates the inline definition of a service.
*
* @param string $id
* @param Definition $definition
* @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, $definition)
private function addServiceInlinedDefinitions($id, array $inlinedDefinitions)
{
$code = '';
$variableMap = $this->definitionVariables;
$nbOccurrences = new \SplObjectStorage();
$processed = new \SplObjectStorage();
$inlinedDefinitions = $this->getInlinedDefinitions($definition);

foreach ($inlinedDefinitions as $definition) {
if (false === $nbOccurrences->contains($definition)) {
Expand Down Expand Up @@ -375,14 +373,14 @@ private function addServiceInlinedDefinitions($id, $definition)
/**
* Adds the service return statement.
*
* @param string $id Service id
* @param Definition $definition
* @param string $id
* @param bool $isSimpleInstance
*
* @return string
*/
private function addServiceReturn($id, $definition)
private function addServiceReturn($id, $isSimpleInstance)
{
if ($this->isSimpleInstance($id, $definition)) {
if ($isSimpleInstance) {
return " }\n";
}

Expand All @@ -394,13 +392,14 @@ private function addServiceReturn($id, $definition)
*
* @param string $id
* @param Definition $definition
* @param bool $isSimpleInstance
*
* @return string
*
* @throws InvalidArgumentException
* @throws RuntimeException
*/
private function addServiceInstance($id, Definition $definition)
private function addServiceInstance($id, Definition $definition, $isSimpleInstance)
{
$class = $definition->getClass();

Expand All @@ -414,26 +413,25 @@ private function addServiceInstance($id, Definition $definition)
throw new InvalidArgumentException(sprintf('"%s" is not a valid class name for the "%s" service.', $class, $id));
}

$simple = $this->isSimpleInstance($id, $definition);
$isProxyCandidate = $this->getProxyDumper()->isProxyCandidate($definition);
$instantiation = '';

if (!$isProxyCandidate && $definition->isShared()) {
$instantiation = "\$this->services['$id'] = ".($simple ? '' : '$instance');
} elseif (!$simple) {
$instantiation = "\$this->services['$id'] = ".($isSimpleInstance ? '' : '$instance');
} elseif (!$isSimpleInstance) {
$instantiation = '$instance';
}

$return = '';
if ($simple) {
if ($isSimpleInstance) {
$return = 'return ';
} else {
$instantiation .= ' = ';
}

$code = $this->addNewInstance($definition, $return, $instantiation, $id);

if (!$simple) {
if (!$isSimpleInstance) {
$code .= "\n";
}

Expand Down Expand Up @@ -500,20 +498,21 @@ private function addServiceProperties($id, Definition $definition, $variableName
/**
* Generates the inline definition setup.
*
* @param string $id
* @param Definition $definition
* @param string $id
* @param array $inlinedDefinitions
* @param bool $isSimpleInstance
*
* @return string
*
* @throws ServiceCircularReferenceException when the container contains a circular reference
*/
private function addServiceInlinedDefinitionsSetup($id, Definition $definition)
private function addServiceInlinedDefinitionsSetup($id, array $inlinedDefinitions, $isSimpleInstance)
{
$this->referenceVariables[$id] = new Variable('instance');

$code = '';
$processed = new \SplObjectStorage();
foreach ($this->getInlinedDefinitions($definition) as $iDefinition) {
foreach ($inlinedDefinitions as $iDefinition) {
if ($processed->contains($iDefinition)) {
continue;
}
Expand All @@ -525,7 +524,7 @@ private function addServiceInlinedDefinitionsSetup($id, Definition $definition)

// if the instance is simple, the return statement has already been generated
// so, the only possible way to get there is because of a circular reference
if ($this->isSimpleInstance($id, $definition)) {
if ($isSimpleInstance) {
throw new ServiceCircularReferenceException($id, array($id));
}

Expand Down Expand Up @@ -683,16 +682,19 @@ private function addService($id, Definition $definition)
$code .= sprintf(" @trigger_error(%s, E_USER_DEPRECATED);\n\n", $this->export($definition->getDeprecationMessage($id)));
}

$inlinedDefinitions = $this->getInlinedDefinitions($definition);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this replaces many calls to getInlinedDefinitions by a single one

$isSimpleInstance = $this->isSimpleInstance($id, $definition, $inlinedDefinitions);

$code .=
$this->addServiceInclude($id, $definition).
$this->addServiceLocalTempVariables($id, $definition).
$this->addServiceInlinedDefinitions($id, $definition).
$this->addServiceInstance($id, $definition).
$this->addServiceInlinedDefinitionsSetup($id, $definition).
$this->addServiceInclude($id, $definition, $inlinedDefinitions).
$this->addServiceLocalTempVariables($id, $definition, $inlinedDefinitions).
$this->addServiceInlinedDefinitions($id, $inlinedDefinitions).
$this->addServiceInstance($id, $definition, $isSimpleInstance).
$this->addServiceInlinedDefinitionsSetup($id, $inlinedDefinitions, $isSimpleInstance).
$this->addServiceProperties($id, $definition).
$this->addServiceMethodCalls($id, $definition).
$this->addServiceConfigurator($id, $definition).
$this->addServiceReturn($id, $definition)
$this->addServiceReturn($id, $isSimpleInstance)
;

$this->definitionVariables = null;
Expand Down Expand Up @@ -1332,8 +1334,6 @@ private function getDefinitionsFromArguments(array $arguments)
foreach ($arguments as $argument) {
if (is_array($argument)) {
$definitions = array_merge($definitions, $this->getDefinitionsFromArguments($argument));
} elseif ($argument instanceof ArgumentInterface) {
$definitions = array_merge($definitions, $this->getDefinitionsFromArguments($argument->getValues()));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a bug, silent for now because we don't have inline definitions in $argument->getValues(), but still a logical mistake

} elseif ($argument instanceof Definition) {
$definitions = array_merge(
$definitions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,12 @@ private function getArgumentsAsPhp(\DOMElement $node, $name, $lowercase = true,
$arguments[$key] = $this->getArgumentsAsPhp($arg, $name, false);
break;
case 'iterator':
$arguments[$key] = new IteratorArgument($this->getArgumentsAsPhp($arg, $name, false));
$arg = $this->getArgumentsAsPhp($arg, $name, false);
try {
$arguments[$key] = new IteratorArgument($arg);
} catch (InvalidArgumentException $e) {
throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="iterator" only accepts collections of type="service" references.', $name));
Copy link
Member

@chalasr chalasr Apr 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tag => Argument? Attribute?

Copy link
Member Author

@nicolas-grekas nicolas-grekas Apr 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's an xml tag, eg Tag "<argument>" ... or Tag "<property>" ...

}
break;
case 'string':
$arguments[$key] = $arg->nodeValue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -654,14 +654,18 @@ private function resolveServices($value, $file, $isParameter = false)
$argument = $value->getValue();
if ('iterator' === $value->getTag()) {
if (!is_array($argument)) {
throw new InvalidArgumentException('"!iterator" tag only accepts sequences.');
throw new InvalidArgumentException(sprintf('"!iterator" tag only accepts sequences in "%s".', $file));
}
$argument = $this->resolveServices($argument, $file, $isParameter);
try {
return new IteratorArgument($argument);
} catch (InvalidArgumentException $e) {
throw new InvalidArgumentException(sprintf('"!iterator" tag only accepts arrays of "@service" references in "%s".', $file));
}

return new IteratorArgument($this->resolveServices($argument, $file, $isParameter));
}
if ('closure_proxy' === $value->getTag()) {
if (!is_array($argument) || array(0, 1) !== array_keys($argument) || !is_string($argument[0]) || !is_string($argument[1]) || 0 !== strpos($argument[0], '@') || 0 === strpos($argument[0], '@@')) {
throw new InvalidArgumentException('"!closure_proxy" tagged values must be arrays of [@service, method].');
throw new InvalidArgumentException(sprintf('"!closure_proxy" tagged values must be arrays of [@service, method] in "%s".', $file));
}

if (0 === strpos($argument[0], '@?')) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@ public function testLazyArgumentProvideGenerator()
$container->register('lazy_referenced', 'stdClass');
$container
->register('lazy_context', 'LazyContext')
->setArguments(array(new IteratorArgument(array('foo', new Reference('lazy_referenced'), 'k1' => array('foo' => 'bar'), true, 'k2' => new Reference('service_container')))))
->setArguments(array(new IteratorArgument(array('k1' => new Reference('lazy_referenced'), 'k2' => new Reference('service_container')))))
;
$container->compile();

Expand All @@ -450,24 +450,12 @@ public function testLazyArgumentProvideGenerator()
foreach ($lazyContext->lazyValues as $k => $v) {
switch (++$i) {
case 0:
$this->assertEquals(0, $k);
$this->assertEquals('foo', $v);
break;
case 1:
$this->assertEquals(1, $k);
$this->assertInstanceOf('stdClass', $v);
break;
case 2:
$this->assertEquals('k1', $k);
$this->assertEquals(array('foo' => 'bar'), $v);
$this->assertInstanceOf('stdCLass', $v);
break;
case 3:
$this->assertEquals(2, $k);
$this->assertTrue($v);
break;
case 4:
case 1:
$this->assertEquals('k2', $k);
$this->assertInstanceOf('\Symfony_DI_PhpDumper_Test_Lazy_Argument_Provide_Generator', $v);
$this->assertInstanceOf('Symfony_DI_PhpDumper_Test_Lazy_Argument_Provide_Generator', $v);
break;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@
;
$container
->register('lazy_context', 'LazyContext')
->setArguments(array(new IteratorArgument(array('foo', new Reference('foo.baz'), array('%foo%' => 'foo is %foo%', 'foobar' => '%foo%'), true, new Reference('service_container')))))
->setArguments(array(new IteratorArgument(array('k1' => new Reference('foo.baz'), 'k2' => new Reference('service_container')))))
;
$container
->register('lazy_context_ignore_invalid_ref', 'LazyContext')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -321,12 +321,9 @@ protected function getFooWithInlineService()
protected function getLazyContextService()
{
return $this->services['lazy_context'] = new \LazyContext(new RewindableGenerator(function () {
yield 0 => 'foo';
yield 1 => ${($_ = isset($this->services['foo.baz']) ? $this->services['foo.baz'] : $this->get('foo.baz')) && false ?: '_'};
yield 2 => array($this->getParameter('foo') => 'foo is '.$this->getParameter('foo').'', 'foobar' => $this->getParameter('foo'));
yield 3 => true;
yield 4 => $this;
}, 5));
yield 'k1' => ${($_ = isset($this->services['foo.baz']) ? $this->services['foo.baz'] : $this->get('foo.baz')) && false ?: '_'};
yield 'k2' => $this;
}, 2));
}

/**
Expand Down
Loading