diff --git a/rules-tests/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector/Fixture/skip_laravel_model_attributes_and_scopes.php.inc b/rules-tests/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector/Fixture/skip_laravel_model_attributes_and_scopes.php.inc new file mode 100644 index 00000000000..b06633a8bc3 --- /dev/null +++ b/rules-tests/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector/Fixture/skip_laravel_model_attributes_and_scopes.php.inc @@ -0,0 +1,31 @@ +is('Illuminate\Database\Eloquent\Model')) { + return false; + } + + $name = (string) $this->nodeNameResolver->getName($classMethod->name); + + return $this->isAttributeMethod($name, $classMethod) + || $this->isScopeMethod($name, $classMethod); + } + + private function isAttributeMethod(string $name, ClassMethod $classMethod): bool + { + if (StringUtils::isMatch($name, self::LARAVEL_MODEL_ATTRIBUTE_REGEX)) { + return true; + } + + if (! $classMethod->returnType instanceof Node) { + return false; + } + + return $this->nodeTypeResolver->isObjectType( + $classMethod->returnType, + new ObjectType('Illuminate\Database\Eloquent\Casts\Attribute') + ); + } + + private function isScopeMethod(string $name, ClassMethod $classMethod): bool + { + if (StringUtils::isMatch($name, self::LARAVEL_MODEL_SCOPE_REGEX)) { + return true; + } + + return $this->phpAttributeAnalyzer->hasPhpAttribute( + $classMethod, + 'Illuminate\Database\Eloquent\Attributes\Scope' + ); + } +} diff --git a/rules/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector.php b/rules/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector.php index 80757ceb7bb..9ebbac2df15 100644 --- a/rules/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector.php +++ b/rules/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector.php @@ -11,6 +11,7 @@ use PHPStan\Reflection\ClassReflection; use Rector\PhpParser\Node\BetterNodeFinder; use Rector\PHPStan\ScopeFetcher; +use Rector\Privatization\Guard\LaravelModelGuard; use Rector\Privatization\Guard\OverrideByParentClassGuard; use Rector\Privatization\NodeManipulator\VisibilityManipulator; use Rector\Privatization\VisibilityGuard\ClassMethodVisibilityGuard; @@ -28,6 +29,7 @@ public function __construct( private readonly VisibilityManipulator $visibilityManipulator, private readonly OverrideByParentClassGuard $overrideByParentClassGuard, private readonly BetterNodeFinder $betterNodeFinder, + private readonly LaravelModelGuard $laravelModelGuard, ) { } @@ -93,6 +95,10 @@ public function refactor(Node $node): ?Node continue; } + if ($this->laravelModelGuard->isProtectedMethod($classReflection, $classMethod)) { + continue; + } + if ($this->classMethodVisibilityGuard->isClassMethodVisibilityGuardedByParent( $classMethod, $classReflection diff --git a/stubs/Illuminate/Database/Eloquent/Attributes/Scope.php b/stubs/Illuminate/Database/Eloquent/Attributes/Scope.php new file mode 100644 index 00000000000..25827f90441 --- /dev/null +++ b/stubs/Illuminate/Database/Eloquent/Attributes/Scope.php @@ -0,0 +1,14 @@ +