[Generic] Add ServiceGetterToConstructorInjectionRector

This commit is contained in:
Tomas Votruba 2019-07-10 08:47:36 +02:00
parent d7a88ddd45
commit e5844eb13a
6 changed files with 311 additions and 0 deletions

View File

@ -0,0 +1,7 @@
services:
Rector\Rector\MethodCall\ServiceGetterToConstructorInjectionRector:
$methodNamesByTypesToServiceTypes:
'Doctrine\Common\Persistence\ManagerRegistry':
'getConnection': 'Doctrine\DBAL\Connection'
'Doctrine\ORM\EntityManagerInterface':
'getConfiguration': 'Doctrine\ORM\Configuration'

View File

@ -0,0 +1,183 @@
<?php declare(strict_types=1);
namespace Rector\Rector\MethodCall;
use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Identifier;
use PhpParser\Node\Stmt\Class_;
use Rector\Naming\PropertyNaming;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\ConfiguredCodeSample;
use Rector\RectorDefinition\RectorDefinition;
/**
* @see \Rector\Tests\Rector\MethodCall\ServiceGetterToConstructorInjectionRector\ServiceGetterToConstructorInjectionRectorTest
*/
final class ServiceGetterToConstructorInjectionRector extends AbstractRector
{
/**
* @var mixed[]
*/
private $methodNamesByTypesToServiceTypes = [];
/**
* @var PropertyNaming
*/
private $propertyNaming;
/**
* @param mixed[] $methodNamesByTypesToServiceTypes
*/
public function __construct(PropertyNaming $propertyNaming, array $methodNamesByTypesToServiceTypes = [])
{
$this->methodNamesByTypesToServiceTypes = $methodNamesByTypesToServiceTypes;
$this->propertyNaming = $propertyNaming;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Get service call to constructor injection', [
new ConfiguredCodeSample(
<<<'CODE_SAMPLE'
final class SomeClass
{
/**
* @var FirstService
*/
private $firstService;
public function __construct(FirstService $firstService)
{
$this->firstService = $firstService;
}
public function run()
{
$anotherService = $this->firstService->getAnotherService();
$anotherService->run();
}
}
class FirstService
{
/**
* @var AnotherService
*/
private $anotherService;
public function __construct(AnotherService $anotherService)
{
$this->anotherService = $anotherService;
}
public function getAnotherService(): AnotherService
{
return $this->anotherService;
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
final class SomeClass
{
/**
* @var FirstService
*/
private $firstService;
/**
* @var AnotherService
*/
private $anotherService;
public function __construct(FirstService $firstService, AnotherService $anotherService)
{
$this->firstService = $firstService;
$this->anotherService = $anotherService;
}
public function run()
{
$anotherService = $this->anotherService;
$anotherService->run();
}
}
CODE_SAMPLE
,
[
'$methodNamesByTypesToServiceTypes' => [
'FirstService' => [
'getAnotherService' => 'AnotherService',
],
],
]
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [MethodCall::class];
}
/**
* @param MethodCall $node
*/
public function refactor(Node $node): ?Node
{
$classNode = $node->getAttribute(AttributeKey::CLASS_NODE);
if (! $this->isNonAnonymousClass($classNode)) {
return null;
}
foreach ($this->methodNamesByTypesToServiceTypes as $type => $methodNamesToServiceTypes) {
if (! $this->isType($node->var, $type)) {
continue;
}
foreach ($methodNamesToServiceTypes as $methodName => $serviceType) {
if (! $this->isName($node->name, $methodName)) {
continue;
}
$propertyName = $this->propertyNaming->fqnToVariableName($serviceType);
// add property via constructor
/** @var Class_ $classNode */
$this->addPropertyToClass($classNode, $serviceType, $propertyName);
return new PropertyFetch(new Variable('this'), new Identifier($propertyName));
}
}
return $node;
}
private function isNonAnonymousClass(?Node $node): bool
{
if ($node === null) {
return false;
}
if (! $node instanceof Class_) {
return false;
}
$name = $this->getName($node);
if ($name === null) {
return false;
}
return ! Strings::contains($name, 'AnonymousClass');
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace Rector\Tests\Rector\MethodCall\ServiceGetterToConstructorInjectionRector\Fixture;
use Rector\Tests\Rector\MethodCall\ServiceGetterToConstructorInjectionRector\Source\FirstService;
final class SomeClass
{
/**
* @var FirstService
*/
private $firstService;
public function __construct(FirstService $firstService)
{
$this->firstService = $firstService;
}
public function run()
{
$anotherService = $this->firstService->getAnotherService();
$anotherService->run();
}
}
?>
-----
<?php
namespace Rector\Tests\Rector\MethodCall\ServiceGetterToConstructorInjectionRector\Fixture;
use Rector\Tests\Rector\MethodCall\ServiceGetterToConstructorInjectionRector\Source\FirstService;
final class SomeClass
{
/**
* @var FirstService
*/
private $firstService;
/**
* @var \Rector\Tests\Rector\MethodCall\ServiceGetterToConstructorInjectionRector\Source\AnotherService
*/
private $anotherService;
public function __construct(FirstService $firstService, \Rector\Tests\Rector\MethodCall\ServiceGetterToConstructorInjectionRector\Source\AnotherService $anotherService)
{
$this->firstService = $firstService;
$this->anotherService = $anotherService;
}
public function run()
{
$anotherService = $this->anotherService;
$anotherService->run();
}
}
?>

View File

@ -0,0 +1,32 @@
<?php declare(strict_types=1);
namespace Rector\Tests\Rector\MethodCall\ServiceGetterToConstructorInjectionRector;
use Rector\Rector\MethodCall\ServiceGetterToConstructorInjectionRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\Tests\Rector\MethodCall\ServiceGetterToConstructorInjectionRector\Source\AnotherService;
use Rector\Tests\Rector\MethodCall\ServiceGetterToConstructorInjectionRector\Source\FirstService;
final class ServiceGetterToConstructorInjectionRectorTest extends AbstractRectorTestCase
{
public function test(): void
{
$this->doTestFiles([__DIR__ . '/Fixture/fixture.php.inc']);
}
/**
* @return mixed[]
*/
protected function getRectorsWithConfiguration(): array
{
return [
ServiceGetterToConstructorInjectionRector::class => [
'$methodNamesByTypesToServiceTypes' => [
FirstService::class => [
'getAnotherService' => AnotherService::class,
],
],
],
];
}
}

View File

@ -0,0 +1,10 @@
<?php declare(strict_types=1);
namespace Rector\Tests\Rector\MethodCall\ServiceGetterToConstructorInjectionRector\Source;
final class AnotherService
{
public function run()
{
}
}

View File

@ -0,0 +1,21 @@
<?php declare(strict_types=1);
namespace Rector\Tests\Rector\MethodCall\ServiceGetterToConstructorInjectionRector\Source;
class FirstService
{
/**
* @var AnotherService
*/
private $anotherService;
public function __construct(AnotherService $anotherService)
{
$this->anotherService = $anotherService;
}
public function getAnotherService(): AnotherService
{
return $this->anotherService;
}
}