[IDE-refactoring cleanup] Remove rather custom PassFactoryToEntityRector and NewUniqueObjectToEntityFactoryRector rules, better use PHPStorm there (#783)

* Remove rather custom PassFactoryToEntityRector and NewUniqueObjectToEntityFactoryRector rules, better use PHPStorm there

* cleanup

* Remove StaticTypeToSetterInjectionRector, use PHPStorm for these refactorings
This commit is contained in:
Tomas Votruba 2021-08-27 16:17:45 +02:00 committed by GitHub
parent 130b634b5e
commit 541e40b48f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 2 additions and 1395 deletions

View File

@ -1,4 +1,4 @@
# 477 Rules Overview
# 474 Rules Overview
<br>
@ -84,7 +84,7 @@
- [Removing](#removing) (6)
- [RemovingStatic](#removingstatic) (8)
- [RemovingStatic](#removingstatic) (5)
- [Renaming](#renaming) (11)
@ -132,31 +132,7 @@ return static function (ContainerConfigurator $containerConfigurator): void {
$someObject = new SomeExampleClass;
-$someObject->someMethod();
+$someObject->someMethod(true);
```
<br>
```php
use Rector\Arguments\Rector\ClassMethod\ArgumentAdderRector;
use Rector\Arguments\ValueObject\ArgumentAdder;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symplify\SymfonyPhpConfig\ValueObjectInliner;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(ArgumentAdderRector::class)
->call('configure', [[
ArgumentAdderRector::ADDED_ARGUMENTS => ValueObjectInliner::inline([
new ArgumentAdder('SomeExampleClass', 'someMethod', 0, 'someArgument', true, 'SomeType', null),
]),
]]);
};
```
```diff
class MyCustomClass extends SomeExampleClass
{
- public function someMethod()
@ -9107,177 +9083,6 @@ Change static method and local-only calls to non-static
<br>
### NewUniqueObjectToEntityFactoryRector
Convert new X to new factories
:wrench: **configure it!**
- class: [`Rector\RemovingStatic\Rector\Class_\NewUniqueObjectToEntityFactoryRector`](../rules/RemovingStatic/Rector/Class_/NewUniqueObjectToEntityFactoryRector.php)
```php
use Rector\RemovingStatic\Rector\Class_\NewUniqueObjectToEntityFactoryRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(NewUniqueObjectToEntityFactoryRector::class)
->call('configure', [[
NewUniqueObjectToEntityFactoryRector::TYPES_TO_SERVICES => ['ClassName'],
]]);
};
```
```diff
class SomeClass
{
+ public function __construct(AnotherClassFactory $anotherClassFactory)
+ {
+ $this->anotherClassFactory = $anotherClassFactory;
+ }
+
public function run()
{
- return new AnotherClass;
+ return $this->anotherClassFactory->create();
}
}
class AnotherClass
{
public function someFun()
{
return StaticClass::staticMethod();
}
}
```
<br>
### PassFactoryToUniqueObjectRector
Convert new `X/Static::call()` to factories in entities, pass them via constructor to each other
:wrench: **configure it!**
- class: [`Rector\RemovingStatic\Rector\Class_\PassFactoryToUniqueObjectRector`](../rules/RemovingStatic/Rector/Class_/PassFactoryToUniqueObjectRector.php)
```php
use Rector\RemovingStatic\Rector\Class_\PassFactoryToUniqueObjectRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(PassFactoryToUniqueObjectRector::class)
->call('configure', [[
PassFactoryToUniqueObjectRector::TYPES_TO_SERVICES => ['StaticClass'],
]]);
};
```
```diff
class SomeClass
{
+ public function __construct(AnotherClassFactory $anotherClassFactory)
+ {
+ $this->anotherClassFactory = $anotherClassFactory;
+ }
+
public function run()
{
- return new AnotherClass;
+ return $this->anotherClassFactory->create();
}
}
class AnotherClass
{
+ public function __construct(StaticClass $staticClass)
+ {
+ $this->staticClass = $staticClass;
+ }
+
public function someFun()
{
- return StaticClass::staticMethod();
+ return $this->staticClass->staticMethod();
+ }
+}
+
+final class AnotherClassFactory
+{
+ /**
+ * @var StaticClass
+ */
+ private $staticClass;
+
+ public function __construct(StaticClass $staticClass)
+ {
+ $this->staticClass = $staticClass;
+ }
+
+ public function create(): AnotherClass
+ {
+ return new AnotherClass($this->staticClass);
}
}
```
<br>
### StaticTypeToSetterInjectionRector
Changes types to setter injection
:wrench: **configure it!**
- class: [`Rector\RemovingStatic\Rector\Class_\StaticTypeToSetterInjectionRector`](../rules/RemovingStatic/Rector/Class_/StaticTypeToSetterInjectionRector.php)
```php
use Rector\RemovingStatic\Rector\Class_\StaticTypeToSetterInjectionRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(StaticTypeToSetterInjectionRector::class)
->call('configure', [[
StaticTypeToSetterInjectionRector::STATIC_TYPES => ['SomeStaticClass'],
]]);
};
```
```diff
final class CheckoutEntityFactory
{
+ /**
+ * @var SomeStaticClass
+ */
+ private $someStaticClass;
+
+ public function setSomeStaticClass(SomeStaticClass $someStaticClass)
+ {
+ $this->someStaticClass = $someStaticClass;
+ }
+
public function run()
{
- return SomeStaticClass::go();
+ return $this->someStaticClass->go();
}
}
```
<br>
## Renaming
### PseudoNamespaceToNamespaceRector

View File

@ -1,64 +0,0 @@
<?php
namespace Rector\Tests\RemovingStatic\Rector\Class_\PassFactoryToEntityRector\Fixture;
use Rector\Tests\RemovingStatic\Rector\Class_\PassFactoryToEntityRector\Source\TurnMeToService;
class SomeClassWithMoreArguments
{
public function run()
{
return new AnotherClassWithMoreArguments(10);
}
}
class AnotherClassWithMoreArguments
{
private $number;
public function __construct($number)
{
$this->number = $number;
}
public function someFun()
{
return TurnMeToService::someStaticCall(5, $this->number);
}
}
?>
-----
<?php
namespace Rector\Tests\RemovingStatic\Rector\Class_\PassFactoryToEntityRector\Fixture;
use Rector\Tests\RemovingStatic\Rector\Class_\PassFactoryToEntityRector\Source\TurnMeToService;
class SomeClassWithMoreArguments
{
public function __construct(private \Rector\Tests\RemovingStatic\Rector\Class_\PassFactoryToEntityRector\Fixture\AnotherClassWithMoreArgumentsFactory $anotherClassWithMoreArgumentsFactory)
{
}
public function run()
{
return $this->anotherClassWithMoreArgumentsFactory->create(10);
}
}
class AnotherClassWithMoreArguments
{
private $number;
public function __construct($number, private \Rector\Tests\RemovingStatic\Rector\Class_\PassFactoryToEntityRector\Source\TurnMeToService $turnMeToService)
{
$this->number = $number;
}
public function someFun()
{
return $this->turnMeToService->someStaticCall(5, $this->number);
}
}
?>

View File

@ -1,42 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\RemovingStatic\Rector\Class_\PassFactoryToEntityRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\EasyTesting\StaticFixtureSplitter;
use Symplify\SmartFileSystem\SmartFileInfo;
final class PassFactoryToEntityRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
$expectedFactoryFilePath = StaticFixtureSplitter::getTemporaryPath() . '/AnotherClassWithMoreArgumentsFactory.php';
$this->assertFileExists($expectedFactoryFilePath);
$this->assertFileEquals(
__DIR__ . '/Source/ExpectedAnotherClassWithMoreArgumentsFactory.php',
$expectedFactoryFilePath
);
}
/**
* @return Iterator<SmartFileInfo>
*/
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/FixtureWithMultipleArguments');
}
public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}

View File

@ -1,19 +0,0 @@
<?php
namespace Rector\Tests\RemovingStatic\Rector\Class_\PassFactoryToEntityRector\Fixture;
final class AnotherClassFactory
{
/**
* @var \Rector\Tests\RemovingStatic\Rector\Class_\PassFactoryToEntityRector\Source\TurnMeToService
*/
private $turnMeToService;
public function __construct(\Rector\Tests\RemovingStatic\Rector\Class_\PassFactoryToEntityRector\Source\TurnMeToService $turnMeToService)
{
$this->turnMeToService = $turnMeToService;
}
public function create(): \Rector\Tests\RemovingStatic\Rector\Class_\PassFactoryToEntityRector\Fixture\AnotherClass
{
return new \Rector\Tests\RemovingStatic\Rector\Class_\PassFactoryToEntityRector\Fixture\AnotherClass($this->turnMeToService);
}
}

View File

@ -1,19 +0,0 @@
<?php
namespace Rector\Tests\RemovingStatic\Rector\Class_\PassFactoryToEntityRector\Fixture;
final class AnotherClassWithMoreArgumentsFactory
{
/**
* @var \Rector\Tests\RemovingStatic\Rector\Class_\PassFactoryToEntityRector\Source\TurnMeToService
*/
private $turnMeToService;
public function __construct(\Rector\Tests\RemovingStatic\Rector\Class_\PassFactoryToEntityRector\Source\TurnMeToService $turnMeToService)
{
$this->turnMeToService = $turnMeToService;
}
public function create($number): \Rector\Tests\RemovingStatic\Rector\Class_\PassFactoryToEntityRector\Fixture\AnotherClassWithMoreArguments
{
return new \Rector\Tests\RemovingStatic\Rector\Class_\PassFactoryToEntityRector\Fixture\AnotherClassWithMoreArguments($number, $this->turnMeToService);
}
}

View File

@ -1,13 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\RemovingStatic\Rector\Class_\PassFactoryToEntityRector\Source;
final class TurnMeToService
{
public static function someStaticCall()
{
return 1;
}
}

View File

@ -1,25 +0,0 @@
<?php
declare(strict_types=1);
use Rector\RemovingStatic\Rector\Class_\NewUniqueObjectToEntityFactoryRector;
use Rector\RemovingStatic\Rector\Class_\PassFactoryToUniqueObjectRector;
use Rector\Tests\RemovingStatic\Rector\Class_\PassFactoryToEntityRector\Fixture\AnotherClassWithMoreArguments;
use Rector\Tests\RemovingStatic\Rector\Class_\PassFactoryToEntityRector\Source\TurnMeToService;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$typesToServices = [TurnMeToService::class, AnotherClassWithMoreArguments::class];
$services->set(PassFactoryToUniqueObjectRector::class)
->call('configure', [[
PassFactoryToUniqueObjectRector::TYPES_TO_SERVICES => $typesToServices,
]]);
$services->set(NewUniqueObjectToEntityFactoryRector::class)
->call('configure', [[
NewUniqueObjectToEntityFactoryRector::TYPES_TO_SERVICES => $typesToServices,
]]);
};

View File

@ -1,43 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\RemovingStatic\Rector\Class_\StaticTypeToSetterInjectionRector\Fixture;
use Rector\Tests\RemovingStatic\Rector\Class_\StaticTypeToSetterInjectionRector\Source\GenericEntityFactory;
final class CheckoutEntityFactory
{
public function run()
{
return GenericEntityFactory::make();
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\Tests\RemovingStatic\Rector\Class_\StaticTypeToSetterInjectionRector\Fixture;
use Rector\Tests\RemovingStatic\Rector\Class_\StaticTypeToSetterInjectionRector\Source\GenericEntityFactory;
final class CheckoutEntityFactory
{
/**
* @var \Rector\Tests\RemovingStatic\Rector\Class_\StaticTypeToSetterInjectionRector\Source\GenericEntityFactory
*/
private $genericEntityFactory;
public function setGenericEntityFactory(\Rector\Tests\RemovingStatic\Rector\Class_\StaticTypeToSetterInjectionRector\Source\GenericEntityFactory $genericEntityFactory): void
{
$this->genericEntityFactory = $genericEntityFactory;
}
public function run()
{
return $this->genericEntityFactory->make();
}
}
?>

View File

@ -1,43 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\RemovingStatic\Rector\Class_\StaticTypeToSetterInjectionRector\Fixture;
use Rector\Tests\RemovingStatic\Rector\Class_\StaticTypeToSetterInjectionRector\Source\GenericEntityFactoryWithInterface;
final class TheSameButWithImplements
{
public function run()
{
return GenericEntityFactoryWithInterface::make();
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\Tests\RemovingStatic\Rector\Class_\StaticTypeToSetterInjectionRector\Fixture;
use Rector\Tests\RemovingStatic\Rector\Class_\StaticTypeToSetterInjectionRector\Source\GenericEntityFactoryWithInterface;
final class TheSameButWithImplements implements \ParentSetterEnforcingInterface
{
/**
* @var \Rector\Tests\RemovingStatic\Rector\Class_\StaticTypeToSetterInjectionRector\Source\GenericEntityFactoryWithInterface
*/
private $genericEntityFactoryWith;
public function setGenericEntityFactoryWith(\Rector\Tests\RemovingStatic\Rector\Class_\StaticTypeToSetterInjectionRector\Source\GenericEntityFactoryWithInterface $genericEntityFactoryWith): void
{
$this->genericEntityFactoryWith = $genericEntityFactoryWith;
}
public function run()
{
return $this->genericEntityFactoryWith->make();
}
}
?>

View File

@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\RemovingStatic\Rector\Class_\StaticTypeToSetterInjectionRector\Source;
use phpDocumentor\Reflection\Types\Integer;
final class GenericEntityFactory
{
public static function make(): Integer
{
return 5;
}
}

View File

@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\RemovingStatic\Rector\Class_\StaticTypeToSetterInjectionRector\Source;
use phpDocumentor\Reflection\Types\Integer;
final class GenericEntityFactoryWithInterface
{
public static function make(): Integer
{
return 5;
}
}

View File

@ -1,33 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\RemovingStatic\Rector\Class_\StaticTypeToSetterInjectionRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
final class StaticTypeToSetterInjectionRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}
/**
* @return Iterator<SmartFileInfo>
*/
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}

View File

@ -1,20 +0,0 @@
<?php
declare(strict_types=1);
use Rector\RemovingStatic\Rector\Class_\StaticTypeToSetterInjectionRector;
use Rector\Tests\RemovingStatic\Rector\Class_\StaticTypeToSetterInjectionRector\Source\GenericEntityFactory;
use Rector\Tests\RemovingStatic\Rector\Class_\StaticTypeToSetterInjectionRector\Source\GenericEntityFactoryWithInterface;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(StaticTypeToSetterInjectionRector::class)
->call('configure', [[
StaticTypeToSetterInjectionRector::STATIC_TYPES => [
GenericEntityFactory::class,
// with adding a parent interface to the class
'ParentSetterEnforcingInterface' => GenericEntityFactoryWithInterface::class,
],
]]);
};

View File

@ -1,61 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\RemovingStatic\Printer;
use Nette\Utils\Strings;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Namespace_;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\PhpParser\Printer\BetterStandardPrinter;
use Rector\Core\Provider\CurrentFileProvider;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Symplify\SmartFileSystem\SmartFileSystem;
final class FactoryClassPrinter
{
public function __construct(
private BetterStandardPrinter $betterStandardPrinter,
private SmartFileSystem $smartFileSystem,
private NodeNameResolver $nodeNameResolver,
private CurrentFileProvider $currentFileProvider
) {
}
public function printFactoryForClass(Class_ $factoryClass, Class_ $oldClass): void
{
$parentNode = $oldClass->getAttribute(AttributeKey::PARENT_NODE);
if ($parentNode instanceof Namespace_) {
$newNamespace = clone $parentNode;
$newNamespace->stmts = [];
$newNamespace->stmts[] = $factoryClass;
$nodeToPrint = $newNamespace;
} else {
$nodeToPrint = $factoryClass;
}
$factoryClassFilePath = $this->createFactoryClassFilePath($oldClass);
$factoryClassContent = $this->betterStandardPrinter->prettyPrintFile([$nodeToPrint]);
$this->smartFileSystem->dumpFile($factoryClassFilePath, $factoryClassContent);
}
private function createFactoryClassFilePath(Class_ $oldClass): string
{
$file = $this->currentFileProvider->getFile();
$smartFileInfo = $file->getSmartFileInfo();
$directoryPath = Strings::before($smartFileInfo->getRealPath(), DIRECTORY_SEPARATOR, -1);
$resolvedOldClass = $this->nodeNameResolver->getName($oldClass);
if ($resolvedOldClass === null) {
throw new ShouldNotHappenException();
}
$bareClassName = Strings::after($resolvedOldClass, '\\', -1) . 'Factory.php';
return $directoryPath . DIRECTORY_SEPARATOR . $bareClassName;
}
}

View File

@ -1,179 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\RemovingStatic\Rector\Class_;
use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\Class_;
use PHPStan\Type\ObjectType;
use Rector\Core\Contract\Rector\ConfigurableRectorInterface;
use Rector\Core\Rector\AbstractRector;
use Rector\Naming\Naming\PropertyNaming;
use Rector\PostRector\Collector\PropertyToAddCollector;
use Rector\PostRector\ValueObject\PropertyMetadata;
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* Depends on @see PassFactoryToUniqueObjectRector
*
* @see \Rector\Tests\RemovingStatic\Rector\Class_\PassFactoryToEntityRector\PassFactoryToEntityRectorTest
*/
final class NewUniqueObjectToEntityFactoryRector extends AbstractRector implements ConfigurableRectorInterface
{
/**
* @var string
*/
public const TYPES_TO_SERVICES = 'types_to_services';
/**
* @var string
*/
private const FACTORY = 'Factory';
/**
* @var ObjectType[]
*/
private array $matchedObjectTypes = [];
/**
* @var ObjectType[]
*/
private array $serviceObjectTypes = [];
public function __construct(
private PropertyNaming $propertyNaming,
private PropertyToAddCollector $propertyToAddCollector
) {
}
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Convert new X to new factories', [
new ConfiguredCodeSample(
<<<'CODE_SAMPLE'
class SomeClass
{
public function run()
{
return new AnotherClass;
}
}
class AnotherClass
{
public function someFun()
{
return StaticClass::staticMethod();
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
class SomeClass
{
public function __construct(AnotherClassFactory $anotherClassFactory)
{
$this->anotherClassFactory = $anotherClassFactory;
}
public function run()
{
return $this->anotherClassFactory->create();
}
}
class AnotherClass
{
public function someFun()
{
return StaticClass::staticMethod();
}
}
CODE_SAMPLE
,
[
self::TYPES_TO_SERVICES => ['ClassName'],
]
), ]);
}
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [Class_::class];
}
/**
* @param Class_ $node
*/
public function refactor(Node $node): Class_
{
$this->matchedObjectTypes = [];
// collect classes with new to factory in all classes
$this->traverseNodesWithCallable($node->stmts, function (Node $node): ?MethodCall {
if (! $node instanceof New_) {
return null;
}
$class = $this->getName($node->class);
if ($class === null) {
return null;
}
if (! $this->isClassMatching($class)) {
return null;
}
$objectType = new FullyQualifiedObjectType($class);
$this->matchedObjectTypes[] = $objectType;
$propertyName = $this->propertyNaming->fqnToVariableName($objectType) . self::FACTORY;
$propertyFetch = new PropertyFetch(new Variable('this'), $propertyName);
return new MethodCall($propertyFetch, 'create', $node->args);
});
foreach ($this->matchedObjectTypes as $matchedObjectType) {
$propertyName = $this->propertyNaming->fqnToVariableName($matchedObjectType) . self::FACTORY;
$propertyType = new FullyQualifiedObjectType($matchedObjectType->getClassName() . self::FACTORY);
$propertyMetadata = new PropertyMetadata($propertyName, $propertyType, Class_::MODIFIER_PRIVATE);
$this->propertyToAddCollector->addPropertyToClass($node, $propertyMetadata);
}
return $node;
}
/**
* @param array<string, mixed[]> $configuration
*/
public function configure(array $configuration): void
{
$typesToServices = $configuration[self::TYPES_TO_SERVICES] ?? [];
foreach ($typesToServices as $typeToService) {
$this->serviceObjectTypes[] = new ObjectType($typeToService);
}
}
private function isClassMatching(string $class): bool
{
foreach ($this->serviceObjectTypes as $serviceObjectType) {
if ($serviceObjectType->isInstanceOf($class)->yes()) {
return true;
}
}
return false;
}
}

View File

@ -1,195 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\RemovingStatic\Rector\Class_;
use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\Class_;
use PHPStan\Type\ObjectType;
use Rector\Core\Contract\Rector\ConfigurableRectorInterface;
use Rector\Core\Rector\AbstractRector;
use Rector\Naming\Naming\PropertyNaming;
use Rector\PostRector\Collector\PropertyToAddCollector;
use Rector\PostRector\ValueObject\PropertyMetadata;
use Rector\RemovingStatic\Printer\FactoryClassPrinter;
use Rector\RemovingStatic\StaticTypesInClassResolver;
use Rector\RemovingStatic\UniqueObjectFactoryFactory;
use Rector\RemovingStatic\UniqueObjectOrServiceDetector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \Rector\Tests\RemovingStatic\Rector\Class_\PassFactoryToEntityRector\PassFactoryToEntityRectorTest
*/
final class PassFactoryToUniqueObjectRector extends AbstractRector implements ConfigurableRectorInterface
{
/**
* @api
* @var string
*/
public const TYPES_TO_SERVICES = 'types_to_services';
/**
* @var ObjectType[]
*/
private array $serviceObjectTypes = [];
public function __construct(
private StaticTypesInClassResolver $staticTypesInClassResolver,
private PropertyNaming $propertyNaming,
private UniqueObjectOrServiceDetector $uniqueObjectOrServiceDetector,
private UniqueObjectFactoryFactory $uniqueObjectFactoryFactory,
private FactoryClassPrinter $factoryClassPrinter,
private PropertyToAddCollector $propertyToAddCollector
) {
}
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Convert new X/Static::call() to factories in entities, pass them via constructor to each other',
[
new ConfiguredCodeSample(
<<<'CODE_SAMPLE'
class SomeClass
{
public function run()
{
return new AnotherClass;
}
}
class AnotherClass
{
public function someFun()
{
return StaticClass::staticMethod();
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
class SomeClass
{
public function __construct(AnotherClassFactory $anotherClassFactory)
{
$this->anotherClassFactory = $anotherClassFactory;
}
public function run()
{
return $this->anotherClassFactory->create();
}
}
class AnotherClass
{
public function __construct(StaticClass $staticClass)
{
$this->staticClass = $staticClass;
}
public function someFun()
{
return $this->staticClass->staticMethod();
}
}
final class AnotherClassFactory
{
/**
* @var StaticClass
*/
private $staticClass;
public function __construct(StaticClass $staticClass)
{
$this->staticClass = $staticClass;
}
public function create(): AnotherClass
{
return new AnotherClass($this->staticClass);
}
}
CODE_SAMPLE
,
[
self::TYPES_TO_SERVICES => ['StaticClass'],
]
), ]
);
}
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [Class_::class, StaticCall::class];
}
/**
* @param StaticCall|Class_ $node
*/
public function refactor(Node $node): ?Node
{
if ($node instanceof Class_) {
return $this->refactorClass($node);
}
foreach ($this->serviceObjectTypes as $serviceObjectType) {
if (! $this->isObjectType($node->class, $serviceObjectType)) {
continue;
}
// is this object created via new somewhere else? use factory!
$variableName = $this->propertyNaming->fqnToVariableName($serviceObjectType);
$thisPropertyFetch = new PropertyFetch(new Variable('this'), $variableName);
return new MethodCall($thisPropertyFetch, $node->name, $node->args);
}
return $node;
}
/**
* @param array<string, mixed[]> $configuration
*/
public function configure(array $configuration): void
{
$typesToServices = $configuration[self::TYPES_TO_SERVICES] ?? [];
foreach ($typesToServices as $typeToService) {
$this->serviceObjectTypes[] = new ObjectType($typeToService);
}
}
private function refactorClass(Class_ $class): Class_
{
$staticTypesInClass = $this->staticTypesInClassResolver->collectStaticCallTypeInClass(
$class,
$this->serviceObjectTypes
);
foreach ($staticTypesInClass as $staticTypeInClass) {
$variableName = $this->propertyNaming->fqnToVariableName($staticTypeInClass);
$propertyMetadata = new PropertyMetadata($variableName, $staticTypeInClass, Class_::MODIFIER_PRIVATE);
$this->propertyToAddCollector->addPropertyToClass($class, $propertyMetadata);
// is this an object? create factory for it next to this :)
if ($this->uniqueObjectOrServiceDetector->isUniqueObject()) {
$factoryClass = $this->uniqueObjectFactoryFactory->createFactoryClass($class, $staticTypeInClass);
$this->factoryClassPrinter->printFactoryForClass($factoryClass, $class);
}
}
return $class;
}
}

