From f3fa53fb96de289f50de64cd0e929ce7bbcf1897 Mon Sep 17 00:00:00 2001 From: InvisibleSmiley Date: Fri, 25 Nov 2022 16:28:36 +0100 Subject: [PATCH] Support container interfaces for get() return type (#39) Co-authored-by: Jens Hatlak --- extension.neon | 8 ++ ...ceLocatorGetDynamicReturnTypeExtension.php | 103 ++++++++++++++++++ ...ContainerGetDynamicReturnTypeExtension.php | 15 +++ ...ContainerGetDynamicReturnTypeExtension.php | 15 +++ ...ceManagerGetDynamicReturnTypeExtension.php | 96 +--------------- 5 files changed, 142 insertions(+), 95 deletions(-) create mode 100644 src/Type/Laminas/AbstractServiceLocatorGetDynamicReturnTypeExtension.php create mode 100644 src/Type/Laminas/InteropContainerGetDynamicReturnTypeExtension.php create mode 100644 src/Type/Laminas/PsrContainerGetDynamicReturnTypeExtension.php diff --git a/extension.neon b/extension.neon index 22ae979..cc98056 100644 --- a/extension.neon +++ b/extension.neon @@ -17,6 +17,14 @@ services: class: LaminasPhpStan\ServiceManagerLoader arguments: serviceManagerLoader: %laminasframework.serviceManagerLoader% + - + class: LaminasPhpStan\Type\Laminas\InteropContainerGetDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: LaminasPhpStan\Type\Laminas\PsrContainerGetDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension - class: LaminasPhpStan\Type\Laminas\ServiceManagerGetDynamicReturnTypeExtension tags: diff --git a/src/Type/Laminas/AbstractServiceLocatorGetDynamicReturnTypeExtension.php b/src/Type/Laminas/AbstractServiceLocatorGetDynamicReturnTypeExtension.php new file mode 100644 index 0000000..a777f31 --- /dev/null +++ b/src/Type/Laminas/AbstractServiceLocatorGetDynamicReturnTypeExtension.php @@ -0,0 +1,103 @@ +getName(); + } + + final public function getTypeFromMethodCall( + MethodReflection $methodReflection, + MethodCall $methodCall, + Scope $scope + ): Type { + $calledOnType = $scope->getType($methodCall->var); + if (! $calledOnType instanceof ObjectType) { + return new MixedType(); + } + + $args = $methodCall->getArgs(); + if (1 !== \count($args)) { + return new MixedType(); + } + + $serviceManager = $this->serviceManagerLoader->getServiceLocator($calledOnType->getClassName()); + + $firstArg = $args[0]; + if (! $firstArg instanceof Arg) { + throw new \PHPStan\ShouldNotHappenException(\sprintf( + 'Argument passed to %s::%s should be a string, %s given', + $methodReflection->getDeclaringClass()->getName(), + $methodReflection->getName(), + $firstArg->getType() + )); + } + $argType = $scope->getType($firstArg->value); + if (! $argType instanceof ConstantStringType) { + if ($serviceManager instanceof AbstractPluginManager) { + $refClass = new ReflectionClass($serviceManager); + $refProperty = $refClass->getProperty('instanceOf'); + $refProperty->setAccessible(true); + $returnedInstance = $refProperty->getValue($serviceManager); + \assert(null === $returnedInstance || \is_string($returnedInstance)); + if (null !== $returnedInstance && $this->reflectionProvider->hasClass($returnedInstance)) { + return new ObjectType($returnedInstance); + } + } + + return new MixedType(); + } + + $serviceName = $argType->getValue(); + if (! $serviceManager->has($serviceName)) { + return new NeverType(); + } + + if (\class_exists($serviceName) || \interface_exists($serviceName)) { + return new ObjectServiceManagerType($serviceName, $serviceName); + } + + $service = $serviceManager->get($serviceName); + if (\is_object($service)) { + $className = $service::class; + $refClass = new ReflectionClass($service); + if ($refClass->isAnonymous()) { + if (false !== ($parentClass = $refClass->getParentClass())) { + $className = $parentClass->getName(); + } elseif ([] !== ($interfaces = $refClass->getInterfaces())) { + $className = \current($interfaces)->getName(); + } + } + + return new ObjectServiceManagerType($className, $serviceName); + } + + return $scope->getTypeFromValue($service); + } +} diff --git a/src/Type/Laminas/InteropContainerGetDynamicReturnTypeExtension.php b/src/Type/Laminas/InteropContainerGetDynamicReturnTypeExtension.php new file mode 100644 index 0000000..ee9a554 --- /dev/null +++ b/src/Type/Laminas/InteropContainerGetDynamicReturnTypeExtension.php @@ -0,0 +1,15 @@ +getName(); - } - - public function getTypeFromMethodCall( - MethodReflection $methodReflection, - MethodCall $methodCall, - Scope $scope - ): Type { - $calledOnType = $scope->getType($methodCall->var); - if (! $calledOnType instanceof ObjectType) { - return new MixedType(); - } - - $args = $methodCall->getArgs(); - if (1 !== \count($args)) { - return new MixedType(); - } - - $serviceManager = $this->serviceManagerLoader->getServiceLocator($calledOnType->getClassName()); - - $firstArg = $args[0]; - if (! $firstArg instanceof Arg) { - throw new \PHPStan\ShouldNotHappenException(\sprintf( - 'Argument passed to %s::%s should be a string, %s given', - $methodReflection->getDeclaringClass()->getName(), - $methodReflection->getName(), - $firstArg->getType() - )); - } - $argType = $scope->getType($firstArg->value); - if (! $argType instanceof ConstantStringType) { - if ($serviceManager instanceof AbstractPluginManager) { - $refClass = new ReflectionClass($serviceManager); - $refProperty = $refClass->getProperty('instanceOf'); - $refProperty->setAccessible(true); - $returnedInstance = $refProperty->getValue($serviceManager); - \assert(null === $returnedInstance || \is_string($returnedInstance)); - if (null !== $returnedInstance && $this->reflectionProvider->hasClass($returnedInstance)) { - return new ObjectType($returnedInstance); - } - } - - return new MixedType(); - } - - $serviceName = $argType->getValue(); - if (! $serviceManager->has($serviceName)) { - return new NeverType(); - } - - if (\class_exists($serviceName) || \interface_exists($serviceName)) { - return new ObjectServiceManagerType($serviceName, $serviceName); - } - - $service = $serviceManager->get($serviceName); - if (\is_object($service)) { - $className = $service::class; - $refClass = new ReflectionClass($service); - if ($refClass->isAnonymous()) { - if (false !== ($parentClass = $refClass->getParentClass())) { - $className = $parentClass->getName(); - } elseif ([] !== ($interfaces = $refClass->getInterfaces())) { - $className = \current($interfaces)->getName(); - } - } - - return new ObjectServiceManagerType($className, $serviceName); - } - - return $scope->getTypeFromValue($service); - } }