Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/set/solid/solid.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ services:

Rector\SOLID\Rector\ClassConst\PrivatizeLocalClassConstantRector: null
Rector\SOLID\Rector\Class_\MakeUnusedClassesWithChildrenAbstractRector: null
Rector\SOLID\Rector\Property\ChangeReadOnlyPropertyWithDefaultValueToConstantRector: null
32 changes: 31 additions & 1 deletion docs/AllRectorsOverview.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# All 450 Rectors Overview
# All 451 Rectors Overview

- [Projects](#projects)
- [General](#general)
Expand Down Expand Up @@ -7662,6 +7662,36 @@ Change nested ifs to early return

<br>

### `ChangeReadOnlyPropertyWithDefaultValueToConstantRector`

- class: `Rector\SOLID\Rector\Property\ChangeReadOnlyPropertyWithDefaultValueToConstantRector`

Change property with read only status with default value to constant

```diff
class SomeClass
{
/**
* @var string[]
*/
- private $magicMethods = [
+ private const MAGIC_METHODS = [
'__toString',
'__wakeup',
];

public function run()
{
- foreach ($this->magicMethods as $magicMethod) {
+ foreach (self::MAGIC_METHODS as $magicMethod) {
echo $magicMethod;
}
}
}
```

<br>

### `FinalizeClassesWithoutChildrenRector`

- class: `Rector\SOLID\Rector\Class_\FinalizeClassesWithoutChildrenRector`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
<?php

declare(strict_types=1);

namespace Rector\SOLID\Rector\Property;

use PhpParser\Node;
use PhpParser\Node\Const_ as ConstConst;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\ClassConst;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\PropertyProperty;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\PhpParser\Node\Manipulator\PropertyManipulator;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\Core\Util\RectorStrings;
use Rector\NodeTypeResolver\Node\AttributeKey;

/**
* @see \Rector\SOLID\Tests\Rector\Property\ChangeReadOnlyPropertyWithDefaultValueToConstantRector\ChangeReadOnlyPropertyWithDefaultValueToConstantRectorTest
*/
final class ChangeReadOnlyPropertyWithDefaultValueToConstantRector extends AbstractRector
{
/**
* @var PropertyManipulator
*/
private $propertyManipulator;

public function __construct(PropertyManipulator $propertyManipulator)
{
$this->propertyManipulator = $propertyManipulator;
}

public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Change property with read only status with default value to constant', [
new CodeSample(
<<<'PHP'
class SomeClass
{
/**
* @var string[]
*/
private $magicMethods = [
'__toString',
'__wakeup',
];

public function run()
{
foreach ($this->magicMethods as $magicMethod) {
echo $magicMethod;
}
}
}
PHP
,
<<<'PHP'
class SomeClass
{
/**
* @var string[]
*/
private const MAGIC_METHODS = [
'__toString',
'__wakeup',
];

public function run()
{
foreach (self::MAGIC_METHODS as $magicMethod) {
echo $magicMethod;
}
}
}
PHP

),
]);
}

/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Property::class];
}

/**
* @param Property $node
*/
public function refactor(Node $node): ?Node
{
if (count($node->props) !== 1) {
return null;
}

/** @var PropertyProperty $onlyProperty */
$onlyProperty = $node->props[0];

// we need default value
if ($onlyProperty->default === null) {
return null;
}

if (! $node->isPrivate()) {
return null;
}

// is property read only?
if (! $this->propertyManipulator->isReadyOnlyProperty($onlyProperty)) {
return null;
}

$this->replacePropertyFetchWithClassConstFetch($node, $onlyProperty);

return $this->createClassConst($node, $onlyProperty);
}

private function createClassConst(Property $property, PropertyProperty $propertyProperty): ClassConst
{
$constantName = $this->createConstantNameFromProperty($propertyProperty);

/** @var Expr $defaultValue */
$defaultValue = $propertyProperty->default;
$constant = new ConstConst($constantName, $defaultValue);

$classConst = new ClassConst([$constant]);
$classConst->flags = $property->flags;
$classConst->setAttribute(AttributeKey::PHP_DOC_INFO, $property->getAttribute(AttributeKey::PHP_DOC_INFO));

return $classConst;
}

