diff --git a/src/Reflection/Annotations/AnnotationsMethodsClassReflectionExtension.php b/src/Reflection/Annotations/AnnotationsMethodsClassReflectionExtension.php index efbf6423255..91fdbe77156 100644 --- a/src/Reflection/Annotations/AnnotationsMethodsClassReflectionExtension.php +++ b/src/Reflection/Annotations/AnnotationsMethodsClassReflectionExtension.php @@ -7,12 +7,14 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\MethodsClassReflectionExtension; +use PHPStan\ShouldNotHappenException; use PHPStan\Type\Generic\TemplateTypeFactory; use PHPStan\Type\Generic\TemplateTypeHelper; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\Generic\TemplateTypeScope; use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\Type; +use function array_key_exists; use function array_map; use function count; @@ -21,25 +23,36 @@ final class AnnotationsMethodsClassReflectionExtension implements MethodsClassReflectionExtension { - /** @var ExtendedMethodReflection[][] */ + /** @var array> */ private array $methods = []; public function hasMethod(ClassReflection $classReflection, string $methodName): bool { - if (!isset($this->methods[$classReflection->getCacheKey()][$methodName])) { - $method = $this->findClassReflectionWithMethod($classReflection, $classReflection, $methodName); - if ($method === null) { - return false; - } - $this->methods[$classReflection->getCacheKey()][$methodName] = $method; + return $this->findMethod($classReflection, $methodName) !== null; + } + + public function getMethod(ClassReflection $classReflection, string $methodName): ExtendedMethodReflection + { + $method = $this->findMethod($classReflection, $methodName); + if ($method === null) { + throw new ShouldNotHappenException(); } - return true; + return $method; } - public function getMethod(ClassReflection $classReflection, string $methodName): ExtendedMethodReflection + /** + * Remembers misses too: the lookup recurses into the parent and every + * interface, which reach the same ancestors many times over. + */ + private function findMethod(ClassReflection $classReflection, string $methodName): ?ExtendedMethodReflection { - return $this->methods[$classReflection->getCacheKey()][$methodName]; + $cacheKey = $classReflection->getCacheKey(); + if (!array_key_exists($methodName, $this->methods[$cacheKey] ?? [])) { + $this->methods[$cacheKey][$methodName] = $this->findClassReflectionWithMethod($classReflection, $classReflection, $methodName); + } + + return $this->methods[$cacheKey][$methodName]; } private function findClassReflectionWithMethod( @@ -102,14 +115,14 @@ private function findClassReflectionWithMethod( $parentClass = $classReflection->getParentClass(); if ($parentClass !== null) { - $methodWithDeclaringClass = $this->findClassReflectionWithMethod($parentClass, $parentClass, $methodName); + $methodWithDeclaringClass = $this->findMethod($parentClass, $methodName); if ($methodWithDeclaringClass !== null) { return $methodWithDeclaringClass; } } foreach ($classReflection->getInterfaces() as $interfaceClass) { - $methodWithDeclaringClass = $this->findClassReflectionWithMethod($interfaceClass, $interfaceClass, $methodName); + $methodWithDeclaringClass = $this->findMethod($interfaceClass, $methodName); if ($methodWithDeclaringClass === null) { continue; } diff --git a/src/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtension.php b/src/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtension.php index f51577a281d..5b9183bfff4 100644 --- a/src/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtension.php +++ b/src/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtension.php @@ -6,34 +6,47 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\PropertiesClassReflectionExtension; +use PHPStan\ShouldNotHappenException; use PHPStan\Type\Generic\TemplateTypeHelper; use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\NeverType; +use function array_key_exists; // autoTag: false - wired explicitly in ClassReflectionExtensionRegistry, must not be tagged #[AutowiredService(autoTag: false)] final class AnnotationsPropertiesClassReflectionExtension implements PropertiesClassReflectionExtension { - /** @var ExtendedPropertyReflection[][] */ + /** @var array> */ private array $properties = []; public function hasProperty(ClassReflection $classReflection, string $propertyName): bool { - if (!isset($this->properties[$classReflection->getCacheKey()][$propertyName])) { - $property = $this->findClassReflectionWithProperty($classReflection, $classReflection, $propertyName); - if ($property === null) { - return false; - } - $this->properties[$classReflection->getCacheKey()][$propertyName] = $property; + return $this->findProperty($classReflection, $propertyName) !== null; + } + + public function getProperty(ClassReflection $classReflection, string $propertyName): ExtendedPropertyReflection + { + $property = $this->findProperty($classReflection, $propertyName); + if ($property === null) { + throw new ShouldNotHappenException(); } - return true; + return $property; } - public function getProperty(ClassReflection $classReflection, string $propertyName): ExtendedPropertyReflection + /** + * Remembers misses too: the lookup recurses into the parent and every + * interface, which reach the same ancestors many times over. + */ + private function findProperty(ClassReflection $classReflection, string $propertyName): ?ExtendedPropertyReflection { - return $this->properties[$classReflection->getCacheKey()][$propertyName]; + $cacheKey = $classReflection->getCacheKey(); + if (!array_key_exists($propertyName, $this->properties[$cacheKey] ?? [])) { + $this->properties[$cacheKey][$propertyName] = $this->findClassReflectionWithProperty($classReflection, $classReflection, $propertyName); + } + + return $this->properties[$cacheKey][$propertyName]; } private function findClassReflectionWithProperty( @@ -87,14 +100,14 @@ private function findClassReflectionWithProperty( $parentClass = $classReflection->getParentClass(); if ($parentClass !== null) { - $methodWithDeclaringClass = $this->findClassReflectionWithProperty($parentClass, $parentClass, $propertyName); + $methodWithDeclaringClass = $this->findProperty($parentClass, $propertyName); if ($methodWithDeclaringClass !== null) { return $methodWithDeclaringClass; } } foreach ($classReflection->getInterfaces() as $interfaceClass) { - $methodWithDeclaringClass = $this->findClassReflectionWithProperty($interfaceClass, $interfaceClass, $propertyName); + $methodWithDeclaringClass = $this->findProperty($interfaceClass, $propertyName); if ($methodWithDeclaringClass === null) { continue; }