From ea262441e7a9ab426ed0eaf2213ea5ee57539741 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Thu, 9 Jul 2020 22:22:50 +0200 Subject: [PATCH] [DependencyInjection] Add the Required attribute. --- .github/patch-types.php | 1 + .../Compiler/AutowireRequiredMethodsPass.php | 9 ++++ .../AutowireRequiredPropertiesPass.php | 8 ++-- .../Tests/Compiler/AutowirePassTest.php | 27 +++++++++++ .../AutowireRequiredMethodsPassTest.php | 46 +++++++++++++++++++ .../AutowireRequiredPropertiesPassTest.php | 25 ++++++++++ .../Fixtures/includes/autowiring_classes.php | 1 + .../includes/autowiring_classes_80.php | 28 +++++++++++ src/Symfony/Contracts/Cache/composer.json | 2 +- .../Contracts/Deprecation/composer.json | 2 +- .../Contracts/EventDispatcher/composer.json | 2 +- .../Contracts/HttpClient/composer.json | 2 +- .../Contracts/Service/Attribute/Required.php | 27 +++++++++++ src/Symfony/Contracts/Service/composer.json | 2 +- .../Contracts/Translation/composer.json | 2 +- src/Symfony/Contracts/composer.json | 2 +- 16 files changed, 175 insertions(+), 11 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes_80.php create mode 100644 src/Symfony/Contracts/Service/Attribute/Required.php diff --git a/.github/patch-types.php b/.github/patch-types.php index 70fea35aaae3e..eaf983e08d7f1 100644 --- a/.github/patch-types.php +++ b/.github/patch-types.php @@ -22,6 +22,7 @@ case false !== strpos($file, '/src/Symfony/Component/Debug/Tests/Fixtures/'): case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Compiler/OptionalServiceClass.php'): case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php'): + case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes_80.php'): case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/uniontype_classes.php'): case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Fixtures/ParentNotExists.php'): case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/BadClasses/MissingParent.php'): diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowireRequiredMethodsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowireRequiredMethodsPass.php index 2c774f781371c..fc1027677d41c 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowireRequiredMethodsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowireRequiredMethodsPass.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Contracts\Service\Attribute\Required; /** * 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) } while (true) { + if (\PHP_VERSION_ID >= 80000 && $r->getAttributes(Required::class)) { + if ($this->isWither($r, $r->getDocComment() ?: '')) { + $withers[] = [$r->name, [], true]; + } else { + $value->addMethodCall($r->name, []); + } + break; + } if (false !== $doc = $r->getDocComment()) { if (false !== stripos($doc, '@required') && preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@required(?:\s|\*/$)#i', $doc)) { if ($this->isWither($reflectionMethod, $doc)) { diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowireRequiredPropertiesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowireRequiredPropertiesPass.php index 24f9c41d2bf50..52024b8074556 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowireRequiredPropertiesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowireRequiredPropertiesPass.php @@ -14,6 +14,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\TypedReference; +use Symfony\Contracts\Service\Attribute\Required; /** * Looks for definitions with autowiring enabled and registers their corresponding "@required" properties. @@ -45,10 +46,9 @@ protected function processValue($value, bool $isRoot = false) if (!($type = $reflectionProperty->getType()) instanceof \ReflectionNamedType) { continue; } - if (false === $doc = $reflectionProperty->getDocComment()) { - continue; - } - if (false === stripos($doc, '@required') || !preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@required(?:\s|\*/$)#i', $doc)) { + if ((\PHP_VERSION_ID < 80000 || !$reflectionProperty->getAttributes(Required::class)) + && ((false === $doc = $reflectionProperty->getDocComment()) || false === stripos($doc, '@required') || !preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@required(?:\s|\*/$)#i', $doc)) + ) { continue; } if (\array_key_exists($name = $reflectionProperty->getName(), $properties)) { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php index 69a0da2127b7d..bdcbf8d959868 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -28,6 +28,7 @@ use Symfony\Component\DependencyInjection\Tests\Fixtures\includes\FooVariadic; use Symfony\Component\DependencyInjection\Tests\Fixtures\includes\MultipleArgumentsOptionalScalarNotReallyOptional; use Symfony\Component\DependencyInjection\TypedReference; +use Symfony\Contracts\Service\Attribute\Required; require_once __DIR__.'/../Fixtures/includes/autowiring_classes.php'; @@ -640,6 +641,32 @@ public function testSetterInjection() ); } + /** + * @requires PHP 8 + */ + public function testSetterInjectionWithAttribute() + { + if (!class_exists(Required::class)) { + $this->markTestSkipped('symfony/service-contracts 2.2 required'); + } + + $container = new ContainerBuilder(); + $container->register(Foo::class); + + $container + ->register('setter_injection', AutowireSetter::class) + ->setAutowired(true); + + (new ResolveClassPass())->process($container); + (new AutowireRequiredMethodsPass())->process($container); + (new AutowirePass())->process($container); + + $methodCalls = $container->getDefinition('setter_injection')->getMethodCalls(); + $this->assertCount(1, $methodCalls); + $this->assertSame('setFoo', $methodCalls[0][0]); + $this->assertSame(Foo::class, (string) $methodCalls[0][1][0]); + } + public function testWithNonExistingSetterAndAutowiring() { $this->expectException(RuntimeException::class); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowireRequiredMethodsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowireRequiredMethodsPassTest.php index 742e53b76e954..4704d1920bbfb 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowireRequiredMethodsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowireRequiredMethodsPassTest.php @@ -16,6 +16,7 @@ use Symfony\Component\DependencyInjection\Compiler\ResolveClassPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Tests\Fixtures\WitherStaticReturnType; +use Symfony\Contracts\Service\Attribute\Required; require_once __DIR__.'/../Fixtures/includes/autowiring_classes.php'; @@ -54,6 +55,29 @@ public function testSetterInjection() $this->assertEquals([], $methodCalls[1][1]); } + /** + * @requires PHP 8 + */ + public function testSetterInjectionWithAttribute() + { + if (!class_exists(Required::class)) { + $this->markTestSkipped('symfony/service-contracts 2.2 required'); + } + + $container = new ContainerBuilder(); + $container->register(Foo::class); + + $container + ->register('setter_injection', AutowireSetter::class) + ->setAutowired(true); + + (new ResolveClassPass())->process($container); + (new AutowireRequiredMethodsPass())->process($container); + + $methodCalls = $container->getDefinition('setter_injection')->getMethodCalls(); + $this->assertSame([['setFoo', []]], $methodCalls); + } + public function testExplicitMethodInjection() { $container = new ContainerBuilder(); @@ -124,4 +148,26 @@ public function testWitherWithStaticReturnTypeInjection() ]; $this->assertSame($expected, $methodCalls); } + + /** + * @requires PHP 8 + */ + public function testWitherInjectionWithAttribute() + { + if (!class_exists(Required::class)) { + $this->markTestSkipped('symfony/service-contracts 2.2 required'); + } + + $container = new ContainerBuilder(); + $container->register(Foo::class); + + $container + ->register('wither', AutowireWither::class) + ->setAutowired(true); + + (new ResolveClassPass())->process($container); + (new AutowireRequiredMethodsPass())->process($container); + + $this->assertSame([['withFoo', [], true]], $container->getDefinition('wither')->getMethodCalls()); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowireRequiredPropertiesPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowireRequiredPropertiesPassTest.php index 241daaaff3358..2de975faac2a2 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowireRequiredPropertiesPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowireRequiredPropertiesPassTest.php @@ -15,6 +15,7 @@ use Symfony\Component\DependencyInjection\Compiler\AutowireRequiredPropertiesPass; use Symfony\Component\DependencyInjection\Compiler\ResolveClassPass; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Contracts\Service\Attribute\Required; require_once __DIR__.'/../Fixtures/includes/autowiring_classes.php'; @@ -43,4 +44,28 @@ public function testInjection() $this->assertArrayHasKey('plop', $properties); $this->assertEquals(Bar::class, (string) $properties['plop']); } + + /** + * @requires PHP 8 + */ + public function testAttribute() + { + if (!class_exists(Required::class)) { + $this->markTestSkipped('symfony/service-contracts 2.2 required'); + } + + $container = new ContainerBuilder(); + $container->register(Foo::class); + + $container->register('property_injection', AutowireProperty::class) + ->setAutowired(true); + + (new ResolveClassPass())->process($container); + (new AutowireRequiredPropertiesPass())->process($container); + + $properties = $container->getDefinition('property_injection')->getProperties(); + + $this->assertArrayHasKey('foo', $properties); + $this->assertEquals(Foo::class, (string) $properties['foo']); + } } 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 2891d4307ca56..33cfdd9d9e403 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php @@ -6,6 +6,7 @@ if (PHP_VERSION_ID >= 80000) { require __DIR__.'/uniontype_classes.php'; + require __DIR__.'/autowiring_classes_80.php'; } class Foo diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes_80.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes_80.php new file mode 100644 index 0000000000000..e22ae85169e5a --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes_80.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Attribute; + +use Attribute; + +/** + * A required dependency. + * + * This attribute indicates that a property holds a required dependency. The annotated property or method should be + * considered during the instantiation process of the containing class. + * + * @author Alexander M. Turek + */ +#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_PROPERTY)] +final class Required +{ +} diff --git a/src/Symfony/Contracts/Service/composer.json b/src/Symfony/Contracts/Service/composer.json index bb1824d72d827..47244fbb1034a 100644 --- a/src/Symfony/Contracts/Service/composer.json +++ b/src/Symfony/Contracts/Service/composer.json @@ -28,7 +28,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "2.2-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/Translation/composer.json b/src/Symfony/Contracts/Translation/composer.json index 0295ce63ce297..2cef00fdd4caf 100644 --- a/src/Symfony/Contracts/Translation/composer.json +++ b/src/Symfony/Contracts/Translation/composer.json @@ -27,7 +27,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "2.2-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/composer.json b/src/Symfony/Contracts/composer.json index 83e5db3996ab4..1ce94b46999c6 100644 --- a/src/Symfony/Contracts/composer.json +++ b/src/Symfony/Contracts/composer.json @@ -49,7 +49,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "2.2-dev" } } }