mirror of
https://github.com/rectorphp/rector.git
synced 2024-05-31 16:30:51 +00:00
[Symfony] add CommandToConstructorInjectionRector
This commit is contained in:
parent
84bcd3bbb4
commit
3142225473
|
@ -13,4 +13,9 @@ final class SetNames
|
|||
* @var string
|
||||
*/
|
||||
public const SYMFONY = 'Symfony';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const SYMFONY_EXTRA = 'SymfonyExtra';
|
||||
}
|
||||
|
|
|
@ -1,178 +0,0 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\Rector\Contrib\Symfony;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Expr\PropertyFetch;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use Rector\Builder\Class_\ClassPropertyCollector;
|
||||
use Rector\Builder\Kernel\ServiceFromKernelResolver;
|
||||
use Rector\Builder\Naming\NameResolver;
|
||||
use Rector\Deprecation\SetNames;
|
||||
use Rector\Rector\AbstractRector;
|
||||
use Rector\Tests\Rector\Contrib\Symfony\GetterToPropertyRector\Source\LocalKernel;
|
||||
|
||||
/**
|
||||
* Converts all:
|
||||
* $this->get('some_service') # where "some_service" is name of the service in container.
|
||||
*
|
||||
* into:
|
||||
* $this->someService # where "someService" is type of the service
|
||||
*/
|
||||
final class GetterToPropertyRector extends AbstractRector
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $className;
|
||||
|
||||
/**
|
||||
* @var NameResolver
|
||||
*/
|
||||
private $nameResolver;
|
||||
|
||||
/**
|
||||
* @var ServiceFromKernelResolver
|
||||
*/
|
||||
private $serviceFromKernelResolver;
|
||||
|
||||
/**
|
||||
* @var ClassPropertyCollector
|
||||
*/
|
||||
private $classPropertyCollector;
|
||||
|
||||
public function __construct(
|
||||
NameResolver $nameResolver,
|
||||
ServiceFromKernelResolver $serviceFromKernelResolver,
|
||||
ClassPropertyCollector $classPropertyCollector
|
||||
) {
|
||||
$this->nameResolver = $nameResolver;
|
||||
$this->serviceFromKernelResolver = $serviceFromKernelResolver;
|
||||
$this->classPropertyCollector = $classPropertyCollector;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Node[] $nodes
|
||||
* @return null|Node[]
|
||||
*/
|
||||
public function beforeTraverse(array $nodes): ?array
|
||||
{
|
||||
$this->className = null;
|
||||
|
||||
foreach ($nodes as $node) {
|
||||
if ($node instanceof Class_) {
|
||||
$this->className = (string) $node->name;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function isCandidate(Node $node): bool
|
||||
{
|
||||
// $var = $this->get('some_service');
|
||||
// $var = $this->get('some_service')->getData();
|
||||
if ($node instanceof Assign && ($node->expr instanceof MethodCall || $node->var instanceof MethodCall)) {
|
||||
if ($this->isContainerGetCall($node->expr)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// ['var => $this->get('some_service')->getData()]
|
||||
if ($node instanceof MethodCall && $node->var instanceof MethodCall) {
|
||||
if ($this->isContainerGetCall($node->var)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function refactor(Node $assignOrMethodCallNode): ?Node
|
||||
{
|
||||
if ($assignOrMethodCallNode instanceof Assign) {
|
||||
$refactoredMethodCall = $this->processMethodCallNode($assignOrMethodCallNode->expr);
|
||||
if ($refactoredMethodCall) {
|
||||
$assignOrMethodCallNode->expr = $refactoredMethodCall;
|
||||
}
|
||||
}
|
||||
|
||||
if ($assignOrMethodCallNode instanceof MethodCall) {
|
||||
$refactoredMethodCall = $this->processMethodCallNode($assignOrMethodCallNode->var);
|
||||
if ($refactoredMethodCall) {
|
||||
$assignOrMethodCallNode->var = $refactoredMethodCall;
|
||||
}
|
||||
}
|
||||
|
||||
return $assignOrMethodCallNode;
|
||||
}
|
||||
|
||||
public function getSetName(): string
|
||||
{
|
||||
return SetNames::SYMFONY;
|
||||
}
|
||||
|
||||
public function sinceVersion(): float
|
||||
{
|
||||
return 3.3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is "$this->get('string')" statements?
|
||||
*/
|
||||
private function isContainerGetCall(MethodCall $methodCall): bool
|
||||
{
|
||||
if ($methodCall->var->name !== 'this') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((string) $methodCall->name !== 'get') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $methodCall->args[0]->value instanceof String_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function processMethodCallNode(MethodCall $methodCall): ?PropertyFetch
|
||||
{
|
||||
/** @var String_ $argument */
|
||||
$argument = $methodCall->args[0]->value;
|
||||
$serviceName = $argument->value;
|
||||
|
||||
$serviceType = $this->serviceFromKernelResolver->resolveServiceClassByNameFromKernel(
|
||||
$serviceName,
|
||||
LocalKernel::class
|
||||
);
|
||||
|
||||
if ($serviceType === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$propertyName = $this->nameResolver->resolvePropertyNameFromType($serviceType);
|
||||
|
||||
$this->classPropertyCollector->addPropertyForClass($this->className, $serviceType, $propertyName);
|
||||
|
||||
return $this->createPropertyFetch($propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates "$this->propertyName".
|
||||
*/
|
||||
private function createPropertyFetch(string $propertyName): PropertyFetch
|
||||
{
|
||||
return new PropertyFetch(
|
||||
new Variable('this', [
|
||||
'name' => $propertyName,
|
||||
]),
|
||||
$propertyName
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\Rector\Contrib\SymfonyExtra;
|
||||
|
||||
use Nette\Utils\Strings;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\ClassConstFetch;
|
||||
use PhpParser\Node\Expr\ConstFetch;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Expr\PropertyFetch;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use Rector\Builder\Class_\ClassPropertyCollector;
|
||||
use Rector\Builder\Kernel\ServiceFromKernelResolver;
|
||||
use Rector\Builder\Naming\NameResolver;
|
||||
use Rector\Deprecation\SetNames;
|
||||
use Rector\Rector\AbstractRector;
|
||||
use Rector\Tests\Rector\Contrib\SymfonyExtra\GetterToPropertyRector\Source\LocalKernel;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
|
||||
/**
|
||||
* Ref: https://github.com/symfony/symfony/blob/master/UPGRADE-4.0.md#console
|
||||
*
|
||||
* Similar to @see \Rector\Rector\Contrib\Symfony\GetterToPropertyRector
|
||||
* @todo Extract common logic!
|
||||
*
|
||||
* Before:
|
||||
* class MyCommand extends ContainerAwareCommand
|
||||
*
|
||||
* $this->getContainer()->get('some_service');
|
||||
*
|
||||
* After:
|
||||
* class MyCommand extends Command
|
||||
*
|
||||
* public function construct(SomeService $someService)
|
||||
* {
|
||||
* $this->someService = $someService;
|
||||
* }
|
||||
*
|
||||
* ...
|
||||
*
|
||||
* $this->someService
|
||||
*/
|
||||
final class CommandToConstructorInjectionRector extends AbstractRector
|
||||
{
|
||||
/**
|
||||
* @var ServiceFromKernelResolver
|
||||
*/
|
||||
private $serviceFromKernelResolver;
|
||||
|
||||
/**
|
||||
* @var ClassPropertyCollector
|
||||
*/
|
||||
private $classPropertyCollector;
|
||||
|
||||
/**
|
||||
* @var NameResolver
|
||||
*/
|
||||
private $nameResolver;
|
||||
|
||||
/**
|
||||
* @var Class_
|
||||
*/
|
||||
private $classNode;
|
||||
|
||||
public function __construct(
|
||||
ServiceFromKernelResolver $serviceFromKernelResolver,
|
||||
ClassPropertyCollector $classPropertyCollector,
|
||||
NameResolver $nameResolver
|
||||
) {
|
||||
$this->serviceFromKernelResolver = $serviceFromKernelResolver;
|
||||
$this->classPropertyCollector = $classPropertyCollector;
|
||||
$this->nameResolver = $nameResolver;
|
||||
}
|
||||
|
||||
public function getSetName(): string
|
||||
{
|
||||
return SetNames::SYMFONY_EXTRA;
|
||||
}
|
||||
|
||||
public function sinceVersion(): float
|
||||
{
|
||||
return 3.3;
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo add node traverser for this or to AbstractRector
|
||||
* @param Node[] $nodes
|
||||
* @return null|Node[]
|
||||
*/
|
||||
public function beforeTraverse(array $nodes): ?array
|
||||
{
|
||||
$this->classNode = null;
|
||||
|
||||
foreach ($nodes as $node) {
|
||||
if ($node instanceof Class_) {
|
||||
$this->classNode = $node;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public function isCandidate(Node $node): bool
|
||||
{
|
||||
if (! Strings::endsWith($this->getClassName(), 'Command')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// $this->getContainer()->get('some_service');
|
||||
if (! $node instanceof MethodCall) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $node->var instanceof MethodCall) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((string) $node->var->var->name !== 'this') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((string) $node->name !== 'get') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! isset($node->args[0])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $node->args[0]->value instanceof String_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param MethodCall $node
|
||||
*/
|
||||
public function refactor(Node $node): ?Node
|
||||
{
|
||||
$this->replaceParentContainerAwareCommandWithCommand();
|
||||
|
||||
$serviceName = $node->args[0]->value->value;
|
||||
|
||||
$serviceType = $this->serviceFromKernelResolver->resolveServiceClassByNameFromKernel(
|
||||
$serviceName,
|
||||
LocalKernel::class
|
||||
);
|
||||
|
||||
if ($serviceType === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$propertyName = $this->nameResolver->resolvePropertyNameFromType($serviceType);
|
||||
|
||||
$this->classPropertyCollector->addPropertyForClass($this->getClassName(), $serviceType, $propertyName);
|
||||
|
||||
return $this->createPropertyFetch($propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo move to NodeFactory...
|
||||
* Creates "$this->propertyName".
|
||||
*/
|
||||
private function createPropertyFetch(string $propertyName): PropertyFetch
|
||||
{
|
||||
return new PropertyFetch(
|
||||
new Node\Expr\Variable('this', [
|
||||
'name' => $propertyName,
|
||||
]),
|
||||
$propertyName
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo move to parent class?
|
||||
*/
|
||||
private function getClassName(): string
|
||||
{
|
||||
return $this->classNode->namespacedName->toString();
|
||||
}
|
||||
|
||||
private function replaceParentContainerAwareCommandWithCommand(): void
|
||||
{
|
||||
$this->classNode->extends = new Name('\Symfony\Component\Console\Command\Command');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
|
||||
|
||||
final class MyCommand extends \Symfony\Component\Console\Command\Command
|
||||
{
|
||||
/**
|
||||
* @var stdClass
|
||||
*/
|
||||
private $stdClass;
|
||||
public function __construct(stdClass $stdClass)
|
||||
{
|
||||
$this->stdClass = $stdClass;
|
||||
}
|
||||
protected function execute()
|
||||
{
|
||||
$someService = $this->stdClass;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\Tests\Rector\Contrib\Symfony\CommandToConstructorInjectionRector;
|
||||
|
||||
use Rector\Rector\Contrib\SymfonyExtra\CommandToConstructorInjectionRector;
|
||||
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
|
||||
|
||||
final class Test extends AbstractRectorTestCase
|
||||
{
|
||||
public function test(): void
|
||||
{
|
||||
$this->doTestFileMatchesExpectedContent(
|
||||
__DIR__ . '/Wrong/wrong.php.inc',
|
||||
__DIR__ . '/Correct/correct.php.inc'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
protected function getRectorClasses(): array
|
||||
{
|
||||
return [CommandToConstructorInjectionRector::class];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
|
||||
|
||||
final class MyCommand extends ContainerAwareCommand
|
||||
{
|
||||
protected function execute()
|
||||
{
|
||||
$someService = $this->getContainer()->get('some_service');
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user