From c8644e130b451d3eaa998bdb40a690b9d3206df0 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 8 May 2025 19:01:09 +0200 Subject: [PATCH 1/2] Add solution for test container --- extension.neon | 9 ++++-- .../ContainerInterfacePrivateServiceRule.php | 11 +++++-- ...GetContainerDynamicReturnTypeExtension.php | 29 +++++++++++++++++++ ...GetContainerDynamicReturnTypeExtension.php | 29 +++++++++++++++++++ .../Symfony/Container/TestContainerType.php | 21 ++++++++++++++ .../Bundle/FrameworkBundle/KernelBrowser.stub | 13 --------- .../FrameworkBundle/Test/KernelTestCase.stub | 16 ---------- .../FrameworkBundle/Test/TestContainer.stub | 7 ----- ...ntainerInterfacePrivateServiceRuleTest.php | 17 +++++++++++ tests/Rules/Symfony/ExampleTest.php | 23 +++++++++++++++ tests/Rules/Symfony/container-extensions.neon | 7 +++++ 11 files changed, 141 insertions(+), 41 deletions(-) create mode 100644 src/Type/Symfony/Container/KernelBrowserGetContainerDynamicReturnTypeExtension.php create mode 100644 src/Type/Symfony/Container/KernelTestCaseGetContainerDynamicReturnTypeExtension.php create mode 100644 src/Type/Symfony/Container/TestContainerType.php delete mode 100644 stubs/Symfony/Bundle/FrameworkBundle/KernelBrowser.stub delete mode 100644 stubs/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.stub delete mode 100644 stubs/Symfony/Bundle/FrameworkBundle/Test/TestContainer.stub create mode 100644 tests/Rules/Symfony/ExampleTest.php create mode 100644 tests/Rules/Symfony/container-extensions.neon diff --git a/extension.neon b/extension.neon index 0803248f..1596580f 100644 --- a/extension.neon +++ b/extension.neon @@ -13,9 +13,6 @@ parameters: - stubs/Psr/Cache/CacheItemInterface.stub - stubs/Psr/Cache/InvalidArgumentException.stub - stubs/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.stub - - stubs/Symfony/Bundle/FrameworkBundle/KernelBrowser.stub - - stubs/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.stub - - stubs/Symfony/Bundle/FrameworkBundle/Test/TestContainer.stub - stubs/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AuthenticatorFactoryInterface.stub - stubs/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FirewallListenerFactoryInterface.stub - stubs/Symfony/Component/Console/Command.stub @@ -364,3 +361,9 @@ services: class: PHPStan\Symfony\SymfonyContainerResultCacheMetaExtension tags: - phpstan.resultCacheMetaExtension + - + class: PHPStan\Type\Symfony\Container\KernelBrowserGetContainerDynamicReturnTypeExtension + tags: [phpstan.broker.dynamicMethodReturnTypeExtension] + - + class: PHPStan\Type\Symfony\Container\KernelTestCaseGetContainerDynamicReturnTypeExtension + tags: [phpstan.broker.dynamicStaticMethodReturnTypeExtension] diff --git a/src/Rules/Symfony/ContainerInterfacePrivateServiceRule.php b/src/Rules/Symfony/ContainerInterfacePrivateServiceRule.php index 96f1efea..7b375e10 100644 --- a/src/Rules/Symfony/ContainerInterfacePrivateServiceRule.php +++ b/src/Rules/Symfony/ContainerInterfacePrivateServiceRule.php @@ -10,6 +10,7 @@ use PHPStan\Symfony\ServiceMap; use PHPStan\TrinaryLogic; use PHPStan\Type\ObjectType; +use PHPStan\Type\Symfony\Container\TestContainerType; use PHPStan\Type\Type; use function sprintf; @@ -43,11 +44,17 @@ public function processNode(Node $node, Scope $scope): array $argType = $scope->getType($node->var); - $isTestContainerType = (new ObjectType('Symfony\Bundle\FrameworkBundle\Test\TestContainer'))->isSuperTypeOf($argType); + if ( + $argType instanceof TestContainerType + || (new ObjectType('Symfony\Bundle\FrameworkBundle\Test\TestContainer'))->isSuperTypeOf($argType)->yes() + ) { + return []; + } + $isOldServiceSubscriber = (new ObjectType('Symfony\Component\DependencyInjection\ServiceSubscriberInterface'))->isSuperTypeOf($argType); $isServiceSubscriber = $this->isServiceSubscriber($argType, $scope); $isServiceLocator = (new ObjectType('Symfony\Component\DependencyInjection\ServiceLocator'))->isSuperTypeOf($argType); - if ($isTestContainerType->yes() || $isOldServiceSubscriber->yes() || $isServiceSubscriber->yes() || $isServiceLocator->yes()) { + if ($isOldServiceSubscriber->yes() || $isServiceSubscriber->yes() || $isServiceLocator->yes()) { return []; } diff --git a/src/Type/Symfony/Container/KernelBrowserGetContainerDynamicReturnTypeExtension.php b/src/Type/Symfony/Container/KernelBrowserGetContainerDynamicReturnTypeExtension.php new file mode 100644 index 00000000..0a8ea235 --- /dev/null +++ b/src/Type/Symfony/Container/KernelBrowserGetContainerDynamicReturnTypeExtension.php @@ -0,0 +1,29 @@ +getName() === 'getContainer'; + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + { + return new TestContainerType(); + } + +} diff --git a/src/Type/Symfony/Container/KernelTestCaseGetContainerDynamicReturnTypeExtension.php b/src/Type/Symfony/Container/KernelTestCaseGetContainerDynamicReturnTypeExtension.php new file mode 100644 index 00000000..aab21970 --- /dev/null +++ b/src/Type/Symfony/Container/KernelTestCaseGetContainerDynamicReturnTypeExtension.php @@ -0,0 +1,29 @@ +getName() === 'getContainer'; + } + + public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): Type + { + return new TestContainerType(); + } + +} diff --git a/src/Type/Symfony/Container/TestContainerType.php b/src/Type/Symfony/Container/TestContainerType.php new file mode 100644 index 00000000..d53668f0 --- /dev/null +++ b/src/Type/Symfony/Container/TestContainerType.php @@ -0,0 +1,21 @@ +analyse( + [ + __DIR__ . '/ExampleTest.php', + ], + [], + ); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/container-extensions.neon', + ]; + } + } diff --git a/tests/Rules/Symfony/ExampleTest.php b/tests/Rules/Symfony/ExampleTest.php new file mode 100644 index 00000000..10b06703 --- /dev/null +++ b/tests/Rules/Symfony/ExampleTest.php @@ -0,0 +1,23 @@ +get('private'); + } + + public function foo(KernelBrowser $browser): void + { + $container = $browser->getContainer(); + $container->get('private'); + } + +} diff --git a/tests/Rules/Symfony/container-extensions.neon b/tests/Rules/Symfony/container-extensions.neon new file mode 100644 index 00000000..5a799e56 --- /dev/null +++ b/tests/Rules/Symfony/container-extensions.neon @@ -0,0 +1,7 @@ +services: + - + class: PHPStan\Type\Symfony\Container\KernelBrowserGetContainerDynamicReturnTypeExtension + tags: [phpstan.broker.dynamicMethodReturnTypeExtension] + - + class: PHPStan\Type\Symfony\Container\KernelTestCaseGetContainerDynamicReturnTypeExtension + tags: [phpstan.broker.dynamicStaticMethodReturnTypeExtension] From 2834e4a27080afea13a15e91b1f617392287fbcc Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 13 May 2025 22:00:14 +0200 Subject: [PATCH 2/2] Simplify --- extension.neon | 6 ---- .../ContainerInterfacePrivateServiceRule.php | 32 +++++++++++++------ ...GetContainerDynamicReturnTypeExtension.php | 29 ----------------- ...GetContainerDynamicReturnTypeExtension.php | 29 ----------------- .../Symfony/Container/TestContainerType.php | 21 ------------ ...ntainerInterfacePrivateServiceRuleTest.php | 7 ---- tests/Rules/Symfony/container-extensions.neon | 7 ---- 7 files changed, 23 insertions(+), 108 deletions(-) delete mode 100644 src/Type/Symfony/Container/KernelBrowserGetContainerDynamicReturnTypeExtension.php delete mode 100644 src/Type/Symfony/Container/KernelTestCaseGetContainerDynamicReturnTypeExtension.php delete mode 100644 src/Type/Symfony/Container/TestContainerType.php delete mode 100644 tests/Rules/Symfony/container-extensions.neon diff --git a/extension.neon b/extension.neon index 1596580f..c76d573f 100644 --- a/extension.neon +++ b/extension.neon @@ -361,9 +361,3 @@ services: class: PHPStan\Symfony\SymfonyContainerResultCacheMetaExtension tags: - phpstan.resultCacheMetaExtension - - - class: PHPStan\Type\Symfony\Container\KernelBrowserGetContainerDynamicReturnTypeExtension - tags: [phpstan.broker.dynamicMethodReturnTypeExtension] - - - class: PHPStan\Type\Symfony\Container\KernelTestCaseGetContainerDynamicReturnTypeExtension - tags: [phpstan.broker.dynamicStaticMethodReturnTypeExtension] diff --git a/src/Rules/Symfony/ContainerInterfacePrivateServiceRule.php b/src/Rules/Symfony/ContainerInterfacePrivateServiceRule.php index 7b375e10..c8ec11a8 100644 --- a/src/Rules/Symfony/ContainerInterfacePrivateServiceRule.php +++ b/src/Rules/Symfony/ContainerInterfacePrivateServiceRule.php @@ -10,7 +10,6 @@ use PHPStan\Symfony\ServiceMap; use PHPStan\TrinaryLogic; use PHPStan\Type\ObjectType; -use PHPStan\Type\Symfony\Container\TestContainerType; use PHPStan\Type\Type; use function sprintf; @@ -44,17 +43,11 @@ public function processNode(Node $node, Scope $scope): array $argType = $scope->getType($node->var); - if ( - $argType instanceof TestContainerType - || (new ObjectType('Symfony\Bundle\FrameworkBundle\Test\TestContainer'))->isSuperTypeOf($argType)->yes() - ) { - return []; - } - + $isTestContainer = $this->isTestContainer($argType, $scope); $isOldServiceSubscriber = (new ObjectType('Symfony\Component\DependencyInjection\ServiceSubscriberInterface'))->isSuperTypeOf($argType); $isServiceSubscriber = $this->isServiceSubscriber($argType, $scope); $isServiceLocator = (new ObjectType('Symfony\Component\DependencyInjection\ServiceLocator'))->isSuperTypeOf($argType); - if ($isOldServiceSubscriber->yes() || $isServiceSubscriber->yes() || $isServiceLocator->yes()) { + if ($isTestContainer->yes() || $isOldServiceSubscriber->yes() || $isServiceSubscriber->yes() || $isServiceLocator->yes()) { return []; } @@ -98,4 +91,25 @@ private function isServiceSubscriber(Type $containerType, Scope $scope): Trinary return $isContainerServiceSubscriber->or($serviceSubscriberInterfaceType->isSuperTypeOf($containedClassType)->result); } + private function isTestContainer(Type $containerType, Scope $scope): TrinaryLogic + { + $testContainer = new ObjectType('Symfony\Bundle\FrameworkBundle\Test\TestContainer'); + $isTestContainer = $testContainer->isSuperTypeOf($containerType)->result; + + $classReflection = $scope->getClassReflection(); + if ($classReflection === null) { + return $isTestContainer; + } + + $containerInterface = new ObjectType('Symfony\Component\DependencyInjection\ContainerInterface'); + $kernelTestCase = new ObjectType('Symfony\Bundle\FrameworkBundle\Test\KernelTestCase'); + $containedClassType = new ObjectType($classReflection->getName()); + + return $isTestContainer->or( + $containerInterface->isSuperTypeOf($containerType)->result->and( + $kernelTestCase->isSuperTypeOf($containedClassType)->result, + ), + ); + } + } diff --git a/src/Type/Symfony/Container/KernelBrowserGetContainerDynamicReturnTypeExtension.php b/src/Type/Symfony/Container/KernelBrowserGetContainerDynamicReturnTypeExtension.php deleted file mode 100644 index 0a8ea235..00000000 --- a/src/Type/Symfony/Container/KernelBrowserGetContainerDynamicReturnTypeExtension.php +++ /dev/null @@ -1,29 +0,0 @@ -getName() === 'getContainer'; - } - - public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type - { - return new TestContainerType(); - } - -} diff --git a/src/Type/Symfony/Container/KernelTestCaseGetContainerDynamicReturnTypeExtension.php b/src/Type/Symfony/Container/KernelTestCaseGetContainerDynamicReturnTypeExtension.php deleted file mode 100644 index aab21970..00000000 --- a/src/Type/Symfony/Container/KernelTestCaseGetContainerDynamicReturnTypeExtension.php +++ /dev/null @@ -1,29 +0,0 @@ -getName() === 'getContainer'; - } - - public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): Type - { - return new TestContainerType(); - } - -} diff --git a/src/Type/Symfony/Container/TestContainerType.php b/src/Type/Symfony/Container/TestContainerType.php deleted file mode 100644 index d53668f0..00000000 --- a/src/Type/Symfony/Container/TestContainerType.php +++ /dev/null @@ -1,21 +0,0 @@ -