8000 feature #49665 [DependencyInjection] Add `constructor` option to `#[A… · symfony/symfony@ff361c8 · GitHub
[go: up one dir, main page]

Skip to content

Commit ff361c8

Browse files
committed
feature #49665 [DependencyInjection] Add constructor option to #[Autoconfigure] (alexandre-daubois)
This PR was merged into the 6.3 branch. Discussion ---------- [DependencyInjection] Add `constructor` option to `#[Autoconfigure]` | Q | A | ------------- | --- | Branch? | 6.3 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | _NA_ | License | MIT | Doc PR | symfony/symfony-docs#18070 Following [this discussion](https://twitter.com/nicolasgrekas/status/1634259754104025088?s=20) on Twitter, here is my try to use service factories with attributes. This PR adds the `constructor` option to the `Autoconfigure` attribute, and more generally, the `constructor` option on service definitions. This allows to write services using a factory this way: ```php #[Autoconfigure(constructor: 'create')] class FactoryAttributeService { public function __construct(private readonly string $bar) { } public function getBar(): string { return $this->bar; } public static function create(string $foo = 'foo'): static { return new self($foo); } } ``` Commits ------- 8dda3f0 [DependencyInjection] Add `constructor` option to services declaration and to `#[Autoconfigure]`
2 parents 6d34700 + 8dda3f0 commit ff361c8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+395
-29
lines changed

src/Symfony/Component/DependencyInjection/Attribute/Autoconfigure.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public function __construct(
2929
public ?bool $autowire = null,
3030
public ?array $properties = null,
3131
public array|string|null $configurator = null,
32+
public string|null $constructor = null,
3233
) {
3334
}
3435
}

src/Symfony/Component/DependencyInjection/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ CHANGELOG
2121
* Make it possible to cast callables into single-method interfaces
2222
* Deprecate `#[MapDecorated]`, use `#[AutowireDecorated]` instead
2323
* Deprecate the `@required` annotation, use the `Symfony\Contracts\Service\Attribute\Required` attribute instead
24+
* Add `constructor` option to services declaration and to `#[Autoconfigure]`
2425

2526
6.2
2627
---

