Skip to content

Commit 4b78d12

Browse files
[fix] Fix PrivatizeFinalClassMethodRector in case of parent dynamic call (#7730)
* [fix] Fix PrivatizeFinalClassMethodRector in case of parent dynamic call * [ci-review] Rector Rectify --------- Co-authored-by: GitHub Action <[email protected]>
1 parent 885ba02 commit 4b78d12

File tree

3 files changed

+107
-0
lines changed

3 files changed

+107
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Rector\Tests\Privatization\Rector\ClassMethod\PrivatizeFinalClassMethodRector\Fixture;
4+
5+
use PhpParser\PrettyPrinter\Standard;
6+
7+
final class KeepMagicParentCalledMethod extends Standard
8+
{
9+
// called from parent by $this->{'p'} method
10+
protected function pFileNode($fileNode)
11+
{
12+
}
13+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Privatization\Guard;
6+
7+
use PhpParser\PrettyPrinterAbstract;
8+
use PhpParser\Node\Expr;
9+
use PhpParser\Node\Expr\MethodCall;
10+
use PhpParser\Node\Name;
11+
use PhpParser\Node\Stmt\Class_;
12+
use PhpParser\PrettyPrinter\Standard;
13+
use Rector\NodeNameResolver\NodeNameResolver;
14+
use Rector\PhpParser\AstResolver;
15+
use Rector\PhpParser\Node\BetterNodeFinder;
16+
17+
final class ParentClassMagicCallGuard
18+
{
19+
/**
20+
* To speed up analysis
21+
* @var string[]
22+
*/
23+
private const KNOWN_DYNAMIC_CALL_CLASSES = [Standard::class, PrettyPrinterAbstract::class];
24+
25+
/**
26+
* @var array<string, bool>
27+
*/
28+
private array $cachedContainsByClassName = [];
29+
30+
public function __construct(
31+
private readonly NodeNameResolver $nodeNameResolver,
32+
private readonly AstResolver $astResolver,
33+
private readonly BetterNodeFinder $betterNodeFinder
34+
) {
35+
foreach (self::KNOWN_DYNAMIC_CALL_CLASSES as $knownDynamicCallClass) {
36+
$this->cachedContainsByClassName[$knownDynamicCallClass] = true;
37+
}
38+
}
39+
40+
/**
41+
* E.g. parent class has $this->{$magicName} call that might call the protected method
42+
* If we make it private, it will break the code
43+
*/
44+
public function containsParentClassMagicCall(Class_ $class): bool
45+
{
46+
if (! $class->extends instanceof Name) {
47+
return false;
48+
}
49+
50+
// cache as heavy AST parsing here
51+
$className = $this->nodeNameResolver->getName($class);
52+
53+
if (isset($this->cachedContainsByClassName[$className])) {
54+
return $this->cachedContainsByClassName[$className];
55+
}
56+
57+
$parentClassName = $this->nodeNameResolver->getName($class->extends);
58+
if (isset($this->cachedContainsByClassName[$parentClassName])) {
59+
return $this->cachedContainsByClassName[$parentClassName];
60+
}
61+
62+
$parentClass = $this->astResolver->resolveClassFromName($parentClassName);
63+
if (! $parentClass instanceof Class_) {
64+
$this->cachedContainsByClassName[$parentClassName] = false;
65+
return false;
66+
}
67+
68+
foreach ($parentClass->getMethods() as $classMethod) {
69+
if ($classMethod->isAbstract()) {
70+
continue;
71+
}
72+
73+
/** @var MethodCall[] $methodCalls */
74+
$methodCalls = $this->betterNodeFinder->findInstancesOfScoped(
75+
(array) $classMethod->stmts,
76+
MethodCall::class
77+
);
78+
foreach ($methodCalls as $methodCall) {
79+
if ($methodCall->name instanceof Expr) {
80+
$this->cachedContainsByClassName[$parentClassName] = true;
81+
return true;
82+
}
83+
}
84+
}
85+
86+
return $this->containsParentClassMagicCall($parentClass);
87+
}
88+
}

rules/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Rector\PHPStan\ScopeFetcher;
1414
use Rector\Privatization\Guard\LaravelModelGuard;
1515
use Rector\Privatization\Guard\OverrideByParentClassGuard;
16+
use Rector\Privatization\Guard\ParentClassMagicCallGuard;
1617
use Rector\Privatization\NodeManipulator\VisibilityManipulator;
1718
use Rector\Privatization\VisibilityGuard\ClassMethodVisibilityGuard;
1819
use Rector\Rector\AbstractRector;
@@ -30,6 +31,7 @@ public function __construct(
3031
private readonly OverrideByParentClassGuard $overrideByParentClassGuard,
3132
private readonly BetterNodeFinder $betterNodeFinder,
3233
private readonly LaravelModelGuard $laravelModelGuard,
34+
private readonly ParentClassMagicCallGuard $parentClassMagicCallGuard,
3335
) {
3436
}
3537

@@ -113,6 +115,10 @@ public function refactor(Node $node): ?Node
113115
continue;
114116
}
115117

118+
if ($this->parentClassMagicCallGuard->containsParentClassMagicCall($node)) {
119+
continue;
120+
}
121+
116122
$this->visibilityManipulator->makePrivate($classMethod);
117123
$hasChanged = true;
118124
}

0 commit comments

Comments
 (0)