private function createConstantNameFromProperty(PropertyProperty $propertyProperty): string
{
$propertyName = $this->getName($propertyProperty);
$constantName = RectorStrings::camelCaseToUnderscore($propertyName);

return strtoupper($constantName);
}

private function replacePropertyFetchWithClassConstFetch(Node $node, PropertyProperty $propertyProperty): void
{
$classNode = $node->getAttribute(AttributeKey::CLASS_NODE);
if ($classNode === null) {
throw new ShouldNotHappenException();
}

$propertyName = $this->getName($propertyProperty);
$constantName = $this->createConstantNameFromProperty($propertyProperty);

$this->traverseNodesWithCallable($classNode, function (Node $node) use ($propertyName, $constantName) {
if (! $node instanceof PropertyFetch) {
return null;
}

if (! $this->isName($node->var, 'this')) {
return null;
}

if (! $this->isName($node->name, $propertyName)) {
return null;
}

// replace with constant fetch
return new ClassConstFetch(new Name('self'), $constantName);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Rector\SOLID\Tests\Rector\Property\ChangeReadOnlyPropertyWithDefaultValueToConstantRector;

use Iterator;
use Rector\Core\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\SOLID\Rector\Property\ChangeReadOnlyPropertyWithDefaultValueToConstantRector;

final class ChangeReadOnlyPropertyWithDefaultValueToConstantRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(string $file): void
{
$this->doTestFile($file);
}

public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}

protected function getRectorClass(): string
{
return ChangeReadOnlyPropertyWithDefaultValueToConstantRector::class;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace Rector\SOLID\Tests\Rector\Property\ChangeReadOnlyPropertyWithDefaultValueToConstantRector\Fixture;

class SomeClass
{
/**
* @var string[]
*/
private $magicMethods = [
'__toString',
'__wakeup',
];

public function run()
{
foreach ($this->magicMethods as $magicMethod) {
echo $magicMethod;
}
}
}

?>
-----
<?php

namespace Rector\SOLID\Tests\Rector\Property\ChangeReadOnlyPropertyWithDefaultValueToConstantRector\Fixture;

class SomeClass
{
/**
* @var string[]
*/
private const MAGIC_METHODS = [
'__toString',
'__wakeup',
];

public function run()
{
foreach (self::MAGIC_METHODS as $magicMethod) {
echo $magicMethod;
}
}
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Rector\SOLID\Tests\Rector\Property\ChangeReadOnlyPropertyWithDefaultValueToConstantRector\Fixture;

class SkipMultipleProperties
{
/**
* @var string[]
*/
private $moreMagic, $magicMethods = [
'__toString',
'__wakeup',
];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace Rector\SOLID\Tests\Rector\Property\ChangeReadOnlyPropertyWithDefaultValueToConstantRector\Fixture;

class SkipOverriden
{
/**
* @var string[]
*/
private $magicMethods = [
'__toString',
'__wakeup',
];

public function run()
{
foreach ($this->magicMethods as $magicMethod) {
echo $magicMethod;
}

$this->magicMethods = 123;
}
}
12 changes: 11 additions & 1 deletion src/PhpParser/Node/Manipulator/PropertyManipulator.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,20 @@ public function getAllPropertyFetch(PropertyProperty $propertyProperty): array
return $propertyFetches;
}

public function isReadyOnlyProperty(PropertyProperty $propertyProperty): bool
{
foreach ($this->getAllPropertyFetch($propertyProperty) as $propertyFetch) {
if (! $this->isReadContext($propertyFetch)) {
return false;
}
}

return true;
}

public function isPropertyUsedInReadContext(PropertyProperty $propertyProperty): bool
{
$property = $this->getProperty($propertyProperty);

if ($this->isDoctrineProperty($property)) {
return true;
}
Expand Down