Skip to content

Commit e6ceac6

Browse files
committed
feat: allow user to skip privatization for specific methods/properties
1 parent 3882cb5 commit e6ceac6

File tree

8 files changed

+273
-7
lines changed

8 files changed

+273
-7
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Tests\Privatization\Rector\ClassMethod\PrivatizeFinalClassMethodRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class CallbackTest extends AbstractRectorTestCase
12+
{
13+
#[DataProvider('provideData')]
14+
public function test(string $filePath): void
15+
{
16+
$this->doTestFile($filePath);
17+
}
18+
19+
public static function provideData(): Iterator
20+
{
21+
return self::yieldFilesFromDirectory(__DIR__ . '/FixtureCallback');
22+
}
23+
24+
public function provideConfigFilePath(): string
25+
{
26+
return __DIR__ . '/config/configured_rule_callback.php';
27+
}
28+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace Rector\Tests\Privatization\Rector\ClassMethod\PrivatizeFinalClassMethodRector\Fixture;
4+
5+
final class FinalClassWithProtectedMethods
6+
{
7+
protected function someMethod() {}
8+
9+
protected function shouldBeProtected() {}
10+
}
11+
12+
?>
13+
-----
14+
<?php
15+
16+
namespace Rector\Tests\Privatization\Rector\ClassMethod\PrivatizeFinalClassMethodRector\Fixture;
17+
18+
final class FinalClassWithProtectedMethods
19+
{
20+
private function someMethod() {}
21+
22+
protected function shouldBeProtected() {}
23+
}
24+
25+
?>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use PHPStan\Reflection\ClassReflection;
6+
use PhpParser\Node\Stmt\ClassMethod;
7+
use Rector\Config\RectorConfig;
8+
use Rector\Privatization\Rector\ClassMethod\PrivatizeFinalClassMethodRector;
9+
10+
return RectorConfig::configure()
11+
->withConfiguredRule(PrivatizeFinalClassMethodRector::class, [
12+
PrivatizeFinalClassMethodRector::SHOULD_SKIP_CALLBACK => static function (
13+
ClassMethod $classMethod,
14+
ClassReflection $classReflection,
15+
): bool {
16+
if (! str_contains($classReflection->getName(), 'FinalClassWithProtectedMethods')) {
17+
return false;
18+
}
19+
20+
21+
return str_contains($classMethod->name->toString(), 'shouldBeProtected');
22+
},
23+
]);
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Tests\Privatization\Rector\Property\PrivatizeFinalClassPropertyRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class CallbackTest extends AbstractRectorTestCase
12+
{
13+
#[DataProvider('provideData')]
14+
public function test(string $filePath): void
15+
{
16+
$this->doTestFile($filePath);
17+
}
18+
19+
public static function provideData(): Iterator
20+
{
21+
return self::yieldFilesFromDirectory(__DIR__ . '/FixtureCallback');
22+
}
23+
24+
public function provideConfigFilePath(): string
25+
{
26+
return __DIR__ . '/config/configured_rule_callback.php';
27+
}
28+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace Rector\Tests\Privatization\Rector\Property\PrivatizeFinalClassPropertyRector\Fixture;
4+
5+
final class FinalClassWithProtectedProperties
6+
{
7+
protected string $value;
8+
protected string $shouldBeProtected;
9+
10+
public function __construct(
11+
protected string $bar,
12+
protected string $shouldBeProtectedAlso,
13+
) {}
14+
}
15+
16+
?>
17+
-----
18+
<?php
19+
20+
namespace Rector\Tests\Privatization\Rector\Property\PrivatizeFinalClassPropertyRector\Fixture;
21+
22+
final class FinalClassWithProtectedProperties
23+
{
24+
private string $value;
25+
protected string $shouldBeProtected;
26+
27+
public function __construct(
28+
private string $bar,
29+
protected string $shouldBeProtectedAlso,
30+
) {}
31+
}
32+
33+
?>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use PhpParser\Node\Stmt\Property;
6+
use PHPStan\Reflection\ClassReflection;
7+
use Rector\Config\RectorConfig;
8+
use Rector\Privatization\Rector\Property\PrivatizeFinalClassPropertyRector;
9+
10+
return RectorConfig::configure()
11+
->withConfiguredRule(PrivatizeFinalClassPropertyRector::class, [
12+
PrivatizeFinalClassPropertyRector::SHOULD_SKIP_CALLBACK => static function (
13+
Property|string $property,
14+
ClassReflection $classReflection,
15+
): bool {
16+
if (! str_contains($classReflection->getName(), 'FinalClassWithProtectedProperties')) {
17+
return false;
18+
}
19+
20+
$name = $property instanceof Property
21+
? (string) $property->props[0]->name
22+
: $property;
23+
24+
return str_contains($name, 'shouldBeProtected');
25+
},
26+
]);

rules/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector.php

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,33 @@
99
use PhpParser\Node\Stmt\Class_;
1010
use PhpParser\Node\Stmt\ClassMethod;
1111
use PHPStan\Reflection\ClassReflection;
12+
use Rector\Contract\Rector\ConfigurableRectorInterface;
1213
use Rector\PhpParser\Node\BetterNodeFinder;
1314
use Rector\PHPStan\ScopeFetcher;
1415
use Rector\Privatization\Guard\OverrideByParentClassGuard;
1516
use Rector\Privatization\NodeManipulator\VisibilityManipulator;
1617
use Rector\Privatization\VisibilityGuard\ClassMethodVisibilityGuard;
1718
use Rector\Rector\AbstractRector;
18-
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
19+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
1920
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
2021

2122
/**
2223
* @see \Rector\Tests\Privatization\Rector\ClassMethod\PrivatizeFinalClassMethodRector\PrivatizeFinalClassMethodRectorTest
24+
* @see \Rector\Tests\Privatization\Rector\ClassMethod\PrivatizeFinalClassMethodRector\CallbackTest
2325
*/
24-
final class PrivatizeFinalClassMethodRector extends AbstractRector
26+
final class PrivatizeFinalClassMethodRector extends AbstractRector implements ConfigurableRectorInterface
2527
{
28+
/**
29+
* @api
30+
* @var string
31+
*/
32+
public const SHOULD_SKIP_CALLBACK = 'should_skip_callback';
33+
34+
/**
35+
* @var ?callable(ClassMethod, ClassReflection): bool
36+
*/
37+
private $shouldSkipCallback = null;
38+
2639
public function __construct(
2740
private readonly ClassMethodVisibilityGuard $classMethodVisibilityGuard,
2841
private readonly VisibilityManipulator $visibilityManipulator,
@@ -36,8 +49,14 @@ public function getRuleDefinition(): RuleDefinition
3649
return new RuleDefinition(
3750
'Change protected class method to private if possible',
3851
[
39-
new CodeSample(
52+
new ConfiguredCodeSample(
4053
<<<'CODE_SAMPLE'
54+
final class SomeOtherClass
55+
{
56+
protected function someMethod()
57+
{
58+
}
59+
}
4160
final class SomeClass
4261
{
4362
protected function someMethod()
@@ -47,18 +66,41 @@ protected function someMethod()
4766
CODE_SAMPLE
4867
,
4968
<<<'CODE_SAMPLE'
69+
final class SomeOtherClass
70+
{
71+
protected function someMethod()
72+
{
73+
}
74+
}
5075
final class SomeClass
5176
{
5277
private function someMethod()
5378
{
5479
}
5580
}
5681
CODE_SAMPLE
82+
,
83+
[
84+
self::SHOULD_SKIP_CALLBACK => static function (
85+
ClassMethod $classMethod,
86+
ClassReflection $classReflection,
87+
): bool {
88+
return $classReflection->is('SomeOtherClass');
89+
},
90+
],
5791
),
5892
]
5993
);
6094
}
6195

96+
/**
97+
* @param mixed[] $configuration
98+
*/
99+
public function configure(array $configuration): void
100+
{
101+
$this->shouldSkipCallback = $configuration[self::SHOULD_SKIP_CALLBACK] ?? null;
102+
}
103+
62104
/**
63105
* @return array<class-string<Node>>
64106
*/
@@ -107,6 +149,13 @@ public function refactor(Node $node): ?Node
107149
continue;
108150
}
109151

152+
if (
153+
is_callable($this->shouldSkipCallback)
154+
&& call_user_func($this->shouldSkipCallback, $classMethod, $classReflection)
155+
) {
156+
continue;
157+
}
158+
110159
$this->visibilityManipulator->makePrivate($classMethod);
111160
$hasChanged = true;
112161
}

rules/Privatization/Rector/Property/PrivatizeFinalClassPropertyRector.php

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,33 @@
99
use PhpParser\Node\Stmt\ClassMethod;
1010
use PhpParser\Node\Stmt\Property;
1111
use PHPStan\Reflection\ClassReflection;
12+
use Rector\Contract\Rector\ConfigurableRectorInterface;
1213
use Rector\Privatization\Guard\ParentPropertyLookupGuard;
1314
use Rector\Privatization\NodeManipulator\VisibilityManipulator;
1415
use Rector\Rector\AbstractRector;
1516
use Rector\Reflection\ReflectionResolver;
1617
use Rector\ValueObject\MethodName;
1718
use Rector\ValueObject\Visibility;
18-
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
19+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
1920
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
2021

2122
/**
2223
* @see \Rector\Tests\Privatization\Rector\Property\PrivatizeFinalClassPropertyRector\PrivatizeFinalClassPropertyRectorTest
24+
* @see \Rector\Tests\Privatization\Rector\Property\PrivatizeFinalClassPropertyRector\CallbackTest
2325
*/
24-
final class PrivatizeFinalClassPropertyRector extends AbstractRector
26+
final class PrivatizeFinalClassPropertyRector extends AbstractRector implements ConfigurableRectorInterface
2527
{
28+
/**
29+
* @api
30+
* @var string
31+
*/
32+
public const SHOULD_SKIP_CALLBACK = 'should_skip_callback';
33+
34+
/**
35+
* @var ?callable(Property|string, ClassReflection): bool
36+
*/
37+
private $shouldSkipCallback = null;
38+
2639
public function __construct(
2740
private readonly VisibilityManipulator $visibilityManipulator,
2841
private readonly ParentPropertyLookupGuard $parentPropertyLookupGuard,
@@ -33,24 +46,49 @@ public function __construct(
3346
public function getRuleDefinition(): RuleDefinition
3447
{
3548
return new RuleDefinition('Change property to private if possible', [
36-
new CodeSample(
49+
new ConfiguredCodeSample(
3750
<<<'CODE_SAMPLE'
51+
final class SomeOtherClass
52+
{
53+
protected $value;
54+
}
3855
final class SomeClass
3956
{
4057
protected $value;
4158
}
4259
CODE_SAMPLE
4360
,
4461
<<<'CODE_SAMPLE'
62+
final class SomeOtherClass
63+
{
64+
protected $value;
65+
}
4566
final class SomeClass
4667
{
4768
private $value;
4869
}
4970
CODE_SAMPLE
71+
,
72+
[
73+
self::SHOULD_SKIP_CALLBACK => static function (
74+
Property|string $property,
75+
ClassReflection $classReflection,
76+
): bool {
77+
return $classReflection->is('SomeOtherClass');
78+
},
79+
],
5080
),
5181
]);
5282
}
5383

84+
/**
85+
* @param mixed[] $configuration
86+
*/
87+
public function configure(array $configuration): void
88+
{
89+
$this->shouldSkipCallback = $configuration[self::SHOULD_SKIP_CALLBACK] ?? null;
90+
}
91+
5492
/**
5593
* @return array<class-string<Node>>
5694
*/
@@ -84,6 +122,13 @@ public function refactor(Node $node): ?Node
84122
continue;
85123
}
86124

125+
if (
126+
is_callable($this->shouldSkipCallback)
127+
&& call_user_func($this->shouldSkipCallback, $property, $classReflection)
128+
) {
129+
continue;
130+
}
131+
87132
$this->visibilityManipulator->makePrivate($property);
88133
$hasChanged = true;
89134
}
@@ -99,7 +144,16 @@ public function refactor(Node $node): ?Node
99144
continue;
100145
}
101146

102-
if (! $this->parentPropertyLookupGuard->isLegal((string) $this->getName($param), $classReflection)) {
147+
$property = (string) $this->getName($param);
148+
149+
if (! $this->parentPropertyLookupGuard->isLegal($property, $classReflection)) {
150+
continue;
151+
}
152+
153+
if (
154+
is_callable($this->shouldSkipCallback)
155+
&& call_user_func($this->shouldSkipCallback, $property, $classReflection)
156+
) {
103157
continue;
104158
}
105159

0 commit comments

Comments
 (0)