diff --git a/src/Rector/Contrib/SymfonyExtra/GetterToPropertyRector.php b/src/Rector/Contrib/SymfonyExtra/GetterToPropertyRector.php new file mode 100644 index 00000000000..414139d4fa1 --- /dev/null +++ b/src/Rector/Contrib/SymfonyExtra/GetterToPropertyRector.php @@ -0,0 +1,190 @@ +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 NameResolver + */ + private $nameResolver; + + /** + * @var ServiceFromKernelResolver + */ + private $serviceFromKernelResolver; + + /** + * @var ClassPropertyCollector + */ + private $classPropertyCollector; + + /** + * @var Class_ + */ + private $classNode; + + public function __construct( + NameResolver $nameResolver, + ServiceFromKernelResolver $serviceFromKernelResolver, + ClassPropertyCollector $classPropertyCollector + ) { + $this->nameResolver = $nameResolver; + $this->serviceFromKernelResolver = $serviceFromKernelResolver; + $this->classPropertyCollector = $classPropertyCollector; + } + + /** + * @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; + } + + /** + * @param Node $node + * Possibly simlify to just "$this->get('some_service')" + */ + 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_EXTRA; + } + + 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->getClassName(), $serviceType, $propertyName); + + return $this->createPropertyFetch($propertyName); + } + + /** + * @todo move to NodeFactory + * Creates "$this->propertyName". + */ + private function createPropertyFetch(string $propertyName): PropertyFetch + { + return new PropertyFetch( + new Variable('this', [ + 'name' => $propertyName, + ]), + $propertyName + ); + } + + private function getClassName(): string + { + return $this->classNode->namespacedName->toString(); + } +} diff --git a/tests/Rector/Contrib/Symfony/GetterToPropertyRector/Correct/correct.php.inc b/tests/Rector/Contrib/SymfonyExtra/GetterToPropertyRector/Correct/correct.php.inc similarity index 100% rename from tests/Rector/Contrib/Symfony/GetterToPropertyRector/Correct/correct.php.inc rename to tests/Rector/Contrib/SymfonyExtra/GetterToPropertyRector/Correct/correct.php.inc diff --git a/tests/Rector/Contrib/Symfony/GetterToPropertyRector/Correct/correct2.php.inc b/tests/Rector/Contrib/SymfonyExtra/GetterToPropertyRector/Correct/correct2.php.inc similarity index 100% rename from tests/Rector/Contrib/Symfony/GetterToPropertyRector/Correct/correct2.php.inc rename to tests/Rector/Contrib/SymfonyExtra/GetterToPropertyRector/Correct/correct2.php.inc diff --git a/tests/Rector/Contrib/Symfony/GetterToPropertyRector/Correct/correct3.php.inc b/tests/Rector/Contrib/SymfonyExtra/GetterToPropertyRector/Correct/correct3.php.inc similarity index 100% rename from tests/Rector/Contrib/Symfony/GetterToPropertyRector/Correct/correct3.php.inc rename to tests/Rector/Contrib/SymfonyExtra/GetterToPropertyRector/Correct/correct3.php.inc diff --git a/tests/Rector/Contrib/Symfony/GetterToPropertyRector/Source/LocalKernel.php b/tests/Rector/Contrib/SymfonyExtra/GetterToPropertyRector/Source/LocalKernel.php similarity index 90% rename from tests/Rector/Contrib/Symfony/GetterToPropertyRector/Source/LocalKernel.php rename to tests/Rector/Contrib/SymfonyExtra/GetterToPropertyRector/Source/LocalKernel.php index e7550ac5298..92cd16465f7 100644 --- a/tests/Rector/Contrib/Symfony/GetterToPropertyRector/Source/LocalKernel.php +++ b/tests/Rector/Contrib/SymfonyExtra/GetterToPropertyRector/Source/LocalKernel.php @@ -1,6 +1,6 @@ get('some.class')->render(); + $this->get('some_service')->render(); } } diff --git a/tests/Rector/Contrib/Symfony/GetterToPropertyRector/Wrong/wrong2.php.inc b/tests/Rector/Contrib/SymfonyExtra/GetterToPropertyRector/Wrong/wrong2.php.inc similarity index 73% rename from tests/Rector/Contrib/Symfony/GetterToPropertyRector/Wrong/wrong2.php.inc rename to tests/Rector/Contrib/SymfonyExtra/GetterToPropertyRector/Wrong/wrong2.php.inc index 4d9df8cc4bd..1f4c8f851f7 100644 --- a/tests/Rector/Contrib/Symfony/GetterToPropertyRector/Wrong/wrong2.php.inc +++ b/tests/Rector/Contrib/SymfonyExtra/GetterToPropertyRector/Wrong/wrong2.php.inc @@ -4,6 +4,6 @@ class ClassWithNamedService implements ContainerAwareInterface { public function render() { - $someService = $this->get('some.class'); + $someService = $this->get('some_service'); } } diff --git a/tests/Rector/Contrib/Symfony/GetterToPropertyRector/Wrong/wrong3.php.inc b/tests/Rector/Contrib/SymfonyExtra/GetterToPropertyRector/Wrong/wrong3.php.inc similarity index 55% rename from tests/Rector/Contrib/Symfony/GetterToPropertyRector/Wrong/wrong3.php.inc rename to tests/Rector/Contrib/SymfonyExtra/GetterToPropertyRector/Wrong/wrong3.php.inc index 2dd299abb08..84bdc37c107 100644 --- a/tests/Rector/Contrib/Symfony/GetterToPropertyRector/Wrong/wrong3.php.inc +++ b/tests/Rector/Contrib/SymfonyExtra/GetterToPropertyRector/Wrong/wrong3.php.inc @@ -4,7 +4,7 @@ class ClassWithNamedService implements ContainerAwareInterface { public function render() { - $someService = $this->get('some.class'); - $someResult = $this->get('some.class')->callMe(); + $someService = $this->get('some_service'); + $someResult = $this->get('some_service')->callMe(); } }