Skip to content

Commit bd73911

Browse files
committed
chore: move laravel model logic to a separate service
1 parent a563ce7 commit bd73911

2 files changed

Lines changed: 87 additions & 56 deletions

File tree

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Privatization\Guard;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Stmt\ClassMethod;
9+
use PHPStan\Reflection\ClassReflection;
10+
use PHPStan\Type\ObjectType;
11+
use Rector\NodeNameResolver\NodeNameResolver;
12+
use Rector\NodeTypeResolver\NodeTypeResolver;
13+
use Rector\Php80\NodeAnalyzer\PhpAttributeAnalyzer;
14+
use Rector\Util\StringUtils;
15+
16+
/**
17+
* Guards against privatizing Laravel model attributes and scopes
18+
*/
19+
final readonly class LaravelModelGuard
20+
{
21+
/**
22+
* @var string
23+
* @see https://regex101.com/r/Dx0WN5/2
24+
*/
25+
private const LARAVEL_MODEL_ATTRIBUTE_REGEX = '#^[gs]et.+Attribute$#';
26+
27+
/**
28+
* @var string
29+
* @see https://regex101.com/r/hxOGeN/2
30+
*/
31+
private const LARAVEL_MODEL_SCOPE_REGEX = '#^scope.+$#';
32+
33+
public function __construct(
34+
private PhpAttributeAnalyzer $phpAttributeAnalyzer,
35+
private NodeNameResolver $nodeNameResolver,
36+
private NodeTypeResolver $nodeTypeResolver,
37+
) {
38+
}
39+
40+
public function isProtectedMethod(ClassReflection $classReflection, ClassMethod $classMethod): bool
41+
{
42+
if (! $classReflection->is('Illuminate\Database\Eloquent\Model')) {
43+
return false;
44+
}
45+
46+
$name = (string) $this->nodeNameResolver->getName($classMethod->name);
47+
48+
return $this->isAttributeMethod($name, $classMethod)
49+
|| $this->isScopeMethod($name, $classMethod);
50+
}
51+
52+
private function isAttributeMethod(string $name, ClassMethod $classMethod): bool
53+
{
54+
if (StringUtils::isMatch($name, self::LARAVEL_MODEL_ATTRIBUTE_REGEX)) {
55+
return true;
56+
}
57+
58+
if (! $classMethod->returnType instanceof Node) {
59+
return false;
60+
}
61+
62+
return $this->nodeTypeResolver->isObjectType(
63+
$classMethod->returnType,
64+
new ObjectType('Illuminate\Database\Eloquent\Casts\Attribute')
65+
);
66+
}
67+
68+
private function isScopeMethod(string $name, ClassMethod $classMethod): bool
69+
{
70+
if (StringUtils::isMatch($name, self::LARAVEL_MODEL_SCOPE_REGEX)) {
71+
return true;
72+
}
73+
74+
return $this->phpAttributeAnalyzer->hasPhpAttribute(
75+
$classMethod,
76+
'Illuminate\Database\Eloquent\Attributes\Scope'
77+
);
78+
}
79+
}

rules/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector.php

Lines changed: 8 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,13 @@
99
use PhpParser\Node\Stmt\Class_;
1010
use PhpParser\Node\Stmt\ClassMethod;
1111
use PHPStan\Reflection\ClassReflection;
12-
use PHPStan\Type\ObjectType;
13-
use Rector\Php80\NodeAnalyzer\PhpAttributeAnalyzer;
1412
use Rector\PhpParser\Node\BetterNodeFinder;
1513
use Rector\PHPStan\ScopeFetcher;
14+
use Rector\Privatization\Guard\LaravelModelGuard;
1615
use Rector\Privatization\Guard\OverrideByParentClassGuard;
1716
use Rector\Privatization\NodeManipulator\VisibilityManipulator;
1817
use Rector\Privatization\VisibilityGuard\ClassMethodVisibilityGuard;
1918
use Rector\Rector\AbstractRector;
20-
use Rector\Util\StringUtils;
2119
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
2220
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
2321

