feature #30255 [DependencyInjection] Invokable Factory Services (zanbaldwin)
This PR was squashed before being merged into the 4.3-dev branch (closes #30255).
Discussion
----------
[DependencyInjection] Invokable Factory Services
| Q | A
| ------------- | ---
| Branch? | `master`
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets |
| License | MIT
| Doc PR | symfony/symfony-docs#11014
> Failing test is in the Twig bridge, and outside of the the scope of this PR.
Allow referencing invokable factory services, just as route definitions reference invokable controllers.
This functionality was also added for service configurators for consistency.
## Example
```php
<?php
namespace App\Factory;
class ServiceFactory
{
public function __invoke(bool $debug)
{
return new Service($debug);
}
}
```
```yaml
services:
App\Service:
# Prepend with "@" to differentiate between service and function.
factory: '@App\Factory\ServiceFactory'
arguments: [ '%kernel.debug%' ]
```
```xml
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<!-- ... -->
<service id="App\Service"
class="App\Service">
<factory service="App\Factory\ServiceFactory" />
</service>
</services>
</container>
```
```php
<?php
use App\Service;
use App\Factory\ServiceFactory;
use Symfony\Component\DependencyInjection\Reference;
$container->register(Service::class, Service::class)
->setFactory(new Reference(ServiceFactory::class));
```
Commits
-------
23cb83f726
[DependencyInjection] Invokable Factory Services
This commit is contained in:
commit
4ad54dad28
|
@ -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;
|
||||
|
@ -783,7 +785,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
|
||||
*/
|
||||
|
@ -793,6 +795,8 @@ class Definition
|
|||
|
||||
if (\is_string($configurator) && false !== strpos($configurator, '::')) {
|
||||
$configurator = explode('::', $configurator, 2);
|
||||
} elseif ($configurator instanceof Reference) {
|
||||
$configurator = [$configurator, '__invoke'];
|
||||
}
|
||||
|
||||
$this->configurator = $configurator;
|
||||
|
|
|
@ -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']);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -573,12 +573,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)));
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
services:
|
||||
factory:
|
||||
class: Baz
|
||||
invalid_factory:
|
||||
class: FooBarClass
|
||||
factory: '@factory:method'
|
|
@ -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' }
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -269,6 +269,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');
|
||||
|
|
|
@ -21,6 +21,7 @@ use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
|
|||
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
|
||||
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;
|
||||
|
@ -159,6 +160,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();
|
||||
|
@ -197,6 +199,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()
|
||||
|
|
Reference in New Issue