diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index 61e2c135a1037..59be45c99780d 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -17,6 +17,7 @@ CHANGELOG * Add `#[Exclude]` to skip autoregistering a class * Add support for autowiring services as closures using `#[AutowireCallable]` or `#[AutowireServiceClosure]` * Deprecate `#[MapDecorated]`, use `#[AutowireDecorated]` instead + * Deprecate the `@required` annotation, use the `Symfony\Contracts\Service\Attribute\Required` attribute instead 6.2 --- diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowireRequiredMethodsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowireRequiredMethodsPass.php index da2fc5947d938..a3f5199ef20c8 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowireRequiredMethodsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowireRequiredMethodsPass.php @@ -15,7 +15,7 @@ use Symfony\Contracts\Service\Attribute\Required; /** - * Looks for definitions with autowiring enabled and registers their corresponding "@required" methods as setters. + * Looks for definitions with autowiring enabled and registers their corresponding "#[Required]" methods as setters. * * @author Nicolas Grekas */ @@ -57,6 +57,8 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed } if (false !== $doc = $r->getDocComment()) { if (false !== stripos($doc, '@required') && preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@required(?:\s|\*/$)#i', $doc)) { + trigger_deprecation('symfony/dependency-injection', '6.3', 'Relying on the "@required" annotation on method "%s::%s()" is deprecated, use the "Symfony\Contracts\Service\Attribute\Required" attribute instead.', $reflectionMethod->class, $reflectionMethod->name); + if ($this->isWither($reflectionMethod, $doc)) { $withers[] = [$reflectionMethod->name, [], true]; } else { diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowireRequiredPropertiesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowireRequiredPropertiesPass.php index d77e9955b004d..0f093bb7fc702 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowireRequiredPropertiesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowireRequiredPropertiesPass.php @@ -17,7 +17,7 @@ use Symfony\Contracts\Service\Attribute\Required; /** - * Looks for definitions with autowiring enabled and registers their corresponding "@required" properties. + * Looks for definitions with autowiring enabled and registers their corresponding "#[Required]" properties. * * @author Sebastien Morel (Plopix) * @author Nicolas Grekas @@ -40,11 +40,15 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed if (!($type = $reflectionProperty->getType()) instanceof \ReflectionNamedType) { continue; } + $doc = false; if (!$reflectionProperty->getAttributes(Required::class) && ((false === $doc = $reflectionProperty->getDocComment()) || false === stripos($doc, '@required') || !preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@required(?:\s|\*/$)#i', $doc)) ) { continue; } + if ($doc) { + trigger_deprecation('symfony/dependency-injection', '6.3', 'Using the "@required" annotation on property "%s::$%s" is deprecated, use the "Symfony\Contracts\Service\Attribute\Required" attribute instead.', $reflectionProperty->class, $reflectionProperty->name); + } if (\array_key_exists($name = $reflectionProperty->getName(), $properties)) { continue; } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php index bdf07b10ff077..bd51657278526 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -15,6 +15,7 @@ use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use Symfony\Bridge\PhpUnit\ClassExistsMock; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\Resource\ClassExistenceResource; use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; @@ -41,6 +42,8 @@ class AutowirePassTest extends TestCase { + use ExpectDeprecationTrait; + public static function setUpBeforeClass(): void { ClassExistsMock::register(AutowirePass::class); @@ -695,8 +698,15 @@ public function testOptionalArgsNoRequiredForCoreClasses() ); } - public function testSetterInjection() + /** + * @group legacy + */ + public function testSetterInjectionAnnotation() { + $this->expectDeprecation('Since symfony/dependency-injection 6.3: Relying on the "@required" annotation on method "Symfony\Component\DependencyInjection\Tests\Compiler\SetterInjectionAnnotation::setFoo()" is deprecated, use the "Symfony\Contracts\Service\Attribute\Required" attribute instead.'); + $this->expectDeprecation('Since symfony/dependency-injection 6.3: Relying on the "@required" annotation on method "Symfony\Component\DependencyInjection\Tests\Compiler\SetterInjectionAnnotation::setChildMethodWithoutDocBlock()" is deprecated, use the "Symfony\Contracts\Service\Attribute\Required" attribute instead.'); + $this->expectDeprecation('Since symfony/dependency-injection 6.3: Relying on the "@required" annotation on method "Symfony\Component\DependencyInjection\Tests\Compiler\SetterInjectionParentAnnotation::setDependencies()" is deprecated, use the "Symfony\Contracts\Service\Attribute\Required" attribute instead.'); + $container = new ContainerBuilder(); $container->register(Foo::class); $container->register(A::class); @@ -705,7 +715,7 @@ public function testSetterInjection() // manually configure *one* call, to override autowiring $container - ->register('setter_injection', SetterInjection::class) + ->register('setter_injection', SetterInjectionAnnotation::class) ->setAutowired(true) ->addMethodCall('setWithCallsConfigured', ['manual_arg1', 'manual_arg2']) ; @@ -717,7 +727,7 @@ public function testSetterInjection() $methodCalls = $container->getDefinition('setter_injection')->getMethodCalls(); $this->assertEquals( - ['setWithCallsConfigured', 'setFoo', 'setDependencies', 'setChildMethodWithoutDocBlock'], + ['setWithCallsConfigured', 'setFoo', 'setChildMethodWithoutDocBlock', 'setDependencies'], array_column($methodCalls, 0) ); @@ -766,7 +776,7 @@ public function testWithNonExistingSetterAndAutowiring() (new AutowirePass())->process($container); } - public function testExplicitMethodInjection() + public function testExplicitMethodInjectionAttribute() { $container = new ContainerBuilder(); $container->register(Foo::class); @@ -821,7 +831,33 @@ public function testIgnoreServiceWithClassNotExisting() $this->assertTrue($container->hasDefinition('bar')); } - public function testSetterInjectionCollisionThrowsException() + /** + * @group legacy + */ + public function testSetterInjectionFromAnnotationCollisionThrowsException() + { + $this->expectDeprecation('Since symfony/dependency-injection 6.3: Relying on the "@required" annotation on method "Symfony\Component\DependencyInjection\Tests\Compiler\SetterInjectionCollisionAnnotation::setMultipleInstancesForOneArg()" is deprecated, use the "Symfony\Contracts\Service\Attribute\Required" attribute instead.'); + + $container = new ContainerBuilder(); + + $container->register('c1', CollisionA::class); + $container->register('c2', CollisionB::class); + $aDefinition = $container->register('setter_injection_collision', SetterInjectionCollisionAnnotation::class); + $aDefinition->setAutowired(true); + + (new AutowireRequiredMethodsPass())->process($container); + + $pass = new AutowirePass(); + + try { + $pass->process($container); + $this->fail('AutowirePass should have thrown an exception'); + } catch (AutowiringFailedException $e) { + $this->assertSame('Cannot autowire service "setter_injection_collision": argument "$collision" of method "Symfony\Component\DependencyInjection\Tests\Compiler\SetterInjectionCollisionAnnotation::setMultipleInstancesForOneArg()" references interface "Symfony\Component\DependencyInjection\Tests\Compiler\CollisionInterface" but no such service exists. You should maybe alias this interface to one of these existing services: "c1", "c2".', (string) $e->getMessage()); + } + } + + public function testSetterInjectionFromAttributeCollisionThrowsException() { $container = new ContainerBuilder(); @@ -1129,6 +1165,36 @@ public function testErroredServiceLocator() $this->assertSame(['Cannot autowire service "some_locator": it has type "Symfony\Component\DependencyInjection\Tests\Compiler\MissingClass" but this class was not found.'], $container->getDefinition('.errored.some_locator.'.MissingClass::class)->getErrors()); } + /** + * @group legacy + */ + public function testNamedArgumentAliasResolveCollisionsAnnotation() + { + $this->expectDeprecation('Since symfony/dependency-injection 6.3: Relying on the "@required" annotation on method "Symfony\Component\DependencyInjection\Tests\Compiler\SetterInjectionCollisionAnnotation::setMultipleInstancesForOneArg()" is deprecated, use the "Symfony\Contracts\Service\Attribute\Required" attribute instead.'); + + $container = new ContainerBuilder(); + + $container->register('c1', CollisionA::class); + $container->register('c2', CollisionB::class); + $container->setAlias(CollisionInterface::class.' $collision', 'c2'); + $aDefinition = $container->register('setter_injection_collision', SetterInjectionCollisionAnnotation::class); + $aDefinition->setAutowired(true); + + (new AutowireRequiredMethodsPass())->process($container); + + $pass = new AutowirePass(); + + $pass->process($container); + + $expected = [ + [ + 'setMultipleInstancesForOneArg', + [new TypedReference(CollisionInterface::class.' $collision', CollisionInterface::class)], + ], + ]; + $this->assertEquals($expected, $container->getDefinition('setter_injection_collision')->getMethodCalls()); + } + public function testNamedArgumentAliasResolveCollisions() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowireRequiredMethodsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowireRequiredMethodsPassTest.php index 9cedd5e02f249..73f9f62bbad75 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowireRequiredMethodsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowireRequiredMethodsPassTest.php @@ -12,26 +12,37 @@ namespace Symfony\Component\DependencyInjection\Tests\Compiler; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\DependencyInjection\Compiler\AutowireRequiredMethodsPass; use Symfony\Component\DependencyInjection\Compiler\ResolveClassPass; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Tests\Fixtures\WitherAnnotationStaticReturnType; use Symfony\Component\DependencyInjection\Tests\Fixtures\WitherStaticReturnType; require_once __DIR__.'/../Fixtures/includes/autowiring_classes.php'; class AutowireRequiredMethodsPassTest extends TestCase { - public function testSetterInjection() + use ExpectDeprecationTrait; + + /** + * @group legacy + */ + public function testSetterInjectionAnnotation() { + $this->expectDeprecation('Since symfony/dependency-injection 6.3: Relying on the "@required" annotation on method "Symfony\Component\DependencyInjection\Tests\Compiler\SetterInjectionAnnotation::setFoo()" is deprecated, use the "Symfony\Contracts\Service\Attribute\Required" attribute instead.'); + $this->expectDeprecation('Since symfony/dependency-injection 6.3: Relying on the "@required" annotation on method "Symfony\Component\DependencyInjection\Tests\Compiler\SetterInjectionAnnotation::setChildMethodWithoutDocBlock()" is deprecated, use the "Symfony\Contracts\Service\Attribute\Required" attribute instead.'); + $this->expectDeprecation('Since symfony/dependency-injection 6.3: Relying on the "@required" annotation on method "Symfony\Component\DependencyInjection\Tests\Compiler\SetterInjectionParentAnnotation::setDependencies()" is deprecated, use the "Symfony\Contracts\Service\Attribute\Required" attribute instead.'); + $container = new ContainerBuilder(); - $container->register(Foo::class); + $container->register(FooAnnotation::class); $container->register(A::class); $container->register(CollisionA::class); $container->register(CollisionB::class); // manually configure *one* call, to override autowiring $container - ->register('setter_injection', SetterInjection::class) + ->register('setter_injection', SetterInjectionAnnotation::class) ->setAutowired(true) ->addMethodCall('setWithCallsConfigured', ['manual_arg1', 'manual_arg2']); @@ -41,7 +52,7 @@ public function testSetterInjection() $methodCalls = $container->getDefinition('setter_injection')->getMethodCalls(); $this->assertEquals( - ['setWithCallsConfigured', 'setFoo', 'setDependencies', 'setChildMethodWithoutDocBlock'], + ['setWithCallsConfigured', 'setFoo', 'setChildMethodWithoutDocBlock', 'setDependencies'], array_column($methodCalls, 0) ); @@ -70,7 +81,41 @@ public function testSetterInjectionWithAttribute() $this->assertSame([['setFoo', []]], $methodCalls); } - public function testExplicitMethodInjection() + /** + * @group legacy + */ + // @deprecated since Symfony 6.3, to be removed in 7.0 + public function testExplicitMethodInjectionAnnotation() + { + $this->expectDeprecation('Since symfony/dependency-injection 6.3: Relying on the "@required" annotation on method "Symfony\Component\DependencyInjection\Tests\Compiler\SetterInjectionAnnotation::setFoo()" is deprecated, use the "Symfony\Contracts\Service\Attribute\Required" attribute instead.'); + $this->expectDeprecation('Since symfony/dependency-injection 6.3: Relying on the "@required" annotation on method "Symfony\Component\DependencyInjection\Tests\Compiler\SetterInjectionAnnotation::setChildMethodWithoutDocBlock()" is deprecated, use the "Symfony\Contracts\Service\Attribute\Required" attribute instead.'); + $this->expectDeprecation('Since symfony/dependency-injection 6.3: Relying on the "@required" annotation on method "Symfony\Component\DependencyInjection\Tests\Compiler\SetterInjectionParentAnnotation::setDependencies()" is deprecated, use the "Symfony\Contracts\Service\Attribute\Required" attribute instead.'); + $this->expectDeprecation('Since symfony/dependency-injection 6.3: Relying on the "@required" annotation on method "Symfony\Component\DependencyInjection\Tests\Compiler\SetterInjectionParentAnnotation::setWithCallsConfigured()" is deprecated, use the "Symfony\Contracts\Service\Attribute\Required" attribute instead.'); + + $container = new ContainerBuilder(); + $container->register(FooAnnotation::class); + $container->register(A::class); + $container->register(CollisionA::class); + $container->register(CollisionB::class); + + $container + ->register('setter_injection', SetterInjectionAnnotation::class) + ->setAutowired(true) + ->addMethodCall('notASetter', []); + + (new ResolveClassPass())->process($container); + (new AutowireRequiredMethodsPass())->process($container); + + $methodCalls = $container->getDefinition('setter_injection')->getMethodCalls(); + + $this->assertEquals( + ['notASetter', 'setFoo', 'setChildMethodWithoutDocBlock', 'setDependencies', 'setWithCallsConfigured'], + array_column($methodCalls, 0) + ); + $this->assertEquals([], $methodCalls[0][1]); + } + + public function testExplicitMethodInjectionAttribute() { $container = new ContainerBuilder(); $container->register(Foo::class); @@ -95,13 +140,19 @@ public function testExplicitMethodInjection() $this->assertEquals([], $methodCalls[0][1]); } + /** + * @group legacy + */ public function testWitherInjection() { + $this->expectDeprecation('Since symfony/dependency-injection 6.3: Relying on the "@required" annotation on method "Symfony\Component\DependencyInjection\Tests\Compiler\WitherAnnotation::withFoo1()" is deprecated, use the "Symfony\Contracts\Service\Attribute\Required" attribute instead.'); + $this->expectDeprecation('Since symfony/dependency-injection 6.3: Relying on the "@required" annotation on method "Symfony\Component\DependencyInjection\Tests\Compiler\WitherAnnotation::withFoo2()" is deprecated, use the "Symfony\Contracts\Service\Attribute\Required" attribute instead.'); + $container = new ContainerBuilder(); - $container->register(Foo::class); + $container->register(FooAnnotation::class); $container - ->register('wither', Wither::class) + ->register('wither', WitherAnnotation::class) ->setAutowired(true); (new ResolveClassPass())->process($container); @@ -117,6 +168,33 @@ public function testWitherInjection() $this->assertSame($expected, $methodCalls); } + /** + * @group legacy + */ + public function testWitherAnnotationWithStaticReturnTypeInjection() + { + $this->expectDeprecation('Since symfony/dependency-injection 6.3: Relying on the "@required" annotation on method "Symfony\Component\DependencyInjection\Tests\Fixtures\WitherAnnotationStaticReturnType::withFoo()" is deprecated, use the "Symfony\Contracts\Service\Attribute\Required" attribute instead.'); + $this->expectDeprecation('Since symfony/dependency-injection 6.3: Relying on the "@required" annotation on method "Symfony\Component\DependencyInjection\Tests\Fixtures\WitherAnnotationStaticReturnType::setFoo()" is deprecated, use the "Symfony\Contracts\Service\Attribute\Required" attribute instead.'); + + $container = new ContainerBuilder(); + $container->register(FooAnnotation::class); + + $container + ->register('wither', WitherAnnotationStaticReturnType::class) + ->setAutowired(true); + + (new ResolveClassPass())->process($container); + (new AutowireRequiredMethodsPass())->process($container); + + $methodCalls = $container->getDefinition('wither')->getMethodCalls(); + + $expected = [ + ['withFoo', [], true], + ['setFoo', []], + ]; + $this->assertSame($expected, $methodCalls); + } + public function testWitherWithStaticReturnTypeInjection() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowireRequiredPropertiesPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowireRequiredPropertiesPassTest.php index 2cee2e7ed5c35..62e12f9e84cd8 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowireRequiredPropertiesPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowireRequiredPropertiesPassTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Compiler; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\DependencyInjection\Compiler\AutowireRequiredPropertiesPass; use Symfony\Component\DependencyInjection\Compiler\ResolveClassPass; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -21,8 +22,15 @@ class AutowireRequiredPropertiesPassTest extends TestCase { + use ExpectDeprecationTrait; + + /** + * @group legacy + */ public function testInjection() { + $this->expectDeprecation('Since symfony/dependency-injection 6.3: Using the "@required" annotation on property "Symfony\Component\DependencyInjection\Tests\Compiler\PropertiesInjection::$plop" is deprecated, use the "Symfony\Contracts\Service\Attribute\Required" attribute instead.'); + $container = new ContainerBuilder(); $container->register(Bar::class); $container->register(A::class); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php index 497acee2f779c..449b60e5bccab 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Compiler; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\DependencyInjection\Argument\BoundArgument; use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; @@ -37,6 +38,8 @@ class ResolveBindingsPassTest extends TestCase { + use ExpectDeprecationTrait; + public function testProcess() { $container = new ContainerBuilder(); @@ -143,7 +146,25 @@ public function testTypedReferenceSupport() $this->assertEquals([new Reference('bar')], $container->getDefinition('def3')->getArguments()); } - public function testScalarSetter() + /** + * @group legacy + */ + public function testScalarSetterAnnotation() + { + $this->expectDeprecation('Since symfony/dependency-injection 6.3: Relying on the "@required" annotation on method "Symfony\Component\DependencyInjection\Tests\Compiler\ScalarSetterAnnotation::setDefaultLocale()" is deprecated, use the "Symfony\Contracts\Service\Attribute\Required" attribute instead.'); + + $container = new ContainerBuilder(); + + $definition = $container->autowire('foo', ScalarSetterAnnotation::class); + $definition->setBindings(['$defaultLocale' => 'fr']); + + (new AutowireRequiredMethodsPass())->process($container); + (new ResolveBindingsPass())->process($container); + + $this->assertEquals([['setDefaultLocale', ['fr']]], $definition->getMethodCalls()); + } + + public function testScalarSetterAttribute() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index f3d0bb270cb19..4ec0624b86288 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -48,6 +48,7 @@ use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\DependencyInjection\Tests\Compiler\Foo; +use Symfony\Component\DependencyInjection\Tests\Compiler\FooAnnotation; use Symfony\Component\DependencyInjection\Tests\Compiler\Wither; use Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass; use Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition; @@ -55,6 +56,7 @@ use Symfony\Component\DependencyInjection\Tests\Fixtures\ScalarFactory; use Symfony\Component\DependencyInjection\Tests\Fixtures\SimilarArgumentsDummy; use Symfony\Component\DependencyInjection\Tests\Fixtures\StringBackedEnum; +use Symfony\Component\DependencyInjection\Tests\Fixtures\WitherAnnotationStaticReturnType; use Symfony\Component\DependencyInjection\Tests\Fixtures\WitherStaticReturnType; use Symfony\Component\DependencyInjection\TypedReference; use Symfony\Component\ExpressionLanguage\Expression; @@ -1832,6 +1834,28 @@ public function testLazyWither() $this->assertInstanceOf(Wither::class, $wither->withFoo1($wither->foo)); } + /** + * @group legacy + */ + public function testWitherAnnotationWithStaticReturnType() + { + $this->expectDeprecation('Since symfony/dependency-injection 6.3: Relying on the "@required" annotation on method "Symfony\Component\DependencyInjection\Tests\Fixtures\WitherAnnotationStaticReturnType::withFoo()" is deprecated, use the "Symfony\Contracts\Service\Attribute\Required" attribute instead.'); + $this->expectDeprecation('Since symfony/dependency-injection 6.3: Relying on the "@required" annotation on method "Symfony\Component\DependencyInjection\Tests\Fixtures\WitherAnnotationStaticReturnType::setFoo()" is deprecated, use the "Symfony\Contracts\Service\Attribute\Required" attribute instead.'); + + $container = new ContainerBuilder(); + $container->register(FooAnnotation::class); + + $container + ->register('wither', WitherAnnotationStaticReturnType::class) + ->setPublic(true) + ->setAutowired(true); + + $container->compile(); + + $wither = $container->get('wither'); + $this->assertInstanceOf(FooAnnotation::class, $wither->foo); + } + public function testWitherWithStaticReturnType() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index 5e313cad403c9..1cc9d514fc37a 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -44,7 +44,9 @@ use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\DependencyInjection\Tests\Compiler\Foo; +use Symfony\Component\DependencyInjection\Tests\Compiler\FooAnnotation; use Symfony\Component\DependencyInjection\Tests\Compiler\Wither; +use Symfony\Component\DependencyInjection\Tests\Compiler\WitherAnnotation; use Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition; use Symfony\Component\DependencyInjection\Tests\Fixtures\FooClassWithEnumAttribute; use Symfony\Component\DependencyInjection\Tests\Fixtures\FooUnitEnum; @@ -1447,7 +1449,38 @@ public function testAliasCanBeFoundInTheDumpedContainerWhenBothTheAliasAndTheSer $this->assertContains('bar', $service_ids); } - public function testWither() + /** + * @group legacy + */ + public function testWitherAnnotation() + { + $this->expectDeprecation('Since symfony/dependency-injection 6.3: Relying on the "@required" annotation on method "Symfony\Component\DependencyInjection\Tests\Compiler\FooAnnotation::cloneFoo()" is deprecated, use the "Symfony\Contracts\Service\Attribute\Required" attribute instead.'); + $this->expectDeprecation('Since symfony/dependency-injection 6.3: Relying on the "@required" annotation on method "Symfony\Component\DependencyInjection\Tests\Compiler\WitherAnnotation::setFoo()" is deprecated, use the "Symfony\Contracts\Service\Attribute\Required" attribute instead.'); + $this->expectDeprecation('Since symfony/dependency-injection 6.3: Relying on the "@required" annotation on method "Symfony\Component\DependencyInjection\Tests\Compiler\WitherAnnotation::withFoo1()" is deprecated, use the "Symfony\Contracts\Service\Attribute\Required" attribute instead.'); + $this->expectDeprecation('Since symfony/dependency-injection 6.3: Relying on the "@required" annotation on method "Symfony\Component\DependencyInjection\Tests\Compiler\WitherAnnotation::withFoo2()" is deprecated, use the "Symfony\Contracts\Service\Attribute\Required" attribute instead.'); + + $container = new ContainerBuilder(); + $container->register(FooAnnotation::class) + ->setAutowired(true); + + $container + ->register('wither', WitherAnnotation::class) + ->setPublic(true) + ->setAutowired(true); + + $container->compile(); + $dumper = new PhpDumper($container); + $dump = $dumper->dump(['class' => 'Symfony_DI_PhpDumper_Service_Wither_Annotation']); + $this->assertStringEqualsFile(self::$fixturesPath.'/php/services_wither_annotation.php', $dump); + eval('?>'.$dump); + + $container = new \Symfony_DI_PhpDumper_Service_Wither_Annotation(); + + $wither = $container->get('wither'); + $this->assertInstanceOf(FooAnnotation::class, $wither->foo); + } + + public function testWitherAttribute() { $container = new ContainerBuilder(); $container->register(Foo::class) diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/WitherAnnotationStaticReturnType.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/WitherAnnotationStaticReturnType.php new file mode 100644 index 0000000000000..14b76d3b202f2 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/WitherAnnotationStaticReturnType.php @@ -0,0 +1,34 @@ +foo = $foo; + + return $new; + } + + /** + * @required + * + * @return $this + */ + public function setFoo(FooAnnotation $foo): static + { + $this->foo = $foo; + + return $this; + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/WitherStaticReturnType.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/WitherStaticReturnType.php index 1236b75dfad24..60063cceb152a 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/WitherStaticReturnType.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/WitherStaticReturnType.php @@ -3,14 +3,13 @@ namespace Symfony\Component\DependencyInjection\Tests\Fixtures; use Symfony\Component\DependencyInjection\Tests\Compiler\Foo; +use Symfony\Contracts\Service\Attribute\Required; class WitherStaticReturnType { public $foo; - /** - * @required - */ + #[Required] public function withFoo(Foo $foo): static { $new = clone $this; @@ -20,9 +19,9 @@ public function withFoo(Foo $foo): static } /** - * @required * @return $this */ + #[Required] public function setFoo(Foo $foo): static { $this->foo = $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 87f5ab65a1277..70c46ecb4fe64 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php @@ -3,6 +3,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Compiler; use Psr\Log\LoggerInterface; +use Symfony\Contracts\Service\Attribute\Required; require __DIR__.'/uniontype_classes.php'; require __DIR__.'/autowiring_classes_80.php'; @@ -11,7 +12,8 @@ require __DIR__.'/compositetype_classes.php'; } -class Foo +// @deprecated since Symfony 6.3, to be removed in 7.0 +class FooAnnotation { /** * @required @@ -22,6 +24,15 @@ public function cloneFoo(): static } } +class Foo +{ + #[Required] + public function cloneFoo(): static + { + return clone $this; + } +} + class Bar { public function __construct(Foo $foo) @@ -225,7 +236,8 @@ public function __construct($foo, Bar $bar, $baz) } } -class SetterInjectionCollision +// @deprecated since Symfony 6.3, to be removed in 7.0 +class SetterInjectionCollisionAnnotation { /** * @required @@ -238,8 +250,21 @@ public function setMultipleInstancesForOneArg(CollisionInterface $collision) } } -class SetterInjection extends SetterInjectionParent +class SetterInjectionCollision +{ + #[Required] + public function setMultipleInstancesForOneArg(CollisionInterface $collision) + { + // The CollisionInterface cannot be autowired - there are multiple + + // should throw an exception + } +} + +// @deprecated since Symfony 6.3, to be removed in 7.0 +class SetterInjectionAnnotation extends SetterInjectionParentAnnotation { + /** * @required */ @@ -248,6 +273,27 @@ public function setFoo(Foo $foo) // should be called } + public function notASetter(A $a) + { + // should be called only when explicitly specified + } + + /** + * @required*/ + public function setChildMethodWithoutDocBlock(A $a) + { + } +} + +// @deprecated since Symfony 6.3, to be removed in 7.0 +class SetterInjection extends SetterInjectionParent +{ + #[Required] + public function setFoo(Foo $foo) + { + // should be called + } + /** @inheritdoc*/ // <- brackets are missing on purpose public function setDependencies(Foo $foo, A $a) { @@ -264,29 +310,24 @@ public function notASetter(A $a) { // should be called only when explicitly specified } - - /** - * @required*/ - public function setChildMethodWithoutDocBlock(A $a) - { - } } -class Wither +// @deprecated since Symfony 6.3, to be removed in 7.0 +class WitherAnnotation { public $foo; /** * @required */ - public function setFoo(Foo $foo) + public function setFoo(FooAnnotation $foo) { } /** * @required */ - public function withFoo1(Foo $foo): static + public function withFoo1(FooAnnotation $foo): static { return $this->withFoo2($foo); } @@ -294,6 +335,31 @@ public function withFoo1(Foo $foo): static /** * @required */ + public function withFoo2(FooAnnotation $foo): static + { + $new = clone $this; + $new->foo = $foo; + + return $new; + } +} + +class Wither +{ + public $foo; + + #[Required] + public function setFoo(Foo $foo) + { + } + + #[Required] + public function withFoo1(Foo $foo): static + { + return $this->withFoo2($foo); + } + + #[Required] public function withFoo2(Foo $foo): static { $new = clone $this; @@ -303,7 +369,8 @@ public function withFoo2(Foo $foo): static } } -class SetterInjectionParent +// @deprecated since Symfony 6.3, to be removed in 7.0 +class SetterInjectionParentAnnotation { /** @required*/ public function setDependencies(Foo $foo, A $a) @@ -327,6 +394,30 @@ public function setChildMethodWithoutDocBlock(A $a) } } +class SetterInjectionParent +{ + #[Required] + public function setDependencies(Foo $foo, A $a) + { + // should be called + } + + public function notASetter(A $a) + { + // #[Required] should be ignored when the child does not add @inheritdoc + } + + #[Required] + public function setWithCallsConfigured(A $a) + { + } + + #[Required] + public function setChildMethodWithoutDocBlock(A $a) + { + } +} + class NotWireable { public function setNotAutowireable(NotARealClass $n) @@ -357,7 +448,7 @@ public function setOptionalArgNoAutowireable($other = 'default_val') { } - /** @required */ + #[Required] protected function setProtectedMethod(A $a) { } @@ -370,7 +461,8 @@ private function __construct() } } -class ScalarSetter +// @deprecated since Symfony 6.3, to be removed in 7.0 +class ScalarSetterAnnotation { /** * @required @@ -380,6 +472,14 @@ public function setDefaultLocale($defaultLocale) } } +class ScalarSetter +{ + #[Required] + public function setDefaultLocale($defaultLocale) + { + } +} + interface DecoratorInterface { } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes_74.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes_74.php index 60b7fa7ca0c89..8e354b28219a1 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes_74.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes_74.php @@ -2,6 +2,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Compiler; +// @deprecated since Symfony 6.3, to be removed in 7.0 class PropertiesInjection { /** diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_annotation.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_annotation.php new file mode 100644 index 0000000000000..a958df8ebdc0d --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_annotation.php @@ -0,0 +1,66 @@ +ref = \WeakReference::create($this); + $this->services = $this->privates = []; + $this->methodMap = [ + 'wither' => 'getWitherService', + ]; + + $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; + } + + public function getRemovedIds(): array + { + return [ + 'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\FooAnnotation' => true, + ]; + } + + /** + * Gets the public 'wither' shared autowired service. + * + * @return \Symfony\Component\DependencyInjection\Tests\Compiler\WitherAnnotation + */ + protected static function getWitherService($container) + { + $instance = new \Symfony\Component\DependencyInjection\Tests\Compiler\WitherAnnotation(); + + $a = new \Symfony\Component\DependencyInjection\Tests\Compiler\FooAnnotation(); + $a = $a->cloneFoo(); + + $instance = $instance->withFoo1($a); + $container->services['wither'] = $instance = $instance->withFoo2($a); + $instance->setFoo($a); + + return $instance; + } +}