[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;
}
@ -598,18 +594,16 @@ EOF;
$return = array();
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, '\\'));
} elseif ($definition->getFactory()) {
$factory = $definition->getFactory();
if (\is_string($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)) {
if (\is_string($factory[0]) || $factory[0] instanceof Reference) {
$return[] = sprintf('@return object An instance returned by %s::%s()', (string) $factory[0], $factory[1]);
} elseif ($factory[0] instanceof Definition) {
$return[] = sprintf('@return object An instance returned by %s::%s()', $factory[0]->getClass(), $factory[1]);
}
$class = $factory[0] instanceof Definition ? $factory[0]->getClass() : (string) $factory[0];
$class = $class instanceof Parameter ? '%'.$class.'%' : $this->container->resolveEnvPlaceholders($class);
$return[] = sprintf('@return object An instance returned by %s::%s()', $class, $factory[1]);
}
}
@ -704,7 +698,7 @@ EOF;
if (\is_array($argument)) {
$hasSelfRef = $this->addInlineVariables($head, $tail, $id, $argument, $forConstructor) || $hasSelfRef;
} 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) {
$hasSelfRef = $this->addInlineService($head, $tail, $id, $argument, $forConstructor) || $hasSelfRef;
}
@ -713,37 +707,31 @@ EOF;
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])) {
return isset($this->circularReferences[$id][$targetId]);
return $hasSelfRef;
}
list($callCount, $behavior) = $this->serviceCalls[$targetId];
if (2 > $callCount && (!$forConstructor || !isset($this->circularReferences[$id][$targetId]))) {
return isset($this->circularReferences[$id][$targetId]);
if (2 > $callCount && (!$hasSelfRef || !$forConstructor)) {
return $hasSelfRef;
}
$name = $this->getNextVariableName();
$this->referenceVariables[$targetId] = new Variable($name);
$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])) {
$head .= $code;
return false;
if (!$hasSelfRef || !$forConstructor) {
return $hasSelfRef;
}
if (!$forConstructor) {
$tail .= $code;
return true;
}
$head .= $code.sprintf(<<<'EOTXT'
$code .= sprintf(<<<'EOTXT'
if (isset($this->%s['%s'])) {
return $this->%1$s['%2$s'];
@ -766,7 +754,7 @@ EOTXT
$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);
}
@ -774,9 +762,13 @@ EOTXT
$this->definitionVariables[$definition] = new Variable($name);
$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);
$hasSelfRef ? $tail .= ('' !== $tail ? "\n" : '').$code : $head .= ('' !== $head ? "\n" : '').$code;
$hasSelfRef && !$forConstructor ? $tail .= ('' !== $tail ? "\n" : '').$code : $head .= ('' !== $head ? "\n" : '').$code;
$code = '';
$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);
}
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()
{
$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'));
}
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
* @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->setParameter('env(FOO)', 'Bar\FaooClass');
$container->setParameter('foo', '%env(FOO)%');
$container
->register('service_from_anonymous_factory', 'Bar\FooClass')
->setFactory(array(new Definition('Bar\FooClass'), 'getInstance'))
->register('service_from_anonymous_factory', '%foo%')
->setFactory(array(new Definition('%foo%'), 'getInstance'))
->setPublic(true)
;

View File

@ -21,6 +21,8 @@ class ProjectServiceContainer extends Container
public function __construct()
{
$this->parameters = $this->getDefaultParameters();
$this->services = array();
$this->methodMap = array(
'service_from_anonymous_factory' => 'getServiceFromAnonymousFactoryService',
@ -58,11 +60,11 @@ class ProjectServiceContainer extends Container
/**
* Gets the public 'service_from_anonymous_factory' shared service.
*
* @return \Bar\FooClass
* @return object A %env(FOO)% instance
*/
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;
}
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']