diff --git a/src/Provider/SymfonyUsageProvider.php b/src/Provider/SymfonyUsageProvider.php index 4e40657..7fe1f8f 100644 --- a/src/Provider/SymfonyUsageProvider.php +++ b/src/Provider/SymfonyUsageProvider.php @@ -1143,6 +1143,11 @@ private function fillDicClasses(string $containerXmlPath): void foreach ($xml->services->service as $serviceDefinition) { /** @var SimpleXMLElement $serviceAttributes */ $serviceAttributes = $serviceDefinition->attributes(); + + if ($this->isServiceNotInstantiatedByContainer($serviceDefinition, $serviceAttributes)) { + continue; + } + $class = isset($serviceAttributes->class) ? (string) $serviceAttributes->class : null; $constructor = isset($serviceAttributes->constructor) ? (string) $serviceAttributes->constructor : '__construct'; @@ -1191,6 +1196,32 @@ private function fillDicClasses(string $containerXmlPath): void } } + private function isServiceNotInstantiatedByContainer( + SimpleXMLElement $serviceDefinition, + SimpleXMLElement $serviceAttributes, + ): bool + { + if (isset($serviceAttributes->abstract) && (string) $serviceAttributes->abstract === 'true') { + return true; + } + + if (isset($serviceAttributes->synthetic) && (string) $serviceAttributes->synthetic === 'true') { + return true; + } + + foreach ($serviceDefinition->tag ?? [] as $tagDefinition) { + /** @var SimpleXMLElement $tagAttributes */ + $tagAttributes = $tagDefinition->attributes(); + $tagName = $tagAttributes->name !== null ? (string) $tagAttributes->name : null; + + if ($tagName === 'container.error' || $tagName === 'container.excluded') { + return true; + } + } + + return false; + } + /** * @return array */ diff --git a/tests/Provider/SymfonyUsageProviderTest.php b/tests/Provider/SymfonyUsageProviderTest.php index 877a7d8..3c305d8 100644 --- a/tests/Provider/SymfonyUsageProviderTest.php +++ b/tests/Provider/SymfonyUsageProviderTest.php @@ -50,6 +50,11 @@ public function testExplicitContainerXmlPaths(): void self::assertArrayHasKey('Symfony\DicClass1', $dicCalls); self::assertArrayHasKey('__construct', $dicCalls['Symfony\DicClass1']); self::assertArrayHasKey('calledViaDic', $dicCalls['Symfony\DicClass1']); + + self::assertArrayNotHasKey('Symfony\DicErroredService', $dicCalls); + self::assertArrayNotHasKey('Symfony\DicExcludedService', $dicCalls); + self::assertArrayNotHasKey('Symfony\DicSyntheticService', $dicCalls); + self::assertArrayNotHasKey('Symfony\DicAbstractService', $dicCalls); } public function testExplicitContainerXmlPathsTakesPrecedenceOverContainer(): void diff --git a/tests/Rule/data/providers/symfony.php b/tests/Rule/data/providers/symfony.php index ee1f6b0..fb3fb94 100644 --- a/tests/Rule/data/providers/symfony.php +++ b/tests/Rule/data/providers/symfony.php @@ -160,6 +160,22 @@ class DicClass4 { public function __construct() {} } +class DicErroredService { + public function __construct(string $name) {} // error: Unused Symfony\DicErroredService::__construct +} + +class DicExcludedService { + public function __construct() {} // error: Unused Symfony\DicExcludedService::__construct +} + +class DicSyntheticService { + public function __construct() {} // error: Unused Symfony\DicSyntheticService::__construct +} + +class DicAbstractService { + public function __construct() {} // error: Unused Symfony\DicAbstractService::__construct +} + class Sftp { const RETRY_LIMIT = 3; // used in yaml via !php/const } diff --git a/tests/Rule/data/providers/symfony/services.xml b/tests/Rule/data/providers/symfony/services.xml index f829fa3..229d5c9 100644 --- a/tests/Rule/data/providers/symfony/services.xml +++ b/tests/Rule/data/providers/symfony/services.xml @@ -24,5 +24,13 @@ + + + + + + + +