View File

@ -1,168 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\RemovingStatic\Rector\Class_;
use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\Class_;
use PHPStan\Type\ObjectType;
use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger;
use Rector\Core\Contract\Rector\ConfigurableRectorInterface;
use Rector\Core\Rector\AbstractRector;
use Rector\Naming\Naming\PropertyNaming;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \Rector\Tests\RemovingStatic\Rector\Class_\StaticTypeToSetterInjectionRector\StaticTypeToSetterInjectionRectorTest
*/
final class StaticTypeToSetterInjectionRector extends AbstractRector implements ConfigurableRectorInterface
{
/**
* @api
* @var string
*/
public const STATIC_TYPES = 'static_types';
/**
* @var array<class-string|int, class-string>
*/
private array $staticTypes = [];
public function __construct(
private PropertyNaming $propertyNaming,
private PhpDocTypeChanger $phpDocTypeChanger
) {
}
public function getRuleDefinition(): RuleDefinition
{
// custom made only for Elasticr
return new RuleDefinition('Changes types to setter injection', [
new ConfiguredCodeSample(
<<<'CODE_SAMPLE'
final class CheckoutEntityFactory
{
public function run()
{
return SomeStaticClass::go();
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
final class CheckoutEntityFactory
{
/**
* @var SomeStaticClass
*/
private $someStaticClass;
public function setSomeStaticClass(SomeStaticClass $someStaticClass)
{
$this->someStaticClass = $someStaticClass;
}
public function run()
{
return $this->someStaticClass->go();
}
}
CODE_SAMPLE
,
[
self::STATIC_TYPES => ['SomeStaticClass'],
]
),
]);
}
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [Class_::class, StaticCall::class];
}
/**
* @param StaticCall|Class_ $node
*/
public function refactor(Node $node): ?Node
{
if ($node instanceof Class_) {
return $this->processClass($node);
}
foreach ($this->staticTypes as $staticType) {
$objectType = new ObjectType($staticType);
if (! $this->isObjectType($node->class, $objectType)) {
continue;
}
$variableName = $this->propertyNaming->fqnToVariableName($objectType);
$propertyFetch = new PropertyFetch(new Variable('this'), $variableName);
return new MethodCall($propertyFetch, $node->name, $node->args);
}
return null;
}
/**
* @param array<string, array<class-string|int, class-string>> $configuration
*/
public function configure(array $configuration): void
{
$this->staticTypes = $configuration[self::STATIC_TYPES] ?? [];
}
private function processClass(Class_ $class): Class_
{
foreach ($this->staticTypes as $implements => $staticType) {
$objectType = new ObjectType($staticType);
$containsEntityFactoryStaticCall = (bool) $this->betterNodeFinder->findFirst(
$class->stmts,
fn (Node $node): bool => $this->isEntityFactoryStaticCall($node, $objectType)
);
if (! $containsEntityFactoryStaticCall) {
continue;
}
if (is_string($implements)) {
$class->implements[] = new FullyQualified($implements);
}
$variableName = $this->propertyNaming->fqnToVariableName($objectType);
$setEntityFactoryMethod = $this->nodeFactory->createSetterClassMethod($variableName, $objectType);
$entityFactoryProperty = $this->nodeFactory->createPrivateProperty($variableName);
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($entityFactoryProperty);
$this->phpDocTypeChanger->changeVarType($phpDocInfo, $objectType);
$class->stmts = array_merge([$entityFactoryProperty, $setEntityFactoryMethod], $class->stmts);
break;
}
return $class;
}
private function isEntityFactoryStaticCall(Node $node, ObjectType $objectType): bool
{
if (! $node instanceof StaticCall) {
return false;
}
return $this->isObjectType($node->class, $objectType);
}
}

View File

@ -1,49 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\RemovingStatic;
use PhpParser\Node;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Stmt\Class_;
use PHPStan\Type\ObjectType;
use Rector\NodeTypeResolver\NodeTypeResolver;
use Symplify\Astral\NodeTraverser\SimpleCallableNodeTraverser;
final class StaticTypesInClassResolver
{
public function __construct(
private SimpleCallableNodeTraverser $simpleCallableNodeTraverser,
private NodeTypeResolver $nodeTypeResolver
) {
}
/**
* @param ObjectType[] $objectTypes
* @return ObjectType[]
*/
public function collectStaticCallTypeInClass(Class_ $class, array $objectTypes): array
{
$staticTypesInClass = [];
$this->simpleCallableNodeTraverser->traverseNodesWithCallable($class->stmts, function (Node $class) use (
$objectTypes,
&$staticTypesInClass
) {
if (! $class instanceof StaticCall) {
return null;
}
foreach ($objectTypes as $objectType) {
if ($this->nodeTypeResolver->isObjectType($class->class, $objectType)) {
$staticTypesInClass[] = $objectType;
}
}
return null;
});
return $staticTypesInClass;
}
}

View File

@ -1,176 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\RemovingStatic;
use Nette\Utils\Strings;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\Return_;
use PHPStan\Type\ObjectType;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\PhpParser\Node\NodeFactory;
use Rector\Core\ValueObject\MethodName;
use Rector\Naming\Naming\PropertyNaming;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\PHPStanStaticTypeMapper\ValueObject\TypeKind;
use Rector\StaticTypeMapper\StaticTypeMapper;
use Symplify\Astral\ValueObject\NodeBuilder\ClassBuilder;
use Symplify\Astral\ValueObject\NodeBuilder\MethodBuilder;
use Symplify\Astral\ValueObject\NodeBuilder\ParamBuilder;
final class UniqueObjectFactoryFactory
{
public function __construct(
private NodeFactory $nodeFactory,
private NodeNameResolver $nodeNameResolver,
private PropertyNaming $propertyNaming,
private StaticTypeMapper $staticTypeMapper,
private PhpDocTypeChanger $phpDocTypeChanger,
private PhpDocInfoFactory $phpDocInfoFactory
) {
}
public function createFactoryClass(Class_ $class, ObjectType $objectType): Class_
{
$className = $this->nodeNameResolver->getName($class);
if ($className === null) {
throw new ShouldNotHappenException();
}
$name = $className . 'Factory';
$shortName = $this->resolveClassShortName($name);
$factoryClassBuilder = new ClassBuilder($shortName);
$factoryClassBuilder->makeFinal();
$properties = $this->createPropertiesFromTypes($objectType);
$factoryClassBuilder->addStmts($properties);
// constructor
$constructorClassMethod = $this->createConstructMethod($objectType);
$factoryClassBuilder->addStmt($constructorClassMethod);
// create
$classMethod = $this->createCreateMethod($class, $className, $properties);
$factoryClassBuilder->addStmt($classMethod);
return $factoryClassBuilder->getNode();
}
private function resolveClassShortName(string $name): string
{
if (\str_contains($name, '\\')) {
return (string) Strings::after($name, '\\', -1);
}
return $name;
}
/**
* @return Property[]
*/
private function createPropertiesFromTypes(ObjectType $objectType): array
{
$properties = [];
$properties[] = $this->createPropertyFromObjectType($objectType);
return $properties;
}
private function createConstructMethod(ObjectType $objectType): ClassMethod
{
$propertyName = $this->propertyNaming->fqnToVariableName($objectType);
$paramBuilder = new ParamBuilder($propertyName);
$typeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($objectType, TypeKind::PARAM());
if ($typeNode !== null) {
$paramBuilder->setType($typeNode);
}
$params = [$paramBuilder->getNode()];
$assigns = $this->createAssignsFromParams($params);
$methodBuilder = new MethodBuilder(MethodName::CONSTRUCT);
$methodBuilder->makePublic();
$methodBuilder->addParams($params);
$methodBuilder->addStmts($assigns);
return $methodBuilder->getNode();
}
/**
* @param Property[] $properties
*/
private function createCreateMethod(Class_ $class, string $className, array $properties): ClassMethod
{
$new = new New_(new FullyQualified($className));
$constructClassMethod = $class->getMethod(MethodName::CONSTRUCT);
$params = [];
if ($constructClassMethod !== null) {
foreach ($constructClassMethod->params as $param) {
$params[] = $param;
$new->args[] = new Arg($param->var);
}
}
foreach ($properties as $property) {
$propertyName = $this->nodeNameResolver->getName($property);
$propertyFetch = new PropertyFetch(new Variable('this'), $propertyName);
$new->args[] = new Arg($propertyFetch);
}
$return = new Return_($new);
$methodBuilder = new MethodBuilder('create');
$methodBuilder->setReturnType(new FullyQualified($className));
$methodBuilder->makePublic();
$methodBuilder->addStmt($return);
$methodBuilder->addParams($params);
return $methodBuilder->getNode();
}
private function createPropertyFromObjectType(ObjectType $objectType): Property
{
$propertyName = $this->propertyNaming->fqnToVariableName($objectType);
$property = $this->nodeFactory->createPrivateProperty($propertyName);
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($property);
$this->phpDocTypeChanger->changeVarType($phpDocInfo, $objectType);
return $property;
}
/**
* @param Param[] $params
*
* @return Assign[]
*/
private function createAssignsFromParams(array $params): array
{
$assigns = [];
/** @var Param $param */
foreach ($params as $param) {
$propertyFetch = new PropertyFetch(new Variable('this'), $param->var->name);
$assigns[] = new Assign($propertyFetch, new Variable($param->var->name));
}
return $assigns;
}
}

View File

@ -1,19 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\RemovingStatic;
final class UniqueObjectOrServiceDetector
{
public function isUniqueObject(): bool
{
// ideas:
// hook in container?
// has scalar arguments?
// is created by new X in the code? → add "NewNodeCollector"
// fallback for now :)
return true;
}
}