src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -167,20 +167,24 @@ private function addService(Definition $definition, ?string $id, \DOMElement $pa
167167
$this->addMethodCalls($definition->getMethodCalls(), $service);
168168

169169
if ($callable = $definition->getFactory()) {
170-
$factory = $this->document->createElement('factory');
171-
172-
if (\is_array($callable) && $callable[0] instanceof Definition) {
173-
$this->addService($callable[0], null, $factory);
174-
$factory->setAttribute('method', $callable[1]);
175-
} elseif (\is_array($callable)) {
176-
if (null !== $callable[0]) {
177-
$factory->setAttribute($callable[0] instanceof Reference ? 'service' : 'class', $callable[0]);
178-
}
179-
$factory->setAttribute('method', $callable[1]);
170+
if (\is_array($callable) && ['Closure', 'fromCallable'] !== $callable && $definition->getClass() === $callable[0]) {
171+
$service->setAttribute('constructor', $callable[1]);
180172
} else {
181-
$factory->setAttribute('function', $callable);
173+
$factory = $this->document->createElement('factory');
174+
175+
if (\is_array($callable) && $callable[0] instanceof Definition) {
176+
$this->addService($callable[0], null, $factory);
177+
$factory->setAttribute('method', $callable[1]);
178+
} elseif (\is_array($callable)) {
179+
if (null !== $callable[0]) {
180+
$factory->setAttribute($callable[0] instanceof Reference ? 'service' : 'class', $callable[0]);
181+
}
182+
$factory->setAttribute('method', $callable[1]);
183+
} else {
184+
$factory->setAttribute('function', $callable);
185+
}
186+
$service->appendChild($factory);
182187
}
183-
$service->appendChild($factory);
184188
}
185189

186190
if ($definition->isDeprecated()) {

src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,11 @@ private function addService(string $id, Definition $definition): string
151151
}
152152

153153
if ($callable = $definition->getFactory()) {
154-
$code .= sprintf(" factory: %s\n", $this->dumper->dump($this->dumpCallable($callable), 0));
154+
if (\is_array($callable) && ['Closure', 'fromCallable'] !== $callable && $definition->getClass() === $callable[0]) {
155+
$code .= sprintf(" constructor: %s\n", $callable[1]);
156+
} else {
157+
$code .= sprintf(" factory: %s\n", $this->dumper->dump($this->dumpCallable($callable), 0));
158+
}
155159
}
156160

157161
if ($callable = $definition->getConfigurator()) {

src/Symfony/Component/DependencyInjection/Loader/Configurator/InlineServiceConfigurator.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class InlineServiceConfigurator extends AbstractConfigurator
2323
use Traits\BindTrait;
2424
use Traits\CallTrait;
2525
use Traits\ConfiguratorTrait;
26+
use Traits\ConstructorTrait;
2627
use Traits\FactoryTrait;
2728
use Traits\FileTrait;
2829
use Traits\LazyTrait;

src/Symfony/Component/DependencyInjection/Loader/Configurator/InstanceofConfigurator.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class InstanceofConfigurator extends AbstractServiceConfigurator
2222
use Traits\BindTrait;
2323
use Traits\CallTrait;
2424
use Traits\ConfiguratorTrait;
25+
use Traits\ConstructorTrait;
2526
use Traits\LazyTrait;
2627
use Traits\PropertyTrait;
2728
use Traits\PublicTrait;

src/Symfony/Component/DependencyInjection/Loader/Configurator/PrototypeConfigurator.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class PrototypeConfigurator extends AbstractServiceConfigurator
2626
use Traits\BindTrait;
2727
use Traits\CallTrait;
2828
use Traits\ConfiguratorTrait;
29+
use Traits\ConstructorTrait;
2930
use Traits\DeprecateTrait;
3031
use Traits\FactoryTrait;
3132
use Traits\LazyTrait;

src/Symfony/Component/DependencyInjection/Loader/Configurator/ServiceConfigurator.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class ServiceConfigurator extends AbstractServiceConfigurator
2727
use Traits\CallTrait;
2828
use Traits\ClassTrait;
2929
use Traits\ConfiguratorTrait;
30+
use Traits\ConstructorTrait;
3031
use Traits\DecorateTrait;
3132
use Traits\DeprecateTrait;
3233
use Traits\FactoryTrait;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
13+
14+
trait ConstructorTrait
15+
{
16+
/**
17+
* Sets a static constructor.
18+
*
19+
* @return $this
20+
*/
21+
final public function constructor(string $constructor): static
22+
{
23+
$this->definition->setFactory([null, $constructor]);
24+
25+
return $this;
26+
}
27+
}

src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use Symfony\Component\DependencyInjection\ContainerInterface;
2525
use Symfony\Component\DependencyInjection\Definition;
2626
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
27+
use Symfony\Component\DependencyInjection\Exception\LogicException;
2728
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
2829
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
2930
use Symfony\Component\DependencyInjection\Reference;
@@ -314,6 +315,14 @@ private function parseDefinition(\DOMElement $service, string $file, Definition
314315
}
315316
}
316317

318+
if ($constructor = $service->getAttribute('constructor')) {
319+
if (null !== $definition->getFactory()) {
320+
throw new LogicException(sprintf('The "%s" service cannot declare a factory as well as a constructor.', $service->getAttribute('id')));
321+
}
322+
323+
$definition->setFactory([null, $constructor]);
324+
}
325+
317326
if ($configurators = $this->getChildren($service, 'configurator')) {
318327
$configurator = $configurators[0];
319328
if ($function = $configurator->getAttribute('function')) {

src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use Symfony\Component\DependencyInjection\ContainerInterface;
2424
use Symfony\Component\DependencyInjection\Definition;
2525
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
26+
use Symfony\Component\DependencyInjection\Exception\LogicException;
2627
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
2728
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
2829
use Symfony\Component\DependencyInjection\Reference;
@@ -63,6 +64,7 @@ class YamlFileLoader extends FileLoader
6364
'autowire' => 'autowire',
6465
'autoconfigure' => 'autoconfigure',
6566
'bind' => 'bind',
67+
'constructor' => 'constructor',
6668
];
6769

6870
private const PROTOTYPE_KEYWORDS = [
@@ -84,6 +86,7 @@ class YamlFileLoader extends FileLoader
8486
'autowire' => 'autowire',
8587
'autoconfigure' => 'autoconfigure',
8688
'bind' => 'bind',
89+
'constructor' => 'constructor',
8790
];
8891

8992
private const INSTANCEOF_KEYWORDS = [
@@ -96,6 +99,7 @@ class YamlFileLoader extends FileLoader
9699
'tags' => 'tags',
97100
'autowire' => 'autowire',
98101
'bind' => 'bind',
102+
'constructor' => 'constructor',
99103
];
100104

101105
private const DEFAULTS_KEYWORDS = [
@@ -517,6 +521,14 @@ private function parseDefinition(string $id, array|string|null $service, string
517521
$definition->setFactory($this->parseCallable($service['factory'], 'factory', $id, $file));
518522
}
519523

524+
if (isset($service['constructor'])) {
525+
if (null !== $definition->getFactory()) {
526+
throw new LogicException(sprintf('The "%s" service cannot declare a factory as well as a constructor.', $id));
527+
}
528+
529+
$definition->setFactory([null, $service['constructor']]);
530+
}
531+
520532
if (isset($service['file'])) {
521533
$definition->setFile($service['file']);
522534
}

src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@
169169
<xsd:attribute name="decoration-priority" type="xsd:integer" />
170170
<xsd:attribute name="autowire" type="boolean" />
171171
<xsd:attribute name="autoconfigure" type="boolean" />
172+
<xsd:attribute name="constructor" type="xsd:string" />
172173
</xsd:complexType>
173174

174175
<xsd:complexType name="instanceof">
@@ -185,6 +186,7 @@
185186
<xsd:attribute name="lazy" type="xsd:string" />
186187
<xsd:attribute name="autowire" type="boolean" />
187188
<xsd:attribute name="autoconfigure" type="boolean" />
189+
<xsd:attribute name="constructor" type="xsd:string" />
188190
</xsd:complexType>
189191

190192
<xsd:complexType name="prototype">
@@ -209,6 +211,7 @@
209211
<xsd:attribute name="parent" type="xsd:string" />
210212
<xsd:attribute name="autowire" type="boolean" />
211213
<xsd:attribute name="autoconfigure" type="boolean" />
214+
<xsd:attribute name="constructor" type="xsd:string" />
212215
</xsd:complexType>
213216

214217
<xsd:complexType name="stack">

src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterAutoconfigureAttributesPassTest.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use Symf F438 ony\Component\DependencyInjection\Tests\Fixtures\AutoconfigureAttributed;
2121
use Symfony\Component\DependencyInjection\Tests\Fixtures\AutoconfiguredInterface;
2222
use Symfony\Component\DependencyInjection\Tests\Fixtures\ParentNotExists;
23+
use Symfony\Component\DependencyInjection\Tests\Fixtures\StaticConstructorAutoconfigure;
2324

2425
class RegisterAutoconfigureAttributesPassTest extends TestCase
2526
{
@@ -47,6 +48,7 @@ public function testProcess()
4748
->addTag('another_tag', ['attr' => 234])
4849
->addMethodCall('setBar', [2, 3])
4950
->setBindings(['$bar' => $argument])
51+
->setFactory([null, 'create'])
5052
;
5153
$this->assertEquals([AutoconfigureAttributed::class => $expected], $container->getAutoconfiguredInstanceof());
5254
}
@@ -88,4 +90,21 @@ public function testMissingParent()
8890

8991
$this->addToAssertionCount(1);
9092
}
93+
94+
public function testStaticConstructor()
95+
{
96+
$container = new ContainerBuilder();
97+
$container->register('foo', StaticConstructorAutoconfigure::class)
98+
->setAutoconfigured(true);
99+
100+
$argument = new BoundArgument('foo', false, BoundArgument::INSTANCEOF_BINDING, realpath(__DIR__.'/../Fixtures/StaticConstructorAutoconfigure.php'));
101+
102+
(new RegisterAutoconfigureAttributesPass())->process($container);
103+
104+
$expected = (new ChildDefinition(''))
105+
->setFactory([null, 'create'])
106+
->setBindings(['$foo' => $argument])
107+
;
108+
$this->assertEquals([StaticConstructorAutoconfigure::class => $expected], $container->getAutoconfiguredInstanceof());
109+
}
91110
}

src/Symfony/Component/DependencyInjection/Tests/Fixtures/AutoconfigureAttributed.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
bind: [
2424
'$bar' => 1,
2525
],
26+
constructor: 'create'
2627
)]
2728
class AutoconfigureAttributed
2829
{

src/Symfony/Component/DependencyInjection/Tests/Fixtures/Bar.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,12 @@ public function __construct($quz = null, \NonExistent $nonExistent = null, BarIn
2323
public static function create(\NonExistent $nonExistent = null, $factory = null)
2424
{
2525
}
26+
27+
public function createNonStatic()
28+
{
29+
}
30+
31+
private static function createPrivateStatic()
32+
{
33+
}
2634
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\StaticConstructor;
4+
5+
class PrototypeStaticConstructor implements PrototypeStaticConstructorInterface
6+
{
7+
public static function create(): static
8+
{
9+
return new self();
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\StaticConstructor;
4+
5+
class PrototypeStaticConstructorAsArgument
6+
{
7+
public function __construct(private PrototypeStaticConstructor $prototypeStaticConstructor)
8+
{
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\StaticConstructor;
4+
5+
interface PrototypeStaticConstructorInterface
6+
{
7+
public static function create(): static;
8+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
13+
14+
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
15+
use Symfony\Component\DependencyInjection\Attribute\Factory;
16+
17+
#[Autoconfigure(bind: ['$foo' => 'foo'], constructor: 'create')]
18+
class StaticConstructorAutoconfigure
19+
{
20+
public function __construct(private readonly string $bar)
21+
{
22+
}
23+
24+
public function getBar(): string
25+
{
26+
return $this->bar;
27+
}
28+
29+
public static function create(string $foo): static
30+
{
31+
return new self($foo);
32+
}
33+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
services:
3+
service_container:
4+
class: Symfony\Component\DependencyInjection\ContainerInterface
5+
public: true
6+
synthetic: true
7+
foo:
8+
class: Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\StaticConstructor\PrototypeStaticConstructorAsArgument
9+
public: true
10+
arguments: [!service { class: Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\StaticConstructor\PrototypeStaticConstructor, constructor: create }]
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
4+
5+
use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\StaticConstructor\PrototypeStaticConstructor;
6+
use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\StaticConstructor\PrototypeStaticConstructorAsArgument;
7+
8+
return function (ContainerConfigurator $c) {
9+
$s = $c->services()->defaults()->public();
10+
$s->set('foo', PrototypeStaticConstructorAsArgument::class)
11+
->args(
12+
[inline_service(PrototypeStaticConstructor::class)
13+
->constructor('create')]
14+
);
15+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
services:
3+
service_container:
4+
class: Symfony\Component\DependencyInjection\ContainerInterface
5+
public: true
6+
synthetic: true
7+
foo:
8+
class: Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\StaticConstructor\PrototypeStaticConstructor
9+
public: true
10+
constructor: create
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
4+
5+
use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\StaticConstructor\PrototypeStaticConstructor;
6+
use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\StaticConstructor\PrototypeStaticConstructorInterface;
7+
8+
return function (ContainerConfigurator $c) {
9+
$s = $c->services()->defaults()->public();
10+
$s->instanceof(PrototypeStaticConstructorInterface::class)
11+
->constructor('create');
12+
13+
$s->set('foo', PrototypeStaticConstructor::class);
14+
};

0 commit comments

Comments
 (0)
0