diff --git a/extension.neon b/extension.neon index 55021a2..6f8b161 100644 --- a/extension.neon +++ b/extension.neon @@ -1,5 +1,15 @@ services: - - class: Proget\PHPStan\Yii2\Reflection\BaseYiiMethodsClassReflectionExtension - tags: - - phpstan.broker.methodsClassReflectionExtension + class: Proget\PHPStan\Yii2\Type\ContainerDynamicMethodReturnTypeExtension + tags: [phpstan.broker.dynamicMethodReturnTypeExtension] + + - Proget\PHPStan\Yii2\ServiceMap(%yii2.config_path%) +parameters: + ignoreErrors: + - '#Access to an undefined property yii\\db\\ActiveRecord#' + - '#Call to an undefined method yii\\db\\ActiveRecord#' + - '#Call to an undefined method yii\\console\\Request#' + - '#Access to an undefined property yii\\console\\Request#' + - '#Call to an undefined method yii\\console\\Response#' + - '#Access to an undefined property yii\\console\\Response#' + - '#Method yii\\db\\ActiveQuery\:\:with\(\) invoked with#' diff --git a/src/Reflection/BaseYiiMethodsClassReflectionExtension.php b/src/Reflection/BaseYiiMethodsClassReflectionExtension.php deleted file mode 100644 index 8b67494..0000000 --- a/src/Reflection/BaseYiiMethodsClassReflectionExtension.php +++ /dev/null @@ -1,68 +0,0 @@ -getName()!=='Yii') { - return false; - } - - return in_array($methodName, $this->methods); - } - - public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection - { - if($classReflection->getName()!=='Yii') { - throw new \InvalidArgumentException(sprintf('Not supported class %s', $classReflection->getName())); - } - - switch ($methodName) { - case 'getVersion': - return new YiiMethodReflection('getVersion', $classReflection, YiiMethodReflection::PUBLIC, true, [], new StringType()); - case 't': - return new YiiMethodReflection('t', $classReflection, YiiMethodReflection::PUBLIC, true, [ - new YiiParameterReflection('category', new StringType()), - new YiiParameterReflection('message', new StringType()), - new YiiParameterReflection('params', new ArrayType(new StringType(), new StringType()), true), - new YiiParameterReflection('language', new StringType(), true), - ], new StringType()); - default: - throw new \InvalidArgumentException(sprintf('Not supported class %s', $methodName)); - } - } - -} \ No newline at end of file diff --git a/src/Reflection/YiiMethodReflection.php b/src/Reflection/YiiMethodReflection.php deleted file mode 100644 index 6877e7d..0000000 --- a/src/Reflection/YiiMethodReflection.php +++ /dev/null @@ -1,109 +0,0 @@ -name = $name; - $this->declaringClass = $declaringClass; - $this->visibility = $visibility; - $this->static = $static; - $this->parameters = $parameters; - $this->returnType = $returnType; - } - - public function getDeclaringClass(): ClassReflection - { - return $this->declaringClass; - } - - public function isStatic(): bool - { - return $this->static; - } - - public function isPrivate(): bool - { - return $this->visibility === self::PRIVATE; - } - - public function isPublic(): bool - { - return $this->visibility === self::PUBLIC; - } - - public function getPrototype(): MethodReflection - { - return $this; - } - - public function getName(): string - { - return $this->name; - } - - public function getParameters(): array - { - return $this->parameters; - } - - public function isVariadic(): bool - { - return false; - } - - public function getReturnType(): Type - { - return $this->returnType; - } - -} \ No newline at end of file diff --git a/src/Reflection/YiiParameterReflection.php b/src/Reflection/YiiParameterReflection.php deleted file mode 100644 index f949026..0000000 --- a/src/Reflection/YiiParameterReflection.php +++ /dev/null @@ -1,79 +0,0 @@ -name = $name; - $this->optional = $optional; - $this->type = $type; - $this->passedByReference = $passedByReference; - $this->variadic = $variadic; - } - - - public function getName(): string - { - return $this->name; - } - - public function isOptional(): bool - { - return $this->optional; - } - - public function getType(): Type - { - return $this->type; - } - - public function isPassedByReference(): bool - { - return $this->passedByReference; - } - - public function isVariadic(): bool - { - return $this->variadic; - } - -} \ No newline at end of file diff --git a/src/ServiceMap.php b/src/ServiceMap.php new file mode 100644 index 0000000..565052d --- /dev/null +++ b/src/ServiceMap.php @@ -0,0 +1,44 @@ + $service) { + if(is_callable($service)) { + $reflection = new \ReflectionFunction($service); + if(!$reflection->hasReturnType()) { + throw new \RuntimeException(sprintf('Please provide return type for %s service closure', $id)); + } + $this->services[$id] = $reflection->getReturnType()->getName(); + } else { + $this->services[$id] = $service['class'] ?? $service[0]['class']; + } + } + } + + public function getServiceClassFromNode(Node $node): ?string + { + if($node instanceof Node\Scalar\String_ && isset($this->services[$node->value])) { + return $this->services[$node->value]; + } + + return null; + } +} diff --git a/src/Type/ContainerDynamicMethodReturnTypeExtension.php b/src/Type/ContainerDynamicMethodReturnTypeExtension.php new file mode 100644 index 0000000..fa36b28 --- /dev/null +++ b/src/Type/ContainerDynamicMethodReturnTypeExtension.php @@ -0,0 +1,51 @@ +serviceMap = $serviceMap; + } + + public function getClass(): string + { + return 'yii\di\Container'; + } + + public function isMethodSupported(MethodReflection $methodReflection): bool + { + return $methodReflection->getName() === 'get'; + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + { + if(isset($methodCall->args[0]) && $methodCall->args[0] instanceof Arg) { + $serviceClass = $this->serviceMap->getServiceClassFromNode($methodCall->args[0]->value); + if($serviceClass !== null) { + return new ObjectType($serviceClass); + } + } + + return $methodReflection->getReturnType(); + } + +} \ No newline at end of file diff --git a/tests/Reflection/BaseYiiMethodsClassReflectionExtensionTest.php b/tests/Reflection/BaseYiiMethodsClassReflectionExtensionTest.php deleted file mode 100644 index 38513fd..0000000 --- a/tests/Reflection/BaseYiiMethodsClassReflectionExtensionTest.php +++ /dev/null @@ -1,59 +0,0 @@ -extension = new BaseYiiMethodsClassReflectionExtension(); - $this->yiiReflection = $this->createMock(ClassReflection::class); - $this->yiiReflection->method('getName')->willReturn('Yii'); - } - - public function testHasExistingMethod() - { - self::assertTrue($this->extension->hasMethod($this->yiiReflection, 'getVersion')); - } - - public function testHasNotExistingMethod() - { - self::assertFalse($this->extension->hasMethod($this->yiiReflection, 'getRequest')); - } - - /** - * @dataProvider getMethodProvider - */ - public function testGetMethod(string $methodName, string $returnType) - { - $method = $this->extension->getMethod($this->yiiReflection, $methodName); - - self::assertEquals($methodName, $method->getName()); - self::assertEquals($returnType, get_class($method->getReturnType())); - } - - public function getMethodProvider() : array - { - return [ - ['getVersion', StringType::class], - ['t', StringType::class] - ]; - } -} \ No newline at end of file