diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index 45b5196232dbf..d24bb13c9bea3 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Make `#[AsTaggedItem]` repeatable * Support `@>` as a shorthand for `!service_closure` in yaml files + * Don't skip classes with private constructor when autodiscovering 7.2 --- diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php index dd8c9cd29660c..55f8ee7e97d88 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php @@ -181,7 +181,7 @@ protected function getConstructor(Definition $definition, bool $required): ?\Ref throw new RuntimeException(\sprintf('Invalid service "%s": class%s has no constructor.', $this->currentId, \sprintf($class !== $this->currentId ? ' "%s"' : '', $class))); } } elseif (!$r->isPublic()) { - throw new RuntimeException(\sprintf('Invalid service "%s": ', $this->currentId).\sprintf($class !== $this->currentId ? 'constructor of class "%s"' : 'its constructor', $class).' must be public.'); + throw new RuntimeException(\sprintf('Invalid service "%s": ', $this->currentId).\sprintf($class !== $this->currentId ? 'constructor of class "%s"' : 'its constructor', $class).' must be public. Did you miss configuring a factory or a static constructor? Try using the "#[Autoconfigure(constructor: ...)]" attribute for the latter.'); } return $r; diff --git a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php index a4d812e37b344..9e17bc424a2a9 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php @@ -126,7 +126,7 @@ public function registerClasses(Definition $prototype, string $namespace, string $autoconfigureAttributes = new RegisterAutoconfigureAttributesPass(); $autoconfigureAttributes = $autoconfigureAttributes->accept($prototype) ? $autoconfigureAttributes : null; - $classes = $this->findClasses($namespace, $resource, (array) $exclude, $autoconfigureAttributes, $source); + $classes = $this->findClasses($namespace, $resource, (array) $exclude, $source); $getPrototype = static fn () => clone $prototype; $serialized = serialize($prototype); @@ -188,41 +188,46 @@ public function registerClasses(Definition $prototype, string $namespace, string } } - if (interface_exists($class, false)) { - $this->interfaces[] = $class; - } else { - $this->setDefinition($class, $definition = $getPrototype()); - if (null !== $errorMessage) { - $definition->addError($errorMessage); - - continue; + $r = null === $errorMessage ? $this->container->getReflectionClass($class) : null; + if ($r?->isAbstract() || $r?->isInterface()) { + if ($r->isInterface()) { + $this->interfaces[] = $class; } - $definition->setClass($class); + $autoconfigureAttributes?->processClass($this->container, $r); + continue; + } - $interfaces = []; - foreach (class_implements($class, false) as $interface) { - $this->singlyImplemented[$interface] = ($this->singlyImplemented[$interface] ?? $class) !== $class ? false : $class; - $interfaces[] = $interface; - } + $this->setDefinition($class, $definition = $getPrototype()); + if (null !== $errorMessage) { + $definition->addError($errorMessage); - if (!$autoconfigureAttributes) { - continue; + continue; + } + $definition->setClass($class); + + $interfaces = []; + foreach (class_implements($class, false) as $interface) { + $this->singlyImplemented[$interface] = ($this->singlyImplemented[$interface] ?? $class) !== $class ? false : $class; + $interfaces[] = $interface; + } + + if (!$autoconfigureAttributes) { + continue; + } + $r = $this->container->getReflectionClass($class); + $defaultAlias = 1 === \count($interfaces) ? $interfaces[0] : null; + foreach ($r->getAttributes(AsAlias::class) as $attr) { + /** @var AsAlias $attribute */ + $attribute = $attr->newInstance(); + $alias = $attribute->id ?? $defaultAlias; + $public = $attribute->public; + if (null === $alias) { + throw new LogicException(\sprintf('Alias cannot be automatically determined for class "%s". If you have used the #[AsAlias] attribute with a class implementing multiple interfaces, add the interface you want to alias to the first parameter of #[AsAlias].', $class)); } - $r = $this->container->getReflectionClass($class); - $defaultAlias = 1 === \count($interfaces) ? $interfaces[0] : null; - foreach ($r->getAttributes(AsAlias::class) as $attr) { - /** @var AsAlias $attribute */ - $attribute = $attr->newInstance(); - $alias = $attribute->id ?? $defaultAlias; - $public = $attribute->public; - if (null === $alias) { - throw new LogicException(\sprintf('Alias cannot be automatically determined for class "%s". If you have used the #[AsAlias] attribute with a class implementing multiple interfaces, add the interface you want to alias to the first parameter of #[AsAlias].', $class)); - } - if (isset($this->aliases[$alias])) { - throw new LogicException(\sprintf('The "%s" alias has already been defined with the #[AsAlias] attribute in "%s".', $alias, $this->aliases[$alias])); - } - $this->aliases[$alias] = new Alias($class, $public); + if (isset($this->aliases[$alias])) { + throw new LogicException(\sprintf('The "%s" alias has already been defined with the #[AsAlias] attribute in "%s".', $alias, $this->aliases[$alias])); } + $this->aliases[$alias] = new Alias($class, $public); } } @@ -304,7 +309,7 @@ protected function setDefinition(string $id, Definition $definition): void } } - private function findClasses(string $namespace, string $pattern, array $excludePatterns, ?RegisterAutoconfigureAttributesPass $autoconfigureAttributes, ?string $source): array + private function findClasses(string $namespace, string $pattern, array $excludePatterns, ?string $source): array { $parameterBag = $this->container->getParameterBag(); @@ -356,13 +361,9 @@ private function findClasses(string $namespace, string $pattern, array $excludeP throw new InvalidArgumentException(\sprintf('Expected to find class "%s" in file "%s" while importing services from resource "%s", but it was not found! Check the namespace prefix used with the resource.', $class, $path, $pattern)); } - if ($r->isInstantiable() || $r->isInterface()) { + if (!$r->isTrait()) { $classes[$class] = null; } - - if ($autoconfigureAttributes && !$r->isInstantiable()) { - $autoconfigureAttributes->processClass($this->container, $r); - } } // track only for new & removed files diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php index f6aafdec910c5..114d514adddec 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -174,7 +174,7 @@ public function testPrivateConstructorThrowsAutowireException() $pass->process($container); $this->fail('AutowirePass should have thrown an exception'); } catch (AutowiringFailedException $e) { - $this->assertSame('Invalid service "private_service": constructor of class "Symfony\Component\DependencyInjection\Tests\Compiler\PrivateConstructor" must be public.', (string) $e->getMessage()); + $this->assertSame('Invalid service "private_service": constructor of class "Symfony\Component\DependencyInjection\Tests\Compiler\PrivateConstructor" must be public. Did you miss configuring a factory or a static constructor? Try using the "#[Autoconfigure(constructor: ...)]" attribute for the latter.', (string) $e->getMessage()); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/StaticConstructor/PrototypeStaticConstructor.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/StaticConstructor/PrototypeStaticConstructor.php index 87de94cb15068..6ac5316a36cf4 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/StaticConstructor/PrototypeStaticConstructor.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/StaticConstructor/PrototypeStaticConstructor.php @@ -4,6 +4,10 @@ class PrototypeStaticConstructor implements PrototypeStaticConstructorInterface { + private function __construct() + { + } + public static function create(): static { return new self(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php index a195f2a93410c..a43b4c5a1a05b 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php @@ -33,6 +33,7 @@ use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\OtherDir\AnotherSub; use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\OtherDir\AnotherSub\DeeperBaz; use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\OtherDir\Baz; +use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\StaticConstructor\PrototypeStaticConstructor; use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Sub\Bar; use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Sub\BarInterface; use Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\AliasBarInterface; @@ -380,11 +381,11 @@ public static function provideResourcesWithAsAliasAttributes(): iterable */ public function testRegisterClassesWithDuplicatedAsAlias(string $resource, string $expectedExceptionMessage) { - $this->expectException(LogicException::class); - $this->expectExceptionMessage($expectedExceptionMessage); - $container = new ContainerBuilder(); $loader = new TestFileLoader($container, new FileLocator(self::$fixturesPath.'/Fixtures')); + + $this->expectException(LogicException::class); + $this->expectExceptionMessage($expectedExceptionMessage); $loader->registerClasses( (new Definition())->setAutoconfigured(true), 'Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\\', @@ -400,17 +401,28 @@ public static function provideResourcesWithDuplicatedAsAliasAttributes(): iterab public function testRegisterClassesWithAsAliasAndImplementingMultipleInterfaces() { - $this->expectException(LogicException::class); - $this->expectExceptionMessage('Alias cannot be automatically determined for class "Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\WithAsAliasMultipleInterface". If you have used the #[AsAlias] attribute with a class implementing multiple interfaces, add the interface you want to alias to the first parameter of #[AsAlias].'); - $container = new ContainerBuilder(); $loader = new TestFileLoader($container, new FileLocator(self::$fixturesPath.'/Fixtures')); + + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Alias cannot be automatically determined for class "Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\WithAsAliasMultipleInterface". If you have used the #[AsAlias] attribute with a class implementing multiple interfaces, add the interface you want to alias to the first parameter of #[AsAlias].'); $loader->registerClasses( (new Definition())->setAutoconfigured(true), 'Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\\', 'PrototypeAsAlias/{WithAsAliasMultipleInterface,AliasBarInterface,AliasFooInterface}.php' ); } + + public function testRegisterClassesWithStaticConstructor() + { + $container = new ContainerBuilder(); + $loader = new TestFileLoader($container, new FileLocator(self::$fixturesPath.'/Fixtures')); + + $prototype = (new Definition())->setAutoconfigured(true); + $loader->registerClasses($prototype, 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\StaticConstructor\\', 'Prototype/StaticConstructor'); + + $this->assertTrue($container->has(PrototypeStaticConstructor::class)); + } } class TestFileLoader extends FileLoader