From 8dda3f0b77495398ebcccd571f20c8accbbe01fa Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Fri, 10 Mar 2023 21:32:08 +0100 Subject: [PATCH] [DependencyInjection] Add `constructor` option to services declaration and to `#[Autoconfigure]` --- .../Attribute/Autoconfigure.php | 1 + .../DependencyInjection/CHANGELOG.md | 1 + .../DependencyInjection/Dumper/XmlDumper.php | 28 ++++++----- .../DependencyInjection/Dumper/YamlDumper.php | 6 ++- .../InlineServiceConfigurator.php | 1 + .../Configurator/InstanceofConfigurator.php | 1 + .../Configurator/PrototypeConfigurator.php | 1 + .../Configurator/ServiceConfigurator.php | 1 + .../Configurator/Traits/ConstructorTrait.php | 27 ++++++++++ .../Loader/XmlFileLoader.php | 9 ++++ .../Loader/YamlFileLoader.php | 12 +++++ .../schema/dic/services/services-1.0.xsd | 3 ++ ...egisterAutoconfigureAttributesPassTest.php | 19 +++++++ .../Fixtures/AutoconfigureAttributed.php | 1 + .../Tests/Fixtures/Bar.php | 8 +++ .../PrototypeStaticConstructor.php | 11 ++++ .../PrototypeStaticConstructorAsArgument.php | 10 ++++ .../PrototypeStaticConstructorInterface.php | 8 +++ .../StaticConstructorAutoconfigure.php | 33 ++++++++++++ .../inline_static_constructor.expected.yml | 10 ++++ .../config/inline_static_constructor.php | 15 ++++++ ...instanceof_static_constructor.expected.yml | 10 ++++ .../config/instanceof_static_constructor.php | 14 ++++++ .../Tests/Fixtures/config/prototype.php | 2 +- .../Tests/Fixtures/config/prototype_array.php | 2 +- .../config/static_constructor.expected.yml | 10 ++++ .../Fixtures/config/static_constructor.php | 9 ++++ .../Fixtures/includes/autowiring_classes.php | 17 +++++++ .../Tests/Fixtures/php/static_constructor.php | 50 +++++++++++++++++++ .../Tests/Fixtures/xml/services9.xml | 10 ++-- .../Tests/Fixtures/xml/services_prototype.xml | 2 +- .../Fixtures/xml/services_prototype_array.xml | 1 + ...rvices_prototype_array_with_space_node.xml | 1 + .../xml/services_prototype_constructor.xml | 6 +++ .../Tests/Fixtures/xml/static_constructor.xml | 7 +++ .../xml/static_constructor_and_factory.xml | 8 +++ .../yaml/constructor_with_factory.yml | 5 ++ .../Tests/Fixtures/yaml/services9.yml | 6 +-- .../Fixtures/yaml/services_prototype.yml | 2 +- .../Fixtures/yaml/static_constructor.yml | 4 ++ .../Tests/Loader/FileLoaderTest.php | 4 +- .../Tests/Loader/PhpFileLoaderTest.php | 3 ++ .../Tests/Loader/XmlFileLoaderTest.php | 23 +++++++++ .../Tests/Loader/YamlFileLoaderTest.php | 22 ++++++++ 44 files changed, 395 insertions(+), 29 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/ConstructorTrait.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/StaticConstructor/PrototypeStaticConstructor.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/StaticConstructor/PrototypeStaticConstructorAsArgument.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/StaticConstructor/PrototypeStaticConstructorInterface.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/StaticConstructorAutoconfigure.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/inline_static_constructor.expected.yml create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/inline_static_constructor.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/instanceof_static_constructor.expected.yml create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/instanceof_static_constructor.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/static_constructor.expected.yml create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/static_constructor.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/static_constructor.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_prototype_constructor.xml create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/static_constructor.xml create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/static_constructor_and_factory.xml create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/constructor_with_factory.yml create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/static_constructor.yml diff --git a/src/Symfony/Component/DependencyInjection/Attribute/Autoconfigure.php b/src/Symfony/Component/DependencyInjection/Attribute/Autoconfigure.php index abab040101532..dec8726ac2087 100644 --- a/src/Symfony/Component/DependencyInjection/Attribute/Autoconfigure.php +++ b/src/Symfony/Component/DependencyInjection/Attribute/Autoconfigure.php @@ -29,6 +29,7 @@ public function __construct( public ?bool $autowire = null, public ?array $properties = null, public array|string|null $configurator = null, + public string|null $constructor = null, ) { } } diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index b92ec95897b85..b3298479bffc6 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -21,6 +21,7 @@ CHANGELOG * Make it possible to cast callables into single-method interfaces * Deprecate `#[MapDecorated]`, use `#[AutowireDecorated]` instead * Deprecate the `@required` annotation, use the `Symfony\Contracts\Service\Attribute\Required` attribute instead + * Add `constructor` option to services declaration and to `#[Autoconfigure]` 6.2 --- diff --git a/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php index 101a4fec9e540..74633a4fbbbc5 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php @@ -167,20 +167,24 @@ private function addService(Definition $definition, ?string $id, \DOMElement $pa $this->addMethodCalls($definition->getMethodCalls(), $service); if ($callable = $definition->getFactory()) { - $factory = $this->document->createElement('factory'); - - if (\is_array($callable) && $callable[0] instanceof Definition) { - $this->addService($callable[0], null, $factory); - $factory->setAttribute('method', $callable[1]); - } elseif (\is_array($callable)) { - if (null !== $callable[0]) { - $factory->setAttribute($callable[0] instanceof Reference ? 'service' : 'class', $callable[0]); - } - $factory->setAttribute('method', $callable[1]); + if (\is_array($callable) && ['Closure', 'fromCallable'] !== $callable && $definition->getClass() === $callable[0]) { + $service->setAttribute('constructor', $callable[1]); } else { - $factory->setAttribute('function', $callable); + $factory = $this->document->createElement('factory'); + + if (\is_array($callable) && $callable[0] instanceof Definition) { + $this->addService($callable[0], null, $factory); + $factory->setAttribute('method', $callable[1]); + } elseif (\is_array($callable)) { + if (null !== $callable[0]) { + $factory->setAttribute($callable[0] instanceof Reference ? 'service' : 'class', $callable[0]); + } + $factory->setAttribute('method', $callable[1]); + } else { + $factory->setAttribute('function', $callable); + } + $service->appendChild($factory); } - $service->appendChild($factory); } if ($definition->isDeprecated()) { diff --git a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php index f0bce187a650c..82789ae7ee52d 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php @@ -151,7 +151,11 @@ private function addService(string $id, Definition $definition): string } if ($callable = $definition->getFactory()) { - $code .= sprintf(" factory: %s\n", $this->dumper->dump($this->dumpCallable($callable), 0)); + if (\is_array($callable) && ['Closure', 'fromCallable'] !== $callable && $definition->getClass() === $callable[0]) { + $code .= sprintf(" constructor: %s\n", $callable[1]); + } else { + $code .= sprintf(" factory: %s\n", $this->dumper->dump($this->dumpCallable($callable), 0)); + } } if ($callable = $definition->getConfigurator()) { diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/InlineServiceConfigurator.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/InlineServiceConfigurator.php index 9d3086a1b753d..0b1990e0607e7 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/InlineServiceConfigurator.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/InlineServiceConfigurator.php @@ -23,6 +23,7 @@ class InlineServiceConfigurator extends AbstractConfigurator use Traits\BindTrait; use Traits\CallTrait; use Traits\ConfiguratorTrait; + use Traits\ConstructorTrait; use Traits\FactoryTrait; use Traits\FileTrait; use Traits\LazyTrait; diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/InstanceofConfigurator.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/InstanceofConfigurator.php index fc5cfdb4a4251..2db004051e5e2 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/InstanceofConfigurator.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/InstanceofConfigurator.php @@ -22,6 +22,7 @@ class InstanceofConfigurator extends AbstractServiceConfigurator use Traits\BindTrait; use Traits\CallTrait; use Traits\ConfiguratorTrait; + use Traits\ConstructorTrait; use Traits\LazyTrait; use Traits\PropertyTrait; use Traits\PublicTrait; diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/PrototypeConfigurator.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/PrototypeConfigurator.php index 091b609647640..4ab957a85ce30 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/PrototypeConfigurator.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/PrototypeConfigurator.php @@ -26,6 +26,7 @@ class PrototypeConfigurator extends AbstractServiceConfigurator use Traits\BindTrait; use Traits\CallTrait; use Traits\ConfiguratorTrait; + use Traits\ConstructorTrait; use Traits\DeprecateTrait; use Traits\FactoryTrait; use Traits\LazyTrait; diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/ServiceConfigurator.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/ServiceConfigurator.php index 2312f3b6e6e97..9042ed1d6b494 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/ServiceConfigurator.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/ServiceConfigurator.php @@ -27,6 +27,7 @@ class ServiceConfigurator extends AbstractServiceConfigurator use Traits\CallTrait; use Traits\ClassTrait; use Traits\ConfiguratorTrait; + use Traits\ConstructorTrait; use Traits\DecorateTrait; use Traits\DeprecateTrait; use Traits\FactoryTrait; diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/ConstructorTrait.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/ConstructorTrait.php new file mode 100644 index 0000000000000..7f16ed589283e --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/ConstructorTrait.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\Loader\Configurator\Traits; + +trait ConstructorTrait +{ + /** + * Sets a static constructor. + * + * @return $this + */ + final public function constructor(string $constructor): static + { + $this->definition->setFactory([null, $constructor]); + + return $this; + } +} diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php index 6ddecf5d54ab6..a3265e0bfa6d9 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -24,6 +24,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use Symfony\Component\DependencyInjection\Reference; @@ -314,6 +315,14 @@ private function parseDefinition(\DOMElement $service, string $file, Definition } } + if ($constructor = $service->getAttribute('constructor')) { + if (null !== $definition->getFactory()) { + throw new LogicException(sprintf('The "%s" service cannot declare a factory as well as a constructor.', $service->getAttribute('id'))); + } + + $definition->setFactory([null, $constructor]); + } + if ($configurators = $this->getChildren($service, 'configurator')) { $configurator = $configurators[0]; if ($function = $configurator->getAttribute('function')) { diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index 901086413bbb4..61bf6b0f7ef1c 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -23,6 +23,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use Symfony\Component\DependencyInjection\Reference; @@ -63,6 +64,7 @@ class YamlFileLoader extends FileLoader 'autowire' => 'autowire', 'autoconfigure' => 'autoconfigure', 'bind' => 'bind', + 'constructor' => 'constructor', ]; private const PROTOTYPE_KEYWORDS = [ @@ -84,6 +86,7 @@ class YamlFileLoader extends FileLoader 'autowire' => 'autowire', 'autoconfigure' => 'autoconfigure', 'bind' => 'bind', + 'constructor' => 'constructor', ]; private const INSTANCEOF_KEYWORDS = [ @@ -96,6 +99,7 @@ class YamlFileLoader extends FileLoader 'tags' => 'tags', 'autowire' => 'autowire', 'bind' => 'bind', + 'constructor' => 'constructor', ]; private const DEFAULTS_KEYWORDS = [ @@ -517,6 +521,14 @@ private function parseDefinition(string $id, array|string|null $service, string $definition->setFactory($this->parseCallable($service['factory'], 'factory', $id, $file)); } + if (isset($service['constructor'])) { + if (null !== $definition->getFactory()) { + throw new LogicException(sprintf('The "%s" service cannot declare a factory as well as a constructor.', $id)); + } + + $definition->setFactory([null, $service['constructor']]); + } + if (isset($service['file'])) { $definition->setFile($service['file']); } 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 399f93dfbba6d..53c81c54d46f6 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 @@ -169,6 +169,7 @@ + @@ -185,6 +186,7 @@ + @@ -209,6 +211,7 @@ + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterAutoconfigureAttributesPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterAutoconfigureAttributesPassTest.php index deaa2fbac3935..4d1d6ba473797 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterAutoconfigureAttributesPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterAutoconfigureAttributesPassTest.php @@ -20,6 +20,7 @@ use Symfony\Component\DependencyInjection\Tests\Fixtures\AutoconfigureAttributed; use Symfony\Component\DependencyInjection\Tests\Fixtures\AutoconfiguredInterface; use Symfony\Component\DependencyInjection\Tests\Fixtures\ParentNotExists; +use Symfony\Component\DependencyInjection\Tests\Fixtures\StaticConstructorAutoconfigure; class RegisterAutoconfigureAttributesPassTest extends TestCase { @@ -47,6 +48,7 @@ public function testProcess() ->addTag('another_tag', ['attr' => 234]) ->addMethodCall('setBar', [2, 3]) ->setBindings(['$bar' => $argument]) + ->setFactory([null, 'create']) ; $this->assertEquals([AutoconfigureAttributed::class => $expected], $container->getAutoconfiguredInstanceof()); } @@ -88,4 +90,21 @@ public function testMissingParent() $this->addToAssertionCount(1); } + + public function testStaticConstructor() + { + $container = new ContainerBuilder(); + $container->register('foo', StaticConstructorAutoconfigure::class) + ->setAutoconfigured(true); + + $argument = new BoundArgument('foo', false, BoundArgument::INSTANCEOF_BINDING, realpath(__DIR__.'/../Fixtures/StaticConstructorAutoconfigure.php')); + + (new RegisterAutoconfigureAttributesPass())->process($container); + + $expected = (new ChildDefinition('')) + ->setFactory([null, 'create']) + ->setBindings(['$foo' => $argument]) + ; + $this->assertEquals([StaticConstructorAutoconfigure::class => $expected], $container->getAutoconfiguredInstanceof()); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfigureAttributed.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfigureAttributed.php index 7761e7134bb22..417f01f104086 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfigureAttributed.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfigureAttributed.php @@ -23,6 +23,7 @@ bind: [ '$bar' => 1, ], + constructor: 'create' )] class AutoconfigureAttributed { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Bar.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Bar.php index 7e1a30b5ffa07..f99a3f9eb5196 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Bar.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Bar.php @@ -23,4 +23,12 @@ public function __construct($quz = null, \NonExistent $nonExistent = null, BarIn public static function create(\NonExistent $nonExistent = null, $factory = null) { } + + public function createNonStatic() + { + } + + private static function createPrivateStatic() + { + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/StaticConstructor/PrototypeStaticConstructor.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/StaticConstructor/PrototypeStaticConstructor.php new file mode 100644 index 0000000000000..87de94cb15068 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/StaticConstructor/PrototypeStaticConstructor.php @@ -0,0 +1,11 @@ + + * + * 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; +use Symfony\Component\DependencyInjection\Attribute\Factory; + +#[Autoconfigure(bind: ['$foo' => 'foo'], constructor: 'create')] +class StaticConstructorAutoconfigure +{ + public function __construct(private readonly string $bar) + { + } + + public function getBar(): string + { + return $this->bar; + } + + public static function create(string $foo): static + { + return new self($foo); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/inline_static_constructor.expected.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/inline_static_constructor.expected.yml new file mode 100644 index 0000000000000..9695af1ff2979 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/inline_static_constructor.expected.yml @@ -0,0 +1,10 @@ + +services: + service_container: + class: Symfony\Component\DependencyInjection\ContainerInterface + public: true + synthetic: true + foo: + class: Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\StaticConstructor\PrototypeStaticConstructorAsArgument + public: true + arguments: [!service { class: Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\StaticConstructor\PrototypeStaticConstructor, constructor: create }] diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/inline_static_constructor.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/inline_static_constructor.php new file mode 100644 index 0000000000000..b3a309e41e318 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/inline_static_constructor.php @@ -0,0 +1,15 @@ +services()->defaults()->public(); + $s->set('foo', PrototypeStaticConstructorAsArgument::class) + ->args( + [inline_service(PrototypeStaticConstructor::class) + ->constructor('create')] + ); +}; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/instanceof_static_constructor.expected.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/instanceof_static_constructor.expected.yml new file mode 100644 index 0000000000000..0640f86753446 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/instanceof_static_constructor.expected.yml @@ -0,0 +1,10 @@ + +services: + service_container: + class: Symfony\Component\DependencyInjection\ContainerInterface + public: true + synthetic: true + foo: + class: Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\StaticConstructor\PrototypeStaticConstructor + public: true + constructor: create diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/instanceof_static_constructor.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/instanceof_static_constructor.php new file mode 100644 index 0000000000000..5623d75709d44 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/instanceof_static_constructor.php @@ -0,0 +1,14 @@ +services()->defaults()->public(); + $s->instanceof(PrototypeStaticConstructorInterface::class) + ->constructor('create'); + + $s->set('foo', PrototypeStaticConstructor::class); +}; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype.php index 48629a64351fa..c1a6e8998c0fc 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype.php @@ -10,7 +10,7 @@ $di->load(Prototype::class.'\\', '../Prototype') ->public() ->autoconfigure() - ->exclude('../Prototype/{OtherDir,BadClasses,SinglyImplementedInterface}') + ->exclude('../Prototype/{OtherDir,BadClasses,SinglyImplementedInterface,StaticConstructor}') ->factory('f') ->deprecate('vendor/package', '1.1', '%service_id%') ->args([0]) diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype_array.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype_array.php index a57365fe50501..cc9d98c4e5d0b 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype_array.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype_array.php @@ -10,7 +10,7 @@ $di->load(Prototype::class.'\\', '../Prototype') ->public() ->autoconfigure() - ->exclude(['../Prototype/OtherDir', '../Prototype/BadClasses', '../Prototype/SinglyImplementedInterface']) + ->exclude(['../Prototype/OtherDir', '../Prototype/BadClasses', '../Prototype/SinglyImplementedInterface', '../Prototype/StaticConstructor']) ->factory('f') ->deprecate('vendor/package', '1.1', '%service_id%') ->args([0]) diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/static_constructor.expected.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/static_constructor.expected.yml new file mode 100644 index 0000000000000..cdb0908398c6a --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/static_constructor.expected.yml @@ -0,0 +1,10 @@ + +services: + service_container: + class: Symfony\Component\DependencyInjection\ContainerInterface + public: true + synthetic: true + foo: + class: Bar\FooClass + public: true + constructor: getInstance diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/static_constructor.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/static_constructor.php new file mode 100644 index 0000000000000..6b7b0e952b3a3 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/static_constructor.php @@ -0,0 +1,9 @@ +services()->defaults()->public(); + + $s->set('foo', 'Bar\FooClass')->constructor('getInstance'); +}; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php index d5f62b9070d31..e76b58eb68c74 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php @@ -528,6 +528,23 @@ public function __construct(NotExisting $notExisting) } } +class StaticConstructor +{ + public function __construct(private string $bar) + { + } + + public function getBar(): string + { + return $this->bar; + } + + public static function create(string $foo): static + { + return new self($foo); + } +} + class AAndIInterfaceConsumer { public function __construct( diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/static_constructor.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/static_constructor.php new file mode 100644 index 0000000000000..a262304550f89 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/static_constructor.php @@ -0,0 +1,50 @@ +ref = \WeakReference::create($this); + $this->services = $this->privates = []; + $this->methodMap = [ + 'static_constructor' => 'getStaticConstructorService', + ]; + + $this->aliases = []; + } + + public function compile(): void + { + throw new LogicException('You cannot compile a dumped container that was already compiled.'); + } + + public function isCompiled(): bool + { + return true; + } + + /** + * Gets the public 'static_constructor' shared service. + * + * @return \Symfony\Component\DependencyInjection\Tests\Compiler\A + */ + protected static function getStaticConstructorService($container) + { + return $container->services['static_constructor'] = \Symfony\Component\DependencyInjection\Tests\Compiler\A::create('foo'); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services9.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services9.xml index 24f025f523f7d..9b2462d076068 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services9.xml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services9.xml @@ -7,7 +7,7 @@ - + foo @@ -30,11 +30,9 @@ - - - + @@ -111,9 +109,7 @@ bar - - - + foo The "%service_id%" service is deprecated. You should stop using it, as it will be removed in the future. diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_prototype.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_prototype.xml index 1aa28bf341f27..2b08ef7844593 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_prototype.xml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_prototype.xml @@ -1,6 +1,6 @@ - + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_prototype_array.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_prototype_array.xml index b24b3af5777aa..463ffdffc34c3 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_prototype_array.xml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_prototype_array.xml @@ -5,6 +5,7 @@ ../Prototype/OtherDir ../Prototype/BadClasses ../Prototype/SinglyImplementedInterface + ../Prototype/StaticConstructor diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_prototype_array_with_space_node.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_prototype_array_with_space_node.xml index 3059ea958dfc1..6f6727b8a4a00 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_prototype_array_with_space_node.xml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_prototype_array_with_space_node.xml @@ -5,6 +5,7 @@ ../Prototype/OtherDir ../Prototype/BadClasses ../Prototype/SinglyImplementedInterface + ../Prototype/StaticConstructor diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_prototype_constructor.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_prototype_constructor.xml new file mode 100644 index 0000000000000..2b08ef7844593 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_prototype_constructor.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/static_constructor.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/static_constructor.xml new file mode 100644 index 0000000000000..9ead589ed478c --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/static_constructor.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/static_constructor_and_factory.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/static_constructor_and_factory.xml new file mode 100644 index 0000000000000..3872eb725c9ab --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/static_constructor_and_factory.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/constructor_with_factory.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/constructor_with_factory.yml new file mode 100644 index 0000000000000..49fc692afd9cd --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/constructor_with_factory.yml @@ -0,0 +1,5 @@ +services: + invalid_service: + class: FooBarClass + factory: 'create' + constructor: 'create' diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml index 8fa97f4f685ab..22a6d5549557e 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml @@ -21,12 +21,12 @@ services: - [setBar, ['@bar']] - [initialize, { }] - factory: [Bar\FooClass, getInstance] + constructor: getInstance configurator: sc_configure public: true foo.baz: class: '%baz_class%' - factory: ['%baz_class%', getInstance] + constructor: getInstance configurator: ['%baz_class%', configureStatic1] public: true bar: @@ -121,7 +121,7 @@ services: public: true service_from_static_method: class: Bar\FooClass - factory: [Bar\FooClass, getInstance] + constructor: getInstance public: true factory_simple: class: SimpleFactoryClass diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_prototype.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_prototype.yml index 43f8d51e04246..8b890c11f4311 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_prototype.yml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_prototype.yml @@ -1,4 +1,4 @@ services: Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\: resource: ../Prototype - exclude: '../Prototype/{OtherDir,BadClasses,SinglyImplementedInterface}' + exclude: '../Prototype/{OtherDir,BadClasses,SinglyImplementedInterface,StaticConstructor}' diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/static_constructor.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/static_constructor.yml new file mode 100644 index 0000000000000..d992c379bc78a --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/static_constructor.yml @@ -0,0 +1,4 @@ +services: + static_constructor: + class: stdClass + constructor: 'create' diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php index b79262e9b9602..dbfb3daf7bf27 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php @@ -123,7 +123,7 @@ public function testRegisterClassesWithExclude() 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\\', 'Prototype/*', // load everything, except OtherDir/AnotherSub & Foo.php - 'Prototype/{%other_dir%/AnotherSub,Foo.php}' + 'Prototype/{%other_dir%/AnotherSub,Foo.php,StaticConstructor}' ); $this->assertFalse($container->getDefinition(Bar::class)->isAbstract()); @@ -191,7 +191,7 @@ public function testNestedRegisterClasses() $loader = new TestFileLoader($container, new FileLocator(self::$fixturesPath.'/Fixtures')); $prototype = (new Definition())->setAutoconfigured(true); - $loader->registerClasses($prototype, 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\\', 'Prototype/*'); + $loader->registerClasses($prototype, 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\\', 'Prototype/*', 'Prototype/{StaticConstructor}'); $this->assertTrue($container->has(Bar::class)); $this->assertTrue($container->has(Baz::class)); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/PhpFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/PhpFileLoaderTest.php index f5652a3fd5ba7..7b24f5e2248e6 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/PhpFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/PhpFileLoaderTest.php @@ -105,6 +105,9 @@ public static function provideConfig() yield ['remove']; yield ['config_builder']; yield ['expression_factory']; + yield ['static_constructor']; + yield ['inline_static_constructor']; + yield ['instanceof_static_constructor']; 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 1a3e7f0493ddf..71b53dd39e21e 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php @@ -32,6 +32,7 @@ use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Dumper\PhpDumper; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Loader\IniFileLoader; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; @@ -785,6 +786,7 @@ public function testPrototype() str_replace(\DIRECTORY_SEPARATOR, '/', $prototypeRealPath.\DIRECTORY_SEPARATOR.'OtherDir') => true, str_replace(\DIRECTORY_SEPARATOR, '/', $prototypeRealPath.\DIRECTORY_SEPARATOR.'BadClasses') => true, str_replace(\DIRECTORY_SEPARATOR, '/', $prototypeRealPath.\DIRECTORY_SEPARATOR.'SinglyImplementedInterface') => true, + str_replace(\DIRECTORY_SEPARATOR, '/', $prototypeRealPath.\DIRECTORY_SEPARATOR.'StaticConstructor') => true, ] ); $this->assertContains((string) $globResource, $resources); @@ -820,6 +822,7 @@ public function testPrototypeExcludeWithArray(string $fileName) str_replace(\DIRECTORY_SEPARATOR, '/', $prototypeRealPath.\DIRECTORY_SEPARATOR.'BadClasses') => true, str_replace(\DIRECTORY_SEPARATOR, '/', $prototypeRealPath.\DIRECTORY_SEPARATOR.'OtherDir') => true, str_replace(\DIRECTORY_SEPARATOR, '/', $prototypeRealPath.\DIRECTORY_SEPARATOR.'SinglyImplementedInterface') => true, + str_replace(\DIRECTORY_SEPARATOR, '/', $prototypeRealPath.\DIRECTORY_SEPARATOR.'StaticConstructor') => true, ] ); $this->assertContains((string) $globResource, $resources); @@ -1203,4 +1206,24 @@ public function testFromCallable() $definition = $container->getDefinition('from_callable'); $this->assertEquals((new Definition('stdClass'))->setFactory(['Closure', 'fromCallable'])->addArgument([new Reference('bar'), 'do'])->setLazy(true), $definition); } + + public function testStaticConstructor() + { + $container = new ContainerBuilder(); + $loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml')); + $loader->load('static_constructor.xml'); + + $definition = $container->getDefinition('static_constructor'); + $this->assertEquals((new Definition('stdClass'))->setFactory([null, 'create']), $definition); + } + + public function testStaticConstructorWithFactoryThrows() + { + $container = new ContainerBuilder(); + $loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml')); + + $this->expectException(LogicException::class); + $this->expectExceptionMessage('The "static_constructor" service cannot declare a factory as well as a constructor.'); + $loader->load('static_constructor_and_factory.xml'); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php index d36ad6ae2caa3..713e89a8db48f 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php @@ -29,6 +29,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Loader\IniFileLoader; use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; @@ -312,6 +313,16 @@ public function testFactorySyntaxError() $loader->load('bad_factory_syntax.yml'); } + public function testStaticConstructorWithFactory() + { + $container = new ContainerBuilder(); + $loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml')); + + $this->expectException(LogicException::class); + $this->expectExceptionMessage('The "invalid_service" service cannot declare a factory as well as a constructor.'); + $loader->load('constructor_with_factory.yml'); + } + public function testExtensions() { $container = new ContainerBuilder(); @@ -545,6 +556,7 @@ public function testPrototype() str_replace(\DIRECTORY_SEPARATOR, '/', $prototypeRealPath.\DIRECTORY_SEPARATOR.'BadClasses') => true, str_replace(\DIRECTORY_SEPARATOR, '/', $prototypeRealPath.\DIRECTORY_SEPARATOR.'OtherDir') => true, str_replace(\DIRECTORY_SEPARATOR, '/', $prototypeRealPath.\DIRECTORY_SEPARATOR.'SinglyImplementedInterface') => true, + str_replace(\DIRECTORY_SEPARATOR, '/', $prototypeRealPath.\DIRECTORY_SEPARATOR.'StaticConstructor') => true, ] ); $this->assertContains((string) $globResource, $resources); @@ -1159,4 +1171,14 @@ public function testFromCallable() $definition = $container->getDefinition('from_callable'); $this->assertEquals((new Definition('stdClass'))->setFactory(['Closure', 'fromCallable'])->addArgument([new Reference('bar'), 'do'])->setLazy(true), $definition); } + + public function testStaticConstructor() + { + $container = new ContainerBuilder(); + $loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml')); + $loader->load('static_constructor.yml'); + + $definition = $container->getDefinition('static_constructor'); + $this->assertEquals((new Definition('stdClass'))->setFactory([null, 'create']), $definition); + } }