Skip to content

Commit

Permalink
[DependencyInjection] Tweaked factories
Browse files Browse the repository at this point in the history
  • Loading branch information
unkind committed Oct 1, 2014
1 parent 6acf3e7 commit ee82392
Show file tree
Hide file tree
Showing 17 changed files with 203 additions and 7 deletions.
14 changes: 13 additions & 1 deletion src/Symfony/Component/DependencyInjection/ContainerBuilder.php
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
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
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
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
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
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
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
@@ -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;
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;
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
@@ -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;
}
}
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
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
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
@@ -1,2 +1,3 @@
services:
factory: { class: FooBarClass, factory: baz:getClass}
factory_with_static_call: { class: FooBarClass, factory: FooBacFactory::createFooBar}
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
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

0 comments on commit ee82392

Please sign in to comment.