mirror of
https://github.com/rectorphp/rector.git
synced 2025-01-17 21:38:22 +01:00
[Legacy] Add ChangeSingletonToServiceRector
This commit is contained in:
parent
3000f593eb
commit
cc2b9df06d
@ -72,7 +72,8 @@
|
||||
"Rector\\Shopware\\": "packages/Shopware/src",
|
||||
"Rector\\NetteTesterToPHPUnit\\": "packages/NetteTesterToPHPUnit/src",
|
||||
"Rector\\Nette\\": "packages/Nette/src",
|
||||
"Rector\\SOLID\\": "packages/SOLID/src"
|
||||
"Rector\\SOLID\\": "packages/SOLID/src",
|
||||
"Rector\\Legacy\\": "packages/Legacy/src"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
@ -107,7 +108,8 @@
|
||||
"Rector\\Shopware\\Tests\\": "packages/Shopware/tests",
|
||||
"Rector\\NetteTesterToPHPUnit\\Tests\\": "packages/NetteTesterToPHPUnit/tests",
|
||||
"Rector\\Nette\\Tests\\": "packages/Nette/tests",
|
||||
"Rector\\SOLID\\Tests\\": "packages/SOLID/tests"
|
||||
"Rector\\SOLID\\Tests\\": "packages/SOLID/tests",
|
||||
"Rector\\Legacy\\Tests\\": "packages/Legacy/tests"
|
||||
},
|
||||
"classmap": [
|
||||
"packages/Symfony/tests/Rector/FrameworkBundle/AbstractToConstructorInjectionRectorSource",
|
||||
|
2
ecs.yaml
2
ecs.yaml
@ -100,6 +100,7 @@ parameters:
|
||||
|
||||
Symplify\CodingStandard\Sniffs\CleanCode\CognitiveComplexitySniff:
|
||||
# tough logic
|
||||
- 'packages/Legacy/src/Rector/ClassMethod/ChangeSingletonToServiceRector.php'
|
||||
- 'src/Rector/Psr4/MultipleClassFileToPsr4ClassesRector.php'
|
||||
- 'src/PhpParser/Node/Resolver/NameResolver.php'
|
||||
- 'src/Rector/MethodBody/NormalToFluentRector.php'
|
||||
@ -111,6 +112,7 @@ parameters:
|
||||
- 'packages/Laravel/src/Rector/FuncCall/HelperFunctionToConstructorInjectionRector.php'
|
||||
- 'packages/PhpSpecToPHPUnit/src/Rector/MethodCall/PhpSpecPromisesToPHPUnitAssertRector.php'
|
||||
- 'packages/NetteTesterToPHPUnit/src/AssertManipulator.php'
|
||||
- 'packages/Legacy/src/NodeAnalyzer/SingletonClassMethodAnalyzer.php'
|
||||
# aliases
|
||||
- 'packages/CodingStyle/src/Rector/Namespace_/ImportFullyQualifiedNamesRector.php'
|
||||
|
||||
|
@ -281,6 +281,21 @@ final class CreateRectorCommand extends Command implements ContributorCommandInt
|
||||
{
|
||||
$content = Json::encode($json, Json::PRETTY);
|
||||
$content = $this->inlineSections($content, ['keywords', 'bin']);
|
||||
$content = $this->inlineAuthors($content);
|
||||
FileSystem::write($filePath, $content);
|
||||
}
|
||||
|
||||
private function inlineAuthors(string $jsonContent): string
|
||||
{
|
||||
$pattern = '#(?<start>"authors": \[\s+)(?<content>.*?)(?<end>\s+\](,))#ms';
|
||||
$jsonContent = Strings::replace($jsonContent, $pattern, function (array $match): string {
|
||||
$inlined = Strings::replace($match['content'], '#\s+#', ' ');
|
||||
$inlined = trim($inlined);
|
||||
$inlined = Strings::replace($inlined, '#},#', "},\n ");
|
||||
|
||||
return $match['start'] . $inlined . $match['end'];
|
||||
});
|
||||
|
||||
return $jsonContent;
|
||||
}
|
||||
}
|
||||
|
8
packages/Legacy/config/config.yaml
Normal file
8
packages/Legacy/config/config.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
services:
|
||||
_defaults:
|
||||
public: true
|
||||
autowire: true
|
||||
|
||||
Rector\Legacy\:
|
||||
resource: '../src'
|
||||
exclude: '../src/{Rector/**/*Rector.php}'
|
@ -0,0 +1,125 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\Legacy\NodeAnalyzer;
|
||||
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\BinaryOp\Identical;
|
||||
use PhpParser\Node\Expr\BooleanNot;
|
||||
use PhpParser\Node\Expr\New_;
|
||||
use PhpParser\Node\Expr\StaticPropertyFetch;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Expression;
|
||||
use PhpParser\Node\Stmt\If_;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\NodeTypeResolver\NodeTypeResolver;
|
||||
use Rector\PhpParser\Node\Manipulator\ConstFetchManipulator;
|
||||
use Rector\PhpParser\Printer\BetterStandardPrinter;
|
||||
|
||||
final class SingletonClassMethodAnalyzer
|
||||
{
|
||||
/**
|
||||
* @var ConstFetchManipulator
|
||||
*/
|
||||
private $constFetchManipulator;
|
||||
|
||||
/**
|
||||
* @var BetterStandardPrinter
|
||||
*/
|
||||
private $betterStandardPrinter;
|
||||
|
||||
/**
|
||||
* @var NodeTypeResolver
|
||||
*/
|
||||
private $nodeTypeResolver;
|
||||
|
||||
public function __construct(
|
||||
ConstFetchManipulator $constFetchManipulator,
|
||||
BetterStandardPrinter $betterStandardPrinter,
|
||||
NodeTypeResolver $nodeTypeResolver
|
||||
) {
|
||||
$this->constFetchManipulator = $constFetchManipulator;
|
||||
$this->betterStandardPrinter = $betterStandardPrinter;
|
||||
$this->nodeTypeResolver = $nodeTypeResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match this code:
|
||||
* if (null === static::$instance) {
|
||||
* static::$instance = new static();
|
||||
* }
|
||||
* return static::$instance;
|
||||
*
|
||||
* Matches "static::$instance" on success
|
||||
*/
|
||||
public function matchStaticPropertyFetch(ClassMethod $classMethod): ?StaticPropertyFetch
|
||||
{
|
||||
if (count((array) $classMethod->stmts) !== 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $classMethod->stmts[0] instanceof If_) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var If_ $if */
|
||||
$if = $classMethod->stmts[0];
|
||||
$staticPropertyFetch = $this->matchStaticPropertyFetchInIfCond($if->cond);
|
||||
|
||||
if (count($if->stmts) !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $if->stmts[0] instanceof Expression) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$stmt = $if->stmts[0]->expr;
|
||||
|
||||
// create self and assign to static property
|
||||
if (! $stmt instanceof Assign) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $this->betterStandardPrinter->areNodesEqual($staticPropertyFetch, $stmt->var)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $stmt->expr instanceof New_) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$class = $classMethod->getAttribute(AttributeKey::CLASS_NAME);
|
||||
|
||||
// the "self" class is created
|
||||
if ($this->nodeTypeResolver->getTypes($stmt->expr->class) !== [$class]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var StaticPropertyFetch $staticPropertyFetch */
|
||||
return $staticPropertyFetch;
|
||||
}
|
||||
|
||||
private function matchStaticPropertyFetchInIfCond(Expr $expr): ?StaticPropertyFetch
|
||||
{
|
||||
// matching: "self::$static === null"
|
||||
if ($expr instanceof Identical) {
|
||||
if ($this->constFetchManipulator->isNull($expr->left) && $expr->right instanceof StaticPropertyFetch) {
|
||||
return $expr->right;
|
||||
}
|
||||
|
||||
if ($this->constFetchManipulator->isNull($expr->right) && $expr->left instanceof StaticPropertyFetch) {
|
||||
return $expr->left;
|
||||
}
|
||||
}
|
||||
|
||||
// matching: "! self::$static"
|
||||
if ($expr instanceof BooleanNot) {
|
||||
if ($expr->expr instanceof StaticPropertyFetch) {
|
||||
return $expr->expr;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\Legacy\Rector\ClassMethod;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Property;
|
||||
use Rector\Legacy\NodeAnalyzer\SingletonClassMethodAnalyzer;
|
||||
use Rector\Rector\AbstractRector;
|
||||
use Rector\RectorDefinition\CodeSample;
|
||||
use Rector\RectorDefinition\RectorDefinition;
|
||||
|
||||
/**
|
||||
* @see https://3v4l.org/lifbH
|
||||
* @see https://stackoverflow.com/a/203359/1348344
|
||||
* @see http://cleancode.blog/2017/07/20/how-to-avoid-many-instances-in-singleton-pattern/
|
||||
*/
|
||||
final class ChangeSingletonToServiceRector extends AbstractRector
|
||||
{
|
||||
/**
|
||||
* @var SingletonClassMethodAnalyzer
|
||||
*/
|
||||
private $singletonClassMethodAnalyzer;
|
||||
|
||||
public function __construct(SingletonClassMethodAnalyzer $singletonClassMethodAnalyzer)
|
||||
{
|
||||
$this->singletonClassMethodAnalyzer = $singletonClassMethodAnalyzer;
|
||||
}
|
||||
|
||||
public function getDefinition(): RectorDefinition
|
||||
{
|
||||
return new RectorDefinition('Change singleton class to normal class that can be registered as a service', [
|
||||
new CodeSample(
|
||||
<<<'CODE_SAMPLE'
|
||||
class SomeClass
|
||||
{
|
||||
private static $instance;
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public static function getInstance()
|
||||
{
|
||||
if (null === static::$instance) {
|
||||
static::$instance = new static();
|
||||
}
|
||||
|
||||
return static::$instance;
|
||||
}
|
||||
}
|
||||
CODE_SAMPLE
|
||||
,
|
||||
<<<'CODE_SAMPLE'
|
||||
class SomeClass
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
}
|
||||
CODE_SAMPLE
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getNodeTypes(): array
|
||||
{
|
||||
return [Class_::class];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Class_ $node
|
||||
*/
|
||||
public function refactor(Node $node): ?Node
|
||||
{
|
||||
if ($node->isAnonymous()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$match = $this->matchStaticPropertyFetchAndGetSingletonMethodName($node);
|
||||
if ($match === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
[$singletonPropertyName, $getSingletonMethodName] = $match;
|
||||
|
||||
return $this->refactorClassStmts($node, $getSingletonMethodName, $singletonPropertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Class_ $class
|
||||
* @return string[]|null
|
||||
*/
|
||||
private function matchStaticPropertyFetchAndGetSingletonMethodName(Class_ $class): ?array
|
||||
{
|
||||
foreach ((array) $class->stmts as $classStmt) {
|
||||
if ($classStmt instanceof ClassMethod) {
|
||||
if (! $classStmt->isStatic()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$staticPropertyFetch = $this->singletonClassMethodAnalyzer->matchStaticPropertyFetch($classStmt);
|
||||
if ($staticPropertyFetch === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [$this->getName($staticPropertyFetch), $this->getName($classStmt)];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function refactorClassStmts(
|
||||
Class_ $node,
|
||||
string $getSingletonMethodName,
|
||||
string $singletonPropertyName
|
||||
): Class_ {
|
||||
foreach ((array) $node->stmts as $key => $classStmt) {
|
||||
if ($classStmt instanceof ClassMethod) {
|
||||
if ($this->isName($classStmt, $getSingletonMethodName)) {
|
||||
unset($node->stmts[$key]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! $this->isNames($classStmt, ['__construct', '__clone', '__wakeup'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! $classStmt->isPublic()) {
|
||||
// remove non-public empty
|
||||
if ($classStmt->stmts === []) {
|
||||
unset($node->stmts[$key]);
|
||||
} else {
|
||||
$this->makePublic($classStmt);
|
||||
}
|
||||
}
|
||||
} elseif ($classStmt instanceof Property) {
|
||||
if ($this->isName($classStmt, $singletonPropertyName)) {
|
||||
unset($node->stmts[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\Legacy\Tests\Rector\ClassMethod\ChangeSingletonToServiceRector;
|
||||
|
||||
use Rector\Legacy\Rector\ClassMethod\ChangeSingletonToServiceRector;
|
||||
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
|
||||
|
||||
final class ChangeSingletonToServiceRectorTest extends AbstractRectorTestCase
|
||||
{
|
||||
public function test(): void
|
||||
{
|
||||
$this->doTestFiles([
|
||||
__DIR__ . '/Fixture/fixture.php.inc',
|
||||
__DIR__ . '/Fixture/static_variable.php.inc',
|
||||
__DIR__ . '/Fixture/protected_construct.php.inc',
|
||||
__DIR__ . '/Fixture/non_empty_protected_construct.php.inc',
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getRectorClass(): string
|
||||
{
|
||||
return ChangeSingletonToServiceRector::class;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\Legacy\Tests\Rector\ClassMethod\ChangeSingletonToServiceRector\Fixture;
|
||||
|
||||
class SomeClass
|
||||
{
|
||||
private static $instance;
|
||||
|
||||
public static function getInstance()
|
||||
{
|
||||
if (null === static::$instance) {
|
||||
static::$instance = new static();
|
||||
}
|
||||
|
||||
return static::$instance;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Legacy\Tests\Rector\ClassMethod\ChangeSingletonToServiceRector\Fixture;
|
||||
|
||||
class SomeClass
|
||||
{
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\Legacy\Tests\Rector\ClassMethod\ChangeSingletonToServiceRector\Fixture;
|
||||
|
||||
class NonEmptyProtectedConstruct
|
||||
{
|
||||
private static $instance;
|
||||
|
||||
public static function getInstance()
|
||||
{
|
||||
if (null === static::$instance) {
|
||||
static::$instance = new static();
|
||||
}
|
||||
|
||||
return static::$instance;
|
||||
}
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
$bla = 1;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Legacy\Tests\Rector\ClassMethod\ChangeSingletonToServiceRector\Fixture;
|
||||
|
||||
class NonEmptyProtectedConstruct
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$bla = 1;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\Legacy\Tests\Rector\ClassMethod\ChangeSingletonToServiceRector\Fixture;
|
||||
|
||||
class ProtectedConstruct
|
||||
{
|
||||
private static $instance;
|
||||
|
||||
public static function getInstance()
|
||||
{
|
||||
if (null === static::$instance) {
|
||||
static::$instance = new static();
|
||||
}
|
||||
|
||||
return static::$instance;
|
||||
}
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Legacy\Tests\Rector\ClassMethod\ChangeSingletonToServiceRector\Fixture;
|
||||
|
||||
class ProtectedConstruct
|
||||
{
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\Legacy\Tests\Rector\ClassMethod\ChangeSingletonToServiceRector\Fixture;
|
||||
|
||||
class StaticVariable
|
||||
{
|
||||
private static $instance;
|
||||
|
||||
public static function getThis()
|
||||
{
|
||||
if(!self::$instance) {
|
||||
self::$instance = new self;
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Legacy\Tests\Rector\ClassMethod\ChangeSingletonToServiceRector\Fixture;
|
||||
|
||||
class StaticVariable
|
||||
{
|
||||
}
|
||||
|
||||
?>
|
@ -19,7 +19,7 @@ final class ReturnTypeDeclarationRector extends AbstractTypeDeclarationRector
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $excludeClassMethodNames = ['__construct', '__destruct', '__clone'];
|
||||
private const EXCLUDED_METHOD_NAMES = ['__construct', '__destruct', '__clone'];
|
||||
|
||||
public function getDefinition(): RectorDefinition
|
||||
{
|
||||
@ -69,7 +69,7 @@ CODE_SAMPLE
|
||||
}
|
||||
|
||||
// skip excluded methods
|
||||
if ($node instanceof ClassMethod && $this->isNames($node, $this->excludeClassMethodNames)) {
|
||||
if ($node instanceof ClassMethod && $this->isNames($node, self::EXCLUDED_METHOD_NAMES)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -169,4 +169,6 @@ parameters:
|
||||
- '#Cannot cast array<string\>\|bool\|string\|null to string#'
|
||||
|
||||
- '#Parameter \#1 \$node of method Rector\\PhpParser\\Node\\Manipulator\\VisibilityManipulator\:\:makeAbstract\(\) expects PhpParser\\Node\\Stmt\\Class_\|PhpParser\\Node\\Stmt\\ClassMethod, PhpParser\\Node given#'
|
||||
- '#Method Rector\\Legacy\\NodeAnalyzer\\SingletonClassMethodAnalyzer\:\:matchStaticPropertyFetch\(\) should return PhpParser\\Node\\Expr\\StaticPropertyFetch\|null but returns PhpParser\\Node\\Expr#'
|
||||
- '#Method Rector\\Legacy\\Rector\\ClassMethod\\ChangeSingletonToServiceRector\:\:matchStaticPropertyFetchAndGetSingletonMethodName\(\) should return array<string\>\|null but returns array<int, string\|null\>#'
|
||||
|
||||
|
@ -4,6 +4,8 @@ parameters:
|
||||
- "/Fixtures/"
|
||||
- "/Expected/"
|
||||
- "/Source/"
|
||||
- "/tests/" # for better performance of local changes
|
||||
|
||||
# autoload-buggy cases
|
||||
- "*.php.inc"
|
||||
# string might not exist for SplitStringClassConstantToClassConstFetchRector
|
||||
@ -15,3 +17,4 @@ parameters:
|
||||
php_version_features: '7.1'
|
||||
|
||||
services:
|
||||
# Rector\CodingStyle\Rector\Namespace_\ImportFullyQualifiedNamesRector: ~
|
||||
|
Loading…
x
Reference in New Issue
Block a user