[DI] fix dumping inline services again

This commit is contained in:
Nicolas Grekas 2018-10-05 22:38:23 +02:00
parent 63d287b70c
commit a854b0a01c
7 changed files with 453 additions and 34 deletions

View File

@ -503,10 +503,6 @@ EOF;
} }
} }
if (false !== strpos($this->dumpLiteralClass($this->dumpValue($definition->getClass())), '$')) {
return false;
}
return true; return true;
} }
@ -598,18 +594,16 @@ EOF;
$return = array(); $return = array();
if ($class = $definition->getClass()) { if ($class = $definition->getClass()) {
$class = $this->container->resolveEnvPlaceholders($class); $class = $class instanceof Parameter ? '%'.$class.'%' : $this->container->resolveEnvPlaceholders($class);
$return[] = sprintf(0 === strpos($class, '%') ? '@return object A %1$s instance' : '@return \%s', ltrim($class, '\\')); $return[] = sprintf(0 === strpos($class, '%') ? '@return object A %1$s instance' : '@return \%s', ltrim($class, '\\'));
} elseif ($definition->getFactory()) { } elseif ($definition->getFactory()) {
$factory = $definition->getFactory(); $factory = $definition->getFactory();
if (\is_string($factory)) { if (\is_string($factory)) {
$return[] = sprintf('@return object An instance returned by %s()', $factory); $return[] = sprintf('@return object An instance returned by %s()', $factory);
} elseif (\is_array($factory) && (\is_string($factory[0]) || $factory[0] instanceof Definition || $factory[0] instanceof Reference)) { } elseif (\is_array($factory) && (\is_string($factory[0]) || $factory[0] instanceof Definition || $factory[0] instanceof Reference)) {
if (\is_string($factory[0]) || $factory[0] instanceof Reference) { $class = $factory[0] instanceof Definition ? $factory[0]->getClass() : (string) $factory[0];
$return[] = sprintf('@return object An instance returned by %s::%s()', (string) $factory[0], $factory[1]); $class = $class instanceof Parameter ? '%'.$class.'%' : $this->container->resolveEnvPlaceholders($class);
} elseif ($factory[0] instanceof Definition) { $return[] = sprintf('@return object An instance returned by %s::%s()', $class, $factory[1]);
$return[] = sprintf('@return object An instance returned by %s::%s()', $factory[0]->getClass(), $factory[1]);
}
} }
} }
@ -704,7 +698,7 @@ EOF;
if (\is_array($argument)) { if (\is_array($argument)) {
$hasSelfRef = $this->addInlineVariables($head, $tail, $id, $argument, $forConstructor) || $hasSelfRef; $hasSelfRef = $this->addInlineVariables($head, $tail, $id, $argument, $forConstructor) || $hasSelfRef;
} elseif ($argument instanceof Reference) { } elseif ($argument instanceof Reference) {
$hasSelfRef = $this->addInlineReference($head, $tail, $id, $this->container->normalizeId($argument), $forConstructor) || $hasSelfRef; $hasSelfRef = $this->addInlineReference($head, $id, $this->container->normalizeId($argument), $forConstructor) || $hasSelfRef;
} elseif ($argument instanceof Definition) { } elseif ($argument instanceof Definition) {
$hasSelfRef = $this->addInlineService($head, $tail, $id, $argument, $forConstructor) || $hasSelfRef; $hasSelfRef = $this->addInlineService($head, $tail, $id, $argument, $forConstructor) || $hasSelfRef;
} }
@ -713,37 +707,31 @@ EOF;
return $hasSelfRef; return $hasSelfRef;
} }
private function addInlineReference(&$head, &$tail, $id, $targetId, $forConstructor) private function addInlineReference(&$code, $id, $targetId, $forConstructor)
{ {
$hasSelfRef = isset($this->circularReferences[$id][$targetId]);
if ('service_container' === $targetId || isset($this->referenceVariables[$targetId])) { if ('service_container' === $targetId || isset($this->referenceVariables[$targetId])) {
return isset($this->circularReferences[$id][$targetId]); return $hasSelfRef;
} }
list($callCount, $behavior) = $this->serviceCalls[$targetId]; list($callCount, $behavior) = $this->serviceCalls[$targetId];
if (2 > $callCount && (!$forConstructor || !isset($this->circularReferences[$id][$targetId]))) { if (2 > $callCount && (!$hasSelfRef || !$forConstructor)) {
return isset($this->circularReferences[$id][$targetId]); return $hasSelfRef;
} }
$name = $this->getNextVariableName(); $name = $this->getNextVariableName();
$this->referenceVariables[$targetId] = new Variable($name); $this->referenceVariables[$targetId] = new Variable($name);
$reference = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $behavior ? new Reference($targetId, $behavior) : null; $reference = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $behavior ? new Reference($targetId, $behavior) : null;
$code = sprintf(" \$%s = %s;\n", $name, $this->getServiceCall($targetId, $reference)); $code .= sprintf(" \$%s = %s;\n", $name, $this->getServiceCall($targetId, $reference));
if (!isset($this->circularReferences[$id][$targetId])) { if (!$hasSelfRef || !$forConstructor) {
$head .= $code; return $hasSelfRef;
return false;
} }
if (!$forConstructor) { $code .= sprintf(<<<'EOTXT'
$tail .= $code;
return true;
}
$head .= $code.sprintf(<<<'EOTXT'
if (isset($this->%s['%s'])) { if (isset($this->%s['%s'])) {
return $this->%1$s['%2$s']; return $this->%1$s['%2$s'];
@ -766,7 +754,7 @@ EOTXT
$arguments = array($definition->getArguments(), $definition->getFactory()); $arguments = array($definition->getArguments(), $definition->getFactory());
if (2 > $this->inlinedDefinitions[$definition] && !$definition->getMethodCalls() && !$definition->getProperties() && !$definition->getConfigurator() && false === strpos($this->dumpValue($definition->getClass()), '$')) { if (2 > $this->inlinedDefinitions[$definition] && !$definition->getMethodCalls() && !$definition->getProperties() && !$definition->getConfigurator()) {
return $this->addInlineVariables($head, $tail, $id, $arguments, $forConstructor); return $this->addInlineVariables($head, $tail, $id, $arguments, $forConstructor);
} }
@ -774,9 +762,13 @@ EOTXT
$this->definitionVariables[$definition] = new Variable($name); $this->definitionVariables[$definition] = new Variable($name);
$code = ''; $code = '';
$hasSelfRef = $this->addInlineVariables($code, $tail, $id, $arguments, $forConstructor); if ($forConstructor) {
$hasSelfRef = $this->addInlineVariables($code, $tail, $id, $arguments, $forConstructor);
} else {
$hasSelfRef = $this->addInlineVariables($code, $code, $id, $arguments, $forConstructor);
}
$code .= $this->addNewInstance($definition, '$'.$name, ' = ', $id); $code .= $this->addNewInstance($definition, '$'.$name, ' = ', $id);
$hasSelfRef ? $tail .= ('' !== $tail ? "\n" : '').$code : $head .= ('' !== $head ? "\n" : '').$code; $hasSelfRef && !$forConstructor ? $tail .= ('' !== $tail ? "\n" : '').$code : $head .= ('' !== $head ? "\n" : '').$code;
$code = ''; $code = '';
$arguments = array($definition->getProperties(), $definition->getMethodCalls(), $definition->getConfigurator()); $arguments = array($definition->getProperties(), $definition->getMethodCalls(), $definition->getConfigurator());

View File

@ -863,6 +863,28 @@ class PhpDumperTest extends TestCase
$this->assertEquals((object) array('p2' => (object) array('p3' => (object) array())), $container->get('foo')->bClone); $this->assertEquals((object) array('p2' => (object) array('p3' => (object) array())), $container->get('foo')->bClone);
} }
public function testInlineSelfRef()
{
$container = new ContainerBuilder();
$bar = (new Definition('App\Bar'))
->setProperty('foo', new Reference('App\Foo'));
$baz = (new Definition('App\Baz'))
->setProperty('bar', $bar)
->addArgument($bar);
$container->register('App\Foo')
->setPublic(true)
->addArgument($baz);
$passConfig = $container->getCompiler()->getPassConfig();
$container->compile();
$dumper = new PhpDumper($container);
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services_inline_self_ref.php', $dumper->dump(array('class' => 'Symfony_DI_PhpDumper_Test_Inline_Self_Ref')));
}
public function testHotPathOptimizations() public function testHotPathOptimizations()
{ {
$container = include self::$fixturesPath.'/containers/container_inline_requires.php'; $container = include self::$fixturesPath.'/containers/container_inline_requires.php';
@ -940,6 +962,18 @@ class PhpDumperTest extends TestCase
$this->assertEquals((object) array('foo' => (object) array(123)), $container->get('bar')); $this->assertEquals((object) array('foo' => (object) array(123)), $container->get('bar'));
} }
public function testAdawsonContainer()
{
$container = new ContainerBuilder();
$loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'));
$loader->load('services_adawson.yml');
$container->compile();
$dumper = new PhpDumper($container);
$dump = $dumper->dump();
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services_adawson.php', $dumper->dump());
}
/** /**
* @group legacy * @group legacy
* @expectedDeprecation The "private" service is private, getting it from the container is deprecated since Symfony 3.2 and will fail in 4.0. You should either make the service public, or stop using the container directly and use dependency injection instead. * @expectedDeprecation The "private" service is private, getting it from the container is deprecated since Symfony 3.2 and will fail in 4.0. You should either make the service public, or stop using the container directly and use dependency injection instead.

View File

@ -7,9 +7,12 @@ require_once __DIR__.'/../includes/classes.php';
$container = new ContainerBuilder(); $container = new ContainerBuilder();
$container->setParameter('env(FOO)', 'Bar\FaooClass');
$container->setParameter('foo', '%env(FOO)%');
$container $container
->register('service_from_anonymous_factory', 'Bar\FooClass') ->register('service_from_anonymous_factory', '%foo%')
->setFactory(array(new Definition('Bar\FooClass'), 'getInstance')) ->setFactory(array(new Definition('%foo%'), 'getInstance'))
->setPublic(true) ->setPublic(true)
; ;

View File

@ -21,6 +21,8 @@ class ProjectServiceContainer extends Container
public function __construct() public function __construct()
{ {
$this->parameters = $this->getDefaultParameters();
$this->services = array(); $this->services = array();
$this->methodMap = array( $this->methodMap = array(
'service_from_anonymous_factory' => 'getServiceFromAnonymousFactoryService', 'service_from_anonymous_factory' => 'getServiceFromAnonymousFactoryService',
@ -58,11 +60,11 @@ class ProjectServiceContainer extends Container
/** /**
* Gets the public 'service_from_anonymous_factory' shared service. * Gets the public 'service_from_anonymous_factory' shared service.
* *
* @return \Bar\FooClass * @return object A %env(FOO)% instance
*/ */
protected function getServiceFromAnonymousFactoryService() protected function getServiceFromAnonymousFactoryService()
{ {
return $this->services['service_from_anonymous_factory'] = (new \Bar\FooClass())->getInstance(); return $this->services['service_from_anonymous_factory'] = (new ${($_ = $this->getEnv('FOO')) && false ?: "_"}())->getInstance();
} }
/** /**
@ -78,4 +80,102 @@ class ProjectServiceContainer extends Container
return $instance; return $instance;
} }
public function getParameter($name)
{
$name = (string) $name;
if (!(isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || array_key_exists($name, $this->parameters))) {
$name = $this->normalizeParameterName($name);
if (!(isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || array_key_exists($name, $this->parameters))) {
throw new InvalidArgumentException(sprintf('The parameter "%s" must be defined.', $name));
}
}
if (isset($this->loadedDynamicParameters[$name])) {
return $this->loadedDynamicParameters[$name] ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name);
}
return $this->parameters[$name];
}
public function hasParameter($name)
{
$name = (string) $name;
$name = $this->normalizeParameterName($name);
return isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || array_key_exists($name, $this->parameters);
}
public function setParameter($name, $value)
{
throw new LogicException('Impossible to call set() on a frozen ParameterBag.');
}
public function getParameterBag()
{
if (null === $this->parameterBag) {
$parameters = $this->parameters;
foreach ($this->loadedDynamicParameters as $name => $loaded) {
$parameters[$name] = $loaded ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name);
}
$this->parameterBag = new FrozenParameterBag($parameters);
}
return $this->parameterBag;
}
private $loadedDynamicParameters = array(
'foo' => false,
);
private $dynamicParameters = array();
/**
* Computes a dynamic parameter.
*
* @param string The name of the dynamic parameter to load
*
* @return mixed The value of the dynamic parameter
*
* @throws InvalidArgumentException When the dynamic parameter does not exist
*/
private function getDynamicParameter($name)
{
switch ($name) {
case 'foo': $value = $this->getEnv('FOO'); break;
default: throw new InvalidArgumentException(sprintf('The dynamic parameter "%s" must be defined.', $name));
}
$this->loadedDynamicParameters[$name] = true;
return $this->dynamicParameters[$name] = $value;
}
private $normalizedParameterNames = array(
'env(foo)' => 'env(FOO)',
);
private function normalizeParameterName($name)
{
if (isset($this->normalizedParameterNames[$normalizedName = strtolower($name)]) || isset($this->parameters[$normalizedName]) || array_key_exists($normalizedName, $this->parameters)) {
$normalizedName = isset($this->normalizedParameterNames[$normalizedName]) ? $this->normalizedParameterNames[$normalizedName] : $normalizedName;
if ((string) $name !== $normalizedName) {
@trigger_error(sprintf('Parameter names will be made case sensitive in Symfony 4.0. Using "%s" instead of "%s" is deprecated since Symfony 3.4.', $name, $normalizedName), E_USER_DEPRECATED);
}
} else {
$normalizedName = $this->normalizedParameterNames[$normalizedName] = (string) $name;
}
return $normalizedName;
}
/**
* Gets the default parameters.
*
* @return array An array of the default parameters
*/
protected function getDefaultParameters()
{
return array(
'env(FOO)' => 'Bar\\FaooClass',
);
}
} }

View File

@ -0,0 +1,184 @@
<?php
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
/**
* This class has been auto-generated
* by the Symfony Dependency Injection Component.
*
* @final since Symfony 3.3
*/
class ProjectServiceContainer extends Container
{
private $parameters;
private $targetDirs = array();
public function __construct()
{
$this->services = array();
$this->normalizedIds = array(
'app\\bus' => 'App\\Bus',
'app\\db' => 'App\\Db',
'app\\handler1' => 'App\\Handler1',
'app\\handler2' => 'App\\Handler2',
'app\\processor' => 'App\\Processor',
'app\\registry' => 'App\\Registry',
'app\\schema' => 'App\\Schema',
);
$this->methodMap = array(
'App\\Bus' => 'getBusService',
'App\\Db' => 'getDbService',
'App\\Handler1' => 'getHandler1Service',
'App\\Handler2' => 'getHandler2Service',
'App\\Processor' => 'getProcessorService',
'App\\Registry' => 'getRegistryService',
'App\\Schema' => 'getSchemaService',
);
$this->privates = array(
'App\\Handler1' => true,
'App\\Handler2' => true,
'App\\Processor' => true,
'App\\Registry' => true,
'App\\Schema' => true,
);
$this->aliases = array();
}
public function getRemovedIds()
{
return array(
'App\\Handler1' => true,
'App\\Handler2' => true,
'App\\Processor' => true,
'App\\Registry' => true,
'App\\Schema' => true,
'Psr\\Container\\ContainerInterface' => true,
'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true,
);
}
public function compile()
{
throw new LogicException('You cannot compile a dumped container that was already compiled.');
}
public function isCompiled()
{
return true;
}
public function isFrozen()
{
@trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the isCompiled() method instead.', __METHOD__), E_USER_DEPRECATED);
return true;
}
/**
* Gets the public 'App\Bus' shared service.
*
* @return \App\Bus
*/
protected function getBusService()
{
$this->services['App\Bus'] = $instance = new \App\Bus(${($_ = isset($this->services['App\Db']) ? $this->services['App\Db'] : $this->getDbService()) && false ?: '_'});
$instance->handler1 = ${($_ = isset($this->services['App\Handler1']) ? $this->services['App\Handler1'] : $this->getHandler1Service()) && false ?: '_'};
$instance->handler2 = ${($_ = isset($this->services['App\Handler2']) ? $this->services['App\Handler2'] : $this->getHandler2Service()) && false ?: '_'};
return $instance;
}
/**
* Gets the public 'App\Db' shared service.
*
* @return \App\Db
*/
protected function getDbService()
{
$this->services['App\Db'] = $instance = new \App\Db();
$instance->schema = ${($_ = isset($this->services['App\Schema']) ? $this->services['App\Schema'] : $this->getSchemaService()) && false ?: '_'};
return $instance;
}
/**
* Gets the private 'App\Handler1' shared service.
*
* @return \App\Handler1
*/
protected function getHandler1Service()
{
$a = ${($_ = isset($this->services['App\Processor']) ? $this->services['App\Processor'] : $this->getProcessorService()) && false ?: '_'};
if (isset($this->services['App\Handler1'])) {
return $this->services['App\Handler1'];
}
return $this->services['App\Handler1'] = new \App\Handler1(${($_ = isset($this->services['App\Db']) ? $this->services['App\Db'] : $this->getDbService()) && false ?: '_'}, ${($_ = isset($this->services['App\Schema']) ? $this->services['App\Schema'] : $this->getSchemaService()) && false ?: '_'}, $a);
}
/**
* Gets the private 'App\Handler2' shared service.
*
* @return \App\Handler2
*/
protected function getHandler2Service()
{
return $this->services['App\Handler2'] = new \App\Handler2(${($_ = isset($this->services['App\Db']) ? $this->services['App\Db'] : $this->getDbService()) && false ?: '_'}, ${($_ = isset($this->services['App\Schema']) ? $this->services['App\Schema'] : $this->getSchemaService()) && false ?: '_'}, ${($_ = isset($this->services['App\Processor']) ? $this->services['App\Processor'] : $this->getProcessorService()) && false ?: '_'});
}
/**
* Gets the private 'App\Processor' shared service.
*
* @return \App\Processor
*/
protected function getProcessorService()
{
$a = ${($_ = isset($this->services['App\Registry']) ? $this->services['App\Registry'] : $this->getRegistryService()) && false ?: '_'};
if (isset($this->services['App\Processor'])) {
return $this->services['App\Processor'];
}
return $this->services['App\Processor'] = new \App\Processor($a, ${($_ = isset($this->services['App\Db']) ? $this->services['App\Db'] : $this->getDbService()) && false ?: '_'});
}
/**
* Gets the private 'App\Registry' shared service.
*
* @return \App\Registry
*/
protected function getRegistryService()
{
$this->services['App\Registry'] = $instance = new \App\Registry();
$instance->processor = array(0 => ${($_ = isset($this->services['App\Db']) ? $this->services['App\Db'] : $this->getDbService()) && false ?: '_'}, 1 => ${($_ = isset($this->services['App\Bus']) ? $this->services['App\Bus'] : $this->getBusService()) && false ?: '_'});
return $instance;
}
/**
* Gets the private 'App\Schema' shared service.
*
* @return \App\Schema
*/
protected function getSchemaService()
{
$a = ${($_ = isset($this->services['App\Db']) ? $this->services['App\Db'] : $this->getDbService()) && false ?: '_'};
if (isset($this->services['App\Schema'])) {
return $this->services['App\Schema'];
}
return $this->services['App\Schema'] = new \App\Schema($a);
}
}

View File

@ -0,0 +1,78 @@
<?php
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
/**
* This class has been auto-generated
* by the Symfony Dependency Injection Component.
*
* @final since Symfony 3.3
*/
class Symfony_DI_PhpDumper_Test_Inline_Self_Ref extends Container
{
private $parameters;
private $targetDirs = array();
public function __construct()
{
$this->services = array();
$this->normalizedIds = array(
'app\\foo' => 'App\\Foo',
);
$this->methodMap = array(
'App\\Foo' => 'getFooService',
);
$this->aliases = array();
}
public function getRemovedIds()
{
return array(
'Psr\\Container\\ContainerInterface' => true,
'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true,
);
}
public function compile()
{
throw new LogicException('You cannot compile a dumped container that was already compiled.');
}
public function isCompiled()
{
return true;
}
public function isFrozen()
{
@trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the isCompiled() method instead.', __METHOD__), E_USER_DEPRECATED);
return true;
}
/**
* Gets the public 'App\Foo' shared service.
*
* @return \App\Foo
*/
protected function getFooService()
{
$b = new \App\Bar();
$a = new \App\Baz($b);
$this->services['App\Foo'] = $instance = new \App\Foo($a);
$b->foo = $instance;
$a->bar = $b;
return $instance;
}
}

View File

@ -0,0 +1,28 @@
services:
App\Db:
public: true
properties:
schema: '@App\Schema'
App\Bus:
public: true
arguments: ['@App\Db']
properties:
handler1: '@App\Handler1'
handler2: '@App\Handler2'
App\Handler1:
['@App\Db', '@App\Schema', '@App\Processor']
App\Handler2:
['@App\Db', '@App\Schema', '@App\Processor']
App\Processor:
['@App\Registry', '@App\Db']
App\Registry:
properties:
processor: ['@App\Db', '@App\Bus']
App\Schema:
arguments: ['@App\Db']