From e5844eb13a7e6d749ca598bf79542f4e775d379b Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Wed, 10 Jul 2019 08:47:36 +0200 Subject: [PATCH] [Generic] Add ServiceGetterToConstructorInjectionRector --- config/set/doctrine/doctrine-services.yaml | 7 + ...viceGetterToConstructorInjectionRector.php | 183 ++++++++++++++++++ .../Fixture/fixture.php.inc | 58 ++++++ ...GetterToConstructorInjectionRectorTest.php | 32 +++ .../Source/AnotherService.php | 10 + .../Source/FirstService.php | 21 ++ 6 files changed, 311 insertions(+) create mode 100644 config/set/doctrine/doctrine-services.yaml create mode 100644 src/Rector/MethodCall/ServiceGetterToConstructorInjectionRector.php create mode 100644 tests/Rector/MethodCall/ServiceGetterToConstructorInjectionRector/Fixture/fixture.php.inc create mode 100644 tests/Rector/MethodCall/ServiceGetterToConstructorInjectionRector/ServiceGetterToConstructorInjectionRectorTest.php create mode 100644 tests/Rector/MethodCall/ServiceGetterToConstructorInjectionRector/Source/AnotherService.php create mode 100644 tests/Rector/MethodCall/ServiceGetterToConstructorInjectionRector/Source/FirstService.php diff --git a/config/set/doctrine/doctrine-services.yaml b/config/set/doctrine/doctrine-services.yaml new file mode 100644 index 00000000000..adf734900c5 --- /dev/null +++ b/config/set/doctrine/doctrine-services.yaml @@ -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' diff --git a/src/Rector/MethodCall/ServiceGetterToConstructorInjectionRector.php b/src/Rector/MethodCall/ServiceGetterToConstructorInjectionRector.php new file mode 100644 index 00000000000..cd4b3b35319 --- /dev/null +++ b/src/Rector/MethodCall/ServiceGetterToConstructorInjectionRector.php @@ -0,0 +1,183 @@ +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'); + } +} diff --git a/tests/Rector/MethodCall/ServiceGetterToConstructorInjectionRector/Fixture/fixture.php.inc b/tests/Rector/MethodCall/ServiceGetterToConstructorInjectionRector/Fixture/fixture.php.inc new file mode 100644 index 00000000000..c48d436f642 --- /dev/null +++ b/tests/Rector/MethodCall/ServiceGetterToConstructorInjectionRector/Fixture/fixture.php.inc @@ -0,0 +1,58 @@ +firstService = $firstService; + } + + public function run() + { + $anotherService = $this->firstService->getAnotherService(); + $anotherService->run(); + } +} + +?> +----- +firstService = $firstService; + $this->anotherService = $anotherService; + } + + public function run() + { + $anotherService = $this->anotherService; + $anotherService->run(); + } +} + +?> diff --git a/tests/Rector/MethodCall/ServiceGetterToConstructorInjectionRector/ServiceGetterToConstructorInjectionRectorTest.php b/tests/Rector/MethodCall/ServiceGetterToConstructorInjectionRector/ServiceGetterToConstructorInjectionRectorTest.php new file mode 100644 index 00000000000..99efef7054f --- /dev/null +++ b/tests/Rector/MethodCall/ServiceGetterToConstructorInjectionRector/ServiceGetterToConstructorInjectionRectorTest.php @@ -0,0 +1,32 @@ +doTestFiles([__DIR__ . '/Fixture/fixture.php.inc']); + } + + /** + * @return mixed[] + */ + protected function getRectorsWithConfiguration(): array + { + return [ + ServiceGetterToConstructorInjectionRector::class => [ + '$methodNamesByTypesToServiceTypes' => [ + FirstService::class => [ + 'getAnotherService' => AnotherService::class, + ], + ], + ], + ]; + } +} diff --git a/tests/Rector/MethodCall/ServiceGetterToConstructorInjectionRector/Source/AnotherService.php b/tests/Rector/MethodCall/ServiceGetterToConstructorInjectionRector/Source/AnotherService.php new file mode 100644 index 00000000000..1f198497f65 --- /dev/null +++ b/tests/Rector/MethodCall/ServiceGetterToConstructorInjectionRector/Source/AnotherService.php @@ -0,0 +1,10 @@ +anotherService = $anotherService; + } + + public function getAnotherService(): AnotherService + { + return $this->anotherService; + } +}