Skip to content

[DependencyInjection] Tweaked factories #12065

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 3, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion src/Symfony/Component/DependencyInjection/ContainerBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down
4 changes: 4 additions & 0 deletions src/Symfony/Component/DependencyInjection/Definition.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
32 changes: 28 additions & 4 deletions src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) : '');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;

require_once __DIR__.'/../includes/classes.php';

$container = new ContainerBuilder();

$container
->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;
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Original file line number Diff line number Diff line change
Expand Up @@ -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"];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Exception\InactiveScopeException;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;

/**
* ProjectServiceContainer
*
* This class has been auto-generated
* by the Symfony Dependency Injection Component.
*/
class ProjectServiceContainer extends Container
{
private static $parameters = array(

);

/**
* Constructor.
*/
public function __construct()
{
parent::__construct(new ParameterBag(self::$parameters));
$this->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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@
<property name="foo">bar</property>
<factory service="new_factory" method="getInstance"/>
</service>
<service id="service_from_static_method" class="Bar\FooClass">
<factory class="Bar\FooClass" method="getInstance"/>
</service>
<service id="alias_for_foo" alias="foo"/>
<service id="alias_for_alias" alias="foo"/>
</services>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
services:
factory: { class: FooBarClass, factory: baz:getClass}
factory_with_static_call: { class: FooBarClass, factory: FooBacFactory::createFooBar}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down