Skip to content

Commit 68e54f0

Browse files
bug #28760 [DI] fix dumping inline services again (nicolas-grekas)
This PR was merged into the 3.4 branch. Discussion ---------- [DI] fix dumping inline services again | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #28720 #28710 | License | MIT | Doc PR | - The main fix is in `PhpDumper::addInlineService()` Fixes a few other glitches found meanwhile. Commits ------- a854b0a [DI] fix dumping inline services again
2 parents af2938e + a854b0a commit 68e54f0

File tree

7 files changed

+453
-34
lines changed

7 files changed

+453
-34
lines changed

src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php

+22-30
Original file line numberDiff line numberDiff line change
@@ -503,10 +503,6 @@ private function isTrivialInstance(Definition $definition)
503503
}
504504
}
505505

506-
if (false !== strpos($this->dumpLiteralClass($this->dumpValue($definition->getClass())), '$')) {
507-
return false;
508-
}
509-
510506
return true;
511507
}
512508

@@ -598,18 +594,16 @@ private function addService($id, Definition $definition, &$file = null)
598594
$return = array();
599595

600596
if ($class = $definition->getClass()) {
601-
$class = $this->container->resolveEnvPlaceholders($class);
597+
$class = $class instanceof Parameter ? '%'.$class.'%' : $this->container->resolveEnvPlaceholders($class);
602598
$return[] = sprintf(0 === strpos($class, '%') ? '@return object A %1$s instance' : '@return \%s', ltrim($class, '\\'));
603599
} elseif ($definition->getFactory()) {
604600
$factory = $definition->getFactory();
605601
if (\is_string($factory)) {
606602
$return[] = sprintf('@return object An instance returned by %s()', $factory);
607603
} elseif (\is_array($factory) && (\is_string($factory[0]) || $factory[0] instanceof Definition || $factory[0] instanceof Reference)) {
608-
if (\is_string($factory[0]) || $factory[0] instanceof Reference) {
609-
$return[] = sprintf('@return object An instance returned by %s::%s()', (string) $factory[0], $factory[1]);
610-
} elseif ($factory[0] instanceof Definition) {
611-
$return[] = sprintf('@return object An instance returned by %s::%s()', $factory[0]->getClass(), $factory[1]);
612-
}
604+
$class = $factory[0] instanceof Definition ? $factory[0]->getClass() : (string) $factory[0];
605+
$class = $class instanceof Parameter ? '%'.$class.'%' : $this->container->resolveEnvPlaceholders($class);
606+
$return[] = sprintf('@return object An instance returned by %s::%s()', $class, $factory[1]);
613607
}
614608
}
615609

