[Decouple] Add DecoupleClassMethodToOwnClassRector

This commit is contained in:
TomasVotruba 2020-06-28 13:20:25 +02:00
parent b3ba3c8dfa
commit b5981133a9
20 changed files with 1097 additions and 2 deletions

View File

@ -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",

View File

@ -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`

View File

@ -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#'

View File

@ -0,0 +1,10 @@
services:
_defaults:
autowire: true
public: true
Rector\Decouple\:
resource: '../src'
exclude:
- '../src/Rector/**/*Rector.php'
- '../src/ValueObject/*'

View 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;
}
}

View File

@ -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));
}
}

View 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();
}
}

View File

@ -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';
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View 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;
}
}

View File

@ -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,
],
],
],
],
];
}
}

View File

@ -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();
}
}
?>

View File

@ -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;
}
}
?>

View File

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Rector\Decouple\Tests\Rector\DecoupleClassMethodToOwnClassRector\Source;
abstract class AbstractFather
{
}

View File

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Rector\Decouple\Tests\Rector\DecoupleClassMethodToOwnClassRector\Source;
final class EventManager
{
public function runEvent()
{
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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