From 9926f72e5aff3b9217b9078b1697589bad6e6689 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sun, 25 Oct 2026 21:22:08 +0100 Subject: [PATCH 1/2] make ReadonlyClassManipulator use class reflection --- .../ReadonlyClassManipulator.php | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/rules/Php82/NodeManipulator/ReadonlyClassManipulator.php b/rules/Php82/NodeManipulator/ReadonlyClassManipulator.php index 1fa9153bcbe..0f7ef3526ce 100644 --- a/rules/Php82/NodeManipulator/ReadonlyClassManipulator.php +++ b/rules/Php82/NodeManipulator/ReadonlyClassManipulator.php @@ -16,7 +16,6 @@ use PHPStan\Reflection\ReflectionProvider; use Rector\Php80\NodeAnalyzer\PhpAttributeAnalyzer; use Rector\Php81\Enum\AttributeName; -use Rector\PHPStan\ScopeFetcher; use Rector\Privatization\NodeManipulator\VisibilityManipulator; use Rector\ValueObject\MethodName; use Rector\ValueObject\Visibility; @@ -32,8 +31,14 @@ public function __construct( public function process(Class_ $class): Class_|null { - $scope = ScopeFetcher::fetch($class); - if ($this->shouldSkip($class, $scope)) { + $className = $class->namespacedName->toString(); + if (! $this->reflectionProvider->hasClass($className)) { + return null; + } + + $classReflection = $this->reflectionProvider->getClass($className); + + if ($this->shouldSkip($classReflection, $class)) { return null; } @@ -75,25 +80,20 @@ private function hasNonTypedProperty(array $properties): bool return array_any($properties, fn (Property $property): bool => ! $property->type instanceof Node); } - private function shouldSkip(Class_ $class, Scope $scope): bool + private function shouldSkip(ClassReflection $classReflection, Class_ $class): bool { - $classReflection = $scope->getClassReflection(); - if (! $classReflection instanceof ClassReflection) { - return true; - } - if ($this->shouldSkipClass($class)) { return true; } - $parents = $this->resolveParentClassReflections($scope); + $parentClassReflections = $classReflection->getParents(); if (! $class->isAnonymous() && ! $class->isFinal()) { - return ! $this->isExtendsReadonlyClass($parents); + return ! $this->isExtendsReadonlyClass($parentClassReflections); } - foreach ($parents as $parent) { - if (! $parent->isReadOnly()) { + foreach ($parentClassReflections as $parentClassReflection) { + if (! $parentClassReflection->isReadOnly()) { return true; } } From 2acc895ba852a584664b8fa2d54824e95b3f7a72 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sun, 25 Oct 2026 22:30:21 +0100 Subject: [PATCH 2/2] resolve readonly parent reflections from extends name, fix anonymous class crash --- .../ReadonlyClassManipulator.php | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/rules/Php82/NodeManipulator/ReadonlyClassManipulator.php b/rules/Php82/NodeManipulator/ReadonlyClassManipulator.php index 0f7ef3526ce..8b45974e0f0 100644 --- a/rules/Php82/NodeManipulator/ReadonlyClassManipulator.php +++ b/rules/Php82/NodeManipulator/ReadonlyClassManipulator.php @@ -5,12 +5,12 @@ namespace Rector\Php82\NodeManipulator; use PhpParser\Node; +use PhpParser\Node\Name; use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Param; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Property; -use PHPStan\Analyser\Scope; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionProperty; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ReflectionProvider; @@ -31,14 +31,9 @@ public function __construct( public function process(Class_ $class): Class_|null { - $className = $class->namespacedName->toString(); - if (! $this->reflectionProvider->hasClass($className)) { - return null; - } - - $classReflection = $this->reflectionProvider->getClass($className); + $parentClassReflections = $this->resolveParentClassReflections($class); - if ($this->shouldSkip($classReflection, $class)) { + if ($this->shouldSkip($class, $parentClassReflections)) { return null; } @@ -60,16 +55,24 @@ public function process(Class_ $class): Class_|null } /** + * Resolve parents from the extends name, so anonymous classes (no name) work too. + * * @return ClassReflection[] */ - private function resolveParentClassReflections(Scope $scope): array + private function resolveParentClassReflections(Class_ $class): array { - $classReflection = $scope->getClassReflection(); - if (! $classReflection instanceof ClassReflection) { + if (! $class->extends instanceof Name) { + return []; + } + + $parentName = $class->extends->toString(); + if (! $this->reflectionProvider->hasClass($parentName)) { return []; } - return $classReflection->getParents(); + $parentClassReflection = $this->reflectionProvider->getClass($parentName); + + return [$parentClassReflection, ...$parentClassReflection->getParents()]; } /** @@ -80,14 +83,15 @@ private function hasNonTypedProperty(array $properties): bool return array_any($properties, fn (Property $property): bool => ! $property->type instanceof Node); } - private function shouldSkip(ClassReflection $classReflection, Class_ $class): bool + /** + * @param ClassReflection[] $parentClassReflections + */ + private function shouldSkip(Class_ $class, array $parentClassReflections): bool { if ($this->shouldSkipClass($class)) { return true; } - $parentClassReflections = $classReflection->getParents(); - if (! $class->isAnonymous() && ! $class->isFinal()) { return ! $this->isExtendsReadonlyClass($parentClassReflections); }