From a5961429aaae23f7761fbf242699c6de17398e53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ismail=20=C3=96zg=C3=BCn=20Turan?= Date: Thu, 30 Nov 2023 11:34:55 +0100 Subject: [PATCH 1/2] [DependencyInjection] Add `#[AutowireInline]` attribute to allow service definition at the class level --- .../Attribute/AutowireCallable.php | 4 +- .../Attribute/AutowireInline.php | 55 +++++ .../DependencyInjection/CHANGELOG.md | 1 + .../Compiler/AutowirePass.php | 6 +- .../Compiler/PassConfig.php | 1 + .../ResolveAutowireInlineAttributesPass.php | 65 ++++++ .../Tests/Attribute/AutowireInlineTest.php | 200 ++++++++++++++++++ ...esolveAutowireInlineAttributesPassTest.php | 49 +++++ .../Tests/Dumper/PhpDumperTest.php | 98 +++++++++ .../Fixtures/includes/autowiring_classes.php | 56 +++++ .../includes/autowiring_classes_80.php | 31 +++ .../Fixtures/php/inline_adapter_consumer.php | 178 ++++++++++++++++ 12 files changed, 739 insertions(+), 5 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Attribute/AutowireInline.php create mode 100644 src/Symfony/Component/DependencyInjection/Compiler/ResolveAutowireInlineAttributesPass.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Attribute/AutowireInlineTest.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveAutowireInlineAttributesPassTest.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/inline_adapter_consumer.php diff --git a/src/Symfony/Component/DependencyInjection/Attribute/AutowireCallable.php b/src/Symfony/Component/DependencyInjection/Attribute/AutowireCallable.php index 869e96e1ab93e..af0c8c46cf76e 100644 --- a/src/Symfony/Component/DependencyInjection/Attribute/AutowireCallable.php +++ b/src/Symfony/Component/DependencyInjection/Attribute/AutowireCallable.php @@ -19,7 +19,7 @@ * Attribute to tell which callable to give to an argument of type Closure. */ #[\Attribute(\Attribute::TARGET_PARAMETER)] -class AutowireCallable extends Autowire +class AutowireCallable extends AutowireInline { /** * @param string|array|null $callable The callable to autowire @@ -40,7 +40,7 @@ public function __construct( throw new LogicException('#[AutowireCallable] attribute cannot have a $method without a $service.'); } - parent::__construct($callable ?? [new Reference($service), $method ?? '__invoke'], lazy: $lazy); + Autowire::__construct($callable ?? [new Reference($service), $method ?? '__invoke'], lazy: $lazy); } public function buildDefinition(mixed $value, ?string $type, \ReflectionParameter $parameter): Definition diff --git a/src/Symfony/Component/DependencyInjection/Attribute/AutowireInline.php b/src/Symfony/Component/DependencyInjection/Attribute/AutowireInline.php new file mode 100644 index 0000000000000..6f1d15bebf40e --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Attribute/AutowireInline.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; + +/** + * Allows inline service definition for a constructor argument. + * Using this attribute on a class autowires it as a new instance + * which is not shared between different services. + * + * @author Ismail Özgün Turan + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class AutowireInline extends Autowire +{ + public function __construct(string|array $class, array $arguments = [], array $calls = [], array $properties = [], ?string $parent = null, bool|string $lazy = false) + { + parent::__construct([ + \is_array($class) ? 'factory' : 'class' => $class, + 'arguments' => $arguments, + 'calls' => $calls, + 'properties' => $properties, + 'parent' => $parent, + ], lazy: $lazy); + } + + public function buildDefinition(mixed $value, ?string $type, \ReflectionParameter $parameter): Definition + { + static $parseDefinition; + static $yamlLoader; + + $parseDefinition ??= new \ReflectionMethod(YamlFileLoader::class, 'parseDefinition'); + $yamlLoader ??= $parseDefinition->getDeclaringClass()->newInstanceWithoutConstructor(); + + if (isset($value['factory'])) { + $value['class'] = $type; + $value['factory'][0] ??= $type; + $value['factory'][1] ??= '__invoke'; + } + $class = $parameter->getDeclaringClass(); + + return $parseDefinition->invoke($yamlLoader, $class->name, $value, $class->getFileName(), ['autowire' => true], true); + } +} diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index cab370e80feef..0520a7add3791 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -13,6 +13,7 @@ CHANGELOG * Add argument `$prepend` to `FileLoader::construct()` to prepend loaded configuration instead of appending it * [BC BREAK] When used in the `prependExtension()` method, the `ContainerConfigurator::import()` method now prepends the configuration instead of appending it * Cast env vars to null or bool when referencing them using `#[Autowire(env: '...')]` depending on the signature of the corresponding parameter + * Add `#[AutowireInline]` attribute to allow service definition at the class level 7.0 --- diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php index 18cb09d6345ba..342820311df20 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -13,8 +13,8 @@ use Symfony\Component\Config\Resource\ClassExistenceResource; use Symfony\Component\DependencyInjection\Attribute\Autowire; -use Symfony\Component\DependencyInjection\Attribute\AutowireCallable; use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated; +use Symfony\Component\DependencyInjection\Attribute\AutowireInline; use Symfony\Component\DependencyInjection\Attribute\Lazy; use Symfony\Component\DependencyInjection\Attribute\Target; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -331,9 +331,9 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a continue 2; } - if ($attribute instanceof AutowireCallable) { + if ($attribute instanceof AutowireInline) { $value = $attribute->buildDefinition($value, $type, $parameter); - $value = $this->doProcessValue($value); + $value = new Reference('.autowire_inline.'.ContainerBuilder::hash($value)); } elseif ($lazy = $attribute->lazy) { $definition = (new Definition($type)) ->setFactory('current') diff --git a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php index d2539961fdf0e..35e2c2058257e 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php @@ -55,6 +55,7 @@ public function __construct() new AutoAliasServicePass(), new ValidateEnvPlaceholdersPass(), new ResolveDecoratorStackPass(), + new ResolveAutowireInlineAttributesPass(), new ResolveChildDefinitionsPass(), new RegisterServiceSubscribersPass(), new ResolveParameterPlaceHoldersPass(false, false), diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveAutowireInlineAttributesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveAutowireInlineAttributesPass.php new file mode 100644 index 0000000000000..feb93a32caef1 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveAutowireInlineAttributesPass.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Attribute\AutowireInline; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\VarExporter\ProxyHelper; + +/** + * Inspects existing autowired services for {@see AutowireInline} attribute and registers the definitions for reuse. + * + * @author Ismail Özgün Turan + */ +class ResolveAutowireInlineAttributesPass extends AbstractRecursivePass +{ + protected bool $skipScalars = true; + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + $value = parent::processValue($value, $isRoot); + + if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) { + return $value; + } + + try { + $constructor = $this->getConstructor($value, false); + } catch (RuntimeException) { + $this->container->log($this, sprintf('Skipping service "%s": Class or interface "%s" cannot be loaded.', $this->currentId, $value->getClass())); + + return $value; + } + + if ($constructor === null) { + return $value; + } + + $reflectionParameters = $constructor->getParameters(); + foreach ($reflectionParameters as $reflectionParameter) { + $autowireInlineAttributes = $reflectionParameter->getAttributes(AutowireInline::class, \ReflectionAttribute::IS_INSTANCEOF); + foreach ($autowireInlineAttributes as $autowireInlineAttribute) { + /** @var AutowireInline $autowireInlineAttributeInstance */ + $autowireInlineAttributeInstance = $autowireInlineAttribute->newInstance(); + + $type = ProxyHelper::exportType($reflectionParameter, true); + $definition = $autowireInlineAttributeInstance->buildDefinition($autowireInlineAttributeInstance->value, $type, $reflectionParameter); + + $this->container->setDefinition('.autowire_inline.'.ContainerBuilder::hash($definition), $definition); + } + } + + return $value; + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Attribute/AutowireInlineTest.php b/src/Symfony/Component/DependencyInjection/Tests/Attribute/AutowireInlineTest.php new file mode 100644 index 0000000000000..a9ae1fb252c66 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Attribute/AutowireInlineTest.php @@ -0,0 +1,200 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Attribute; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Attribute\AutowireInline; +use Symfony\Component\DependencyInjection\Reference; + +class AutowireInlineTest extends TestCase +{ + public function testInvalidFactoryArray() + { + $autowireInline = new AutowireInline([123, 456]); + + self::assertSame([123, 456], $autowireInline->value['factory']); + } + + /** + * @dataProvider provideInvalidCalls + */ + public function testInvalidCallsArray(array $calls) + { + $autowireInline = new AutowireInline('someClass', calls: $calls); + + self::assertSame('someClass', $autowireInline->value['class']); + self::assertSame($calls, $autowireInline->value['calls']); + } + + public static function provideInvalidCalls(): iterable + { + yield 'missing method' => [[[]]]; + yield 'invalid method value type1' => [[[null]]]; + yield 'invalid method value type2' => [[[123]]]; + yield 'invalid method value type3' => [[[true]]]; + yield 'invalid method value type4' => [[[false]]]; + yield 'invalid method value type5' => [[[new \stdClass()]]]; + yield 'invalid method value type6' => [[[[]]]]; + + yield 'invalid arguments value type1' => [[['someMethod', null]]]; + yield 'invalid arguments value type2' => [[['someMethod', 123]]]; + yield 'invalid arguments value type3' => [[['someMethod', true]]]; + yield 'invalid arguments value type4' => [[['someMethod', false]]]; + yield 'invalid arguments value type5' => [[['someMethod', new \stdClass()]]]; + yield 'invalid arguments value type6' => [[['someMethod', '']]]; + } + + public function testClass() + { + $attribute = new AutowireInline('someClass'); + + $buildDefinition = $attribute->buildDefinition($attribute->value, null, $this->createReflectionParameter()); + + self::assertSame('someClass', $buildDefinition->getClass()); + self::assertSame([], $buildDefinition->getArguments()); + self::assertFalse($attribute->lazy); + } + + public function testClassAndParams() + { + $attribute = new AutowireInline('someClass', ['someParam']); + + $buildDefinition = $attribute->buildDefinition($attribute->value, null, $this->createReflectionParameter()); + + self::assertSame('someClass', $buildDefinition->getClass()); + self::assertSame(['someParam'], $buildDefinition->getArguments()); + self::assertFalse($attribute->lazy); + } + + public function testClassAndParamsLazy() + { + $attribute = new AutowireInline('someClass', ['someParam'], lazy: true); + + $buildDefinition = $attribute->buildDefinition($attribute->value, null, $this->createReflectionParameter()); + + self::assertSame('someClass', $buildDefinition->getClass()); + self::assertSame(['someParam'], $buildDefinition->getArguments()); + self::assertTrue($attribute->lazy); + } + + /** + * @dataProvider provideFactories + */ + public function testFactory(string|array $factory, string|array $expectedResult) + { + $attribute = new AutowireInline($factory); + + $buildDefinition = $attribute->buildDefinition($attribute->value, null, $this->createReflectionParameter()); + + self::assertNull($buildDefinition->getClass()); + self::assertEquals($expectedResult, $buildDefinition->getFactory()); + self::assertSame([], $buildDefinition->getArguments()); + self::assertFalse($attribute->lazy); + } + + /** + * @dataProvider provideFactories + */ + public function testFactoryAndParams(string|array $factory, string|array $expectedResult) + { + $attribute = new AutowireInline($factory, ['someParam']); + + $buildDefinition = $attribute->buildDefinition($attribute->value, null, $this->createReflectionParameter()); + + self::assertNull($buildDefinition->getClass()); + self::assertEquals($expectedResult, $buildDefinition->getFactory()); + self::assertSame(['someParam'], $buildDefinition->getArguments()); + self::assertFalse($attribute->lazy); + } + + /** + * @dataProvider provideFactories + */ + public function testFactoryAndParamsLazy(string|array $factory, string|array $expectedResult) + { + $attribute = new AutowireInline($factory, ['someParam'], lazy: true); + + $buildDefinition = $attribute->buildDefinition($attribute->value, null, $this->createReflectionParameter()); + + self::assertNull($buildDefinition->getClass()); + self::assertEquals($expectedResult, $buildDefinition->getFactory()); + self::assertSame(['someParam'], $buildDefinition->getArguments()); + self::assertTrue($attribute->lazy); + } + + public static function provideFactories(): iterable + { + yield 'string callable' => [[null, 'someFunction'], [null, 'someFunction']]; + + yield 'class only' => [['someClass'], ['someClass', '__invoke']]; + yield 'reference only' => [[new Reference('someClass')], [new Reference('someClass'), '__invoke']]; + + yield 'class with method' => [['someClass', 'someStaticMethod'], ['someClass', 'someStaticMethod']]; + yield 'reference with method' => [[new Reference('someClass'), 'someMethod'], [new Reference('someClass'), 'someMethod']]; + yield '@reference with method' => [['@someClass', 'someMethod'], [new Reference('someClass'), 'someMethod']]; + } + + /** + * @dataProvider provideCalls + */ + public function testCalls(string|array $calls, array $expectedResult) + { + $attribute = new AutowireInline('someClass', calls: $calls); + + $buildDefinition = $attribute->buildDefinition($attribute->value, null, $this->createReflectionParameter()); + + self::assertSame('someClass', $buildDefinition->getClass()); + self::assertSame($expectedResult, $buildDefinition->getMethodCalls()); + self::assertSame([], $buildDefinition->getArguments()); + self::assertFalse($attribute->lazy); + } + + public static function provideCalls(): iterable + { + yield 'method with empty arguments' => [ + [['someMethod', []]], + [['someMethod', []]], + ]; + yield 'method with arguments' => [ + [['someMethod', ['someArgument']]], + [['someMethod', ['someArgument']]], + ]; + yield 'method without arguments with return clone true' => [ + [['someMethod', [], true]], + [['someMethod', [], true]], + ]; + yield 'method without arguments with return clone false' => [ + [['someMethod', [], false]], + [['someMethod', []]], + ]; + yield 'method with arguments with return clone true' => [ + [['someMethod', ['someArgument'], true]], + [['someMethod', ['someArgument'], true]], + ]; + yield 'method with arguments with return clone false' => [ + [['someMethod', ['someArgument'], false]], + [['someMethod', ['someArgument']]], + ]; + } + + private function createReflectionParameter() + { + $class = new class('someValue') { + public function __construct($someParameter) + { + } + }; + $reflectionClass = new \ReflectionClass($class); + + return $reflectionClass->getConstructor()->getParameters()[0]; + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveAutowireInlineAttributesPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveAutowireInlineAttributesPassTest.php new file mode 100644 index 0000000000000..8a11e1b42fe8b --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveAutowireInlineAttributesPassTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Compiler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Compiler\AutowirePass; +use Symfony\Component\DependencyInjection\Compiler\ResolveAutowireInlineAttributesPass; +use Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass; +use Symfony\Component\DependencyInjection\Compiler\ResolveClassPass; +use Symfony\Component\DependencyInjection\Compiler\ResolveNamedArgumentsPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +require_once __DIR__.'/../Fixtures/includes/autowiring_classes.php'; + +class ResolveAutowireInlineAttributesPassTest extends TestCase +{ + public function testAttribute() + { + $container = new ContainerBuilder(); + $container->register(Foo::class)->setAutowired(true); + + $container->register('autowire_inline1', AutowireInlineAttributes1::class) + ->setAutowired(true); + + $container->register('autowire_inline2', AutowireInlineAttributes2::class) + ->setAutowired(true); + + (new ResolveNamedArgumentsPass())->process($container); + (new ResolveClassPass())->process($container); + (new ResolveChildDefinitionsPass())->process($container); + (new ResolveAutowireInlineAttributesPass())->process($container); + (new AutowirePass())->process($container); + + $autowireInlineAttributes1 = $container->get('autowire_inline1'); + self::assertInstanceOf(AutowireInlineAttributes1::class, $autowireInlineAttributes1); + + $autowireInlineAttributes2 = $container->get('autowire_inline2'); + self::assertInstanceOf(AutowireInlineAttributes2::class, $autowireInlineAttributes2); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index deb1e23f2b3b1..025dea24afcd5 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -24,6 +24,7 @@ use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\DependencyInjection\Attribute\AutowireCallable; +use Symfony\Component\DependencyInjection\Attribute\AutowireInline; use Symfony\Component\DependencyInjection\Attribute\AutowireServiceClosure; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\PassConfig; @@ -50,6 +51,8 @@ use Symfony\Component\DependencyInjection\Tests\Compiler\FooVoid; use Symfony\Component\DependencyInjection\Tests\Compiler\IInterface; use Symfony\Component\DependencyInjection\Tests\Compiler\MyCallable; +use Symfony\Component\DependencyInjection\Tests\Compiler\MyFactory; +use Symfony\Component\DependencyInjection\Tests\Compiler\MyInlineService; use Symfony\Component\DependencyInjection\Tests\Compiler\SingleMethodInterface; use Symfony\Component\DependencyInjection\Tests\Compiler\Wither; use Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition; @@ -1946,6 +1949,56 @@ public function testCallableAdapterConsumer() $this->assertInstanceOf(Foo::class, $container->get('bar')->foo->theMethod()); } + public function testInlineAdapterConsumer() + { + $container = new ContainerBuilder(); + $container->setParameter('someParam', 123); + $container->register('factory', MyFactory::class) + ->setAutowired(true); + $container->register('inlineService', MyInlineService::class) + ->setAutowired(true); + $container->register(InlineAdapterConsumer::class) + ->setPublic(true) + ->setAutowired(true); + $container->register('foo', InlineAdapterConsumer::class) + ->setPublic(true) + ->setAutowired(true); + $container->register('bar', InlineAdapterConsumer::class) + ->setPublic(true) + ->setAutowired(true); + $container->compile(); + $dumper = new PhpDumper($container); + + $this->assertStringEqualsFile(self::$fixturesPath.'/php/inline_adapter_consumer.php', $dumper->dump(['class' => 'Symfony_DI_PhpDumper_Test_Inline_Adapter_Consumer'])); + + require self::$fixturesPath.'/php/inline_adapter_consumer.php'; + + $container = new \Symfony_DI_PhpDumper_Test_Inline_Adapter_Consumer(); + + $this->assertInstanceOf(InlineAdapterConsumer::class, $container->get(InlineAdapterConsumer::class)); + $fooService = $container->get('foo'); + $barService = $container->get('bar'); + $this->assertInstanceOf(InlineAdapterConsumer::class, $fooService); + $this->assertInstanceOf(InlineAdapterConsumer::class, $barService); + $this->assertNotSame($fooService, $barService); + foreach ([$fooService, $barService] as $service) { + $this->assertNotSame($service->inlined, $service->inlinedWithParams); + $this->assertNotSame($service->inlinedWithParams, $service->factoredFromClass); + $this->assertNotSame($service->factoredFromClass, $service->factoredFromClassWithParams); + $this->assertNotSame($service->factoredFromClassWithParams, $service->factoredFromService); + $this->assertNotSame($service->factoredFromService, $service->factoredFromClass); + $this->assertNotSame($service->factoredFromService, $service->factoredFromServiceWithParam); + $this->assertNotSame($service->factoredFromServiceWithParam, $service->inlined); + } + $this->assertNotSame($fooService->inlined, $barService->inlined); + $this->assertNotSame($fooService->inlinedWithParams, $barService->inlinedWithParams); + $this->assertNotSame($fooService->factoredFromClass, $barService->factoredFromClass); + $this->assertNotSame($fooService->factoredFromClassWithParams, $barService->factoredFromClassWithParams); + $this->assertNotSame($fooService->factoredFromService, $barService->factoredFromService); + $this->assertNotSame($fooService->factoredFromService, $barService->factoredFromService); + $this->assertNotSame($fooService->factoredFromServiceWithParam, $barService->factoredFromServiceWithParam); + } + /** * @dataProvider getStripCommentsCodes */ @@ -2107,3 +2160,48 @@ public function __construct( ) { } } + +class InlineAdapterConsumer +{ + public function __construct( + #[AutowireInline(MyInlineService::class)] + public MyInlineService $inlined, + + #[AutowireInline(MyInlineService::class, ['bar'])] + public MyInlineService $inlinedWithParams, + + #[AutowireInline([MyFactory::class, 'staticCreateFoo'])] + public MyInlineService $factoredFromClass, + + #[AutowireInline([MyFactory::class, 'staticCreateFooWithParam'], ['someParam'])] + public MyInlineService $factoredFromClassWithParams, + + #[AutowireInline([new Reference('factory'), 'createFoo'])] + public MyInlineService $factoredFromService, + + #[AutowireInline([new Reference('factory'), 'createFooWithParam'], ['someParam'])] + public MyInlineService $factoredFromServiceWithParam, + + #[AutowireInline([new Reference('factory')])] + public MyInlineService $factoredFromClassWithoutMethod, + + #[AutowireInline([new Reference('factory')], ['someParam'])] + public MyInlineService $factoredFromClassWithoutMethodWithParams, + + #[AutowireInline(MyInlineService::class, calls: [['someMethod', []]])] + public MyInlineService $inlinedWithCall, + + #[AutowireInline(MyInlineService::class, calls: [['someMethod1', []], ['someMethod2', []]])] + public MyInlineService $inlinedWithCalls, + + #[AutowireInline(MyInlineService::class, calls: [['someMethod1', ['someArg']], ['someMethod2', []]])] + public MyInlineService $inlinedWithCallsWithArgument, + + #[AutowireInline(MyInlineService::class, calls: [['someMethod1', [new Reference('factory')]], ['someMethod2', []]])] + public MyInlineService $inlinedWithCallsWithReferenceArgument, + + #[AutowireInline(MyInlineService::class, calls: [['someMethod1', ['%someParam%']], ['someMethod2', []]])] + public MyInlineService $inlinedWithCallsWithParamArgument, + ) { + } +} 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 5246587bfa63d..7349cb1a076d8 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php @@ -462,3 +462,59 @@ public function __invoke(): void { } } + +class MyInlineService +{ + public function __construct(private readonly ?string $someParam = null) + { + } + + public function someMethod(): void + { + } + + public function someMethod1(): void + { + } + + public function someMethod2(): void + { + } + + public function getSomeParam(): ?string + { + return $this->someParam; + } +} + +class MyFactory +{ + public function __construct() + { + } + + public function __invoke(mixed $someParam = null): MyInlineService + { + return new MyInlineService($someParam ?? 'someString'); + } + + public function createFoo(): MyInlineService + { + return new MyInlineService('someString'); + } + + public function createFooWithParam(mixed $someParam): MyInlineService + { + return new MyInlineService($someParam); + } + + public static function staticCreateFoo(): MyInlineService + { + return new MyInlineService('someString'); + } + + public static function staticCreateFooWithParam(mixed $someParam): MyInlineService + { + return new MyInlineService($someParam); + } +} 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 index 13ce28fd8e904..f9b4505eec691 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes_80.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes_80.php @@ -5,6 +5,7 @@ use Symfony\Component\DependencyInjection\Attribute\AsDecorator; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated; +use Symfony\Component\DependencyInjection\Attribute\AutowireInline; use Symfony\Component\DependencyInjection\Attribute\AutowireIterator; use Symfony\Component\DependencyInjection\Attribute\AutowireLocator; use Symfony\Component\DependencyInjection\Attribute\Lazy; @@ -151,3 +152,33 @@ public function __construct(#[Lazy, Autowire(lazy: true)] A $a) { } } + +class AutowireInlineAttributesBar +{ + public function __construct(Foo $foo, string $someString) + { + } +} + +class AutowireInlineAttributes1 +{ + public function __construct( + #[AutowireInline(AutowireInlineAttributesBar::class, [ + '$foo' => Foo::class, + '$someString' => 'testString', + ])] + public AutowireInlineAttributesBar $inlined, + ) { + } +} + +class AutowireInlineAttributes2 +{ + public function __construct( + #[AutowireInline(AutowireInlineAttributesBar::class, [ + '$someString' => 'testString', + ])] + public AutowireInlineAttributesBar $inlined, + ) { + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/inline_adapter_consumer.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/inline_adapter_consumer.php new file mode 100644 index 0000000000000..7445a54a5e06a --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/inline_adapter_consumer.php @@ -0,0 +1,178 @@ +parameters = $this->getDefaultParameters(); + + $this->services = $this->privates = []; + $this->methodMap = [ + 'Symfony\\Component\\DependencyInjection\\Tests\\Dumper\\InlineAdapterConsumer' => 'getInlineAdapterConsumerService', + 'bar' => 'getBarService', + 'foo' => 'getFooService', + ]; + + $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 [ + 'factory' => true, + 'inlineService' => true, + ]; + } + + /** + * Gets the public 'Symfony\Component\DependencyInjection\Tests\Dumper\InlineAdapterConsumer' shared autowired service. + * + * @return \Symfony\Component\DependencyInjection\Tests\Dumper\InlineAdapterConsumer + */ + protected static function getInlineAdapterConsumerService($container) + { + $a = ($container->privates['factory'] ??= new \Symfony\Component\DependencyInjection\Tests\Compiler\MyFactory()); + $b = new \Symfony\Component\DependencyInjection\Tests\Compiler\MyInlineService(); + $b->someMethod(); + $c = new \Symfony\Component\DependencyInjection\Tests\Compiler\MyInlineService(); + $c->someMethod1(); + $c->someMethod2(); + $d = new \Symfony\Component\DependencyInjection\Tests\Compiler\MyInlineService(); + $d->someMethod1('someArg'); + $d->someMethod2(); + $e = new \Symfony\Component\DependencyInjection\Tests\Compiler\MyInlineService(); + $e->someMethod1($a); + $e->someMethod2(); + $f = new \Symfony\Component\DependencyInjection\Tests\Compiler\MyInlineService(); + $f->someMethod1(123); + $f->someMethod2(); + + return $container->services['Symfony\\Component\\DependencyInjection\\Tests\\Dumper\\InlineAdapterConsumer'] = new \Symfony\Component\DependencyInjection\Tests\Dumper\InlineAdapterConsumer(new \Symfony\Component\DependencyInjection\Tests\Compiler\MyInlineService(), new \Symfony\Component\DependencyInjection\Tests\Compiler\MyInlineService('bar'), \Symfony\Component\DependencyInjection\Tests\Compiler\MyFactory::staticCreateFoo(), \Symfony\Component\DependencyInjection\Tests\Compiler\MyFactory::staticCreateFooWithParam('someParam'), $a->createFoo(), $a->createFooWithParam('someParam'), $a->__invoke(), $a->__invoke('someParam'), $b, $c, $d, $e, $f); + } + + /** + * Gets the public 'bar' shared autowired service. + * + * @return \Symfony\Component\DependencyInjection\Tests\Dumper\InlineAdapterConsumer + */ + protected static function getBarService($container) + { + $a = ($container->privates['factory'] ??= new \Symfony\Component\DependencyInjection\Tests\Compiler\MyFactory()); + $b = new \Symfony\Component\DependencyInjection\Tests\Compiler\MyInlineService(); + $b->someMethod(); + $c = new \Symfony\Component\DependencyInjection\Tests\Compiler\MyInlineService(); + $c->someMethod1(); + $c->someMethod2(); + $d = new \Symfony\Component\DependencyInjection\Tests\Compiler\MyInlineService(); + $d->someMethod1('someArg'); + $d->someMethod2(); + $e = new \Symfony\Component\DependencyInjection\Tests\Compiler\MyInlineService(); + $e->someMethod1($a); + $e->someMethod2(); + $f = new \Symfony\Component\DependencyInjection\Tests\Compiler\MyInlineService(); + $f->someMethod1(123); + $f->someMethod2(); + + return $container->services['bar'] = new \Symfony\Component\DependencyInjection\Tests\Dumper\InlineAdapterConsumer(new \Symfony\Component\DependencyInjection\Tests\Compiler\MyInlineService(), new \Symfony\Component\DependencyInjection\Tests\Compiler\MyInlineService('bar'), \Symfony\Component\DependencyInjection\Tests\Compiler\MyFactory::staticCreateFoo(), \Symfony\Component\DependencyInjection\Tests\Compiler\MyFactory::staticCreateFooWithParam('someParam'), $a->createFoo(), $a->createFooWithParam('someParam'), $a->__invoke(), $a->__invoke('someParam'), $b, $c, $d, $e, $f); + } + + /** + * Gets the public 'foo' shared autowired service. + * + * @return \Symfony\Component\DependencyInjection\Tests\Dumper\InlineAdapterConsumer + */ + protected static function getFooService($container) + { + $a = ($container->privates['factory'] ??= new \Symfony\Component\DependencyInjection\Tests\Compiler\MyFactory()); + $b = new \Symfony\Component\DependencyInjection\Tests\Compiler\MyInlineService(); + $b->someMethod(); + $c = new \Symfony\Component\DependencyInjection\Tests\Compiler\MyInlineService(); + $c->someMethod1(); + $c->someMethod2(); + $d = new \Symfony\Component\DependencyInjection\Tests\Compiler\MyInlineService(); + $d->someMethod1('someArg'); + $d->someMethod2(); + $e = new \Symfony\Component\DependencyInjection\Tests\Compiler\MyInlineService(); + $e->someMethod1($a); + $e->someMethod2(); + $f = new \Symfony\Component\DependencyInjection\Tests\Compiler\MyInlineService(); + $f->someMethod1(123); + $f->someMethod2(); + + return $container->services['foo'] = new \Symfony\Component\DependencyInjection\Tests\Dumper\InlineAdapterConsumer(new \Symfony\Component\DependencyInjection\Tests\Compiler\MyInlineService(), new \Symfony\Component\DependencyInjection\Tests\Compiler\MyInlineService('bar'), \Symfony\Component\DependencyInjection\Tests\Compiler\MyFactory::staticCreateFoo(), \Symfony\Component\DependencyInjection\Tests\Compiler\MyFactory::staticCreateFooWithParam('someParam'), $a->createFoo(), $a->createFooWithParam('someParam'), $a->__invoke(), $a->__invoke('someParam'), $b, $c, $d, $e, $f); + } + + public function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null + { + if (!(isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || \array_key_exists($name, $this->parameters))) { + throw new ParameterNotFoundException($name); + } + if (isset($this->loadedDynamicParameters[$name])) { + return $this->loadedDynamicParameters[$name] ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name); + } + + return $this->parameters[$name]; + } + + public function hasParameter(string $name): bool + { + return isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || \array_key_exists($name, $this->parameters); + } + + public function setParameter(string $name, $value): void + { + throw new LogicException('Impossible to call set() on a frozen ParameterBag.'); + } + + public function getParameterBag(): ParameterBagInterface + { + if (!isset($this->parameterBag)) { + $parameters = $this->parameters; + foreach ($this->loadedDynamicParameters as $name => $loaded) { + $parameters[$name] = $loaded ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name); + } + $this->parameterBag = new FrozenParameterBag($parameters); + } + + return $this->parameterBag; + } + + private $loadedDynamicParameters = []; + private $dynamicParameters = []; + + private function getDynamicParameter(string $name) + { + throw new ParameterNotFoundException($name); + } + + protected function getDefaultParameters(): array + { + return [ + 'someParam' => 123, + ]; + } +} From b9a838e61b297cca95b4ad0bd14d0617b2b55eb6 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 22 Apr 2024 14:29:33 +0200 Subject: [PATCH 2/2] Finish implementing AutowireInline attribute --- .../Attribute/AutowireInline.php | 15 ++- .../Compiler/AutowirePass.php | 2 +- .../Compiler/InlineServiceDefinitionsPass.php | 3 + .../ResolveAutowireInlineAttributesPass.php | 106 +++++++++++++++--- .../DependencyInjection/ContainerBuilder.php | 11 +- .../DependencyInjection/Dumper/PhpDumper.php | 4 +- ...esolveAutowireInlineAttributesPassTest.php | 25 +++-- .../includes/autowiring_classes_80.php | 20 +++- .../Fixtures/php/lazy_autowire_attribute.php | 1 - ...y_autowire_attribute_with_intersection.php | 7 -- .../Fixtures/php/services_deep_graph.php | 6 - .../php/services_non_shared_duplicates.php | 1 - .../Tests/Fixtures/php/services_rot13_env.php | 7 -- .../php/services_service_locator_argument.php | 1 - .../Fixtures/php/services_subscriber.php | 3 - .../Tests/Fixtures/php/services_tsantos.php | 6 - 16 files changed, 152 insertions(+), 66 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Attribute/AutowireInline.php b/src/Symfony/Component/DependencyInjection/Attribute/AutowireInline.php index 6f1d15bebf40e..157df67930c1c 100644 --- a/src/Symfony/Component/DependencyInjection/Attribute/AutowireInline.php +++ b/src/Symfony/Component/DependencyInjection/Attribute/AutowireInline.php @@ -12,20 +12,29 @@ namespace Symfony\Component\DependencyInjection\Attribute; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; /** - * Allows inline service definition for a constructor argument. - * Using this attribute on a class autowires it as a new instance + * Allows inline service definition for an argument. + * + * Using this attribute on a class autowires a new instance * which is not shared between different services. * + * $class a FQCN, or an array to define a factory. + * Use the "@" prefix to reference a service. + * * @author Ismail Özgün Turan */ #[\Attribute(\Attribute::TARGET_PARAMETER)] class AutowireInline extends Autowire { - public function __construct(string|array $class, array $arguments = [], array $calls = [], array $properties = [], ?string $parent = null, bool|string $lazy = false) + public function __construct(string|array|null $class = null, array $arguments = [], array $calls = [], array $properties = [], ?string $parent = null, bool|string $lazy = false) { + if (null === $class && null === $parent) { + throw new LogicException('#[AutowireInline] attribute should declare either $class or $parent.'); + } + parent::__construct([ \is_array($class) ? 'factory' : 'class' => $class, 'arguments' => $arguments, diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php index 342820311df20..ca1d3e8eab8d5 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -333,7 +333,7 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a if ($attribute instanceof AutowireInline) { $value = $attribute->buildDefinition($value, $type, $parameter); - $value = new Reference('.autowire_inline.'.ContainerBuilder::hash($value)); + $value = $this->doProcessValue($value); } elseif ($lazy = $attribute->lazy) { $definition = (new Definition($type)) ->setFactory('current') diff --git a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php index b87ad69b0d536..3a99b0a8dbba2 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php @@ -166,6 +166,9 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed */ private function isInlineableDefinition(string $id, Definition $definition): bool { + if (str_starts_with($id, '.autowire_inline.')) { + return true; + } if ($definition->hasErrors() || $definition->isDeprecated() || $definition->isLazy() || $definition->isSynthetic() || $definition->hasTag('container.do_not_inline')) { return false; } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveAutowireInlineAttributesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveAutowireInlineAttributesPass.php index feb93a32caef1..5073b62e5d78e 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveAutowireInlineAttributesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveAutowireInlineAttributesPass.php @@ -12,13 +12,15 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Attribute\AutowireInline; +use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\VarExporter\ProxyHelper; /** - * Inspects existing autowired services for {@see AutowireInline} attribute and registers the definitions for reuse. + * Inspects existing autowired services for {@see AutowireInline} attributes and registers the definitions for reuse. * * @author Ismail Özgün Turan */ @@ -30,36 +32,110 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed { $value = parent::processValue($value, $isRoot); - if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) { + if (!$value instanceof Definition || !$value->isAutowired() || !$value->getClass() || $value->hasTag('container.ignore_attributes')) { return $value; } + $isChildDefinition = $value instanceof ChildDefinition; + try { $constructor = $this->getConstructor($value, false); } catch (RuntimeException) { - $this->container->log($this, sprintf('Skipping service "%s": Class or interface "%s" cannot be loaded.', $this->currentId, $value->getClass())); - return $value; } - if ($constructor === null) { - return $value; + if ($constructor) { + $arguments = $this->registerAutowireInlineAttributes($constructor, $value->getArguments(), $isChildDefinition); + + if ($arguments !== $value->getArguments()) { + $value->setArguments($arguments); + } } - $reflectionParameters = $constructor->getParameters(); - foreach ($reflectionParameters as $reflectionParameter) { - $autowireInlineAttributes = $reflectionParameter->getAttributes(AutowireInline::class, \ReflectionAttribute::IS_INSTANCEOF); - foreach ($autowireInlineAttributes as $autowireInlineAttribute) { - /** @var AutowireInline $autowireInlineAttributeInstance */ - $autowireInlineAttributeInstance = $autowireInlineAttribute->newInstance(); + $dummy = $value; + while (null === $dummy->getClass() && $dummy instanceof ChildDefinition) { + $dummy = $this->container->findDefinition($dummy->getParent()); + } + + $methodCalls = $value->getMethodCalls(); - $type = ProxyHelper::exportType($reflectionParameter, true); - $definition = $autowireInlineAttributeInstance->buildDefinition($autowireInlineAttributeInstance->value, $type, $reflectionParameter); + foreach ($methodCalls as $i => $call) { + [$method, $arguments] = $call; - $this->container->setDefinition('.autowire_inline.'.ContainerBuilder::hash($definition), $definition); + try { + $method = $this->getReflectionMethod($dummy, $method); + } catch (RuntimeException) { + continue; } + + $arguments = $this->registerAutowireInlineAttributes($method, $arguments, $isChildDefinition); + + if ($arguments !== $call[1]) { + $methodCalls[$i][1] = $arguments; + } + } + + if ($methodCalls !== $value->getMethodCalls()) { + $value->setMethodCalls($methodCalls); } return $value; } + + private function registerAutowireInlineAttributes(\ReflectionFunctionAbstract $method, array $arguments, bool $isChildDefinition): array + { + $parameters = $method->getParameters(); + + if ($method->isVariadic()) { + array_pop($parameters); + } + $dummyContainer = new ContainerBuilder($this->container->getParameterBag()); + + foreach ($parameters as $index => $parameter) { + if ($isChildDefinition) { + $index = 'index_'.$index; + } + + $name = '$'.$parameter->name; + if (\array_key_exists($name, $arguments)) { + $arguments[$index] = $arguments[$name]; + unset($arguments[$name]); + } + if (\array_key_exists($index, $arguments) && '' !== $arguments[$index]) { + continue; + } + if (!$attribute = $parameter->getAttributes(AutowireInline::class, \ReflectionAttribute::IS_INSTANCEOF)[0] ?? null) { + continue; + } + + $type = ProxyHelper::exportType($parameter, true); + + if (!$type && isset($arguments[$index])) { + continue; + } + + $attribute = $attribute->newInstance(); + $definition = $attribute->buildDefinition($attribute->value, $type, $parameter); + + $dummyContainer->setDefinition('.autowire_inline', $definition); + (new ResolveParameterPlaceHoldersPass(false, false))->process($dummyContainer); + + $id = '.autowire_inline.'.ContainerBuilder::hash([$this->currentId, $method->class ?? null, $method->name, (string) $parameter]); + + $this->container->setDefinition($id, $definition); + $arguments[$index] = new Reference($id); + + if ($definition->isAutowired()) { + $currentId = $this->currentId; + try { + $this->currentId = $id; + $this->processValue($definition, true); + } finally { + $this->currentId = $currentId; + } + } + } + + return $arguments; + } } diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 3f8aa599185fd..dcdec0ac25788 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -494,7 +494,9 @@ public function removeDefinition(string $id): void { if (isset($this->definitions[$id])) { unset($this->definitions[$id]); - $this->removedIds[$id] = true; + if ('.' !== ($id[0] ?? '-')) { + $this->removedIds[$id] = true; + } } } @@ -768,6 +770,9 @@ public function compile(bool $resolveEnvPlaceholders = false): void parent::compile(); foreach ($this->definitions + $this->aliasDefinitions as $id => $definition) { + if ('.' === ($id[0] ?? '-')) { + continue; + } if (!$definition->isPublic() || $definition->isPrivate()) { $this->removedIds[$id] = true; } @@ -841,7 +846,9 @@ public function removeAlias(string $alias): void { if (isset($this->aliasDefinitions[$alias])) { unset($this->aliasDefinitions[$alias]); - $this->removedIds[$alias] = true; + if ('.' !== ($alias[0] ?? '-')) { + $this->removedIds[$alias] = true; + } } } diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index adb05e76465da..55dd2754edd45 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -257,7 +257,7 @@ class %s extends {$options['class']} $preloadedFiles = []; $ids = $this->container->getRemovedIds(); foreach ($this->container->getDefinitions() as $id => $definition) { - if (!$definition->isPublic()) { + if (!$definition->isPublic() && '.' !== ($id[0] ?? '-')) { $ids[$id] = true; } } @@ -1380,7 +1380,7 @@ private function addRemovedIds(): string { $ids = $this->container->getRemovedIds(); foreach ($this->container->getDefinitions() as $id => $definition) { - if (!$definition->isPublic()) { + if (!$definition->isPublic() && '.' !== ($id[0] ?? '-')) { $ids[$id] = true; } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveAutowireInlineAttributesPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveAutowireInlineAttributesPassTest.php index 8a11e1b42fe8b..bae6a3ada8ff7 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveAutowireInlineAttributesPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveAutowireInlineAttributesPassTest.php @@ -15,7 +15,6 @@ use Symfony\Component\DependencyInjection\Compiler\AutowirePass; use Symfony\Component\DependencyInjection\Compiler\ResolveAutowireInlineAttributesPass; use Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass; -use Symfony\Component\DependencyInjection\Compiler\ResolveClassPass; use Symfony\Component\DependencyInjection\Compiler\ResolveNamedArgumentsPass; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -26,24 +25,32 @@ class ResolveAutowireInlineAttributesPassTest extends TestCase public function testAttribute() { $container = new ContainerBuilder(); - $container->register(Foo::class)->setAutowired(true); + $container->register(Foo::class, Foo::class) + ->setAutowired(true); $container->register('autowire_inline1', AutowireInlineAttributes1::class) ->setAutowired(true); $container->register('autowire_inline2', AutowireInlineAttributes2::class) + ->setArgument(1, 234) + ->setAutowired(true); + + $container->register('autowire_inline3', AutowireInlineAttributes3::class) ->setAutowired(true); - (new ResolveNamedArgumentsPass())->process($container); - (new ResolveClassPass())->process($container); - (new ResolveChildDefinitionsPass())->process($container); (new ResolveAutowireInlineAttributesPass())->process($container); + (new ResolveChildDefinitionsPass())->process($container); + (new ResolveNamedArgumentsPass())->process($container); (new AutowirePass())->process($container); - $autowireInlineAttributes1 = $container->get('autowire_inline1'); - self::assertInstanceOf(AutowireInlineAttributes1::class, $autowireInlineAttributes1); + $a = $container->get('autowire_inline1'); + self::assertInstanceOf(AutowireInlineAttributes1::class, $a); + + $a = $container->get('autowire_inline2'); + self::assertInstanceOf(AutowireInlineAttributes2::class, $a); - $autowireInlineAttributes2 = $container->get('autowire_inline2'); - self::assertInstanceOf(AutowireInlineAttributes2::class, $autowireInlineAttributes2); + $a = $container->get('autowire_inline3'); + self::assertInstanceOf(AutowireInlineAttributes2::class, $a->inlined); + self::assertSame(345, $a->inlined->bar); } } 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 index f9b4505eec691..4da8f0f4ee436 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes_80.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes_80.php @@ -164,8 +164,8 @@ class AutowireInlineAttributes1 { public function __construct( #[AutowireInline(AutowireInlineAttributesBar::class, [ - '$foo' => Foo::class, '$someString' => 'testString', + '$foo' => new Foo(), ])] public AutowireInlineAttributesBar $inlined, ) { @@ -176,9 +176,25 @@ class AutowireInlineAttributes2 { public function __construct( #[AutowireInline(AutowireInlineAttributesBar::class, [ - '$someString' => 'testString', + new Foo(), + 'testString', ])] public AutowireInlineAttributesBar $inlined, + public int $bar, + ) { + } +} + +class AutowireInlineAttributes3 +{ + public function __construct( + #[AutowireInline( + parent: 'autowire_inline2', + arguments: [ + 'index_1' => 345, + ], + )] + public AutowireInlineAttributes2 $inlined, ) { } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_autowire_attribute.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_autowire_attribute.php index 950c28ae12f0a..4f596a2b90597 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_autowire_attribute.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_autowire_attribute.php @@ -40,7 +40,6 @@ public function isCompiled(): bool public function getRemovedIds(): array { return [ - '.lazy.Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Foo' => true, 'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Foo' => true, ]; } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_autowire_attribute_with_intersection.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_autowire_attribute_with_intersection.php index d09a2133b70e5..fcf66ad12157b 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_autowire_attribute_with_intersection.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_autowire_attribute_with_intersection.php @@ -36,13 +36,6 @@ public function isCompiled(): bool return true; } - public function getRemovedIds(): array - { - return [ - '.lazy.foo.qFdMZVK' => true, - ]; - } - protected function createProxy($class, \Closure $factory) { return $factory(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_deep_graph.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_deep_graph.php index 9c22c5f9e7ae1..982366cc0687c 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_deep_graph.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_deep_graph.php @@ -37,12 +37,6 @@ public function isCompiled(): bool return true; } - public function getRemovedIds(): array - { - return [ - ]; - } - /** * Gets the public 'bar' shared service. * diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_duplicates.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_duplicates.php index 913d2ab4d829f..b7849d8bd16e2 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_duplicates.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_duplicates.php @@ -41,7 +41,6 @@ public function isCompiled(): bool public function getRemovedIds(): array { return [ - '.service_locator.lViPm9k' => true, 'foo' => true, ]; } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_rot13_env.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_rot13_env.php index 130d73c8240e7..06093919e1e54 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_rot13_env.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_rot13_env.php @@ -40,13 +40,6 @@ public function isCompiled(): bool return true; } - public function getRemovedIds(): array - { - return [ - '.service_locator.DyWBOhJ' => true, - ]; - } - /** * Gets the public 'Symfony\Component\DependencyInjection\Tests\Dumper\Rot13EnvVarProcessor' shared service. * diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_service_locator_argument.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_service_locator_argument.php index 963f7ea10306e..3cd32e745859c 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_service_locator_argument.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_service_locator_argument.php @@ -44,7 +44,6 @@ public function isCompiled(): bool public function getRemovedIds(): array { return [ - '.service_locator.X7o4UPP' => true, 'foo2' => true, 'foo3' => true, 'foo4' => true, diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php index 67242fe119f95..e0cc6382c121e 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php @@ -43,9 +43,6 @@ public function isCompiled(): bool public function getRemovedIds(): array { return [ - '.service_locator.2x56Fsq' => true, - '.service_locator.2x56Fsq.foo_service' => true, - '.service_locator.K8KBCZO' => true, 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition' => true, ]; } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_tsantos.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_tsantos.php index 77d78a6558fa6..93471ae0a0fa9 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_tsantos.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_tsantos.php @@ -37,12 +37,6 @@ public function isCompiled(): bool return true; } - public function getRemovedIds(): array - { - return [ - ]; - } - /** * Gets the public 'tsantos_serializer' shared service. *