Skip to content

Commit 91f0692

Browse files
committed
[DependencyInjection] Add the Required attribute.
1 parent 4ee85e8 commit 91f0692

File tree

15 files changed

+174
-11
lines changed

15 files changed

+174
-11
lines changed

src/Symfony/Component/DependencyInjection/Compiler/AutowireRequiredMethodsPass.php

+9
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\DependencyInjection\Compiler;
1313

1414
use Symfony\Component\DependencyInjection\Definition;
15+
use Symfony\Contracts\Service\Attribute\Required;
1516

1617
/**
1718
* Looks for definitions with autowiring enabled and registers their corresponding "@required" methods as setters.
@@ -49,6 +50,14 @@ protected function processValue($value, bool $isRoot = false)
4950
}
5051

5152
while (true) {
53+
if (method_exists($r, 'getAttributes') && $r->getAttributes(Required::class)) {
54+
if ($this->isWither($r, $r->getDocComment() ?: '')) {
55+
$withers[] = [$r->name, [], true];
56+
} else {
57+
$value->addMethodCall($r->name, []);
58+
}
59+
break;
60+
}
5261
if (false !== $doc = $r->getDocComment()) {
5362
if (false !== stripos($doc, '@required') && preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@required(?:\s|\*/$)#i', $doc)) {
5463
if ($this->isWither($reflectionMethod, $doc)) {

src/Symfony/Component/DependencyInjection/Compiler/AutowireRequiredPropertiesPass.php

+4-4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Component\DependencyInjection\ContainerInterface;
1515
use Symfony\Component\DependencyInjection\Definition;
1616
use Symfony\Component\DependencyInjection\TypedReference;
17+
use Symfony\Contracts\Service\Attribute\Required;
1718

1819
/**
1920
* Looks for definitions with autowiring enabled and registers their corresponding "@required" properties.
@@ -45,10 +46,9 @@ protected function processValue($value, bool $isRoot = false)
4546
if (!($type = $reflectionProperty->getType()) instanceof \ReflectionNamedType) {
4647
continue;
4748
}
48-
if (false === $doc = $reflectionProperty->getDocComment()) {
49-
continue;
50-
}
51-
if (false === stripos($doc, '@required') || !preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@required(?:\s|\*/$)#i', $doc)) {
49+
if ((!method_exists($reflectionProperty, 'getAttributes') || !$reflectionProperty->getAttributes(Required::class))
50+
&& ((false === $doc = $reflectionProperty->getDocComment()) || false === stripos($doc, '@required') || !preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@required(?:\s|\*/$)#i', $doc))
51+
) {
5252
continue;
5353
}
5454
if (\array_key_exists($name = $reflectionProperty->getName(), $properties)) {

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

+27
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
use Symfony\Component\DependencyInjection\Tests\Fixtures\includes\FooVariadic;
2929
use Symfony\Component\DependencyInjection\Tests\Fixtures\includes\MultipleArgumentsOptionalScalarNotReallyOptional;
3030
use Symfony\Component\DependencyInjection\TypedReference;
31+
use Symfony\Contracts\Service\Attribute\Required;
3132

3233
require_once __DIR__.'/../Fixtures/includes/autowiring_classes.php';
3334

@@ -640,6 +641,32 @@ public function testSetterInjection()
640641
);
641642
}
642643

644+
/**
645+
* @requires PHP 8
646+
*/
647+
public function testSetterInjectionWithAttribute()
648+
{
649+
if (!class_exists(Required::class)) {
650+
$this->markTestSkipped('symfony/service-contracts 2.2 required');
651+
}
652+
653+
$container = new ContainerBuilder();
654+
$container->register(Foo::class);
655+
656+
$container
657+
->register('setter_injection', AutowireSetter::class)
658+
->setAutowired(true);
659+
660+
(new ResolveClassPass())->process($container);
661+
(new AutowireRequiredMethodsPass())->process($container);
662+
(new AutowirePass())->process($container);
663+
664+
$methodCalls = $container->getDefinition('setter_injection')->getMethodCalls();
665+
$this->assertCount(1, $methodCalls);
666+
$this->assertSame('setFoo', $methodCalls[0][0]);
667+
$this->assertSame(Foo::class, (string) $methodCalls[0][1][0]);
668+
}
669+
643670
public function testWithNonExistingSetterAndAutowiring()
644671
{
645672
$this->expectException(RuntimeException::class);

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

+46
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Component\DependencyInjection\Compiler\ResolveClassPass;
1717
use Symfony\Component\DependencyInjection\ContainerBuilder;
1818
use Symfony\Component\DependencyInjection\Tests\Fixtures\WitherStaticReturnType;
19+
use Symfony\Contracts\Service\Attribute\Required;
1920

2021
require_once __DIR__.'/../Fixtures/includes/autowiring_classes.php';
2122

@@ -54,6 +55,29 @@ public function testSetterInjection()
5455
$this->assertEquals([], $methodCalls[1][1]);
5556
}
5657

