diff --git a/build/target-repository/docs/rector_rules_overview.md b/build/target-repository/docs/rector_rules_overview.md
index a36a1c17ca8..48194ea860f 100644
--- a/build/target-repository/docs/rector_rules_overview.md
+++ b/build/target-repository/docs/rector_rules_overview.md
@@ -1,4 +1,4 @@
-# 412 Rules Overview
+# 413 Rules Overview
@@ -48,7 +48,7 @@
- [Php81](#php81) (11)
-- [Php82](#php82) (1)
+- [Php82](#php82) (2)
- [Privatization](#privatization) (8)
@@ -596,10 +596,25 @@ Change multiple null compares to ?? queue
### ConvertStaticPrivateConstantToSelfRector
-Replaces static::* access to private constants with self::* on final classes
+Replaces static::* access to private constants with self::*
+
+:wrench: **configure it!**
- class: [`Rector\CodeQuality\Rector\ClassConstFetch\ConvertStaticPrivateConstantToSelfRector`](../rules/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector.php)
+```php
+use Rector\CodeQuality\Rector\ClassConstFetch\ConvertStaticPrivateConstantToSelfRector;
+use Rector\Config\RectorConfig;
+
+return static function (RectorConfig $rectorConfig): void {
+ $rectorConfig->ruleWithConfiguration(ConvertStaticPrivateConstantToSelfRector::class, [
+ ConvertStaticPrivateConstantToSelfRector::ENABLE_FOR_NON_FINAL_CLASSES => false,
+ ]);
+};
+```
+
+↓
+
```diff
final class Foo {
private const BAR = 'bar';
@@ -6511,6 +6526,21 @@ Decorate read-only class with `readonly` attribute
+### Utf8DecodeEncodeToMbConvertEncodingRector
+
+Change deprecated utf8_decode and utf8_encode to mb_convert_encoding
+
+- class: [`Rector\Php82\Rector\FuncCall\Utf8DecodeEncodeToMbConvertEncodingRector`](../rules/Php82/Rector/FuncCall/Utf8DecodeEncodeToMbConvertEncodingRector.php)
+
+```diff
+-utf8_decode($value);
+-utf8_encode($value);
++mb_convert_encoding($value, 'ISO-8859-1');
++mb_convert_encoding($value, 'UTF-8', 'ISO-8859-1');
+```
+
+
+
## Privatization
### ChangeGlobalVariablesToPropertiesRector
diff --git a/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/ConvertStaticPrivateConstantToSelfRectorForNonFinalClassesTest.php b/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/ConvertStaticPrivateConstantToSelfRectorForNonFinalClassesTest.php
new file mode 100644
index 00000000000..d73858d4ea0
--- /dev/null
+++ b/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/ConvertStaticPrivateConstantToSelfRectorForNonFinalClassesTest.php
@@ -0,0 +1,32 @@
+doTestFile($filePath);
+ }
+
+ /**
+ * @return Iterator>
+ */
+ public function provideData(): Iterator
+ {
+ return $this->yieldFilesFromDirectory(__DIR__ . '/FixtureEnableForNonFinalClasses');
+ }
+
+ public function provideConfigFilePath(): string
+ {
+ return __DIR__ . '/config/config_non_final_classes.php';
+ }
+}
diff --git a/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/Fixture/replace-in-private-methods.php.inc b/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/Fixture/replace-in-private-methods.php.inc
new file mode 100644
index 00000000000..f7b2636e8ce
--- /dev/null
+++ b/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/Fixture/replace-in-private-methods.php.inc
@@ -0,0 +1,27 @@
+
+-----
+
diff --git a/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/FixtureEnableForNonFinalClasses/do-not-replace-if-used-in-child-classes-as-protected.php.inc b/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/FixtureEnableForNonFinalClasses/do-not-replace-if-used-in-child-classes-as-protected.php.inc
new file mode 100644
index 00000000000..a254d511f01
--- /dev/null
+++ b/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/FixtureEnableForNonFinalClasses/do-not-replace-if-used-in-child-classes-as-protected.php.inc
@@ -0,0 +1,18 @@
+
diff --git a/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/FixtureEnableForNonFinalClasses/do-not-replace-if-used-in-child-classes-as-public.php.inc b/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/FixtureEnableForNonFinalClasses/do-not-replace-if-used-in-child-classes-as-public.php.inc
new file mode 100644
index 00000000000..948655afbe8
--- /dev/null
+++ b/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/FixtureEnableForNonFinalClasses/do-not-replace-if-used-in-child-classes-as-public.php.inc
@@ -0,0 +1,18 @@
+
diff --git a/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/FixtureEnableForNonFinalClasses/replace-if-not-used-in-child-classes.php.inc b/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/FixtureEnableForNonFinalClasses/replace-if-not-used-in-child-classes.php.inc
new file mode 100644
index 00000000000..68075fb21f3
--- /dev/null
+++ b/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/FixtureEnableForNonFinalClasses/replace-if-not-used-in-child-classes.php.inc
@@ -0,0 +1,33 @@
+
+-----
+
diff --git a/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/config/config_non_final_classes.php b/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/config/config_non_final_classes.php
new file mode 100644
index 00000000000..62d719faef2
--- /dev/null
+++ b/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/config/config_non_final_classes.php
@@ -0,0 +1,16 @@
+ruleWithConfiguration(
+ ConvertStaticPrivateConstantToSelfRector::class,
+ [
+ ConvertStaticPrivateConstantToSelfRector::ENABLE_FOR_NON_FINAL_CLASSES => true,
+ ]
+ );
+};
diff --git a/rules/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector.php b/rules/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector.php
index e5ec5179cf6..ed646b104d6 100644
--- a/rules/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector.php
+++ b/rules/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector.php
@@ -9,8 +9,12 @@
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Class_;
+use PHPStan\Reflection\ClassReflection;
+use Rector\Core\Contract\Rector\AllowEmptyConfigurableRectorInterface;
use Rector\Core\Rector\AbstractRector;
-use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
+use Rector\Core\Reflection\ReflectionResolver;
+use Rector\FamilyTree\Reflection\FamilyRelationsAnalyzer;
+use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
@@ -18,14 +22,28 @@
* @see https://3v4l.org/8Y0ba
* @see https://phpstan.org/r/11d4c850-1a40-4fae-b665-291f96104d11
*/
-final class ConvertStaticPrivateConstantToSelfRector extends AbstractRector
+final class ConvertStaticPrivateConstantToSelfRector extends AbstractRector implements AllowEmptyConfigurableRectorInterface
{
+ /**
+ * @api
+ * @var string
+ */
+ public const ENABLE_FOR_NON_FINAL_CLASSES = 'enable_for_non_final_classes';
+
+ private bool $enableForNonFinalClasses = false;
+
+ public function __construct(
+ private readonly FamilyRelationsAnalyzer $familyRelationsAnalyzer,
+ private readonly ReflectionResolver $reflectionResolver,
+ ) {
+ }
+
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
- 'Replaces static::* access to private constants with self::* on final classes',
+ 'Replaces static::* access to private constants with self::*',
[
- new CodeSample(
+ new ConfiguredCodeSample(
<<<'CODE_SAMPLE'
final class Foo {
private const BAR = 'bar';
@@ -46,6 +64,9 @@ public function run()
}
CODE_SAMPLE
,
+ [
+ self::ENABLE_FOR_NON_FINAL_CLASSES => false,
+ ],
),
],
);
@@ -56,16 +77,24 @@ public function getNodeTypes(): array
return [ClassConstFetch::class];
}
+ public function configure(array $configuration): void
+ {
+ $this->enableForNonFinalClasses = $configuration[self::ENABLE_FOR_NON_FINAL_CLASSES] ?? (bool) current(
+ $configuration
+ );
+ }
+
/**
* @param ClassConstFetch $node
*/
public function refactor(Node $node): ?ClassConstFetch
{
- if (! $this->isUsingStatic($node)) {
+ $class = $this->betterNodeFinder->findParentType($node, Class_::class);
+ if (! $class instanceof Class_) {
return null;
}
- if (! $this->isPrivateConstant($node)) {
+ if ($this->shouldBeSkipped($class, $node)) {
return null;
}
@@ -83,30 +112,82 @@ private function isUsingStatic(ClassConstFetch $classConstFetch): bool
return $classConstFetch->class->toString() === 'static';
}
- private function isPrivateConstant(ClassConstFetch $classConstFetch): bool
+ private function isPrivateConstant(ClassConstFetch $constant, Class_ $class): bool
{
- $class = $this->betterNodeFinder->findParentType($classConstFetch, Class_::class);
- if (! $class instanceof Class_) {
+ $constantName = $this->getConstantName($constant);
+ if ($constantName === null) {
return false;
}
+ foreach ($class->getConstants() as $classConst) {
+ if (! $this->nodeNameResolver->isName($classConst, $constantName)) {
+ continue;
+ }
+
+ return $classConst->isPrivate();
+ }
+
+ return false;
+ }
+
+ private function isUsedInPrivateMethod(ClassConstFetch $node): bool
+ {
+ $method = $this->betterNodeFinder->findParentType($node, Node\Stmt\ClassMethod::class);
- if (! $class->isFinal()) {
+ if (! $method instanceof Node\Stmt\ClassMethod) {
return false;
}
- $constantName = $classConstFetch->name;
- if (! $constantName instanceof Identifier) {
+ return $method->flags === Class_::MODIFIER_PRIVATE;
+ }
+
+ private function shouldBeSkipped(Class_ $class, ClassConstFetch $classConstFetch): bool
+ {
+ if (! $this->isUsingStatic($classConstFetch)) {
+ return true;
+ }
+ if (! $this->isPrivateConstant($classConstFetch, $class)) {
+ return true;
+ }
+ if ($this->isUsedInPrivateMethod($classConstFetch)) {
return false;
}
- foreach ($class->getConstants() as $classConst) {
- if (! $this->nodeNameResolver->isName($classConst, $constantName->toString())) {
- continue;
- }
+ if ($this->enableForNonFinalClasses) {
+ return $this->isOverwrittenInChildClass($classConstFetch);
+ }
- return $classConst->isPrivate();
+ return ! $class->isFinal();
+ }
+
+ private function isOverwrittenInChildClass(ClassConstFetch $classConstFetch): bool
+ {
+ $constantName = $this->getConstantName($classConstFetch);
+ if ($constantName === null) {
+ return false;
+ }
+
+ $classReflection = $this->reflectionResolver->resolveClassReflection($classConstFetch);
+ if (! $classReflection instanceof ClassReflection) {
+ return false;
+ }
+ $childrenClassReflections = $this->familyRelationsAnalyzer->getChildrenOfClassReflection($classReflection);
+
+ foreach ($childrenClassReflections as $childrenClassReflection) {
+ if ($childrenClassReflection->hasConstant($constantName)) {
+ return true;
+ }
}
return false;
}
+
+ private function getConstantName(ClassConstFetch $classConstFetch): ?string
+ {
+ $constantNameIdentifier = $classConstFetch->name;
+ if (! $constantNameIdentifier instanceof Identifier) {
+ return null;
+ }
+
+ return $constantNameIdentifier->toString();
+ }
}