diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index 49e140de11..4232369e2e 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 3.4.0 ----- + * added support for ignore-on-uninitialized references * deprecated service auto-registration while autowiring * deprecated the ability to check for the initialization of a private service with the `Container::initialized()` method * deprecated support for top-level anonymous services in XML diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php index 3a375f3516..99eed81fcc 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\ExpressionLanguage; use Symfony\Component\DependencyInjection\Reference; @@ -96,7 +97,8 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements Repe $this->getDefinitionId((string) $value), $targetDefinition, $value, - $this->lazy || ($targetDefinition && $targetDefinition->isLazy()) + $this->lazy || ($targetDefinition && $targetDefinition->isLazy()), + ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $value->getInvalidBehavior() ); return $value; diff --git a/src/Symfony/Component/DependencyInjection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php b/src/Symfony/Component/DependencyInjection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php index 35fb325e74..7ffedd3dc0 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php @@ -11,6 +11,7 @@ namespace Symfony\Component\DependencyInjection\Compiler; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Reference; @@ -24,14 +25,16 @@ class CheckExceptionOnInvalidReferenceBehaviorPass extends AbstractRecursivePass { protected function processValue($value, $isRoot = false) { - if ($value instanceof Reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $value->getInvalidBehavior()) { - $destId = (string) $value; - - if (!$this->container->has($destId)) { - throw new ServiceNotFoundException($destId, $this->currentId); - } + if (!$value instanceof Reference) { + return parent::processValue($value, $isRoot); + } + if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $value->getInvalidBehavior() && !$this->container->has($id = (string) $value)) { + throw new ServiceNotFoundException($id, $this->currentId); + } + if (ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $value->getInvalidBehavior() && $this->container->has($id = (string) $value) && !$this->container->findDefinition($id)->isShared()) { + throw new InvalidArgumentException(sprintf('Invalid ignore-on-uninitialized reference found in service "%s": target service "%s" is not shared.', $this->currentId, $id)); } - return parent::processValue($value, $isRoot); + return $value; } } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php index f2ef363c83..240e1ab655 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php @@ -96,6 +96,9 @@ class InlineServiceDefinitionsPass extends AbstractRecursivePass implements Repe $ids = array(); foreach ($graph->getNode($id)->getInEdges() as $edge) { + if ($edge->isWeak()) { + return false; + } $ids[] = $edge->getSourceNode()->getId(); } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php b/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php index f8dba86a0b..8c81452b31 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php @@ -74,6 +74,9 @@ class RegisterServiceSubscribersPass extends AbstractRecursivePass if ($optionalBehavior = '?' === $type[0]) { $type = substr($type, 1); $optionalBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; + } elseif ($optionalBehavior = '!' === $type[0]) { + $type = substr($type, 1); + $optionalBehavior = ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE; } if (is_int($key)) { $key = $type; diff --git a/src/Symfony/Component/DependencyInjection/Compiler/RemoveUnusedDefinitionsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/RemoveUnusedDefinitionsPass.php index 79a2600d8f..a8a01be6d5 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/RemoveUnusedDefinitionsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/RemoveUnusedDefinitionsPass.php @@ -50,6 +50,9 @@ class RemoveUnusedDefinitionsPass implements RepeatablePassInterface $referencingAliases = array(); $sourceIds = array(); foreach ($edges as $edge) { + if ($edge->isWeak()) { + continue; + } $node = $edge->getSourceNode(); $sourceIds[] = $node->getId(); diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraph.php b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraph.php index 193a37e20b..3a6f03ee6d 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraph.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraph.php @@ -20,6 +20,8 @@ use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; * it themselves which improves performance quite a lot. * * @author Johannes M. Schmitt + * + * @final since version 3.4 */ class ServiceReferenceGraph { @@ -85,23 +87,16 @@ class ServiceReferenceGraph * @param string $destValue * @param string $reference * @param bool $lazy + * @param bool $weak */ - public function connect($sourceId, $sourceValue, $destId, $destValue = null, $reference = null/*, bool $lazy = false*/) + public function connect($sourceId, $sourceValue, $destId, $destValue = null, $reference = null/*, bool $lazy = false, bool $weak = false*/) { - if (func_num_args() >= 6) { - $lazy = func_get_arg(5); - } else { - if (__CLASS__ !== get_class($this)) { - $r = new \ReflectionMethod($this, __FUNCTION__); - if (__CLASS__ !== $r->getDeclaringClass()->getName()) { - @trigger_error(sprintf('Method %s() will have a 6th `bool $lazy = false` argument in version 4.0. Not defining it is deprecated since 3.3.', __METHOD__), E_USER_DEPRECATED); - } - } - $lazy = false; - } + $lazy = func_num_args() >= 6 ? func_get_arg(5) : false; + $weak = func_num_args() >= 7 ? func_get_arg(6) : false; + $sourceNode = $this->createNode($sourceId, $sourceValue); $destNode = $this->createNode($destId, $destValue); - $edge = new ServiceReferenceGraphEdge($sourceNode, $destNode, $reference, $lazy); + $edge = new ServiceReferenceGraphEdge($sourceNode, $destNode, $reference, $lazy, $weak); $sourceNode->addOutEdge($edge); $destNode->addInEdge($edge); diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphEdge.php b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphEdge.php index 17dd5d9559..5b8256977f 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphEdge.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphEdge.php @@ -24,19 +24,22 @@ class ServiceReferenceGraphEdge private $destNode; private $value; private $lazy; + private $weak; /** * @param ServiceReferenceGraphNode $sourceNode * @param ServiceReferenceGraphNode $destNode * @param string $value * @param bool $lazy + * @param bool $weak */ - public function __construct(ServiceReferenceGraphNode $sourceNode, ServiceReferenceGraphNode $destNode, $value = null, $lazy = false) + public function __construct(ServiceReferenceGraphNode $sourceNode, ServiceReferenceGraphNode $destNode, $value = null, $lazy = false, $weak = false) { $this->sourceNode = $sourceNode; $this->destNode = $destNode; $this->value = $value; $this->lazy = $lazy; + $this->weak = $weak; } /** @@ -78,4 +81,14 @@ class ServiceReferenceGraphEdge { return $this->lazy; } + + /** + * Returns true if the edge is weak, meaning it shouldn't prevent removing the target service. + * + * @return bool + */ + public function isWeak() + { + return $this->weak; + } } diff --git a/src/Symfony/Component/DependencyInjection/Container.php b/src/Symfony/Component/DependencyInjection/Container.php index 30bf1a0a6c..ac423bd4a4 100644 --- a/src/Symfony/Component/DependencyInjection/Container.php +++ b/src/Symfony/Component/DependencyInjection/Container.php @@ -23,26 +23,15 @@ use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag; * Container is a dependency injection container. * * It gives access to object instances (services). - * * Services and parameters are simple key/pair stores. - * - * Parameter and service keys are case insensitive. - * - * A service can also be defined by creating a method named - * getXXXService(), where XXX is the camelized version of the id: - * - * - * - * The container can have three possible behaviors when a service does not exist: + * The container can have four possible behaviors when a service + * does not exist (or is not initialized for the last case): * * * EXCEPTION_ON_INVALID_REFERENCE: Throws an exception (the default) * * NULL_ON_INVALID_REFERENCE: Returns null * * IGNORE_ON_INVALID_REFERENCE: Ignores the wrapping command asking for the reference * (for instance, ignore a setter if the service does not exist) + * * IGNORE_ON_UNINITIALIZED_REFERENCE: Ignores/returns null for uninitialized services or invalid references * * @author Fabien Potencier * @author Johannes M. Schmitt @@ -304,9 +293,9 @@ class Container implements ResettableContainerInterface try { if (isset($this->fileMap[$id])) { - return $this->load($this->fileMap[$id]); + return self::IGNORE_ON_UNINITIALIZED_REFERENCE === $invalidBehavior ? null : $this->load($this->fileMap[$id]); } elseif (isset($this->methodMap[$id])) { - return $this->{$this->methodMap[$id]}(); + return self::IGNORE_ON_UNINITIALIZED_REFERENCE === $invalidBehavior ? null : $this->{$this->methodMap[$id]}(); } elseif (--$i && $id !== $normalizedId = $this->normalizeId($id)) { $id = $normalizedId; continue; @@ -315,7 +304,7 @@ class Container implements ResettableContainerInterface // and only when the dumper has not generated the method map (otherwise the method map is considered to be fully populated by the dumper) @trigger_error('Generating a dumped container without populating the method map is deprecated since 3.2 and will be unsupported in 4.0. Update your dumper to generate the method map.', E_USER_DEPRECATED); - return $this->{$method}(); + return self::IGNORE_ON_UNINITIALIZED_REFERENCE === $invalidBehavior ? null : $this->{$method}(); } break; diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 683f8777e5..2b8dfd6a3e 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -565,6 +565,9 @@ class ContainerBuilder extends Container implements TaggedContainerInterface { $id = $this->normalizeId($id); + if (ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $invalidBehavior) { + return parent::get($id, $invalidBehavior); + } if ($service = parent::get($id, ContainerInterface::NULL_ON_INVALID_REFERENCE)) { return $service; } @@ -1160,6 +1163,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface continue 2; } } + foreach (self::getInitializedConditionals($v) as $s) { + if (!$this->get($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE)) { + continue 2; + } + } yield $k => $this->resolveServices($v); } @@ -1171,6 +1179,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface continue 2; } } + foreach (self::getInitializedConditionals($v) as $s) { + if (!$this->get($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE)) { + continue 2; + } + } ++$count; } @@ -1397,6 +1410,8 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * @param mixed $value An array of conditionals to return * * @return array An array of Service conditionals + * + * @internal since version 3.4 */ public static function getServiceConditionals($value) { @@ -1413,6 +1428,30 @@ class ContainerBuilder extends Container implements TaggedContainerInterface return $services; } + /** + * Returns the initialized conditionals. + * + * @param mixed $value An array of conditionals to return + * + * @return array An array of uninitialized conditionals + * + * @internal + */ + public static function getInitializedConditionals($value) + { + $services = array(); + + if (is_array($value)) { + foreach ($value as $v) { + $services = array_unique(array_merge($services, self::getInitializedConditionals($v))); + } + } elseif ($value instanceof Reference && $value->getInvalidBehavior() === ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE) { + $services[] = (string) $value; + } + + return $services; + } + /** * Computes a reasonably unique hash of a value. * @@ -1465,13 +1504,16 @@ class ContainerBuilder extends Container implements TaggedContainerInterface private function callMethod($service, $call) { - $services = self::getServiceConditionals($call[1]); - - foreach ($services as $s) { + foreach (self::getServiceConditionals($call[1]) as $s) { if (!$this->has($s)) { return; } } + foreach (self::getInitializedConditionals($call[1]) as $s) { + if (!$this->get($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE)) { + return; + } + } call_user_func_array(array($service, $call[0]), $this->resolveServices($this->getParameterBag()->unescapeValue($this->getParameterBag()->resolveValue($call[1])))); } diff --git a/src/Symfony/Component/DependencyInjection/ContainerInterface.php b/src/Symfony/Component/DependencyInjection/ContainerInterface.php index cfbc828722..2274ec7bb3 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerInterface.php +++ b/src/Symfony/Component/DependencyInjection/ContainerInterface.php @@ -27,6 +27,7 @@ interface ContainerInterface extends PsrContainerInterface const EXCEPTION_ON_INVALID_REFERENCE = 1; const NULL_ON_INVALID_REFERENCE = 2; const IGNORE_ON_INVALID_REFERENCE = 3; + const IGNORE_ON_UNINITIALIZED_REFERENCE = 4; /** * Sets a service. diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 49cd09f02a..49e90a4115 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -277,7 +277,7 @@ EOF; if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $behavior[$id]) { $code .= sprintf($template, $name, $this->getServiceCall($id)); } else { - $code .= sprintf($template, $name, $this->getServiceCall($id, new Reference($id, ContainerInterface::NULL_ON_INVALID_REFERENCE))); + $code .= sprintf($template, $name, $this->getServiceCall($id, new Reference($id, $behavior[$id]))); } } } @@ -1295,12 +1295,14 @@ EOF; */ private function getServiceConditionals($value) { - if (!$services = ContainerBuilder::getServiceConditionals($value)) { - return null; - } - $conditions = array(); - foreach ($services as $service) { + foreach (ContainerBuilder::getInitializedConditionals($value) as $service) { + if (!$this->container->hasDefinition($service)) { + return 'false'; + } + $conditions[] = sprintf("isset(\$this->services['%s'])", $service); + } + foreach (ContainerBuilder::getServiceConditionals($value) as $service) { if ($this->container->hasDefinition($service) && !$this->container->getDefinition($service)->isPublic()) { continue; } @@ -1335,8 +1337,8 @@ EOF; } if (!isset($behavior[$id])) { $behavior[$id] = $argument->getInvalidBehavior(); - } elseif (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $behavior[$id]) { - $behavior[$id] = $argument->getInvalidBehavior(); + } else { + $behavior[$id] = min($behavior[$id], $argument->getInvalidBehavior()); } ++$calls[$id]; @@ -1665,7 +1667,9 @@ EOF; return '$this'; } - if ($this->asFiles && $this->container->hasDefinition($id)) { + if (null !== $reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $reference->getInvalidBehavior()) { + $code = 'null'; + } elseif ($this->asFiles && $this->container->hasDefinition($id)) { if ($this->container->getDefinition($id)->isShared()) { $code = sprintf("\$this->load(__DIR__.'/%s.php')", $this->generateMethodName($id)); } else { diff --git a/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php index c6b3be06a1..2bfefeb2d1 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php @@ -309,6 +309,8 @@ class XmlDumper extends Dumper $element->setAttribute('on-invalid', 'null'); } elseif ($behaviour == ContainerInterface::IGNORE_ON_INVALID_REFERENCE) { $element->setAttribute('on-invalid', 'ignore'); + } elseif ($behaviour == ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE) { + $element->setAttribute('on-invalid', 'ignore_uninitialized'); } } elseif ($value instanceof Definition) { $element->setAttribute('type', 'service'); diff --git a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php index 76f97315c7..d8f07edc08 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php @@ -304,8 +304,12 @@ class YamlDumper extends Dumper */ private function getServiceCall($id, Reference $reference = null) { - if (null !== $reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $reference->getInvalidBehavior()) { - return sprintf('@?%s', $id); + if (null !== $reference) { + switch ($reference->getInvalidBehavior()) { + case ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE: break; + case ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE: return sprintf('@!%s', $id); + default: return sprintf('@?%s', $id); + } } return sprintf('@%s', $id); diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php index 0a5f9593d4..a5ee960a70 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -492,6 +492,8 @@ class XmlFileLoader extends FileLoader $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; if ('ignore' == $onInvalid) { $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; + } elseif ('ignore_uninitialized' == $onInvalid) { + $invalidBehavior = ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE; } elseif ('null' == $onInvalid) { $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE; } diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index 8a1c51f4e6..b57ead2c89 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -758,6 +758,9 @@ class YamlFileLoader extends FileLoader if (0 === strpos($value, '@@')) { $value = substr($value, 1); $invalidBehavior = null; + } elseif (0 === strpos($value, '@!')) { + $value = substr($value, 2); + $invalidBehavior = ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE; } elseif (0 === strpos($value, '@?')) { $value = substr($value, 2); $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; diff --git a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd index aad40ac631..0be8658e3d 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd +++ b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd @@ -266,6 +266,7 @@ + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckExceptionOnInvalidReferenceBehaviorPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckExceptionOnInvalidReferenceBehaviorPassTest.php index 65a782a4a8..dc20b39bc4 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckExceptionOnInvalidReferenceBehaviorPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckExceptionOnInvalidReferenceBehaviorPassTest.php @@ -67,6 +67,27 @@ class CheckExceptionOnInvalidReferenceBehaviorPassTest extends TestCase $this->process($container); } + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException + * @expectedExceptionMessage Invalid ignore-on-uninitialized reference found in service + */ + public function testProcessThrowsExceptionOnNonSharedUninitializedReference() + { + $container = new ContainerBuilder(); + + $container + ->register('a', 'stdClass') + ->addArgument(new Reference('b', $container::IGNORE_ON_UNINITIALIZED_REFERENCE)) + ; + + $container + ->register('b', 'stdClass') + ->setShared(false) + ; + + $this->process($container); + } + private function process(ContainerBuilder $container) { $pass = new CheckExceptionOnInvalidReferenceBehaviorPass(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index 895e035185..54e3227d23 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -1098,6 +1098,38 @@ class ContainerBuilderTest extends TestCase $this->assertSame($container->get('bar_service'), $foo->get('bar')); } + public function testUninitializedReference() + { + $container = include __DIR__.'/Fixtures/containers/container_uninitialized_ref.php'; + $container->compile(); + + $bar = $container->get('bar'); + + $this->assertNull($bar->foo1); + $this->assertNull($bar->foo2); + $this->assertNull($bar->foo3); + $this->assertNull($bar->closures[0]()); + $this->assertNull($bar->closures[1]()); + $this->assertNull($bar->closures[2]()); + $this->assertSame(array(), iterator_to_array($bar->iter)); + + $container = include __DIR__.'/Fixtures/containers/container_uninitialized_ref.php'; + $container->compile(); + + $container->get('foo1'); + $container->get('baz'); + + $bar = $container->get('bar'); + + $this->assertEquals(new \stdClass(), $bar->foo1); + $this->assertNull($bar->foo2); + $this->assertEquals(new \stdClass(), $bar->foo3); + $this->assertEquals(new \stdClass(), $bar->closures[0]()); + $this->assertNull($bar->closures[1]()); + $this->assertEquals(new \stdClass(), $bar->closures[2]()); + $this->assertEquals(array('foo1' => new \stdClass(), 'foo3' => new \stdClass()), iterator_to_array($bar->iter)); + } + public function testRegisterForAutoconfiguration() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index 338d7b5bb3..62213ee280 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -631,6 +631,44 @@ class PhpDumperTest extends TestCase $this->assertStringEqualsFile(self::$fixturesPath.'/php/services_private_in_expression.php', $dumper->dump()); } + public function testUninitializedReference() + { + $container = include self::$fixturesPath.'/containers/container_uninitialized_ref.php'; + $container->compile(); + $dumper = new PhpDumper($container); + + $this->assertStringEqualsFile(self::$fixturesPath.'/php/services_uninitialized_ref.php', $dumper->dump(array('class' => 'Symfony_DI_PhpDumper_Test_Uninitialized_Reference'))); + + require self::$fixturesPath.'/php/services_uninitialized_ref.php'; + + $container = new \Symfony_DI_PhpDumper_Test_Uninitialized_Reference(); + + $bar = $container->get('bar'); + + $this->assertNull($bar->foo1); + $this->assertNull($bar->foo2); + $this->assertNull($bar->foo3); + $this->assertNull($bar->closures[0]()); + $this->assertNull($bar->closures[1]()); + $this->assertNull($bar->closures[2]()); + $this->assertSame(array(), iterator_to_array($bar->iter)); + + $container = new \Symfony_DI_PhpDumper_Test_Uninitialized_Reference(); + + $container->get('foo1'); + $container->get('baz'); + + $bar = $container->get('bar'); + + $this->assertEquals(new \stdClass(), $bar->foo1); + $this->assertNull($bar->foo2); + $this->assertEquals(new \stdClass(), $bar->foo3); + $this->assertEquals(new \stdClass(), $bar->closures[0]()); + $this->assertNull($bar->closures[1]()); + $this->assertEquals(new \stdClass(), $bar->closures[2]()); + $this->assertEquals(array('foo1' => new \stdClass(), 'foo3' => new \stdClass()), iterator_to_array($bar->iter)); + } + public function testDumpHandlesLiteralClassWithRootNamespace() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/XmlDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/XmlDumperTest.php index 3ac6628b73..8a34a2b19a 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/XmlDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/XmlDumperTest.php @@ -12,8 +12,12 @@ namespace Symfony\Component\DependencyInjection\Tests\Dumper; use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Dumper\XmlDumper; +use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; +use Symfony\Component\DependencyInjection\Reference; class XmlDumperTest extends TestCase { @@ -184,6 +188,18 @@ class XmlDumperTest extends TestCase $this->assertEquals(file_get_contents(self::$fixturesPath.'/xml/services24.xml'), $dumper->dump()); } + public function testDumpLoad() + { + $container = new ContainerBuilder(); + $loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml')); + $loader->load('services_dump_load.xml'); + + $this->assertEquals(array(new Reference('bar', ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE)), $container->getDefinition('foo')->getArguments()); + + $dumper = new XmlDumper($container); + $this->assertStringEqualsFile(self::$fixturesPath.'/xml/services_dump_load.xml', $dumper->dump()); + } + public function testDumpAbstractServices() { $container = include self::$fixturesPath.'/containers/container_abstract.php'; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php index 968385633b..85ce181461 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php @@ -14,9 +14,11 @@ namespace Symfony\Component\DependencyInjection\Tests\Dumper; use PHPUnit\Framework\TestCase; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Dumper\YamlDumper; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; +use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Parser; @@ -73,6 +75,8 @@ class YamlDumperTest extends TestCase $loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml')); $loader->load('services_dump_load.yml'); + $this->assertEquals(array(new Reference('bar', ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE)), $container->getDefinition('foo')->getArguments()); + $dumper = new YamlDumper($container); $this->assertStringEqualsFile(self::$fixturesPath.'/yaml/services_dump_load.yml', $dumper->dump()); } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container_uninitialized_ref.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container_uninitialized_ref.php new file mode 100644 index 0000000000..9ecf7c909f --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container_uninitialized_ref.php @@ -0,0 +1,46 @@ +register('foo1', 'stdClass') +; + +$container + ->register('foo2', 'stdClass') + ->setPublic(false) +; + +$container + ->register('foo3', 'stdClass') + ->setPublic(false) +; + +$container + ->register('baz', 'stdClass') + ->setProperty('foo3', new Reference('foo3')) +; + +$container + ->register('bar', 'stdClass') + ->setProperty('foo1', new Reference('foo1', $container::IGNORE_ON_UNINITIALIZED_REFERENCE)) + ->setProperty('foo2', new Reference('foo2', $container::IGNORE_ON_UNINITIALIZED_REFERENCE)) + ->setProperty('foo3', new Reference('foo3', $container::IGNORE_ON_UNINITIALIZED_REFERENCE)) + ->setProperty('closures', array( + new ServiceClosureArgument(new Reference('foo1', $container::IGNORE_ON_UNINITIALIZED_REFERENCE)), + new ServiceClosureArgument(new Reference('foo2', $container::IGNORE_ON_UNINITIALIZED_REFERENCE)), + new ServiceClosureArgument(new Reference('foo3', $container::IGNORE_ON_UNINITIALIZED_REFERENCE)), + )) + ->setProperty('iter', new IteratorArgument(array( + 'foo1' => new Reference('foo1', $container::IGNORE_ON_UNINITIALIZED_REFERENCE), + 'foo2' => new Reference('foo2', $container::IGNORE_ON_UNINITIALIZED_REFERENCE), + 'foo3' => new Reference('foo3', $container::IGNORE_ON_UNINITIALIZED_REFERENCE), + ))) +; + +return $container; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_uninitialized_ref.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_uninitialized_ref.php new file mode 100644 index 0000000000..df0d74c07c --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_uninitialized_ref.php @@ -0,0 +1,138 @@ +services = array(); + $this->methodMap = array( + 'bar' => 'getBarService', + 'baz' => 'getBazService', + 'foo1' => 'getFoo1Service', + 'foo3' => 'getFoo3Service', + ); + $this->privates = array( + 'foo3' => true, + ); + + $this->aliases = array(); + } + + /** + * {@inheritdoc} + */ + public function compile() + { + throw new LogicException('You cannot compile a dumped container that was already compiled.'); + } + + /** + * {@inheritdoc} + */ + public function isCompiled() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function isFrozen() + { + @trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0. Use the isCompiled() method instead.', __METHOD__), E_USER_DEPRECATED); + + return true; + } + + /** + * Gets the public 'bar' shared service. + * + * @return \stdClass + */ + protected function getBarService() + { + $this->services['bar'] = $instance = new \stdClass(); + + $instance->foo1 = ${($_ = isset($this->services['foo1']) ? $this->services['foo1'] : null) && false ?: '_'}; + $instance->foo2 = null; + $instance->foo3 = ${($_ = isset($this->services['foo3']) ? $this->services['foo3'] : null) && false ?: '_'}; + $instance->closures = array(0 => function () { + return ${($_ = isset($this->services['foo1']) ? $this->services['foo1'] : null) && false ?: '_'}; + }, 1 => function () { + return null; + }, 2 => function () { + return ${($_ = isset($this->services['foo3']) ? $this->services['foo3'] : null) && false ?: '_'}; + }); + $instance->iter = new RewindableGenerator(function () { + if (isset($this->services['foo1'])) { + yield 'foo1' => ${($_ = isset($this->services['foo1']) ? $this->services['foo1'] : null) && false ?: '_'}; + } + if (false) { + yield 'foo2' => null; + } + if (isset($this->services['foo3'])) { + yield 'foo3' => ${($_ = isset($this->services['foo3']) ? $this->services['foo3'] : null) && false ?: '_'}; + } + }, function () { + return 0 + (int) (isset($this->services['foo1'])) + (int) (false) + (int) (isset($this->services['foo3'])); + }); + + return $instance; + } + + /** + * Gets the public 'baz' shared service. + * + * @return \stdClass + */ + protected function getBazService() + { + $this->services['baz'] = $instance = new \stdClass(); + + $instance->foo3 = ${($_ = isset($this->services['foo3']) ? $this->services['foo3'] : $this->getFoo3Service()) && false ?: '_'}; + + return $instance; + } + + /** + * Gets the public 'foo1' shared service. + * + * @return \stdClass + */ + protected function getFoo1Service() + { + return $this->services['foo1'] = new \stdClass(); + } + + /** + * Gets the private 'foo3' shared service. + * + * @return \stdClass + */ + protected function getFoo3Service() + { + return $this->services['foo3'] = new \stdClass(); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_dump_load.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_dump_load.xml new file mode 100644 index 0000000000..e763aa870a --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_dump_load.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_dump_load.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_dump_load.yml index 43b0c7d58a..8f3e153afc 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_dump_load.yml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_dump_load.yml @@ -6,6 +6,7 @@ services: foo: autoconfigure: true abstract: true + arguments: ['@!bar'] Psr\Container\ContainerInterface: alias: service_container public: false