58+
/**
59+
* @requires PHP 8
60+
*/
61+
public function testSetterInjectionWithAttribute()
62+
{
63+
if (!class_exists(Required::class)) {
64+
$this->markTestSkipped('symfony/service-contracts 2.2 required');
65+
}
66+
67+
$container = new ContainerBuilder();
68+
$container->register(Foo::class);
69+
70+
$container
71+
->register('setter_injection', AutowireSetter::class)
72+
->setAutowired(true);
73+
74+
(new ResolveClassPass())->process($container);
75+
(new AutowireRequiredMethodsPass())->process($container);
76+
77+
$methodCalls = $container->getDefinition('setter_injection')->getMethodCalls();
78+
$this->assertSame([['setFoo', []]], $methodCalls);
79+
}
80+
5781
public function testExplicitMethodInjection()
5882
{
5983
$container = new ContainerBuilder();
@@ -124,4 +148,26 @@ public function testWitherWithStaticReturnTypeInjection()
124148
];
125149
$this->assertSame($expected, $methodCalls);
126150
}
151+
152+
/**
153+
* @requires PHP 8
154+
*/
155+
public function testWitherInjectionWithAttribute()
156+
{
157+
if (!class_exists(Required::class)) {
158+
$this->markTestSkipped('symfony/service-contracts 2.2 required');
159+
}
160+
161+
$container = new ContainerBuilder();
162+
$container->register(Foo::class);
163+
164+
$container
165+
->register('wither', AutowireWither::class)
166+
->setAutowired(true);
167+
168+
(new ResolveClassPass())->process($container);
169+
(new AutowireRequiredMethodsPass())->process($container);
170+
171+
$this->assertSame([['withFoo', [], true]], $container->getDefinition('wither')->getMethodCalls());
172+
}
127173
}

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

+25
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\DependencyInjection\Compiler\AutowireRequiredPropertiesPass;
1616
use Symfony\Component\DependencyInjection\Compiler\ResolveClassPass;
1717
use Symfony\Component\DependencyInjection\ContainerBuilder;
18+
use Symfony\Contracts\Service\Attribute\Required;
1819

1920
require_once __DIR__.'/../Fixtures/includes/autowiring_classes.php';
2021

@@ -43,4 +44,28 @@ public function testInjection()
4344
$this->assertArrayHasKey('plop', $properties);
4445
$this->assertEquals(Bar::class, (string) $properties['plop']);
4546
}
47+
48+
/**
49+
* @requires PHP 8
50+
*/
51+
public function testAttribute()
52+
{
53+
if (!class_exists(Required::class)) {
54+
$this->markTestSkipped('symfony/service-contracts 2.2 required');
55+
}
56+
57+
$container = new ContainerBuilder();
58+
$container->register(Foo::class);
59+
60+
$container->register('property_injection', AutowireProperty::class)
61+
->setAutowired(true);
62+
63+
(new ResolveClassPass())->process($container);
64+
(new AutowireRequiredPropertiesPass())->process($container);
65+
66+
$properties = $container->getDefinition('property_injection')->getProperties();
67+
68+
$this->assertArrayHasKey('foo', $properties);
69+
$this->assertEquals(Foo::class, (string) $properties['foo']);
70+
}
4671
}

src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
if (PHP_VERSION_ID >= 80000) {
88
require __DIR__.'/uniontype_classes.php';
9+
require __DIR__.'/autowiring_classes_80.php';
910
}
1011

1112
class Foo
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Compiler;
4+
5+
use Symfony\Contracts\Service\Attribute\Required;
6+
7+
class AutowireSetter
8+
{
9+
#[Required]
10+
public function setFoo(Foo $foo): void
11+
{
12+
}
13+
}
14+
15+
class AutowireWither
16+
{
17+
#[Required]
18+
public function withFoo(Foo $foo): static
19+
{
20+
return $this;
21+
}
22+
}
23+
24+
class AutowireProperty
25+
{
26+
#[Required]
27+
public Foo $foo;
28+
}

src/Symfony/Contracts/Cache/composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"minimum-stability": "dev",
2929
"extra": {
3030
"branch-alias": {
31-
"dev-master": "2.1-dev"
31+
"dev-master": "2.2-dev"
3232
},
3333
"thanks": {
3434
"name": "symfony/contracts",

src/Symfony/Contracts/Deprecation/composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"minimum-stability": "dev",
2626
"extra": {
2727
"branch-alias": {
28-
"dev-master": "2.1-dev"
28+
"dev-master": "2.2-dev"
2929
},
3030
"thanks": {
3131
"name": "symfony/contracts",

src/Symfony/Contracts/EventDispatcher/composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"minimum-stability": "dev",
2929
"extra": {
3030
"branch-alias": {
31-
"dev-master": "2.1-dev"
31+
"dev-master": "2.2-dev"
3232
},
3333
"thanks": {
3434
"name": "symfony/contracts",

src/Symfony/Contracts/HttpClient/composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"minimum-stability": "dev",
2828
"extra": {
2929
"branch-alias": {
30-
"dev-master": "2.1-dev"
30+
"dev-master": "2.2-dev"
3131
},
3232
"thanks": {
3333
"name": "symfony/contracts",
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\Contracts\Service\Attribute;
13+
14+
use Attribute;
15+
16+
/**
17+
* A required dependency.
18+
*
19+
* This attribute indicates that a property holds a required dependency. The annotated property or method should be
20+
* considered during the instantiation process of the containing class.
21+
*
22+
* @author Alexander M. Turek <me@derrabus.de>
23+
*/
24+
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_PROPERTY)]
25+
final class Required
26+
{
27+
}

src/Symfony/Contracts/Service/composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"minimum-stability": "dev",
2929
"extra": {
3030
"branch-alias": {
31-
"dev-master": "2.1-dev"
31+
"dev-master": "2.2-dev"
3232
},
3333
"thanks": {
3434
"name": "symfony/contracts",

src/Symfony/Contracts/Translation/composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"minimum-stability": "dev",
2828
"extra": {
2929
"branch-alias": {
30-
"dev-master": "2.1-dev"
30+
"dev-master": "2.2-dev"
3131
},
3232
"thanks": {
3333
"name": "symfony/contracts",

src/Symfony/Contracts/composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
"minimum-stability": "dev",
5050
"extra": {
5151
"branch-alias": {
52-
"dev-master": "2.1-dev"
52+
"dev-master": "2.2-dev"
5353
}
5454
}
5555
}

0 commit comments

Comments
 (0)