From 5f98c23d1ed29411867b1ae592849e76e8a09c0b Mon Sep 17 00:00:00 2001 From: Santiago San Martin Date: Thu, 29 May 2025 23:22:17 -0300 Subject: [PATCH] [DependencyInjection] add `factory` argument in the `#[Autoconfigure]` attribute and allow instanceof factory --- .../Attribute/Autoconfigure.php | 2 + .../DependencyInjection/CHANGELOG.md | 2 + .../Configurator/InstanceofConfigurator.php | 1 + .../Loader/YamlFileLoader.php | 1 + .../schema/dic/services/services-1.0.xsd | 1 + ...egisterAutoconfigureAttributesPassTest.php | 88 +++++++++++++++++++ .../AutoconfigureWithExpressionFactory.php | 22 +++++ ...toconfigureWithInstanceExternalFactory.php | 22 +++++ .../AutoconfigureWithInvokableFactory.php | 22 +++++ ...AutoconfigureWithStaticExternalFactory.php | 22 +++++ .../AutoconfigureWithStaticSelfFactory.php | 27 ++++++ .../config/instanceof_factory.expected.yml | 16 ++++ .../Fixtures/config/instanceof_factory.php | 32 +++++++ .../xml/services_instanceof_factory.xml | 16 ++++ .../yaml/service_instanceof_factory2.yml | 12 +++ .../Tests/Loader/PhpFileLoaderTest.php | 1 + .../Tests/Loader/XmlFileLoaderTest.php | 15 +++- .../Tests/Loader/YamlFileLoaderTest.php | 15 +++- 18 files changed, 313 insertions(+), 4 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfigureWithExpressionFactory.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfigureWithInstanceExternalFactory.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfigureWithInvokableFactory.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfigureWithStaticExternalFactory.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfigureWithStaticSelfFactory.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/instanceof_factory.expected.yml create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/instanceof_factory.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_instanceof_factory.xml create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/service_instanceof_factory2.yml diff --git a/src/Symfony/Component/DependencyInjection/Attribute/Autoconfigure.php b/src/Symfony/Component/DependencyInjection/Attribute/Autoconfigure.php index 06513fd903e01..4d7e592575070 100644 --- a/src/Symfony/Component/DependencyInjection/Attribute/Autoconfigure.php +++ b/src/Symfony/Component/DependencyInjection/Attribute/Autoconfigure.php @@ -30,6 +30,7 @@ class Autoconfigure * @param array|null $properties The properties to define when creating the service * @param array{string, string}|string|null $configurator A PHP function, reference or an array containing a class/reference and a method to call after the service is fully initialized * @param string|null $constructor The public static method to use to instantiate the service + * @param array|string|null $factory The factory that defines how to create the service */ public function __construct( public ?array $tags = null, @@ -42,6 +43,7 @@ public function __construct( public ?array $properties = null, public array|string|null $configurator = null, public ?string $constructor = null, + public array|string|null $factory = null, ) { } } diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index 9451c0f76fd1e..ed7747a00cc0b 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -6,6 +6,8 @@ CHANGELOG * Allow `#[AsAlias]` to be extended * Add argument `$target` to `ContainerBuilder::registerAliasForArgument()` + * Add support for a `factory` argument in the `#[Autoconfigure]` attribute to define service instantiation via a factory + * Enable the use of `factory` within `instanceof` 7.3 --- diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/InstanceofConfigurator.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/InstanceofConfigurator.php index a26e5a84becfb..e871ae353b304 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/InstanceofConfigurator.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/InstanceofConfigurator.php @@ -23,6 +23,7 @@ class InstanceofConfigurator extends AbstractServiceConfigurator use Traits\CallTrait; use Traits\ConfiguratorTrait; use Traits\ConstructorTrait; + use Traits\FactoryTrait; use Traits\LazyTrait; use Traits\PropertyTrait; use Traits\PublicTrait; diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index c3b1bf255e8b1..748dc053dc7f4 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -100,6 +100,7 @@ class YamlFileLoader extends FileLoader 'autowire' => 'autowire', 'bind' => 'bind', 'constructor' => 'constructor', + 'factory' => 'factory', ]; private const DEFAULTS_KEYWORDS = [ diff --git a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd index befdb658f38ef..aca2e86623f01 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd +++ b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd @@ -175,6 +175,7 @@ + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterAutoconfigureAttributesPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterAutoconfigureAttributesPassTest.php index 931a4f5c4405b..aa8174ea011de 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterAutoconfigureAttributesPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterAutoconfigureAttributesPassTest.php @@ -26,6 +26,12 @@ use Symfony\Component\DependencyInjection\Tests\Fixtures\AutoconfigureRepeatedOverwrite; use Symfony\Component\DependencyInjection\Tests\Fixtures\AutoconfigureRepeatedProperties; use Symfony\Component\DependencyInjection\Tests\Fixtures\AutoconfigureRepeatedTag; +use Symfony\Component\DependencyInjection\Tests\Fixtures\AutoconfigureWithExpressionFactory; +use Symfony\Component\DependencyInjection\Tests\Fixtures\AutoconfigureWithInstanceExternalFactory; +use Symfony\Component\DependencyInjection\Tests\Fixtures\AutoconfigureWithInvokableFactory; +use Symfony\Component\DependencyInjection\Tests\Fixtures\AutoconfigureWithStaticExternalFactory; +use Symfony\Component\DependencyInjection\Tests\Fixtures\AutoconfigureWithStaticSelfFactory; +use Symfony\Component\DependencyInjection\Tests\Fixtures\FactoryDummy; use Symfony\Component\DependencyInjection\Tests\Fixtures\LazyAutoconfigured; use Symfony\Component\DependencyInjection\Tests\Fixtures\LazyLoaded; use Symfony\Component\DependencyInjection\Tests\Fixtures\MultipleAutoconfigureAttributed; @@ -208,6 +214,88 @@ public function testStaticConstructor() $this->assertEquals([StaticConstructorAutoconfigure::class => $expected], $container->getAutoconfiguredInstanceof()); } + public function testAutoconfigureWithStaticSelfFactory() + { + $container = new ContainerBuilder(); + $container->register('foo', AutoconfigureWithStaticSelfFactory::class) + ->setAutoconfigured(true); + + $argument = new BoundArgument('foo', false, BoundArgument::INSTANCEOF_BINDING, realpath(__DIR__.'/../Fixtures/AutoconfigureWithStaticSelfFactory.php')); + + (new RegisterAutoconfigureAttributesPass())->process($container); + + $expected = (new ChildDefinition('')) + ->setFactory([null, 'create']) + ->setBindings(['$foo' => $argument]) + ; + $this->assertEquals([AutoconfigureWithStaticSelfFactory::class => $expected], $container->getAutoconfiguredInstanceof()); + } + + public function testAutoconfigureWithStaticExternalFactory() + { + $container = new ContainerBuilder(); + $container->register('foo', AutoconfigureWithStaticExternalFactory::class) + ->setAutoconfigured(true); + + $argument = new BoundArgument('foo', false, BoundArgument::INSTANCEOF_BINDING, realpath(__DIR__.'/../Fixtures/AutoconfigureWithStaticExternalFactory.php')); + + (new RegisterAutoconfigureAttributesPass())->process($container); + + $expected = (new ChildDefinition('')) + ->setFactory([FactoryDummy::class, 'create']) + ->setBindings(['$foo' => $argument]) + ; + $this->assertEquals([AutoconfigureWithStaticExternalFactory::class => $expected], $container->getAutoconfiguredInstanceof()); + } + + public function testAutoconfigureWithInstanceExternalFactory() + { + $container = new ContainerBuilder(); + $container->register('foo', AutoconfigureWithInstanceExternalFactory::class) + ->setAutoconfigured(true); + + $argument = new BoundArgument('foo', false, BoundArgument::INSTANCEOF_BINDING, realpath(__DIR__.'/../Fixtures/AutoconfigureWithInstanceExternalFactory.php')); + + (new RegisterAutoconfigureAttributesPass())->process($container); + + $expected = (new ChildDefinition('')) + ->setFactory([new Reference('factory_for_autoconfigure'), 'createStatic']) + ->setBindings(['$foo' => $argument]) + ; + $this->assertEquals([AutoconfigureWithInstanceExternalFactory::class => $expected], $container->getAutoconfiguredInstanceof()); + } + + public function testAutoconfigureWithInvokableFactory() + { + $container = new ContainerBuilder(); + $container->register('foo', AutoconfigureWithInvokableFactory::class) + ->setAutoconfigured(true); + + $argument = new BoundArgument('foo', false, BoundArgument::INSTANCEOF_BINDING, realpath(__DIR__.'/../Fixtures/AutoconfigureWithInvokableFactory.php')); + + (new RegisterAutoconfigureAttributesPass())->process($container); + + $expected = (new ChildDefinition('')) + ->setFactory([new Reference('factory_for_autoconfigure'), '__invoke']) + ->setBindings(['$foo' => $argument]) + ; + $this->assertEquals([AutoconfigureWithInvokableFactory::class => $expected], $container->getAutoconfiguredInstanceof()); + } + + public function testAutoconfigureWithExpressionFactory() + { + $container = new ContainerBuilder(); + $container->register('foo', AutoconfigureWithExpressionFactory::class) + ->setAutoconfigured(true); + + (new RegisterAutoconfigureAttributesPass())->process($container); + + $expected = (new ChildDefinition('')) + ->setFactory('@=service("factory_for_autoconfigure").create()') + ; + $this->assertEquals([AutoconfigureWithExpressionFactory::class => $expected], $container->getAutoconfiguredInstanceof()); + } + public function testLazyServiceAttribute() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfigureWithExpressionFactory.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfigureWithExpressionFactory.php new file mode 100644 index 0000000000000..4e382ec2c63e0 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfigureWithExpressionFactory.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Fixtures; + +use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; + +#[Autoconfigure(factory: '@=service("factory_for_autoconfigure").create()')] +class AutoconfigureWithExpressionFactory +{ + public function __construct(public readonly string $foo) + { + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfigureWithInstanceExternalFactory.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfigureWithInstanceExternalFactory.php new file mode 100644 index 0000000000000..b6adc9dc598ef --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfigureWithInstanceExternalFactory.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Fixtures; + +use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; + +#[Autoconfigure(bind: ['$foo' => 'foo'], factory: ['@factory_for_autoconfigure', 'createStatic'])] +class AutoconfigureWithInstanceExternalFactory +{ + public function __construct(public readonly string $foo) + { + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfigureWithInvokableFactory.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfigureWithInvokableFactory.php new file mode 100644 index 0000000000000..9d482fddf2445 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfigureWithInvokableFactory.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Fixtures; + +use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; + +#[Autoconfigure(bind: ['$foo' => 'foo'], factory: '@factory_for_autoconfigure')] +class AutoconfigureWithInvokableFactory +{ + public function __construct(public readonly string $foo) + { + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfigureWithStaticExternalFactory.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfigureWithStaticExternalFactory.php new file mode 100644 index 0000000000000..e05ef25adbfad --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfigureWithStaticExternalFactory.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Fixtures; + +use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; + +#[Autoconfigure(bind: ['$foo' => 'foo'], factory: [FactoryDummy::class, 'create'])] +class AutoconfigureWithStaticExternalFactory +{ + public function __construct(public readonly string $foo) + { + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfigureWithStaticSelfFactory.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfigureWithStaticSelfFactory.php new file mode 100644 index 0000000000000..b84fb9ebf1bb8 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfigureWithStaticSelfFactory.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Fixtures; + +use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; + +#[Autoconfigure(bind: ['$foo' => 'foo'], factory: [null, 'create'])] +class AutoconfigureWithStaticSelfFactory +{ + public function __construct(public readonly string $foo) + { + } + + public static function create(string $foo): static + { + return new self($foo); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/instanceof_factory.expected.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/instanceof_factory.expected.yml new file mode 100644 index 0000000000000..1f7c75750c8a2 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/instanceof_factory.expected.yml @@ -0,0 +1,16 @@ + +services: + service_container: + class: Symfony\Component\DependencyInjection\ContainerInterface + public: true + synthetic: true + Symfony\Component\DependencyInjection\Tests\Fixtures\Bar: + class: Symfony\Component\DependencyInjection\Tests\Fixtures\Bar + public: true + tags: + - bar + factory: ['@Symfony\Component\DependencyInjection\Tests\Fixtures\BarFactory', getDefaultBar] + Symfony\Component\DependencyInjection\Tests\Fixtures\BarFactory: + class: Symfony\Component\DependencyInjection\Tests\Fixtures\BarFactory + public: true + arguments: [!tagged_iterator bar] diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/instanceof_factory.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/instanceof_factory.php new file mode 100644 index 0000000000000..db1e49c19de22 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/instanceof_factory.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\Tests\Fixtures\Bar; +use Symfony\Component\DependencyInjection\Tests\Fixtures\BarFactory; +use Symfony\Component\DependencyInjection\Tests\Fixtures\BarInterface; + +return function (ContainerConfigurator $container) { + $services = $container->services()->defaults()->public(); + + $services->instanceof(BarInterface::class) + ->factory([new Reference(BarFactory::class), 'getDefaultBar']) + ->tag('bar'); + + $services->set(Bar::class) + ->public(); + + $services->set(BarFactory::class) + ->args([new TaggedIteratorArgument('bar')]); +}; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_instanceof_factory.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_instanceof_factory.xml new file mode 100644 index 0000000000000..c91081d7f6fad --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_instanceof_factory.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/service_instanceof_factory2.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/service_instanceof_factory2.yml new file mode 100644 index 0000000000000..36417c0727ba3 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/service_instanceof_factory2.yml @@ -0,0 +1,12 @@ +services: + _instanceof: + Symfony\Component\DependencyInjection\Tests\Fixtures\BarInterface: + factory: ['@Symfony\Component\DependencyInjection\Tests\Fixtures\BarFactory', 'getDefaultBar'] + tags: + - { name: bar } + + Symfony\Component\DependencyInjection\Tests\Fixtures\Bar: + public: true + + Symfony\Component\DependencyInjection\Tests\Fixtures\BarFactory: + arguments: [!tagged_iterator 'bar'] diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/PhpFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/PhpFileLoaderTest.php index 72ededfd07329..ffa57557749a5 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/PhpFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/PhpFileLoaderTest.php @@ -139,6 +139,7 @@ public static function provideConfig() yield ['static_constructor']; yield ['inline_static_constructor']; yield ['instanceof_static_constructor']; + yield ['instanceof_factory']; yield ['closure']; yield ['from_callable']; yield ['env_param']; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php index f962fa1062bb5..7cd317a6ae4ea 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php @@ -941,11 +941,14 @@ public function testNamedArguments() $this->assertEquals([['setApiKey', ['123']]], $container->getDefinition(NamedArgumentsDummy::class)->getMethodCalls()); } - public function testInstanceof() + /** + * @dataProvider provideServiceInstanceOfFactoryFiles + */ + public function testInstanceof(string $fileName) { $container = new ContainerBuilder(); $loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml')); - $loader->load('services_instanceof.xml'); + $loader->load($fileName); $container->compile(); $definition = $container->getDefinition(Bar::class); @@ -954,6 +957,14 @@ public function testInstanceof() $this->assertSame(['foo' => [[]], 'bar' => [[]]], $definition->getTags()); } + public static function provideServiceInstanceOfFactoryFiles(): iterable + { + return [ + ['services_instanceof.xml'], + ['services_instanceof_factory.xml'], + ]; + } + public function testEnumeration() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php index 54900e4c3e146..4119a1b1c5e22 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php @@ -990,17 +990,28 @@ public function testFqcnLazyProxy() $this->assertSame([['interface' => 'SomeInterface']], $definition->getTag('proxy')); } - public function testServiceWithSameNameAsInterfaceAndFactoryIsNotTagged() + /** + * @dataProvider provideServiceInstanceOfFactoryFiles + */ + public function testServiceWithSameNameAsInterfaceAndFactoryIsNotTagged(string $fileName) { $container = new ContainerBuilder(); $loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml')); - $loader->load('service_instanceof_factory.yml'); + $loader->load($fileName); $container->compile(); $tagged = $container->findTaggedServiceIds('bar'); $this->assertCount(1, $tagged); } + public static function provideServiceInstanceOfFactoryFiles(): iterable + { + return [ + ['service_instanceof_factory.yml'], + ['service_instanceof_factory2.yml'], + ]; + } + /** * The pass may throw an exception, which will cause the test to fail. */ pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy