diff --git a/src/NodeAnalyzer/ScopeAnalyzer.php b/src/NodeAnalyzer/ScopeAnalyzer.php new file mode 100644 index 00000000..d2c6bf79 --- /dev/null +++ b/src/NodeAnalyzer/ScopeAnalyzer.php @@ -0,0 +1,63 @@ +nodeNameResolver->getName($classMethod); + + if (! (bool) preg_match('/^scope[A-Z].+$/', $name)) { + return false; + } + + return $this->hasBuilderFirstParameter($classMethod); + } + + /** + * Named scope OR #[Scope] attribute. + */ + public function isScopeMethod(ClassMethod $classMethod): bool + { + return $this->isNamedScope($classMethod) + || $this->phpAttributeAnalyzer->hasPhpAttribute($classMethod, self::SCOPE_ATTRIBUTE); + } + + private function hasBuilderFirstParameter(ClassMethod $classMethod): bool + { + if ($classMethod->params === []) { + return false; + } + + $firstParam = $classMethod->params[0]; + + if ($firstParam->type === null) { + return true; + } + + return $this->nodeTypeResolver->isObjectType($firstParam->type, new ObjectType(self::ELOQUENT_BUILDER)); + } +} diff --git a/src/Rector/ClassMethod/MakeModelAttributesAndScopesProtectedRector.php b/src/Rector/ClassMethod/MakeModelAttributesAndScopesProtectedRector.php index 0005999e..811017e0 100644 --- a/src/Rector/ClassMethod/MakeModelAttributesAndScopesProtectedRector.php +++ b/src/Rector/ClassMethod/MakeModelAttributesAndScopesProtectedRector.php @@ -9,10 +9,10 @@ use PHPStan\Analyser\Scope; use PHPStan\Reflection\ClassReflection; use PHPStan\Type\ObjectType; -use Rector\Php80\NodeAnalyzer\PhpAttributeAnalyzer; use Rector\PHPStan\ScopeFetcher; use Rector\Privatization\NodeManipulator\VisibilityManipulator; use RectorLaravel\AbstractRector; +use RectorLaravel\NodeAnalyzer\ScopeAnalyzer; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -23,7 +23,7 @@ class MakeModelAttributesAndScopesProtectedRector extends AbstractRector { public function __construct( private readonly VisibilityManipulator $visibilityManipulator, - private readonly PhpAttributeAnalyzer $phpAttributeAnalyzer + private readonly ScopeAnalyzer $scopeAnalyzer, ) {} public function getRuleDefinition(): RuleDefinition @@ -97,7 +97,7 @@ private function shouldSkipNode(ClassMethod $classMethod, Scope $scope): bool return true; } - if (! $this->isAttributeMethod($classMethod) && ! $this->isScopeMethod($classMethod)) { + if (! $this->isAttributeMethod($classMethod) && ! $this->scopeAnalyzer->isScopeMethod($classMethod)) { return true; } @@ -133,15 +133,4 @@ private function isAttributeMethod(ClassMethod $classMethod): bool return $this->isObjectType($classMethod->returnType, new ObjectType('Illuminate\Database\Eloquent\Casts\Attribute')); } - - private function isScopeMethod(ClassMethod $classMethod): bool - { - $name = $this->getName($classMethod); - - if ((bool) preg_match('/^scope.+$/', $name)) { - return true; - } - - return $this->phpAttributeAnalyzer->hasPhpAttribute($classMethod, 'Illuminate\Database\Eloquent\Attributes\Scope'); - } } diff --git a/src/Rector/ClassMethod/ScopeNamedClassMethodToScopeAttributedClassMethodRector.php b/src/Rector/ClassMethod/ScopeNamedClassMethodToScopeAttributedClassMethodRector.php index f0cefc07..8dff25d1 100644 --- a/src/Rector/ClassMethod/ScopeNamedClassMethodToScopeAttributedClassMethodRector.php +++ b/src/Rector/ClassMethod/ScopeNamedClassMethodToScopeAttributedClassMethodRector.php @@ -15,6 +15,7 @@ use PHPStan\Type\ObjectType; use Rector\Php80\NodeAnalyzer\PhpAttributeAnalyzer; use RectorLaravel\AbstractRector; +use RectorLaravel\NodeAnalyzer\ScopeAnalyzer; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -28,6 +29,7 @@ final class ScopeNamedClassMethodToScopeAttributedClassMethodRector extends Abst public function __construct( private readonly PhpAttributeAnalyzer $phpAttributeAnalyzer, private readonly ReflectionProvider $reflectionProvider, + private readonly ScopeAnalyzer $scopeAnalyzer, ) {} public function getRuleDefinition(): RuleDefinition @@ -87,13 +89,12 @@ public function refactor(Node $node): ?Node continue; } - $name = $this->getName($classMethod); - // make sure it starts with scope and the next character is upper case - if (! str_starts_with($name, 'scope') || ! ctype_upper(substr($name, 5, 1))) { + if (! $this->scopeAnalyzer->isNamedScope($classMethod)) { continue; } - $newName = lcfirst(str_replace('scope', '', $name)); + $name = $this->getName($classMethod); + $newName = lcfirst(substr($name, 5)); if ($classReflection->hasMethod($newName)) { continue; diff --git a/tests/Rector/ClassMethod/MakeModelAttributesAndScopesProtectedRector/Fixture/protects_scope_with_non_builder_return.php.inc b/tests/Rector/ClassMethod/MakeModelAttributesAndScopesProtectedRector/Fixture/protects_scope_with_non_builder_return.php.inc new file mode 100644 index 00000000..bfdfab31 --- /dev/null +++ b/tests/Rector/ClassMethod/MakeModelAttributesAndScopesProtectedRector/Fixture/protects_scope_with_non_builder_return.php.inc @@ -0,0 +1,33 @@ + +----- + diff --git a/tests/Rector/ClassMethod/MakeModelAttributesAndScopesProtectedRector/Fixture/skip_non_scope_signatures.php.inc b/tests/Rector/ClassMethod/MakeModelAttributesAndScopesProtectedRector/Fixture/skip_non_scope_signatures.php.inc new file mode 100644 index 00000000..2dea9756 --- /dev/null +++ b/tests/Rector/ClassMethod/MakeModelAttributesAndScopesProtectedRector/Fixture/skip_non_scope_signatures.php.inc @@ -0,0 +1,24 @@ +where('foo', true); + } + + public function scopeVoid(): void {} + + public function scopeItem(): BelongsTo + { + return $this->belongsTo(ScopeItem::class); + } +} + +?> diff --git a/tests/Rector/ClassMethod/MakeModelAttributesAndScopesProtectedRector/Fixture/skip_relation_methods.php.inc b/tests/Rector/ClassMethod/MakeModelAttributesAndScopesProtectedRector/Fixture/skip_relation_methods.php.inc new file mode 100644 index 00000000..84050356 --- /dev/null +++ b/tests/Rector/ClassMethod/MakeModelAttributesAndScopesProtectedRector/Fixture/skip_relation_methods.php.inc @@ -0,0 +1,16 @@ +hasMany(ScopeType::class); + } +} + +?> diff --git a/tests/Rector/ClassMethod/ScopeNamedClassMethodToScopeAttributedClassMethodRector/Fixture/fixture.php.inc b/tests/Rector/ClassMethod/ScopeNamedClassMethodToScopeAttributedClassMethodRector/Fixture/fixture.php.inc index 038f9403..7e83ba65 100644 --- a/tests/Rector/ClassMethod/ScopeNamedClassMethodToScopeAttributedClassMethodRector/Fixture/fixture.php.inc +++ b/tests/Rector/ClassMethod/ScopeNamedClassMethodToScopeAttributedClassMethodRector/Fixture/fixture.php.inc @@ -6,7 +6,7 @@ use Illuminate\Database\Eloquent\Model; class SomeClass extends Model { - public function scopeSomeMethod() + public function scopeSomeMethod($query) { } @@ -23,7 +23,7 @@ use Illuminate\Database\Eloquent\Model; class SomeClass extends Model { #[\Illuminate\Database\Eloquent\Attributes\Scope] - protected function someMethod() + protected function someMethod($query) { } diff --git a/tests/Rector/ClassMethod/ScopeNamedClassMethodToScopeAttributedClassMethodRector/Fixture/non_builder_return_type.php.inc b/tests/Rector/ClassMethod/ScopeNamedClassMethodToScopeAttributedClassMethodRector/Fixture/non_builder_return_type.php.inc new file mode 100644 index 00000000..312767bd --- /dev/null +++ b/tests/Rector/ClassMethod/ScopeNamedClassMethodToScopeAttributedClassMethodRector/Fixture/non_builder_return_type.php.inc @@ -0,0 +1,34 @@ + +----- + diff --git a/tests/Rector/ClassMethod/ScopeNamedClassMethodToScopeAttributedClassMethodRector/Fixture/non_duplicate_nodes.php.inc b/tests/Rector/ClassMethod/ScopeNamedClassMethodToScopeAttributedClassMethodRector/Fixture/non_duplicate_nodes.php.inc index 671fff60..833349a0 100644 --- a/tests/Rector/ClassMethod/ScopeNamedClassMethodToScopeAttributedClassMethodRector/Fixture/non_duplicate_nodes.php.inc +++ b/tests/Rector/ClassMethod/ScopeNamedClassMethodToScopeAttributedClassMethodRector/Fixture/non_duplicate_nodes.php.inc @@ -7,7 +7,7 @@ use Illuminate\Database\Eloquent\Model; class NonDuplicateAttributeNodes extends Model { #[\Illuminate\Database\Eloquent\Attributes\Scope] - protected function scopeSomeMethod() + protected function scopeSomeMethod($query) { } diff --git a/tests/Rector/ClassMethod/ScopeNamedClassMethodToScopeAttributedClassMethodRector/Fixture/non_method_duplicate.php.inc b/tests/Rector/ClassMethod/ScopeNamedClassMethodToScopeAttributedClassMethodRector/Fixture/non_method_duplicate.php.inc index 4bfd156d..5be287eb 100644 --- a/tests/Rector/ClassMethod/ScopeNamedClassMethodToScopeAttributedClassMethodRector/Fixture/non_method_duplicate.php.inc +++ b/tests/Rector/ClassMethod/ScopeNamedClassMethodToScopeAttributedClassMethodRector/Fixture/non_method_duplicate.php.inc @@ -6,7 +6,7 @@ use Illuminate\Database\Eloquent\Model; class NonDuplicateMethod extends Model { - public function scopeSomeMethod() + public function scopeSomeMethod($query) { } diff --git a/tests/Rector/ClassMethod/ScopeNamedClassMethodToScopeAttributedClassMethodRector/Fixture/scope_with_extra_parameters.php.inc b/tests/Rector/ClassMethod/ScopeNamedClassMethodToScopeAttributedClassMethodRector/Fixture/scope_with_extra_parameters.php.inc new file mode 100644 index 00000000..3249c313 --- /dev/null +++ b/tests/Rector/ClassMethod/ScopeNamedClassMethodToScopeAttributedClassMethodRector/Fixture/scope_with_extra_parameters.php.inc @@ -0,0 +1,34 @@ +where('type', $type); + } +} + +?> +----- +where('type', $type); + } +} + +?> diff --git a/tests/Rector/ClassMethod/ScopeNamedClassMethodToScopeAttributedClassMethodRector/Fixture/skip_non_builder_first_param.php.inc b/tests/Rector/ClassMethod/ScopeNamedClassMethodToScopeAttributedClassMethodRector/Fixture/skip_non_builder_first_param.php.inc new file mode 100644 index 00000000..64432963 --- /dev/null +++ b/tests/Rector/ClassMethod/ScopeNamedClassMethodToScopeAttributedClassMethodRector/Fixture/skip_non_builder_first_param.php.inc @@ -0,0 +1,14 @@ + diff --git a/tests/Rector/ClassMethod/ScopeNamedClassMethodToScopeAttributedClassMethodRector/Fixture/skip_relation_methods.php.inc b/tests/Rector/ClassMethod/ScopeNamedClassMethodToScopeAttributedClassMethodRector/Fixture/skip_relation_methods.php.inc new file mode 100644 index 00000000..dc94876f --- /dev/null +++ b/tests/Rector/ClassMethod/ScopeNamedClassMethodToScopeAttributedClassMethodRector/Fixture/skip_relation_methods.php.inc @@ -0,0 +1,16 @@ +hasMany(ScopeType::class); + } +} + +?> diff --git a/tests/Rector/ClassMethod/ScopeNamedClassMethodToScopeAttributedClassMethodRector/Fixture/typed_builder_parameter.php.inc b/tests/Rector/ClassMethod/ScopeNamedClassMethodToScopeAttributedClassMethodRector/Fixture/typed_builder_parameter.php.inc new file mode 100644 index 00000000..acc7cccb --- /dev/null +++ b/tests/Rector/ClassMethod/ScopeNamedClassMethodToScopeAttributedClassMethodRector/Fixture/typed_builder_parameter.php.inc @@ -0,0 +1,34 @@ +where('active', 1); + } +} + +?> +----- +where('active', 1); + } +} + +?>