@@ -704,7 +698,7 @@ private function addInlineVariables(&$head, &$tail, $id, array $arguments, $forC
704698
if (\is_array($argument)) {
705699
$hasSelfRef = $this->addInlineVariables($head, $tail, $id, $argument, $forConstructor) || $hasSelfRef;
706700
} elseif ($argument instanceof Reference) {
707-
$hasSelfRef = $this->addInlineReference($head, $tail, $id, $this->container->normalizeId($argument), $forConstructor) || $hasSelfRef;
701+
$hasSelfRef = $this->addInlineReference($head, $id, $this->container->normalizeId($argument), $forConstructor) || $hasSelfRef;
708702
} elseif ($argument instanceof Definition) {
709703
$hasSelfRef = $this->addInlineService($head, $tail, $id, $argument, $forConstructor) || $hasSelfRef;
710704
}
@@ -713,37 +707,31 @@ private function addInlineVariables(&$head, &$tail, $id, array $arguments, $forC
713707
return $hasSelfRef;
714708
}
715709

716-
private function addInlineReference(&$head, &$tail, $id, $targetId, $forConstructor)
710+
private function addInlineReference(&$code, $id, $targetId, $forConstructor)
717711
{
712+
$hasSelfRef = isset($this->circularReferences[$id][$targetId]);
713+
718714
if ('service_container' === $targetId || isset($this->referenceVariables[$targetId])) {
719-
return isset($this->circularReferences[$id][$targetId]);
715+
return $hasSelfRef;
720716
}
721717

722718
list($callCount, $behavior) = $this->serviceCalls[$targetId];
723719

724-
if (2 > $callCount && (!$forConstructor || !isset($this->circularReferences[$id][$targetId]))) {
725-
return isset($this->circularReferences[$id][$targetId]);
720+
if (2 > $callCount && (!$hasSelfRef || !$forConstructor)) {
721+
return $hasSelfRef;
726722
}
727723

728724
$name = $this->getNextVariableName();
729725
$this->referenceVariables[$targetId] = new Variable($name);
730726

731727
$reference = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $behavior ? new Reference($targetId, $behavior) : null;
732-
$code = sprintf(" \$%s = %s;\n", $name, $this->getServiceCall($targetId, $reference));
733-
734-
if (!isset($this->circularReferences[$id][$targetId])) {
735-
$head .= $code;
728+
$code .= sprintf(" \$%s = %s;\n", $name, $this->getServiceCall($targetId, $reference));
736729

737-
return false;
730+
if (!$hasSelfRef || !$forConstructor) {
731+
return $hasSelfRef;
738732
}
739733

740-
if (!$forConstructor) {
741-
$tail .= $code;
742-
743-
return true;
744-
}
745-
746-
$head .= $code.sprintf(<<<'EOTXT'
734+
$code .= sprintf(<<<'EOTXT'
747735
748736
if (isset($this->%s['%s'])) {
749737
return $this->%1$s['%2$s'];
@@ -766,17 +754,21 @@ private function addInlineService(&$head, &$tail, $id, Definition $definition, $
766754

767755
$arguments = array($definition->getArguments(), $definition->getFactory());
768756

769-
if (2 > $this->inlinedDefinitions[$definition] && !$definition->getMethodCalls() && !$definition->getProperties() && !$definition->getConfigurator() && false === strpos($this->dumpValue($definition->getClass()), '$')) {
757+
if (2 > $this->inlinedDefinitions[$definition] && !$definition->getMethodCalls() && !$definition->getProperties() && !$definition->getConfigurator()) {
770758
return $this->addInlineVariables($head, $tail, $id, $arguments, $forConstructor);
771759
}
772760

773761
$name = $this->getNextVariableName();
774762
$this->definitionVariables[$definition] = new Variable($name);
775763

776764
$code = '';
777-
$hasSelfRef = $this->addInlineVariables($code, $tail, $id, $arguments, $forConstructor);
765+
if ($forConstructor) {
766+
$hasSelfRef = $this->addInlineVariables($code, $tail, $id, $arguments, $forConstructor);
767+
} else {
768+
$hasSelfRef = $this->addInlineVariables($code, $code, $id, $arguments, $forConstructor);
769+
}
778770
$code .= $this->addNewInstance($definition, '$'.$name, ' = ', $id);
779-
$hasSelfRef ? $tail .= ('' !== $tail ? "\n" : '').$code : $head .= ('' !== $head ? "\n" : '').$code;
771+
$hasSelfRef && !$forConstructor ? $tail .= ('' !== $tail ? "\n" : '').$code : $head .= ('' !== $head ? "\n" : '').$code;
780772

781773
$code = '';
782774
$arguments = array($definition->getProperties(), $definition->getMethodCalls(), $definition->getConfigurator());

src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php

+34
Original file line numberDiff line numberDiff line change
@@ -863,6 +863,28 @@ public function testDeepServiceGraph()
863863
$this->assertEquals((object) array('p2' => (object) array('p3' => (object) array())), $container->get('foo')->bClone);
864864
}
865865

866+
public function testInlineSelfRef()
867+
{
868+
$container = new ContainerBuilder();
869+
870+
$bar = (new Definition('App\Bar'))
871+
->setProperty('foo', new Reference('App\Foo'));
872+
873+
$baz = (new Definition('App\Baz'))
874+
->setProperty('bar', $bar)
875+
->addArgument($bar);
876+
877+
$container->register('App\Foo')
878+
->setPublic(true)
879+
->addArgument($baz);
880+
881+
$passConfig = $container->getCompiler()->getPassConfig();
882+
$container->compile();
883+
884+
$dumper = new PhpDumper($container);
885+
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services_inline_self_ref.php', $dumper->dump(array('class' => 'Symfony_DI_PhpDumper_Test_Inline_Self_Ref')));
886+
}
887+
866888
public function testHotPathOptimizations()
867889
{
868890
$container = include self::$fixturesPath.'/containers/container_inline_requires.php';
@@ -940,6 +962,18 @@ public function testUninitializedSyntheticReference()
940962
$this->assertEquals((object) array('foo' => (object) array(123)), $container->get('bar'));
941963
}
942964

965+
public function testAdawsonContainer()
966+
{
967+
$container = new ContainerBuilder();
968+
$loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'));
969+
$loader->load('services_adawson.yml');
970+
$container->compile();
971+
972+
$dumper = new PhpDumper($container);
973+
$dump = $dumper->dump();
974+
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services_adawson.php', $dumper->dump());
975+
}
976+
943977
/**
944978
* @group legacy
945979
* @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.

src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container19.php

+5-2
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@
77

88
$container = new ContainerBuilder();
99

10+
$container->setParameter('env(FOO)', 'Bar\FaooClass');
11+
$container->setParameter('foo', '%env(FOO)%');
12+
1013
$container
11-
->register('service_from_anonymous_factory', 'Bar\FooClass')
12-
->setFactory(array(new Definition('Bar\FooClass'), 'getInstance'))
14+
->register('service_from_anonymous_factory', '%foo%')
15+
->setFactory(array(new Definition('%foo%'), 'getInstance'))
1316
->setPublic(true)
1417
;
1518

src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services19.php

+102-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ class ProjectServiceContainer extends Container
2121

2222
public function __construct()
2323
{
24+
$this->parameters = $this->getDefaultParameters();
25+
2426
$this->services = array();
2527
$this->methodMap = array(
2628
'service_from_anonymous_factory' => 'getServiceFromAnonymousFactoryService',
@@ -58,11 +60,11 @@ public function isFrozen()
5860
/**
5961
* Gets the public 'service_from_anonymous_factory' shared service.
6062
*
61-
* @return \Bar\FooClass
63+
* @return object A %env(FOO)% instance
6264
*/
6365
protected function getServiceFromAnonymousFactoryService()
6466
{
65-
return $this->services['service_from_anonymous_factory'] = (new \Bar\FooClass())->getInstance();
67+
return $this->services['service_from_anonymous_factory'] = (new ${($_ = $this->getEnv('FOO')) && false ?: "_"}())->getInstance();
6668
}
6769

6870
/**
@@ -78,4 +80,102 @@ protected function getServiceWithMethodCallAndFactoryService()
7880

7981
return $instance;
8082
}
83+
84+
public function getParameter($name)
85+
{
86+
$name = (string) $name;
87+
if (!(isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || array_key_exists($name, $this->parameters))) {
88+
$name = $this->normalizeParameterName($name);
89+
90+
if (!(isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || array_key_exists($name, $this->parameters))) {
91+
throw new InvalidArgumentException(sprintf('The parameter "%s" must be defined.', $name));
92+
}
93+
}
94+
if (isset($this->loadedDynamicParameters[$name])) {
95+
return $this->loadedDynamicParameters[$name] ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name);
96+
}
97+
98+
return $this->parameters[$name];
99+
}
100+
101+
public function hasParameter($name)
102+
{
103+
$name = (string) $name;
104+
$name = $this->normalizeParameterName($name);
105+
106+
return isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || array_key_exists($name, $this->parameters);
107+
}
108+
109+
public function setParameter($name, $value)
110+
{
111+
throw new LogicException('Impossible to call set() on a frozen ParameterBag.');
112+
}
113+
114+
public function getParameterBag()
115+
{
116+
if (null === $this->parameterBag) {
117+
$parameters = $this->parameters;
118+
foreach ($this->loadedDynamicParameters as $name => $loaded) {
119+
$parameters[$name] = $loaded ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name);
120+
}
121+
$this->parameterBag = new FrozenParameterBag($parameters);
122+
}
123+
124+
return $this->parameterBag;
125+
}
126+
127+
private $loadedDynamicParameters = array(
128+
'foo' => false,
129+
);
130+
private $dynamicParameters = array();
131+
132+
/**
133+
* Computes a dynamic parameter.
134+
*
135+
* @param string The name of the dynamic parameter to load
136+
*
137+
* @return mixed The value of the dynamic parameter
138+
*
139+
* @throws InvalidArgumentException When the dynamic parameter does not exist
140+
*/
141+
private function getDynamicParameter($name)
142+
{
143+
switch ($name) {
144+
case 'foo': $value = $this->getEnv('FOO'); break;
145+
default: throw new InvalidArgumentException(sprintf('The dynamic parameter "%s" must be defined.', $name));
146+
}
147+
$this->loadedDynamicParameters[$name] = true;
148+
149+
return $this->dynamicParameters[$name] = $value;
150+
}
151+
152+
private $normalizedParameterNames = array(
153+
'env(foo)' => 'env(FOO)',
154+
);
155+
156+
private function normalizeParameterName($name)
157+
{
158+
if (isset($this->normalizedParameterNames[$normalizedName = strtolower($name)]) || isset($this->parameters[$normalizedName]) || array_key_exists($normalizedName, $this->parameters)) {
159+
$normalizedName = isset($this->normalizedParameterNames[$normalizedName]) ? $this->normalizedParameterNames[$normalizedName] : $normalizedName;
160+
if ((string) $name !== $normalizedName) {
161+
@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);
162+
}
163+
} else {
164+
$normalizedName = $this->normalizedParameterNames[$normalizedName] = (string) $name;
165+
}
166+
167+
return $normalizedName;
168+
}
169+
170+
/**
171+
* Gets the default parameters.
172+
*
173+
* @return array An array of the default parameters
174+
*/
175+
protected function getDefaultParameters()
176+
{
177+
return array(
178+
'env(FOO)' => 'Bar\\FaooClass',
179+
);
180+
}
81181
}

0 commit comments

Comments
 (0)