[DependencyInjection] Invokable Factory Services

This commit is contained in:
Zan Baldwin 2019-02-18 14:13:45 +00:00 committed by Fabien Potencier
parent 3895acd175
commit 23cb83f726
10 changed files with 40 additions and 5 deletions

View File

@ -95,7 +95,7 @@ class Definition
/**
* Sets a factory.
*
* @param string|array $factory A PHP function or an array containing a class/Reference and a method to call
* @param string|array|Reference $factory A PHP function, reference or an array containing a class/Reference and a method to call
*
* @return $this
*/
@ -105,6 +105,8 @@ class Definition
if (\is_string($factory) && false !== strpos($factory, '::')) {
$factory = explode('::', $factory, 2);
} elseif ($factory instanceof Reference) {
$factory = [$factory, '__invoke'];
}
$this->factory = $factory;
@ -782,7 +784,7 @@ class Definition
/**
* Sets a configurator to call after the service is fully initialized.
*
* @param string|array $configurator A PHP callable
* @param string|array|Reference $configurator A PHP function, reference or an array containing a class/Reference and a method to call
*
* @return $this
*/
@ -792,6 +794,8 @@ class Definition
if (\is_string($configurator) && false !== strpos($configurator, '::')) {
$configurator = explode('::', $configurator, 2);
} elseif ($configurator instanceof Reference) {
$configurator = [$configurator, '__invoke'];
}
$this->configurator = $configurator;

View File

@ -317,7 +317,7 @@ class XmlFileLoader extends FileLoader
$class = $factory->hasAttribute('class') ? $factory->getAttribute('class') : null;
}
$definition->setFactory([$class, $factory->getAttribute('method')]);
$definition->setFactory([$class, $factory->getAttribute('method') ?: '__invoke']);
}
}
@ -332,7 +332,7 @@ class XmlFileLoader extends FileLoader
$class = $configurator->getAttribute('class');
}
$definition->setConfigurator([$class, $configurator->getAttribute('method')]);
$definition->setConfigurator([$class, $configurator->getAttribute('method') ?: '__invoke']);
}
}

View File

@ -571,12 +571,15 @@ class YamlFileLoader extends FileLoader
*
* @throws InvalidArgumentException When errors occur
*
* @return string|array A parsed callable
* @return string|array|Reference A parsed callable
*/
private function parseCallable($callable, $parameter, $id, $file)
{
if (\is_string($callable)) {
if ('' !== $callable && '@' === $callable[0]) {
if (false === strpos($callable, ':')) {
return [$this->resolveServices($callable, $file), '__invoke'];
}
throw new InvalidArgumentException(sprintf('The value of the "%s" option for the "%s" service must be the id of the service without the "@" prefix (replace "%s" with "%s").', $parameter, $id, $callable, substr($callable, 1)));
}

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\DependencyInjection\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
class DefinitionTest extends TestCase
{
@ -35,6 +36,9 @@ class DefinitionTest extends TestCase
$def->setFactory('Foo::bar');
$this->assertEquals(['Foo', 'bar'], $def->getFactory(), '->setFactory() converts string static method call to the array');
$def->setFactory($ref = new Reference('baz'));
$this->assertSame([$ref, '__invoke'], $def->getFactory(), '->setFactory() converts service reference to class invoke call');
$this->assertSame(['factory' => true], $def->getChanges());
}

View File

@ -59,6 +59,9 @@
<service id="new_factory4" class="BazClass">
<factory method="getInstance" />
</service>
<service id="new_factory5" class="FooBarClass">
<factory service="baz" />
</service>
<service id="alias_for_foo" alias="foo" />
<service id="another_alias_for_foo" alias="foo" public="false" />
<service id="0" class="FooClass" />

View File

@ -0,0 +1,6 @@
services:
factory:
class: Baz
invalid_factory:
class: FooBarClass
factory: '@factory:method'

View File

@ -1,3 +1,4 @@
services:
factory: { class: FooBarClass, factory: baz:getClass}
factory_with_static_call: { class: FooBarClass, factory: FooBacFactory::createFooBar}
invokable_factory: { class: FooBarClass, factory: '@factory' }

View File

@ -34,6 +34,7 @@ services:
new_factory2: { class: FooBarClass, factory: ['@baz', getClass]}
new_factory3: { class: FooBarClass, factory: [BazClass, getInstance]}
new_factory4: { class: BazClass, factory: [~, getInstance]}
new_factory5: { class: FooBarClass, factory: '@baz' }
Acme\WithShortCutArgs: [foo, '@baz']
alias_for_foo: '@foo'
another_alias_for_foo:

View File

@ -268,6 +268,7 @@ class XmlFileLoaderTest extends TestCase
$this->assertEquals([new Reference('baz'), 'getClass'], $services['new_factory2']->getFactory(), '->load() parses the factory tag');
$this->assertEquals(['BazClass', 'getInstance'], $services['new_factory3']->getFactory(), '->load() parses the factory tag');
$this->assertSame([null, 'getInstance'], $services['new_factory4']->getFactory(), '->load() accepts factory tag without class');
$this->assertEquals([new Reference('baz'), '__invoke'], $services['new_factory5']->getFactory(), '->load() accepts service reference as invokable factory');
$aliases = $container->getAliases();
$this->assertArrayHasKey('alias_for_foo', $aliases, '->load() parses <service> elements');

View File

@ -20,6 +20,7 @@ use Symfony\Component\DependencyInjection\Argument\BoundArgument;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Loader\IniFileLoader;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
@ -158,6 +159,7 @@ class YamlFileLoaderTest extends TestCase
$this->assertEquals([new Reference('baz'), 'getClass'], $services['new_factory2']->getFactory(), '->load() parses the factory tag');
$this->assertEquals(['BazClass', 'getInstance'], $services['new_factory3']->getFactory(), '->load() parses the factory tag');
$this->assertSame([null, 'getInstance'], $services['new_factory4']->getFactory(), '->load() accepts factory tag without class');
$this->assertEquals([new Reference('baz'), '__invoke'], $services['new_factory5']->getFactory(), '->load() accepts service reference as invokable factory');
$this->assertEquals(['foo', new Reference('baz')], $services['Acme\WithShortCutArgs']->getArguments(), '->load() parses short service definition');
$aliases = $container->getAliases();
@ -196,6 +198,16 @@ class YamlFileLoaderTest extends TestCase
$this->assertEquals([new Reference('baz'), 'getClass'], $services['factory']->getFactory(), '->load() parses the factory tag with service:method');
$this->assertEquals(['FooBacFactory', 'createFooBar'], $services['factory_with_static_call']->getFactory(), '->load() parses the factory tag with Class::method');
$this->assertEquals([new Reference('factory'), '__invoke'], $services['invokable_factory']->getFactory(), '->load() parses string service reference');
}
public function testFactorySyntaxError()
{
$container = new ContainerBuilder();
$loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'));
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('The value of the "factory" option for the "invalid_factory" service must be the id of the service without the "@" prefix (replace "@factory:method" with "factory:method").');
$loader->load('bad_factory_syntax.yml');
}
public function testLoadConfiguratorShortSyntax()