mirror of
https://github.com/rectorphp/rector.git
synced 2024-06-01 08:50:50 +00:00
[Decouple] Add DecoupleClassMethodToOwnClassRector
This commit is contained in:
parent
b3ba3c8dfa
commit
b5981133a9
|
@ -92,6 +92,7 @@
|
|||
"Rector\\NetteToSymfony\\": "rules/nette-to-symfony/src",
|
||||
"Rector\\Nette\\": "rules/nette/src",
|
||||
"Rector\\NodeCollector\\": "packages/node-collector/src",
|
||||
"Rector\\Decouple\\": "rules/decouple/src",
|
||||
"Rector\\NodeNameResolver\\": "packages/node-name-resolver/src",
|
||||
"Rector\\NodeTypeResolver\\": "packages/node-type-resolver/src",
|
||||
"Rector\\PhpAttribute\\": "packages/php-attribute/src",
|
||||
|
@ -154,6 +155,7 @@
|
|||
"Rector\\Caching\\Tests\\": "packages/caching/tests",
|
||||
"Rector\\CodingStyle\\Tests\\": "rules/coding-style/tests",
|
||||
"Rector\\Core\\Tests\\": "tests",
|
||||
"Rector\\Decouple\\Tests\\": "rules/decouple/tests",
|
||||
"Rector\\DeadCode\\Tests\\": "rules/dead-code/tests",
|
||||
"Rector\\DoctrineCodeQuality\\Tests\\": "rules/doctrine-code-quality/tests",
|
||||
"Rector\\DoctrineGedmoToKnplabs\\Tests\\": "rules/doctrine-gedmo-to-knplabs/tests",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# All 511 Rectors Overview
|
||||
# All 512 Rectors Overview
|
||||
|
||||
- [Projects](#projects)
|
||||
- [General](#general)
|
||||
|
@ -13,6 +13,7 @@
|
|||
- [CodeQuality](#codequality) (54)
|
||||
- [CodingStyle](#codingstyle) (32)
|
||||
- [DeadCode](#deadcode) (40)
|
||||
- [Decouple](#decouple) (1)
|
||||
- [Doctrine](#doctrine) (16)
|
||||
- [DoctrineCodeQuality](#doctrinecodequality) (2)
|
||||
- [DoctrineGedmoToKnplabs](#doctrinegedmotoknplabs) (7)
|
||||
|
@ -3204,6 +3205,62 @@ Change ternary of bool : false to && bool
|
|||
|
||||
<br><br>
|
||||
|
||||
## Decouple
|
||||
|
||||
### `DecoupleClassMethodToOwnClassRector`
|
||||
|
||||
- class: [`Rector\Decouple\Rector\DecoupleClassMethodToOwnClassRector`](/../master/rules/decouple/src/Rector/DecoupleClassMethodToOwnClassRector.php)
|
||||
- [test fixtures](/../master/rules/decouple/tests/Rector/DecoupleClassMethodToOwnClassRector/Fixture)
|
||||
|
||||
Move class method with its all dependencies to own class by method name
|
||||
|
||||
```yaml
|
||||
services:
|
||||
Rector\Decouple\Rector\DecoupleClassMethodToOwnClassRector:
|
||||
$methodNamesByClass:
|
||||
SomeClass:
|
||||
someMethod:
|
||||
class: NewDecoupledClass
|
||||
method: someRenamedMethod
|
||||
parent_class: AddedParentClass
|
||||
```
|
||||
|
||||
↓
|
||||
|
||||
```diff
|
||||
class SomeClass
|
||||
{
|
||||
- public function someMethod()
|
||||
- {
|
||||
- $this->alsoCallThis();
|
||||
- }
|
||||
-
|
||||
- private function alsoCallThis()
|
||||
- {
|
||||
- }
|
||||
}
|
||||
```
|
||||
|
||||
**New file**
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
class NewDecoupledClass extends AddedParentClass
|
||||
{
|
||||
public function someRenamedMethod()
|
||||
{
|
||||
$this->alsoCallThis();
|
||||
}
|
||||
|
||||
private function alsoCallThis()
|
||||
{
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<br><br>
|
||||
|
||||
## Doctrine
|
||||
|
||||
### `AddEntityIdByConditionRector`
|
||||
|
|
|
@ -323,3 +323,4 @@ parameters:
|
|||
- '#Cognitive complexity for "Rector\\Core\\Application\\RectorApplication\:\:runOnFileInfos\(\)" is 10, keep it under 9#'
|
||||
- '#Class "Rector\\FileSystemRector\\Rector\\Removing\\RemoveProjectFileRector" is missing @see annotation with test case class reference#'
|
||||
- '#Class "Rector\\Php73\\Rector\\FuncCall\\SetCookieRector" is missing @see annotation with test case class reference#'
|
||||
- '#Parameter \#1 \$type of method PhpParser\\Builder\\Param\:\:setType\(\) expects PhpParser\\Node\\Name\|PhpParser\\Node\\NullableType\|PhpParser\\Node\\UnionType\|string, PhpParser\\Node\\Identifier\|PhpParser\\Node\\Name\|PhpParser\\Node\\NullableType\|PhpParser\\Node\\UnionType given#'
|
||||
|
|
10
rules/decouple/config/config.yaml
Normal file
10
rules/decouple/config/config.yaml
Normal file
|
@ -0,0 +1,10 @@
|
|||
services:
|
||||
_defaults:
|
||||
autowire: true
|
||||
public: true
|
||||
|
||||
Rector\Decouple\:
|
||||
resource: '../src'
|
||||
exclude:
|
||||
- '../src/Rector/**/*Rector.php'
|
||||
- '../src/ValueObject/*'
|
58
rules/decouple/src/Matcher/DecoupledClassMethodMatcher.php
Normal file
58
rules/decouple/src/Matcher/DecoupledClassMethodMatcher.php
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Decouple\Matcher;
|
||||
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use Rector\Decouple\ValueObject\DecoupleClassMethodMatch;
|
||||
use Rector\NodeNameResolver\NodeNameResolver;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\NodeTypeResolver\NodeTypeResolver;
|
||||
|
||||
final class DecoupledClassMethodMatcher
|
||||
{
|
||||
/**
|
||||
* @var NodeNameResolver
|
||||
*/
|
||||
private $nodeNameResolver;
|
||||
|
||||
/**
|
||||
* @var NodeTypeResolver
|
||||
*/
|
||||
private $nodeTypeResolver;
|
||||
|
||||
public function __construct(NodeNameResolver $nodeNameResolver, NodeTypeResolver $nodeTypeResolver)
|
||||
{
|
||||
$this->nodeNameResolver = $nodeNameResolver;
|
||||
$this->nodeTypeResolver = $nodeTypeResolver;
|
||||
}
|
||||
|
||||
public function matchDecoupled(ClassMethod $classMethod, array $methodNamesByClass): ?DecoupleClassMethodMatch
|
||||
{
|
||||
$class = $classMethod->getAttribute(AttributeKey::CLASS_NODE);
|
||||
if ($class === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($methodNamesByClass as $className => $configuration) {
|
||||
if (! $this->nodeTypeResolver->isObjectType($class, $className)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($configuration as $methodName => $newClassConfiguration) {
|
||||
if (! $this->nodeNameResolver->isName($classMethod->name, $methodName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return new DecoupleClassMethodMatch(
|
||||
$newClassConfiguration['class'],
|
||||
$newClassConfiguration['method'],
|
||||
$newClassConfiguration['parent_class'] ?? null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Decouple\NodeFactory;
|
||||
|
||||
use function count;
|
||||
use PhpParser\Builder\Method;
|
||||
use PhpParser\Builder\Param;
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\PropertyFetch;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Property;
|
||||
|
||||
final class ConstructorClassMethodFactory
|
||||
{
|
||||
/**
|
||||
* @param array<string, Property> $properties
|
||||
*/
|
||||
public function create(array $properties): ?ClassMethod
|
||||
{
|
||||
if (count($properties) === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$methodBuilder = new Method('__construct');
|
||||
$methodBuilder->makePublic();
|
||||
|
||||
foreach ($properties as $propertyName => $property) {
|
||||
/** @var string $propertyName */
|
||||
$paramBuilder = new Param($propertyName);
|
||||
|
||||
/** @var Property $property */
|
||||
if ($property->type !== null) {
|
||||
$paramBuilder->setType($property->type);
|
||||
}
|
||||
|
||||
$methodBuilder->addParam($paramBuilder->getNode());
|
||||
|
||||
// add assign
|
||||
$assign = $this->createAssign($propertyName);
|
||||
|
||||
$methodBuilder->addStmt($assign);
|
||||
}
|
||||
|
||||
return $methodBuilder->getNode();
|
||||
}
|
||||
|
||||
private function createAssign(string $propertyName): Assign
|
||||
{
|
||||
$localPropertyFetch = new PropertyFetch(new Variable('this'), $propertyName);
|
||||
|
||||
return new Assign($localPropertyFetch, new Variable($propertyName));
|
||||
}
|
||||
}
|
61
rules/decouple/src/NodeFactory/NamespaceFactory.php
Normal file
61
rules/decouple/src/NodeFactory/NamespaceFactory.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Decouple\NodeFactory;
|
||||
|
||||
use PhpParser\Builder\Class_ as ClassBuilder;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\Namespace_;
|
||||
use Rector\Core\Exception\ShouldNotHappenException;
|
||||
use Rector\Decouple\ValueObject\DecoupleClassMethodMatch;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
|
||||
final class NamespaceFactory
|
||||
{
|
||||
/**
|
||||
* @param Stmt[] $classStmts
|
||||
*/
|
||||
public function createNamespacedClassByNameAndStmts(
|
||||
Class_ $class,
|
||||
DecoupleClassMethodMatch $decoupleClassMethodMatch,
|
||||
array $classStmts
|
||||
): Namespace_ {
|
||||
/** @var Namespace_|null $namespace */
|
||||
$namespace = $class->getAttribute(AttributeKey::NAMESPACE_NODE);
|
||||
if ($namespace === null) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
||||
foreach ($namespace->stmts as $key => $stmt) {
|
||||
if (! $stmt instanceof Class_) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$namespace->stmts[$key] = $this->createNewClass($decoupleClassMethodMatch, $classStmts);
|
||||
}
|
||||
|
||||
$this->createNewClass($decoupleClassMethodMatch, $classStmts);
|
||||
|
||||
return $namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Stmt[] $classStmts
|
||||
*/
|
||||
private function createNewClass(DecoupleClassMethodMatch $decoupleClassMethodMatch, array $classStmts): Class_
|
||||
{
|
||||
$classBuilder = new ClassBuilder($decoupleClassMethodMatch->getClassName());
|
||||
$classBuilder->addStmts($classStmts);
|
||||
$classBuilder->makeFinal();
|
||||
|
||||
$parentClassName = $decoupleClassMethodMatch->getParentClassName();
|
||||
if ($parentClassName !== null) {
|
||||
$classBuilder->extend(new FullyQualified($parentClassName));
|
||||
}
|
||||
|
||||
return $classBuilder->getNode();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,217 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Decouple\Rector;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use Rector\Core\Exception\ShouldNotHappenException;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
use Rector\Core\RectorDefinition\ConfiguredCodeSample;
|
||||
use Rector\Core\RectorDefinition\RectorDefinition;
|
||||
use Rector\Decouple\Matcher\DecoupledClassMethodMatcher;
|
||||
use Rector\Decouple\NodeFactory\ConstructorClassMethodFactory;
|
||||
use Rector\Decouple\NodeFactory\NamespaceFactory;
|
||||
use Rector\Decouple\UsedNodesExtractor\UsedClassConstsExtractor;
|
||||
use Rector\Decouple\UsedNodesExtractor\UsedClassMethodsExtractor;
|
||||
use Rector\Decouple\UsedNodesExtractor\UsedClassPropertyExtractor;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Symplify\SmartFileSystem\SmartFileInfo;
|
||||
|
||||
/**
|
||||
* @sponsor Thanks https://amateri.com for sponsoring this rule - visit them on https://www.startupjobs.cz/startup/scrumworks-s-r-o
|
||||
*
|
||||
* @see \Rector\Decouple\Tests\Rector\DecoupleClassMethodToOwnClassRector\DecoupleClassMethodToOwnClassRectorTest
|
||||
*/
|
||||
final class DecoupleClassMethodToOwnClassRector extends AbstractRector
|
||||
{
|
||||
/**
|
||||
* @var mixed[][]
|
||||
*/
|
||||
private $methodNamesByClass = [];
|
||||
|
||||
/**
|
||||
* @var NamespaceFactory
|
||||
*/
|
||||
private $namespaceFactory;
|
||||
|
||||
/**
|
||||
* @var UsedClassMethodsExtractor
|
||||
*/
|
||||
private $usedClassMethodsExtractor;
|
||||
|
||||
/**
|
||||
* @var UsedClassConstsExtractor
|
||||
*/
|
||||
private $usedClassConstsExtractor;
|
||||
|
||||
/**
|
||||
* @var UsedClassPropertyExtractor
|
||||
*/
|
||||
private $usedClassPropertyExtractor;
|
||||
|
||||
/**
|
||||
* @var DecoupledClassMethodMatcher
|
||||
*/
|
||||
private $decoupledClassMethodMatcher;
|
||||
|
||||
/**
|
||||
* @var ConstructorClassMethodFactory
|
||||
*/
|
||||
private $constructorClassMethodFactory;
|
||||
|
||||
/**
|
||||
* @param mixed[][] $methodNamesByClass
|
||||
*/
|
||||
public function __construct(
|
||||
NamespaceFactory $namespaceFactory,
|
||||
UsedClassMethodsExtractor $usedClassMethodsExtractor,
|
||||
UsedClassConstsExtractor $usedClassConstsExtractor,
|
||||
UsedClassPropertyExtractor $usedClassPropertyExtractor,
|
||||
DecoupledClassMethodMatcher $decoupledClassMethodMatcher,
|
||||
ConstructorClassMethodFactory $constructorClassMethodFactory,
|
||||
array $methodNamesByClass = []
|
||||
) {
|
||||
$this->methodNamesByClass = $methodNamesByClass;
|
||||
$this->namespaceFactory = $namespaceFactory;
|
||||
$this->usedClassMethodsExtractor = $usedClassMethodsExtractor;
|
||||
$this->usedClassConstsExtractor = $usedClassConstsExtractor;
|
||||
$this->usedClassPropertyExtractor = $usedClassPropertyExtractor;
|
||||
$this->decoupledClassMethodMatcher = $decoupledClassMethodMatcher;
|
||||
$this->constructorClassMethodFactory = $constructorClassMethodFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getNodeTypes(): array
|
||||
{
|
||||
return [ClassMethod::class];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClassMethod $node
|
||||
*/
|
||||
public function refactor(Node $node): ?Node
|
||||
{
|
||||
$matchedConfiguration = $this->decoupledClassMethodMatcher->matchDecoupled($node, $this->methodNamesByClass);
|
||||
if ($matchedConfiguration === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$mainClassMethod = clone $node;
|
||||
$mainClassMethod->name = new Identifier($matchedConfiguration->getMethodName());
|
||||
$this->makePublic($mainClassMethod);
|
||||
|
||||
// 2. get related class constants in the same class
|
||||
$usedClassConsts = $this->usedClassConstsExtractor->extract($node);
|
||||
|
||||
// 3. get class method related methods call in the same class
|
||||
$usedClassMethods = $this->usedClassMethodsExtractor->extractFromClassMethod(
|
||||
$node,
|
||||
$matchedConfiguration->getParentClassName()
|
||||
);
|
||||
|
||||
// 4. get class method related property fetches in the same class - add to constructor
|
||||
$usedProperties = $this->usedClassPropertyExtractor->extractFromClassMethods(
|
||||
array_merge($usedClassMethods, [$node]),
|
||||
$matchedConfiguration->getParentClassName()
|
||||
);
|
||||
|
||||
// 5. add constructor dependencies $requiredLocalPropertyFetches
|
||||
/** @var Class_ $class */
|
||||
$class = $node->getAttribute(AttributeKey::CLASS_NODE);
|
||||
$constructClassMethod = $this->constructorClassMethodFactory->create($usedProperties);
|
||||
|
||||
// 6. build a class
|
||||
$usedClassStmts = array_merge(
|
||||
$usedClassConsts,
|
||||
$usedProperties,
|
||||
$constructClassMethod !== null ? [$constructClassMethod] : [],
|
||||
[$mainClassMethod],
|
||||
$usedClassMethods
|
||||
);
|
||||
|
||||
$namespace = $this->namespaceFactory->createNamespacedClassByNameAndStmts(
|
||||
$class,
|
||||
$matchedConfiguration,
|
||||
$usedClassStmts
|
||||
);
|
||||
|
||||
$newClassLocation = $this->createNewClassLocation($node, $matchedConfiguration->getClassName());
|
||||
$this->printNodesToFilePath($namespace, $newClassLocation);
|
||||
|
||||
// 7. cleanup this class method
|
||||
$this->removeNode($node);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getDefinition(): RectorDefinition
|
||||
{
|
||||
return new RectorDefinition('Move class method with its all dependencies to own class by method name', [
|
||||
new ConfiguredCodeSample(
|
||||
<<<'CODE_SAMPLE'
|
||||
class SomeClass
|
||||
{
|
||||
public function someMethod()
|
||||
{
|
||||
$this->alsoCallThis();
|
||||
}
|
||||
|
||||
private function alsoCallThis()
|
||||
{
|
||||
}
|
||||
}
|
||||
CODE_SAMPLE
|
||||
,
|
||||
<<<'CODE_SAMPLE'
|
||||
class SomeClass
|
||||
{
|
||||
}
|
||||
CODE_SAMPLE
|
||||
,
|
||||
[
|
||||
'$methodNamesByClass' => [
|
||||
'SomeClass' => [
|
||||
'someMethod' => [
|
||||
'class' => 'NewDecoupledClass',
|
||||
'method' => 'someRenamedMethod',
|
||||
'parent_class' => 'AddedParentClass',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
<<<'CODE_SAMPLE'
|
||||
<?php
|
||||
|
||||
class NewDecoupledClass extends AddedParentClass
|
||||
{
|
||||
public function someRenamedMethod()
|
||||
{
|
||||
$this->alsoCallThis();
|
||||
}
|
||||
|
||||
private function alsoCallThis()
|
||||
{
|
||||
}
|
||||
}
|
||||
CODE_SAMPLE
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
private function createNewClassLocation(ClassMethod $classMethod, string $newClassName): string
|
||||
{
|
||||
/** @var SmartFileInfo|null $fileInfo */
|
||||
$fileInfo = $classMethod->getAttribute(AttributeKey::FILE_INFO);
|
||||
if ($fileInfo === null) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
||||
return $fileInfo->getPath() . DIRECTORY_SEPARATOR . $newClassName . '.php';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Decouple\UsedNodesExtractor;
|
||||
|
||||
use function implode;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\ClassConstFetch;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassConst;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use Rector\Core\Exception\ShouldNotHappenException;
|
||||
use Rector\Core\PhpParser\NodeTraverser\CallableNodeTraverser;
|
||||
use Rector\NodeNameResolver\NodeNameResolver;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use function sprintf;
|
||||
|
||||
final class UsedClassConstsExtractor
|
||||
{
|
||||
/**
|
||||
* @var CallableNodeTraverser
|
||||
*/
|
||||
private $callableNodeTraverser;
|
||||
|
||||
/**
|
||||
* @var NodeNameResolver
|
||||
*/
|
||||
private $nodeNameResolver;
|
||||
|
||||
public function __construct(CallableNodeTraverser $callableNodeTraverser, NodeNameResolver $nodeNameResolver)
|
||||
{
|
||||
$this->callableNodeTraverser = $callableNodeTraverser;
|
||||
$this->nodeNameResolver = $nodeNameResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ClassConst[]
|
||||
*/
|
||||
public function extract(ClassMethod $classMethod): array
|
||||
{
|
||||
$classConsts = [];
|
||||
|
||||
/** @var Class_ $class */
|
||||
$class = $classMethod->getAttribute(AttributeKey::CLASS_NODE);
|
||||
|
||||
$this->callableNodeTraverser->traverseNodesWithCallable((array) $classMethod->stmts, function (Node $node) use (
|
||||
&$classConsts,
|
||||
$class
|
||||
) {
|
||||
if (! $node instanceof ClassConstFetch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $this->nodeNameResolver->isName($node->class, 'self')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$classConstName = $this->nodeNameResolver->getName($node->name);
|
||||
if ($classConstName === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$classConsts[$classConstName] = $this->getClassConstByName($class, $classConstName);
|
||||
});
|
||||
|
||||
return $classConsts;
|
||||
}
|
||||
|
||||
private function getClassConstByName(Class_ $class, string $classConstName): ClassConst
|
||||
{
|
||||
$classConstantNames = [];
|
||||
foreach ($class->getConstants() as $constant) {
|
||||
$classConstantNames[] = $this->nodeNameResolver->getName($constant);
|
||||
|
||||
if (! $this->nodeNameResolver->isName($constant, $classConstName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $constant;
|
||||
}
|
||||
|
||||
$className = $this->nodeNameResolver->getName($class);
|
||||
$message = sprintf(
|
||||
'Cannot find "%s::%s" constant. Pick one of: %s',
|
||||
$className,
|
||||
$classConstName,
|
||||
implode(', ', $classConstantNames)
|
||||
);
|
||||
|
||||
throw new ShouldNotHappenException($message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Decouple\UsedNodesExtractor;
|
||||
|
||||
use function array_keys;
|
||||
use function array_merge;
|
||||
use function in_array;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use Rector\Core\Exception\ShouldNotHappenException;
|
||||
use Rector\Core\PhpParser\NodeTraverser\CallableNodeTraverser;
|
||||
use Rector\NodeNameResolver\NodeNameResolver;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use ReflectionClass;
|
||||
|
||||
final class UsedClassMethodsExtractor
|
||||
{
|
||||
/**
|
||||
* @var CallableNodeTraverser
|
||||
*/
|
||||
private $callableNodeTraverser;
|
||||
|
||||
/**
|
||||
* @var NodeNameResolver
|
||||
*/
|
||||
private $nodeNameResolver;
|
||||
|
||||
public function __construct(CallableNodeTraverser $callableNodeTraverser, NodeNameResolver $nodeNameResolver)
|
||||
{
|
||||
$this->callableNodeTraverser = $callableNodeTraverser;
|
||||
$this->nodeNameResolver = $nodeNameResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ClassMethod[]
|
||||
*/
|
||||
public function extractFromClassMethod(ClassMethod $classMethod, ?string $parentClassName = null): array
|
||||
{
|
||||
/** @var Class_ $class */
|
||||
$class = $classMethod->getAttribute(AttributeKey::CLASS_NODE);
|
||||
|
||||
$classMethods = [];
|
||||
|
||||
$this->callableNodeTraverser->traverseNodesWithCallable((array) $classMethod->stmts, function (Node $node) use (
|
||||
&$classMethods,
|
||||
$class
|
||||
) {
|
||||
if (! $node instanceof MethodCall) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $this->isThisPropertyFetch($node->var)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var string $methodName */
|
||||
$methodName = $this->nodeNameResolver->getName($node->name);
|
||||
|
||||
$classMethod = $class->getMethod($methodName);
|
||||
if ($classMethod === null) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
||||
$classMethods[$methodName] = $classMethod;
|
||||
});
|
||||
|
||||
// 2nd nesting method calls
|
||||
foreach ($classMethods as $nestedClassMethod) {
|
||||
$classMethods = array_merge($classMethods, $this->extractFromClassMethod($nestedClassMethod));
|
||||
}
|
||||
|
||||
$uniqueClassMethods = $this->makeClassesMethodsUnique($classMethods);
|
||||
|
||||
if ($parentClassName !== null) {
|
||||
return $this->filterOutParentClassMethods($uniqueClassMethods, $parentClassName);
|
||||
}
|
||||
|
||||
return $uniqueClassMethods;
|
||||
}
|
||||
|
||||
private function isThisPropertyFetch(Node $node): bool
|
||||
{
|
||||
if ($node instanceof MethodCall) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($node instanceof StaticCall) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->nodeNameResolver->isName($node, 'this');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClassMethod[] $classMethods
|
||||
* @return ClassMethod[]
|
||||
*/
|
||||
private function makeClassesMethodsUnique(array $classMethods): array
|
||||
{
|
||||
$uniqueClassMethods = [];
|
||||
|
||||
foreach ($classMethods as $classMethod) {
|
||||
/** @var string $classMethodName */
|
||||
$classMethodName = $this->nodeNameResolver->getName($classMethod);
|
||||
$uniqueClassMethods[$classMethodName] = $classMethod;
|
||||
}
|
||||
|
||||
return $uniqueClassMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClassMethod[] $classMethods
|
||||
* @return ClassMethod[]
|
||||
*/
|
||||
private function filterOutParentClassMethods(array $classMethods, string $parentClassName): array
|
||||
{
|
||||
$reflectionClass = new ReflectionClass($parentClassName);
|
||||
$parentClassMethodNames = [];
|
||||
foreach ($reflectionClass->getMethods() as $reflectionMethod) {
|
||||
$parentClassMethodNames[] = $reflectionMethod->getName();
|
||||
}
|
||||
|
||||
foreach (array_keys($classMethods) as $methodName) {
|
||||
if (! in_array($methodName, $parentClassMethodNames, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
unset($classMethods[$methodName]);
|
||||
}
|
||||
|
||||
return $classMethods;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Decouple\UsedNodesExtractor;
|
||||
|
||||
use function array_keys;
|
||||
use function array_merge;
|
||||
use function in_array;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Expr\PropertyFetch;
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Property;
|
||||
use Rector\Core\Exception\ShouldNotHappenException;
|
||||
use Rector\Core\PhpParser\NodeTraverser\CallableNodeTraverser;
|
||||
use Rector\NodeNameResolver\NodeNameResolver;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use ReflectionClass;
|
||||
|
||||
final class UsedClassPropertyExtractor
|
||||
{
|
||||
/**
|
||||
* @var CallableNodeTraverser
|
||||
*/
|
||||
private $callableNodeTraverser;
|
||||
|
||||
/**
|
||||
* @var NodeNameResolver
|
||||
*/
|
||||
private $nodeNameResolver;
|
||||
|
||||
public function __construct(CallableNodeTraverser $callableNodeTraverser, NodeNameResolver $nodeNameResolver)
|
||||
{
|
||||
$this->callableNodeTraverser = $callableNodeTraverser;
|
||||
$this->nodeNameResolver = $nodeNameResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClassMethod[] $classMethods
|
||||
* @return Property[]
|
||||
*/
|
||||
public function extractFromClassMethods(array $classMethods, ?string $parentClassName = null): array
|
||||
{
|
||||
$properties = [];
|
||||
|
||||
foreach ($classMethods as $classMethod) {
|
||||
$properties = array_merge($properties, $this->extract($classMethod));
|
||||
}
|
||||
|
||||
if ($parentClassName !== null) {
|
||||
return $this->filterOutParentClassProperties($properties, $parentClassName);
|
||||
}
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Property[]
|
||||
*/
|
||||
private function extract(ClassMethod $classMethod): array
|
||||
{
|
||||
$properties = [];
|
||||
|
||||
/** @var Class_ $class */
|
||||
$class = $classMethod->getAttribute(AttributeKey::CLASS_NODE);
|
||||
|
||||
$this->callableNodeTraverser->traverseNodesWithCallable((array) $classMethod->stmts, function (Node $node) use (
|
||||
&$properties,
|
||||
$class
|
||||
) {
|
||||
if (! $node instanceof PropertyFetch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $this->isThisPropertyFetch($node->var)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$propertyName = $this->nodeNameResolver->getName($node->name);
|
||||
if ($propertyName === null) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
||||
/** @var Property|null $property */
|
||||
$property = $class->getProperty($propertyName);
|
||||
if ($property === null) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
||||
$properties[$propertyName] = $property;
|
||||
});
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
private function isThisPropertyFetch(Node $node): bool
|
||||
{
|
||||
if ($node instanceof MethodCall) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($node instanceof StaticCall) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->nodeNameResolver->isName($node, 'this');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Property[] $classProperties
|
||||
* @return Property[]
|
||||
*/
|
||||
private function filterOutParentClassProperties(array $classProperties, string $parentClassName): array
|
||||
{
|
||||
$reflectionClass = new ReflectionClass($parentClassName);
|
||||
|
||||
$parentClassPropertyNames = [];
|
||||
foreach ($reflectionClass->getProperties() as $reflectionProperty) {
|
||||
$parentClassPropertyNames[] = $reflectionProperty->getName();
|
||||
}
|
||||
|
||||
foreach (array_keys($classProperties) as $propertyName) {
|
||||
if (! in_array($propertyName, $parentClassPropertyNames, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
unset($classProperties[$propertyName]);
|
||||
}
|
||||
|
||||
return $classProperties;
|
||||
}
|
||||
}
|
45
rules/decouple/src/ValueObject/DecoupleClassMethodMatch.php
Normal file
45
rules/decouple/src/ValueObject/DecoupleClassMethodMatch.php
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Decouple\ValueObject;
|
||||
|
||||
final class DecoupleClassMethodMatch
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $className;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $parentClassName;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $methodName;
|
||||
|
||||
public function __construct(string $className, string $methodName, ?string $parentClassName = null)
|
||||
{
|
||||
$this->className = $className;
|
||||
$this->parentClassName = $parentClassName;
|
||||
$this->methodName = $methodName;
|
||||
}
|
||||
|
||||
public function getClassName(): string
|
||||
{
|
||||
return $this->className;
|
||||
}
|
||||
|
||||
public function getParentClassName(): ?string
|
||||
{
|
||||
return $this->parentClassName;
|
||||
}
|
||||
|
||||
public function getMethodName(): string
|
||||
{
|
||||
return $this->methodName;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Decouple\Tests\Rector\DecoupleClassMethodToOwnClassRector;
|
||||
|
||||
use Rector\Core\Testing\PHPUnit\AbstractRectorTestCase;
|
||||
use Rector\Decouple\Rector\DecoupleClassMethodToOwnClassRector;
|
||||
use Rector\Decouple\Tests\Rector\DecoupleClassMethodToOwnClassRector\Source\AbstractFather;
|
||||
use Symplify\SmartFileSystem\SmartFileInfo;
|
||||
|
||||
final class DecoupleClassMethodToOwnClassRectorTest extends AbstractRectorTestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideData()
|
||||
*/
|
||||
public function test(SmartFileInfo $fileInfo, string $expectedFilePath, string $expectedContentFilePath): void
|
||||
{
|
||||
$this->doTestFileInfo($fileInfo);
|
||||
|
||||
$this->assertFileExists($expectedFilePath);
|
||||
$this->assertFileEquals($expectedContentFilePath, $expectedFilePath);
|
||||
}
|
||||
|
||||
public function provideData()
|
||||
{
|
||||
yield [
|
||||
new SmartFileInfo(__DIR__ . '/Fixture/basic.php.inc'),
|
||||
// expected new file location
|
||||
$this->getTempPath() . '/ExtraClassName.php',
|
||||
// expected new file content
|
||||
__DIR__ . '/Source/ExpectedExtraClassName.php',
|
||||
];
|
||||
|
||||
yield [
|
||||
new SmartFileInfo(__DIR__ . '/Fixture/with_property_dependency.php.inc'),
|
||||
// expected new file location
|
||||
$this->getTempPath() . '/ExtraUsingProperty.php',
|
||||
// expected new file content
|
||||
__DIR__ . '/Source/ExpectedExtraUsingProperty.php',
|
||||
];
|
||||
}
|
||||
|
||||
protected function getRectorsWithConfiguration(): array
|
||||
{
|
||||
return [
|
||||
DecoupleClassMethodToOwnClassRector::class => [
|
||||
'$methodNamesByClass' => [
|
||||
'Rector\Decouple\Tests\Rector\DecoupleClassMethodToOwnClassRector\Fixture\Basic' => [
|
||||
'someMethod' => [
|
||||
'method' => 'newMethodName',
|
||||
'class' => 'ExtraClassName',
|
||||
// optionally: "parent_class" =>
|
||||
],
|
||||
],
|
||||
'Rector\Decouple\Tests\Rector\DecoupleClassMethodToOwnClassRector\Fixture\WithPropertyDependency' => [
|
||||
'usingProperty' => [
|
||||
'method' => 'newUsingProperty',
|
||||
'class' => 'ExtraUsingProperty',
|
||||
'parent_class' => AbstractFather::class,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Decouple\Tests\Rector\DecoupleClassMethodToOwnClassRector\Fixture;
|
||||
|
||||
final class Basic
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const VALUE = 'value';
|
||||
|
||||
public function another()
|
||||
{
|
||||
return $this->someMethod();
|
||||
}
|
||||
|
||||
private function someMethod()
|
||||
{
|
||||
return self::VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Decouple\Tests\Rector\DecoupleClassMethodToOwnClassRector\Fixture;
|
||||
|
||||
final class Basic
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const VALUE = 'value';
|
||||
|
||||
public function another()
|
||||
{
|
||||
return $this->someMethod();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Decouple\Tests\Rector\DecoupleClassMethodToOwnClassRector\Fixture;
|
||||
|
||||
use Rector\Decouple\Tests\Rector\DecoupleClassMethodToOwnClassRector\Source\EventManager;
|
||||
|
||||
final class WithPropertyDependency
|
||||
{
|
||||
/**
|
||||
* @var EventManager
|
||||
*/
|
||||
private $eventManager;
|
||||
|
||||
public function __construct(EventManager $eventManager)
|
||||
{
|
||||
$this->eventManager = $eventManager;
|
||||
}
|
||||
|
||||
public function usingProperty()
|
||||
{
|
||||
return $this->eventManager->runEvent();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Decouple\Tests\Rector\DecoupleClassMethodToOwnClassRector\Fixture;
|
||||
|
||||
use Rector\Decouple\Tests\Rector\DecoupleClassMethodToOwnClassRector\Source\EventManager;
|
||||
|
||||
final class WithPropertyDependency
|
||||
{
|
||||
/**
|
||||
* @var EventManager
|
||||
*/
|
||||
private $eventManager;
|
||||
|
||||
public function __construct(EventManager $eventManager)
|
||||
{
|
||||
$this->eventManager = $eventManager;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Decouple\Tests\Rector\DecoupleClassMethodToOwnClassRector\Source;
|
||||
|
||||
abstract class AbstractFather
|
||||
{
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Decouple\Tests\Rector\DecoupleClassMethodToOwnClassRector\Source;
|
||||
|
||||
final class EventManager
|
||||
{
|
||||
public function runEvent()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Decouple\Tests\Rector\DecoupleClassMethodToOwnClassRector\Fixture;
|
||||
|
||||
final class ExtraClassName
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const VALUE = 'value';
|
||||
public function newMethodName()
|
||||
{
|
||||
return self::VALUE;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Decouple\Tests\Rector\DecoupleClassMethodToOwnClassRector\Fixture;
|
||||
|
||||
use Rector\Decouple\Tests\Rector\DecoupleClassMethodToOwnClassRector\Source\EventManager;
|
||||
final class ExtraUsingProperty extends \Rector\Decouple\Tests\Rector\DecoupleClassMethodToOwnClassRector\Source\AbstractFather
|
||||
{
|
||||
/**
|
||||
* @var EventManager
|
||||
*/
|
||||
private $eventManager;
|
||||
public function __construct($eventManager)
|
||||
{
|
||||
$this->eventManager = $eventManager;
|
||||
}
|
||||
public function newUsingProperty()
|
||||
{
|
||||
return $this->eventManager->runEvent();
|
||||
}
|
||||
}
|
|
@ -178,7 +178,7 @@ abstract class AbstractRectorTestCase extends AbstractGenericRectorTestCase
|
|||
|
||||
protected function getTempPath(): string
|
||||
{
|
||||
return sys_get_temp_dir() . '/rector_temp_tests';
|
||||
return StaticFixtureSplitter::getTemporaryPath();
|
||||
}
|
||||
|
||||
protected function getPhpVersion(): string
|
||||
|
|
Loading…
Reference in New Issue
Block a user