@@ -26,24 +24,12 @@
2624
*/
2725
final class PrivatizeFinalClassMethodRector extends AbstractRector
2826
{
29-
/**
30-
* @var string
31-
* @see https://regex101.com/r/Dx0WN5/2
32-
*/
33-
private const LARAVEL_MODEL_ATTRIBUTE_REGEX = '/^[gs]et.+Attribute$/';
34-
35-
/**
36-
* @var string
37-
* @see https://regex101.com/r/hxOGeN/2
38-
*/
39-
private const LARAVEL_MODEL_SCOPE_REGEX = '/^scope.+$/';
40-
4127
public function __construct(
4228
private readonly ClassMethodVisibilityGuard $classMethodVisibilityGuard,
4329
private readonly VisibilityManipulator $visibilityManipulator,
4430
private readonly OverrideByParentClassGuard $overrideByParentClassGuard,
4531
private readonly BetterNodeFinder $betterNodeFinder,
46-
private readonly PhpAttributeAnalyzer $phpAttributeAnalyzer,
32+
private readonly LaravelModelGuard $laravelModelGuard,
4733
) {
4834
}
4935

@@ -105,7 +91,11 @@ public function refactor(Node $node): ?Node
10591
$hasChanged = false;
10692

10793
foreach ($node->getMethods() as $classMethod) {
108-
if ($this->shouldSkipClassMethod($classReflection, $classMethod)) {
94+
if ($this->shouldSkipClassMethod($classMethod)) {
95+
continue;
96+
}
97+
98+
if ($this->laravelModelGuard->isProtectedMethod($classReflection, $classMethod)) {
10999
continue;
110100
}
111101

@@ -134,7 +124,7 @@ public function refactor(Node $node): ?Node
134124
return null;
135125
}
136126

137-
private function shouldSkipClassMethod(ClassReflection $classReflection, ClassMethod $classMethod): bool
127+
private function shouldSkipClassMethod(ClassMethod $classMethod): bool
138128
{
139129
// edge case in nette framework
140130
/** @var string $methodName */
@@ -151,10 +141,6 @@ private function shouldSkipClassMethod(ClassReflection $classReflection, ClassMe
151141
return true;
152142
}
153143

154-
if ($this->shouldSkipClassMethodLaravel($classReflection, $classMethod)) {
155-
return true;
156-
}
157-
158144
// if has parent call, its probably overriding parent one → skip it
159145
$hasParentCall = (bool) $this->betterNodeFinder->findFirst(
160146
(array) $classMethod->stmts,
@@ -169,38 +155,4 @@ function (Node $node): bool {
169155

170156
return $hasParentCall;
171157
}
172-
173-
private function shouldSkipClassMethodLaravel(ClassReflection $classReflection, ClassMethod $classMethod): bool
174-
{
175-
if (! $classReflection->is('Illuminate\Database\Eloquent\Model')) {
176-
return false;
177-
}
178-
179-
$name = (string) $this->getName($classMethod->name);
180-
$returnType = $classMethod->returnType;
181-
182-
// Model attributes should be protected
183-
if (
184-
StringUtils::isMatch($name, self::LARAVEL_MODEL_ATTRIBUTE_REGEX)
185-
|| ($returnType instanceof Node && $this->isObjectType(
186-
$returnType,
187-
new ObjectType('Illuminate\Database\Eloquent\Casts\Attribute')
188-
))
189-
) {
190-
return true;
191-
}
192-
193-
// Model scopes should be protected
194-
if (
195-
StringUtils::isMatch($name, self::LARAVEL_MODEL_SCOPE_REGEX)
196-
|| $this->phpAttributeAnalyzer->hasPhpAttribute(
197-
$classMethod,
198-
'Illuminate\Database\Eloquent\Attributes\Scope'
199-
)
200-
) {
201-
return true;
202-
}
203-
204-
return false;
205-
}
206158
}

0 commit comments

Comments
 (0)