diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index e244bf185782..1d091a097f2b 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -941,7 +941,19 @@ public function createService(Definition $definition, $id, $tryProxy = true) $arguments = $this->resolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getArguments()))); - if (null !== $definition->getFactoryMethod()) { + if (null !== $definition->getFactory()) { + $factory = $definition->getFactory(); + + if (is_string($factory)) { + $callable = $definition->getFactory(); + } elseif (is_array($factory)) { + $callable = array($this->resolveServices($factory[0]), $factory[1]); + } else { + throw new RuntimeException(sprintf('Cannot create service "%s" because of invalid factory', $id)); + } + + $service = call_user_func_array($callable, $arguments); + } elseif (null !== $definition->getFactoryMethod()) { if (null !== $definition->getFactoryClass()) { $factory = $parameterBag->resolveValue($definition->getFactoryClass()); } elseif (null !== $definition->getFactoryService()) { diff --git a/src/Symfony/Component/DependencyInjection/Definition.php b/src/Symfony/Component/DependencyInjection/Definition.php index 920fd3bd5766..b77a13737a5b 100644 --- a/src/Symfony/Component/DependencyInjection/Definition.php +++ b/src/Symfony/Component/DependencyInjection/Definition.php @@ -66,6 +66,10 @@ public function __construct($class = null, array $arguments = array()) */ public function setFactory($factory) { + if (is_string($factory) && strpos($factory, '::') !== false) { + $factory = explode('::', $factory, 2); + } + $this->factory = $factory; return $this; diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 71732b12bfec..ba90bf514e86 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -730,13 +730,13 @@ private function addNewInstance($id, Definition $definition, $return, $instantia $class = $this->dumpValue($callable[0]); // If the class is a string we can optimize call_user_func away if (strpos($class, "'") === 0) { - return sprintf(" $return{$instantiation}\%s::%s(%s);\n", substr($class, 1, -1), $callable[1], $arguments ? implode(', ', $arguments) : ''); + return sprintf(" $return{$instantiation}%s::%s(%s);\n", $this->dumpLiteralClass($class), $callable[1], $arguments ? implode(', ', $arguments) : ''); } - return sprintf(" $return{$instantiation}call_user_func(array(%s, '%s'), %s);\n", $this->dumpValue($callable[0]), $callable[1], $arguments ? ', '.implode(', ', $arguments) : ''); + return sprintf(" $return{$instantiation}call_user_func(array(%s, '%s')%s);\n", $this->dumpValue($callable[0]), $callable[1], $arguments ? ', '.implode(', ', $arguments) : ''); } - return sprintf(" $return{$instantiation}%s(%s);\n", $callable, $arguments ? implode(', ', $arguments) : ''); + return sprintf(" $return{$instantiation}\\%s(%s);\n", $callable, $arguments ? implode(', ', $arguments) : ''); } elseif (null !== $definition->getFactoryMethod()) { if (null !== $definition->getFactoryClass()) { $class = $this->dumpValue($definition->getFactoryClass()); @@ -1212,7 +1212,7 @@ private function hasReference($id, array $arguments, $deep = false, array $visit /** * Dumps values. * - * @param array $value + * @param mixed $value * @param bool $interpolate * * @return string @@ -1249,6 +1249,30 @@ private function dumpValue($value, $interpolate = true) throw new RuntimeException('Cannot dump definitions which have a variable class name.'); } + if (null !== $value->getFactory()) { + $factory = $value->getFactory(); + + if (is_string($factory)) { + return sprintf('\\%s(%s)', $factory, implode(', ', $arguments)); + } + + if (is_array($factory)) { + if (is_string($factory[0])) { + return sprintf('\\%s::%s(%s)', $factory[0], $factory[1], implode(', ', $arguments)); + } + + if ($factory[0] instanceof Definition) { + return sprintf("call_user_func(array(%s, '%s')%s)", $this->dumpValue($factory[0]), $factory[1], count($arguments) > 0 ? ', '.implode(', ', $arguments) : ''); + } + + if ($factory[0] instanceof Reference) { + return sprintf('%s->%s(%s)', $this->dumpValue($factory[0]), $factory[1], implode(', ', $arguments)); + } + } + + throw new RuntimeException('Cannot dump definition because of invalid factory'); + } + if (null !== $value->getFactoryMethod()) { if (null !== $value->getFactoryClass()) { return sprintf("call_user_func(array(%s, '%s')%s)", $this->dumpValue($value->getFactoryClass()), $value->getFactoryMethod(), count($arguments) > 0 ? ', '.implode(', ', $arguments) : ''); diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index 202ac138edf3..bb53f8be1387 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -196,7 +196,7 @@ private function parseDefinition($id, $service, $file) if (isset($service['factory'])) { if (is_string($service['factory'])) { - if (strpos($service['factory'], ':')) { + if (strpos($service['factory'], ':') !== false && strpos($service['factory'], '::') === false) { $parts = explode(':', $service['factory']); $definition->setFactory(array($this->resolveServices('@'.$parts[0]), $parts[1])); } else { diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index 714a07b76818..6e839653aa02 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -341,6 +341,23 @@ public function testCreateServiceFactoryService() $this->assertInstanceOf('BazClass', $builder->get('baz_service')); } + /** + * @covers Symfony\Component\DependencyInjection\ContainerBuilder::createService + */ + public function testCreateServiceFactory() + { + $builder = new ContainerBuilder(); + $builder->register('foo', 'Bar\FooClass')->setFactory('Bar\FooClass::getInstance'); + $builder->register('qux', 'Bar\FooClass')->setFactory(array('Bar\FooClass', 'getInstance')); + $builder->register('bar', 'Bar\FooClass')->setFactory(array(new Definition('Bar\FooClass'), 'getInstance')); + $builder->register('baz', 'Bar\FooClass')->setFactory(array(new Reference('bar'), 'getInstance')); + + $this->assertTrue($builder->get('foo')->called, '->createService() calls the factory method to create the service instance'); + $this->assertTrue($builder->get('qux')->called, '->createService() calls the factory method to create the service instance'); + $this->assertTrue($builder->get('bar')->called, '->createService() uses anonymous service as factory'); + $this->assertTrue($builder->get('baz')->called, '->createService() uses another service as factory'); + } + /** * @covers Symfony\Component\DependencyInjection\ContainerBuilder::createService */ diff --git a/src/Symfony/Component/DependencyInjection/Tests/DefinitionTest.php b/src/Symfony/Component/DependencyInjection/Tests/DefinitionTest.php index dfe8a03bf6aa..cfcb5f486f0c 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/DefinitionTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/DefinitionTest.php @@ -34,8 +34,12 @@ public function testConstructor() public function testSetGetFactory() { $def = new Definition('stdClass'); + $this->assertSame($def, $def->setFactory('foo'), '->setFactory() implements a fluent interface'); $this->assertEquals('foo', $def->getFactory(), '->getFactory() returns the factory'); + + $def->setFactory('Foo::bar'); + $this->assertEquals(array('Foo', 'bar'), $def->getFactory(), '->setFactory() converts string static method call to the array'); } public function testSetGetFactoryClass() diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index 231ab773460a..6cb04446e093 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -120,6 +120,14 @@ public function testAddService() } } + public function testServicesWithAnonymousFactories() + { + $container = include self::$fixturesPath.'/containers/container19.php'; + $dumper = new PhpDumper($container); + + $this->assertStringEqualsFile(self::$fixturesPath.'/php/services19.php', $dumper->dump(), '->dump() dumps services with anonymous factories'); + } + /** * @expectedException \InvalidArgumentException * @expectedExceptionMessage Service id "bar$" cannot be converted to a valid PHP method name. diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container19.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container19.php new file mode 100644 index 000000000000..64b8f066dca1 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container19.php @@ -0,0 +1,22 @@ +register('service_from_anonymous_factory', 'Bar\FooClass') + ->setFactory(array(new Definition('Bar\FooClass'), 'getInstance')) +; + +$anonymousServiceWithFactory = new Definition('Bar\FooClass'); +$anonymousServiceWithFactory->setFactory('Bar\FooClass::getInstance'); +$container + ->register('service_with_method_call_and_factory', 'Bar\FooClass') + ->addMethodCall('setBar', array($anonymousServiceWithFactory)) +; + +return $container; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container9.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container9.php index aca2d012056a..49262e86326e 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container9.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container9.php @@ -114,5 +114,9 @@ ->setProperty('foo', 'bar') ->setFactory(array(new Reference('new_factory'), 'getInstance')) ; +$container + ->register('service_from_static_method', 'Bar\FooClass') + ->setFactory(array('Bar\FooClass', 'getInstance')) +; return $container; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/graphviz/services9.dot b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/graphviz/services9.dot index f4f498df2ada..e233d62594d0 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/graphviz/services9.dot +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/graphviz/services9.dot @@ -21,6 +21,7 @@ digraph sc { node_decorator_service_with_name [label="decorator_service_with_name\nstdClass\n", shape=record, fillcolor="#eeeeee", style="filled"]; node_new_factory [label="new_factory\nFactoryClass\n", shape=record, fillcolor="#eeeeee", style="filled"]; node_new_factory_service [label="new_factory_service\nFooBarBaz\n", shape=record, fillcolor="#eeeeee", style="filled"]; + node_service_from_static_method [label="service_from_static_method\nBar\\FooClass\n", shape=record, fillcolor="#eeeeee", style="filled"]; node_service_container [label="service_container\nSymfony\\Component\\DependencyInjection\\ContainerBuilder\n", shape=record, fillcolor="#9999ff", style="filled"]; node_foo2 [label="foo2\n\n", shape=record, fillcolor="#ff9999", style="filled"]; node_foo3 [label="foo3\n\n", shape=record, fillcolor="#ff9999", style="filled"]; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services19.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services19.php new file mode 100644 index 000000000000..746b3fa7765f --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services19.php @@ -0,0 +1,64 @@ +methodMap = array( + 'service_from_anonymous_factory' => 'getServiceFromAnonymousFactoryService', + 'service_with_method_call_and_factory' => 'getServiceWithMethodCallAndFactoryService', + ); + } + + /** + * Gets the 'service_from_anonymous_factory' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * @return \Bar\FooClass A Bar\FooClass instance. + */ + protected function getServiceFromAnonymousFactoryService() + { + return $this->services['service_from_anonymous_factory'] = call_user_func(array(new \Bar\FooClass(), 'getInstance')); + } + + /** + * Gets the 'service_with_method_call_and_factory' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * @return \Bar\FooClass A Bar\FooClass instance. + */ + protected function getServiceWithMethodCallAndFactoryService() + { + $this->services['service_with_method_call_and_factory'] = $instance = new \Bar\FooClass(); + + $instance->setBar(\Bar\FooClass::getInstance()); + + return $instance; + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php index 78eb1d8ee8ef..3cc3aac8b845 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php @@ -47,6 +47,7 @@ public function __construct() 'new_factory' => 'getNewFactoryService', 'new_factory_service' => 'getNewFactoryServiceService', 'request' => 'getRequestService', + 'service_from_static_method' => 'getServiceFromStaticMethodService', ); $this->aliases = array( 'alias_for_alias' => 'foo', @@ -303,6 +304,19 @@ protected function getRequestService() throw new RuntimeException('You have requested a synthetic service ("request"). The DIC does not know how to construct this service.'); } + /** + * Gets the 'service_from_static_method' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * @return \Bar\FooClass A Bar\FooClass instance. + */ + protected function getServiceFromStaticMethodService() + { + return $this->services['service_from_static_method'] = \Bar\FooClass::getInstance(); + } + /** * Updates the 'request' service. */ diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php index 9f25dc289b32..5e8103bdd0cc 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php @@ -50,6 +50,7 @@ public function __construct() 'method_call1' => 'getMethodCall1Service', 'new_factory_service' => 'getNewFactoryServiceService', 'request' => 'getRequestService', + 'service_from_static_method' => 'getServiceFromStaticMethodService', ); $this->aliases = array( 'alias_for_alias' => 'foo', @@ -305,6 +306,19 @@ protected function getRequestService() throw new RuntimeException('You have requested a synthetic service ("request"). The DIC does not know how to construct this service.'); } + /** + * Gets the 'service_from_static_method' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * @return \Bar\FooClass A Bar\FooClass instance. + */ + protected function getServiceFromStaticMethodService() + { + return $this->services['service_from_static_method'] = \Bar\FooClass::getInstance(); + } + /** * Updates the 'request' service. */ diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services9.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services9.xml index 459563bb0e1e..7234a82cdc4f 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services9.xml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services9.xml @@ -98,6 +98,9 @@ bar + + + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services14.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services14.yml index a6f825e42e31..4c188c5fbc7b 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services14.yml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services14.yml @@ -1,2 +1,3 @@ services: factory: { class: FooBarClass, factory: baz:getClass} + factory_with_static_call: { class: FooBarClass, factory: FooBacFactory::createFooBar} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml index e782ca5165ef..0b8da4396878 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml @@ -96,5 +96,8 @@ services: class: FooBarBaz properties: { foo: bar } factory: ['@new_factory', getInstance] + service_from_static_method: + class: Bar\FooClass + factory: [Bar\FooClass, getInstance] alias_for_foo: @foo alias_for_alias: @foo diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php index 1bba6a1ac830..3de0843dfbdf 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php @@ -169,7 +169,8 @@ public function testLoadFactoryShortSyntax() $loader->load('services14.yml'); $services = $container->getDefinitions(); - $this->assertEquals(array(new Reference('baz'), 'getClass'), $services['factory']->getFactory(), '->load() parses the factory tag'); + $this->assertEquals(array(new Reference('baz'), 'getClass'), $services['factory']->getFactory(), '->load() parses the factory tag with service:method'); + $this->assertEquals(array('FooBacFactory', 'createFooBar'), $services['factory_with_static_call']->getFactory(), '->load() parses the factory tag with Class::method'); } public function testExtensions()