diff --git a/extension.neon b/extension.neon index f8516ed0..809f5974 100644 --- a/extension.neon +++ b/extension.neon @@ -137,16 +137,16 @@ services: # ControllerTrait::get()/has() return type - - factory: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Symfony\Component\DependencyInjection\ContainerInterface, %symfony.constantHassers%) + factory: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Symfony\Component\DependencyInjection\ContainerInterface, %symfony.constantHassers%, containerXmlPath: %symfony.containerXmlPath%) tags: [phpstan.broker.dynamicMethodReturnTypeExtension] - - factory: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Psr\Container\ContainerInterface, %symfony.constantHassers%) + factory: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Psr\Container\ContainerInterface, %symfony.constantHassers%, containerXmlPath: %symfony.containerXmlPath%) tags: [phpstan.broker.dynamicMethodReturnTypeExtension] - - factory: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Symfony\Bundle\FrameworkBundle\Controller\Controller, %symfony.constantHassers%) + factory: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Symfony\Bundle\FrameworkBundle\Controller\Controller, %symfony.constantHassers%, containerXmlPath: %symfony.containerXmlPath%) tags: [phpstan.broker.dynamicMethodReturnTypeExtension] - - factory: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Symfony\Bundle\FrameworkBundle\Controller\AbstractController, %symfony.constantHassers%) + factory: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Symfony\Bundle\FrameworkBundle\Controller\AbstractController, %symfony.constantHassers%, containerXmlPath: %symfony.containerXmlPath%) tags: [phpstan.broker.dynamicMethodReturnTypeExtension] # ControllerTrait::has() type specification @@ -314,20 +314,20 @@ services: # ParameterBagInterface::get()/has() return type - - factory: PHPStan\Type\Symfony\ParameterDynamicReturnTypeExtension(Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface, 'get', 'has', %symfony.constantHassers%) + factory: PHPStan\Type\Symfony\ParameterDynamicReturnTypeExtension(Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface, 'get', 'has', %symfony.constantHassers%, containerXmlPath: %symfony.containerXmlPath%) tags: [phpstan.broker.dynamicMethodReturnTypeExtension] # ContainerInterface::getParameter()/hasParameter() return type - - factory: PHPStan\Type\Symfony\ParameterDynamicReturnTypeExtension(Symfony\Component\DependencyInjection\ContainerInterface, 'getParameter', 'hasParameter', %symfony.constantHassers%) + factory: PHPStan\Type\Symfony\ParameterDynamicReturnTypeExtension(Symfony\Component\DependencyInjection\ContainerInterface, 'getParameter', 'hasParameter', %symfony.constantHassers%, containerXmlPath: %symfony.containerXmlPath%) tags: [phpstan.broker.dynamicMethodReturnTypeExtension] # (Abstract)Controller::getParameter() return type - - factory: PHPStan\Type\Symfony\ParameterDynamicReturnTypeExtension(Symfony\Bundle\FrameworkBundle\Controller\AbstractController, 'getParameter', null, %symfony.constantHassers%) + factory: PHPStan\Type\Symfony\ParameterDynamicReturnTypeExtension(Symfony\Bundle\FrameworkBundle\Controller\AbstractController, 'getParameter', null, %symfony.constantHassers%, containerXmlPath: %symfony.containerXmlPath%) tags: [phpstan.broker.dynamicMethodReturnTypeExtension] - - factory: PHPStan\Type\Symfony\ParameterDynamicReturnTypeExtension(Symfony\Bundle\FrameworkBundle\Controller\Controller, 'getParameter', null, %symfony.constantHassers%) + factory: PHPStan\Type\Symfony\ParameterDynamicReturnTypeExtension(Symfony\Bundle\FrameworkBundle\Controller\Controller, 'getParameter', null, %symfony.constantHassers%, containerXmlPath: %symfony.containerXmlPath%) tags: [phpstan.broker.dynamicMethodReturnTypeExtension] - class: PHPStan\Symfony\InputBagStubFilesExtension @@ -375,7 +375,6 @@ services: factory: PHPStan\Type\Symfony\ExtensionGetConfigurationReturnTypeExtension tags: [phpstan.broker.dynamicMethodReturnTypeExtension] - - - class: PHPStan\Symfony\SymfonyContainerResultCacheMetaExtension - tags: - - phpstan.resultCacheMetaExtension + # SymfonyContainerResultCacheMetaExtension removed: replaced by external file + # dependency tracking which provides incremental (per-file) cache invalidation + # instead of full cache invalidation when the container XML changes. diff --git a/rules.neon b/rules.neon index cedcea7a..beabc71b 100644 --- a/rules.neon +++ b/rules.neon @@ -1,8 +1,18 @@ rules: - - PHPStan\Rules\Symfony\ContainerInterfacePrivateServiceRule - - PHPStan\Rules\Symfony\ContainerInterfaceUnknownServiceRule - PHPStan\Rules\Symfony\UndefinedArgumentRule - PHPStan\Rules\Symfony\InvalidArgumentDefaultValueRule - PHPStan\Rules\Symfony\UndefinedOptionRule - PHPStan\Rules\Symfony\InvalidOptionDefaultValueRule +services: + - + class: PHPStan\Rules\Symfony\ContainerInterfacePrivateServiceRule + arguments: + containerXmlPath: %symfony.containerXmlPath% + tags: [phpstan.rules.rule] + - + class: PHPStan\Rules\Symfony\ContainerInterfaceUnknownServiceRule + arguments: + containerXmlPath: %symfony.containerXmlPath% + tags: [phpstan.rules.rule] + diff --git a/src/Rules/Symfony/ContainerInterfacePrivateServiceRule.php b/src/Rules/Symfony/ContainerInterfacePrivateServiceRule.php index c8ec11a8..667e1082 100644 --- a/src/Rules/Symfony/ContainerInterfacePrivateServiceRule.php +++ b/src/Rules/Symfony/ContainerInterfacePrivateServiceRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PhpParser\Node\Expr\MethodCall; +use PHPStan\Analyser\ExternalFileDependencyRegistrar; use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; @@ -21,9 +22,19 @@ final class ContainerInterfacePrivateServiceRule implements Rule private ServiceMap $serviceMap; - public function __construct(ServiceMap $symfonyServiceMap) + private ?string $containerXmlPath; + + private ?ExternalFileDependencyRegistrar $externalFileDependencyRegistrar; + + public function __construct( + ServiceMap $symfonyServiceMap, + ?string $containerXmlPath = null, + ?ExternalFileDependencyRegistrar $externalFileDependencyRegistrar = null, + ) { $this->serviceMap = $symfonyServiceMap; + $this->containerXmlPath = $containerXmlPath; + $this->externalFileDependencyRegistrar = $externalFileDependencyRegistrar; } public function getNodeType(): string @@ -66,6 +77,9 @@ public function processNode(Node $node, Scope $scope): array $serviceId = $this->serviceMap::getServiceIdFromNode($node->getArgs()[0]->value, $scope); if ($serviceId !== null) { + if ($this->containerXmlPath !== null && $this->externalFileDependencyRegistrar !== null) { + $this->externalFileDependencyRegistrar->add($this->containerXmlPath); + } $service = $this->serviceMap->getService($serviceId); if ($service !== null && !$service->isPublic()) { return [ diff --git a/src/Rules/Symfony/ContainerInterfaceUnknownServiceRule.php b/src/Rules/Symfony/ContainerInterfaceUnknownServiceRule.php index 23444b6b..f5399ab6 100644 --- a/src/Rules/Symfony/ContainerInterfaceUnknownServiceRule.php +++ b/src/Rules/Symfony/ContainerInterfaceUnknownServiceRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PhpParser\Node\Expr\MethodCall; +use PHPStan\Analyser\ExternalFileDependencyRegistrar; use PHPStan\Analyser\Scope; use PHPStan\Node\Printer\Printer; use PHPStan\Rules\Rule; @@ -23,10 +24,21 @@ final class ContainerInterfaceUnknownServiceRule implements Rule private Printer $printer; - public function __construct(ServiceMap $symfonyServiceMap, Printer $printer) + private ?string $containerXmlPath; + + private ?ExternalFileDependencyRegistrar $externalFileDependencyRegistrar; + + public function __construct( + ServiceMap $symfonyServiceMap, + Printer $printer, + ?string $containerXmlPath = null, + ?ExternalFileDependencyRegistrar $externalFileDependencyRegistrar = null, + ) { $this->serviceMap = $symfonyServiceMap; $this->printer = $printer; + $this->containerXmlPath = $containerXmlPath; + $this->externalFileDependencyRegistrar = $externalFileDependencyRegistrar; } public function getNodeType(): string @@ -65,6 +77,9 @@ public function processNode(Node $node, Scope $scope): array $serviceId = $this->serviceMap::getServiceIdFromNode($node->getArgs()[0]->value, $scope); if ($serviceId !== null) { + if ($this->containerXmlPath !== null && $this->externalFileDependencyRegistrar !== null) { + $this->externalFileDependencyRegistrar->add($this->containerXmlPath); + } $service = $this->serviceMap->getService($serviceId); $serviceIdType = $scope->getType($node->getArgs()[0]->value); if ($service === null && !$scope->getType(Helper::createMarkerNode($node->var, $serviceIdType, $this->printer))->equals($serviceIdType)) { diff --git a/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php b/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php index 687b0c33..19fa0814 100644 --- a/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php +++ b/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php @@ -3,6 +3,7 @@ namespace PHPStan\Type\Symfony; use PhpParser\Node\Expr\MethodCall; +use PHPStan\Analyser\ExternalFileDependencyRegistrar; use PHPStan\Analyser\Scope; use PHPStan\PhpDoc\TypeStringResolver; use PHPStan\Reflection\MethodReflection; @@ -54,6 +55,10 @@ final class ParameterDynamicReturnTypeExtension implements DynamicMethodReturnTy private TypeStringResolver $typeStringResolver; + private ?string $containerXmlPath; + + private ?ExternalFileDependencyRegistrar $externalFileDependencyRegistrar; + /** * @param class-string $className */ @@ -63,7 +68,9 @@ public function __construct( ?string $methodHas, bool $constantHassers, ParameterMap $symfonyParameterMap, - TypeStringResolver $typeStringResolver + TypeStringResolver $typeStringResolver, + ?string $containerXmlPath = null, + ?ExternalFileDependencyRegistrar $externalFileDependencyRegistrar = null, ) { $this->className = $className; @@ -72,6 +79,8 @@ public function __construct( $this->constantHassers = $constantHassers; $this->parameterMap = $symfonyParameterMap; $this->typeStringResolver = $typeStringResolver; + $this->containerXmlPath = $containerXmlPath; + $this->externalFileDependencyRegistrar = $externalFileDependencyRegistrar; } public function getClass(): string @@ -122,6 +131,8 @@ private function getGetTypeFromMethodCall( return $defaultReturnType; } + $this->registerExternalDependency(); + $returnTypes = []; foreach ($parameterKeys as $parameterKey) { $parameter = $this->parameterMap->getParameter($parameterKey); @@ -203,6 +214,13 @@ private function generalizeType(Type $type): Type }); } + private function registerExternalDependency(): void + { + if ($this->containerXmlPath !== null && $this->externalFileDependencyRegistrar !== null) { + $this->externalFileDependencyRegistrar->add($this->containerXmlPath); + } + } + private function getHasTypeFromMethodCall( MethodReflection $methodReflection, MethodCall $methodCall, @@ -218,6 +236,8 @@ private function getHasTypeFromMethodCall( return null; } + $this->registerExternalDependency(); + $has = null; foreach ($parameterKeys as $parameterKey) { $parameter = $this->parameterMap->getParameter($parameterKey); diff --git a/src/Type/Symfony/ServiceDynamicReturnTypeExtension.php b/src/Type/Symfony/ServiceDynamicReturnTypeExtension.php index 0667d30c..deae075f 100644 --- a/src/Type/Symfony/ServiceDynamicReturnTypeExtension.php +++ b/src/Type/Symfony/ServiceDynamicReturnTypeExtension.php @@ -3,6 +3,7 @@ namespace PHPStan\Type\Symfony; use PhpParser\Node\Expr\MethodCall; +use PHPStan\Analyser\ExternalFileDependencyRegistrar; use PHPStan\Analyser\Scope; use PHPStan\Reflection\MethodReflection; use PHPStan\ShouldNotHappenException; @@ -30,6 +31,10 @@ final class ServiceDynamicReturnTypeExtension implements DynamicMethodReturnType private ParameterMap $parameterMap; + private ?string $containerXmlPath; + + private ?ExternalFileDependencyRegistrar $externalFileDependencyRegistrar; + private ?ParameterBag $parameterBag = null; /** @@ -39,13 +44,17 @@ public function __construct( string $className, bool $constantHassers, ServiceMap $symfonyServiceMap, - ParameterMap $symfonyParameterMap + ParameterMap $symfonyParameterMap, + ?string $containerXmlPath = null, + ?ExternalFileDependencyRegistrar $externalFileDependencyRegistrar = null, ) { $this->className = $className; $this->constantHassers = $constantHassers; $this->serviceMap = $symfonyServiceMap; $this->parameterMap = $symfonyParameterMap; + $this->containerXmlPath = $containerXmlPath; + $this->externalFileDependencyRegistrar = $externalFileDependencyRegistrar; } public function getClass(): string @@ -86,6 +95,7 @@ private function getGetTypeFromMethodCall( $serviceId = $this->serviceMap::getServiceIdFromNode($methodCall->getArgs()[0]->value, $scope); if ($serviceId !== null) { + $this->registerExternalDependency(); $service = $this->serviceMap->getService($serviceId); if ($service !== null && (!$service->isSynthetic() || $service->getClass() !== null)) { return new ObjectType($this->determineServiceClass($parameterBag, $service) ?? $serviceId); @@ -131,6 +141,7 @@ private function getHasTypeFromMethodCall( $serviceId = $this->serviceMap::getServiceIdFromNode($methodCall->getArgs()[0]->value, $scope); if ($serviceId !== null) { + $this->registerExternalDependency(); $service = $this->serviceMap->getService($serviceId); return new ConstantBooleanType($service !== null && $service->isPublic()); } @@ -138,6 +149,13 @@ private function getHasTypeFromMethodCall( return null; } + private function registerExternalDependency(): void + { + if ($this->containerXmlPath !== null && $this->externalFileDependencyRegistrar !== null) { + $this->externalFileDependencyRegistrar->add($this->containerXmlPath); + } + } + private function determineServiceClass(ParameterBag $parameterBag, ServiceDefinition $service): ?string { $class = $service->getClass();