From a550868574cb140dcabc273626a17eb132441920 Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Fri, 17 Jan 2020 17:03:57 +0100 Subject: [PATCH 1/8] [CakePHPToSymfony] Add CakePHPControllerToSymfonyControllerRector --- composer.json | 6 +- ...PHPControllerToSymfonyControllerRector.php | 73 +++++++++++++++++++ ...ontrollerToSymfonyControllerRectorTest.php | 28 +++++++ .../Fixture/fixture.php.inc | 31 ++++++++ 4 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 packages/CakePHPToSymfony/src/Rector/Class_/CakePHPControllerToSymfonyControllerRector.php create mode 100644 packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerToSymfonyControllerRector/CakePHPControllerToSymfonyControllerRectorTest.php create mode 100644 packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerToSymfonyControllerRector/Fixture/fixture.php.inc diff --git a/composer.json b/composer.json index c88bd0e32861..c798c734542e 100644 --- a/composer.json +++ b/composer.json @@ -111,7 +111,8 @@ "Rector\\Phalcon\\": "packages/Phalcon/src", "Rector\\DoctrineGedmoToKnplabs\\": "packages/DoctrineGedmoToKnplabs/src", "Rector\\MinimalScope\\": "packages/MinimalScope/src", - "Rector\\Polyfill\\": "packages/Polyfill/src" + "Rector\\Polyfill\\": "packages/Polyfill/src", + "Rector\\CakePHPToSymfony\\": "packages/CakePHPToSymfony/src" } }, "autoload-dev": { @@ -174,7 +175,8 @@ "Rector\\DoctrineGedmoToKnplabs\\Tests\\": "packages/DoctrineGedmoToKnplabs/tests", "Rector\\MinimalScope\\Tests\\": "packages/MinimalScope/tests", "Rector\\Utils\\PHPStanStaticTypeMapperChecker\\": "utils/PHPStanStaticTypeMapperChecker/src", - "Rector\\Polyfill\\Tests\\": "packages/Polyfill/tests" + "Rector\\Polyfill\\Tests\\": "packages/Polyfill/tests", + "Rector\\CakePHPToSymfony\\Tests\\": "packages/CakePHPToSymfony/tests" }, "classmap": [ "packages/CakePHP/tests/Rector/Name/ImplicitShortClassNameUseStatementRector/Source", diff --git a/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPControllerToSymfonyControllerRector.php b/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPControllerToSymfonyControllerRector.php new file mode 100644 index 000000000000..fe259c47d289 --- /dev/null +++ b/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPControllerToSymfonyControllerRector.php @@ -0,0 +1,73 @@ +set('name', $value); + } +} +PHP +, + <<<'PHP' +use Symfony\Component\HttpFoundation\Response; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + +class HomepageController extends AbstractController +{ + public function index(): Response + { + $value = 5; + return $this->renderResponse('homepage/index.ctp', [ + 'name' => $value + ]); + } +} +PHP + + ) + ]); + } + + /** + * @return string[] + */ + public function getNodeTypes(): array + { + return [\PhpParser\Node\Stmt\Class_::class]; + } + + /** + * @param \PhpParser\Node\Stmt\Class_ $node + */ + public function refactor(Node $node): ?Node + { + // change the node + + return $node; + } +} diff --git a/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerToSymfonyControllerRector/CakePHPControllerToSymfonyControllerRectorTest.php b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerToSymfonyControllerRector/CakePHPControllerToSymfonyControllerRectorTest.php new file mode 100644 index 000000000000..98bf9a747e31 --- /dev/null +++ b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerToSymfonyControllerRector/CakePHPControllerToSymfonyControllerRectorTest.php @@ -0,0 +1,28 @@ +doTestFile($file); + } + + public function provideDataForTest(): \Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return \Rector\CakePHPToSymfony\Rector\Class_\CakePHPControllerToSymfonyControllerRector::class; + } +} diff --git a/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerToSymfonyControllerRector/Fixture/fixture.php.inc b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerToSymfonyControllerRector/Fixture/fixture.php.inc new file mode 100644 index 000000000000..a391a8f6fe2c --- /dev/null +++ b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerToSymfonyControllerRector/Fixture/fixture.php.inc @@ -0,0 +1,31 @@ +set('name', $value); + } +} + +?> +----- +renderResponse('homepage/index.twig', [ + 'name' => $value + ]); + } +} + +?> From 95b5486747cfcaf28f661adb49e49e408731a8ba Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Fri, 17 Jan 2020 17:29:48 +0100 Subject: [PATCH 2/8] [CakePHPToSymfony] Add CakePHPControllerActionToSymfonyControllerActionRector --- .../cakephp-24-to-symfony-50.yaml | 3 + docs/AllRectorsOverview.md | 54 ++++- ...rActionToSymfonyControllerActionRector.php | 213 ++++++++++++++++++ ...PHPControllerToSymfonyControllerRector.php | 20 +- ...ionToSymfonyControllerActionRectorTest.php | 30 +++ .../Fixture/compact_too.php.inc | 29 +++ .../Fixture/fixture.php.inc | 29 +++ ...ontrollerToSymfonyControllerRectorTest.php | 6 +- .../Fixture/fixture.php.inc | 8 +- packages/CodeQuality/config/config.yaml | 9 + packages/CodeQuality/src/CompactConverter.php | 43 ++++ .../FuncCall/CompactToVariablesRector.php | 24 +- 12 files changed, 435 insertions(+), 33 deletions(-) create mode 100644 config/set/cakephp-to-symfony/cakephp-24-to-symfony-50.yaml create mode 100644 packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector.php create mode 100644 packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/CakePHPControllerActionToSymfonyControllerActionRectorTest.php create mode 100644 packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/Fixture/compact_too.php.inc create mode 100644 packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/Fixture/fixture.php.inc create mode 100644 packages/CodeQuality/config/config.yaml create mode 100644 packages/CodeQuality/src/CompactConverter.php diff --git a/config/set/cakephp-to-symfony/cakephp-24-to-symfony-50.yaml b/config/set/cakephp-to-symfony/cakephp-24-to-symfony-50.yaml new file mode 100644 index 000000000000..79b328081d68 --- /dev/null +++ b/config/set/cakephp-to-symfony/cakephp-24-to-symfony-50.yaml @@ -0,0 +1,3 @@ +services: + Rector\CakePHPToSymfony\Rector\Class_\CakePHPControllerToSymfonyControllerRector: null + Rector\CakePHPToSymfony\Rector\ClassMethod\CakePHPControllerActionToSymfonyControllerActionRector: null diff --git a/docs/AllRectorsOverview.md b/docs/AllRectorsOverview.md index d92366c482d7..55be8114a68c 100644 --- a/docs/AllRectorsOverview.md +++ b/docs/AllRectorsOverview.md @@ -1,4 +1,4 @@ -# All 432 Rectors Overview +# All 434 Rectors Overview - [Projects](#projects) - [General](#general) @@ -8,6 +8,7 @@ - [Architecture](#architecture) - [Autodiscovery](#autodiscovery) - [CakePHP](#cakephp) +- [CakePHPToSymfony](#cakephptosymfony) - [Celebrity](#celebrity) - [CodeQuality](#codequality) - [CodingStyle](#codingstyle) @@ -283,6 +284,55 @@ services:
+## CakePHPToSymfony + +### `CakePHPControllerActionToSymfonyControllerActionRector` + +- class: `Rector\CakePHPToSymfony\Rector\ClassMethod\CakePHPControllerActionToSymfonyControllerActionRector` + +Migrate CakePHP 2.4 Controller action to Symfony 5 + +```diff ++use Symfony\Component\HttpFoundation\Response; ++ + class HomepageController extends \AppController + { +- public function index() ++ public function index(): Response + { + $value = 5; +- $this->set('name', $value); ++ return $this->renderResponse('homepage/index.twig', [ ++ 'name' => $value ++ ]); + } + } +``` + +
+ +### `CakePHPControllerToSymfonyControllerRector` + +- class: `Rector\CakePHPToSymfony\Rector\Class_\CakePHPControllerToSymfonyControllerRector` + +Migrate CakePHP 2.4 Controller to Symfony 5 + +```diff +-class HomepageController extends AppController ++use Symfony\Component\HttpFoundation\Response; ++use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; ++ ++class HomepageController extends AbstractController + { +- public function index() ++ public function index(): Response + { + } + } +``` + +
+ ## Celebrity ### `CommonNotEqualRector` @@ -8724,7 +8774,7 @@ services: Rector\Rector\Visibility\ChangeMethodVisibilityRector: $methodToVisibilityByClass: FrameworkClass: - someMethod: protected + someMethod: protected ``` ↓ diff --git a/packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector.php b/packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector.php new file mode 100644 index 000000000000..b3111625f2b4 --- /dev/null +++ b/packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector.php @@ -0,0 +1,213 @@ +classNaming = $classNaming; + $this->compactConverter = $compactConverter; + } + + public function getDefinition(): RectorDefinition + { + return new RectorDefinition('Migrate CakePHP 2.4 Controller action to Symfony 5', [ + new CodeSample( + <<<'PHP' +class HomepageController extends \AppController +{ + public function index() + { + $value = 5; + $this->set('name', $value); + } +} +PHP +, + <<<'PHP' +use Symfony\Component\HttpFoundation\Response; + +class HomepageController extends \AppController +{ + public function index(): Response + { + $value = 5; + return $this->renderResponse('homepage/index.twig', [ + 'name' => $value + ]); + } +} +PHP + + ), + ]); + } + + /** + * @return string[] + */ + public function getNodeTypes(): array + { + return [ClassMethod::class]; + } + + /** + * @param ClassMethod $node + */ + public function refactor(Node $node): ?Node + { + /** @var Node\Stmt\Class_|null $classNode */ + $classNode = $node->getAttribute(AttributeKey::CLASS_NODE); + if ($classNode === null) { + return null; + } + + if (! $this->isObjectType($classNode, 'AppController')) { + return null; + } + + if (! $node->isPublic()) { + return null; + } + + $methodCall = $this->createThisRenderMethodCall($node); + $return = new Return_($methodCall); + $node->stmts[] = $return; + + $node->returnType = new FullyQualified('Symfony\Component\HttpFoundation\Response'); + + return $node; + } + + /** + * @return Arg[][] + */ + private function collectAndRemoveSetMethodCallArgs(array $stmts): array + { + $setMethodCallArgs = []; + + $this->traverseNodesWithCallable($stmts, function (Node $node) use (&$setMethodCallArgs) { + if (! $node instanceof MethodCall) { + return null; + } + + if (! $this->isName($node->name, 'set')) { + return null; + } + + $setMethodCallArgs[] = $node->args; + $this->removeNode($node); + + return null; + }); + + return $setMethodCallArgs; + } + + private function resolveTemplateName(ClassMethod $classMethod): string + { + /** @var string $className */ + $className = $classMethod->getAttribute(AttributeKey::CLASS_NAME); + $shortClassName = $this->classNaming->getShortName($className); + + $shortClassName = Strings::replace($shortClassName, '#Controller$#i'); + $shortClassName = Strings::lower($shortClassName); + + $methodName = $classMethod->getAttribute(AttributeKey::METHOD_NAME); + + return $shortClassName . '/' . $methodName . '.twig'; + } + + /** + * @param Arg[][] $setValues + */ + private function createArrayFromSetValues(array $setValues): Array_ + { + $arrayItems = []; + + foreach ($setValues as $setValue) { + if (count($setValue) > 1) { + $arrayItems[] = new ArrayItem($setValue[1]->value, $setValue[0]->value); + } elseif ($this->isCompactFuncCall($setValue[0]->value)) { + /** @var FuncCall $compactFuncCall */ + $compactFuncCall = $setValue[0]->value; + + $array = $this->compactConverter->convertToArray($compactFuncCall); + + // ... + dump($array); + + // is it compact? + die; + } + } + + return new Array_($arrayItems); + } + + private function isCompactFuncCall(Node $node): bool + { + if (! $node instanceof FuncCall) { + return false; + } + + return $this->isName($node, 'compact'); + } + + private function createThisRenderMethodCall(ClassMethod $classMethod): MethodCall + { + $thisVariable = new Variable('this'); + $thisRenderMethodCall = new MethodCall($thisVariable, 'render'); + + $templateName = $this->resolveTemplateName($classMethod); + $thisRenderMethodCall->args[] = new Arg(new String_($templateName)); + + $setValues = $this->collectAndRemoveSetMethodCallArgs((array) $classMethod->stmts); + + if ($setValues !== []) { + $parametersArray = $this->createArrayFromSetValues($setValues); + $thisRenderMethodCall->args[] = new Arg($parametersArray); + } + + return $thisRenderMethodCall; + } +} diff --git a/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPControllerToSymfonyControllerRector.php b/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPControllerToSymfonyControllerRector.php index fe259c47d289..59dfac6e4891 100644 --- a/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPControllerToSymfonyControllerRector.php +++ b/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPControllerToSymfonyControllerRector.php @@ -5,6 +5,8 @@ namespace Rector\CakePHPToSymfony\Rector\Class_; use PhpParser\Node; +use PhpParser\Node\Name\FullyQualified; +use PhpParser\Node\Stmt\Class_; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\RectorDefinition; @@ -27,8 +29,6 @@ class HomepageController extends AppController { public function index() { - $value = 5; - $this->set('name', $value); } } PHP @@ -41,15 +41,11 @@ class HomepageController extends AbstractController { public function index(): Response { - $value = 5; - return $this->renderResponse('homepage/index.ctp', [ - 'name' => $value - ]); } } PHP - ) + ), ]); } @@ -58,15 +54,19 @@ public function index(): Response */ public function getNodeTypes(): array { - return [\PhpParser\Node\Stmt\Class_::class]; + return [Class_::class]; } /** - * @param \PhpParser\Node\Stmt\Class_ $node + * @param Class_ $node */ public function refactor(Node $node): ?Node { - // change the node + if (! $this->isObjectType($node, 'AppController')) { + return null; + } + + $node->extends = new FullyQualified('Symfony\Bundle\FrameworkBundle\Controller\AbstractController'); return $node; } diff --git a/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/CakePHPControllerActionToSymfonyControllerActionRectorTest.php b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/CakePHPControllerActionToSymfonyControllerActionRectorTest.php new file mode 100644 index 000000000000..daabcd930758 --- /dev/null +++ b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/CakePHPControllerActionToSymfonyControllerActionRectorTest.php @@ -0,0 +1,30 @@ +doTestFile($file); + } + + public function provideDataForTest(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return CakePHPControllerActionToSymfonyControllerActionRector::class; + } +} diff --git a/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/Fixture/compact_too.php.inc b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/Fixture/compact_too.php.inc new file mode 100644 index 000000000000..1be9a50b2295 --- /dev/null +++ b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/Fixture/compact_too.php.inc @@ -0,0 +1,29 @@ +set(compact('clientId', 'states', 'invoiceTemplates')); + } +} + +?> +----- +render('homepage/index.twig', ['clientId' => $clientId, 'states' => $states, 'invoiceTemplates' => $invoiceTemplates]); + } +} + +?> diff --git a/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/Fixture/fixture.php.inc b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/Fixture/fixture.php.inc new file mode 100644 index 000000000000..cf0d3bd4d35f --- /dev/null +++ b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/Fixture/fixture.php.inc @@ -0,0 +1,29 @@ +set('name', $value); + } +} + +?> +----- +render('homepage/index.twig', ['name' => $value]); + } +} + +?> diff --git a/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerToSymfonyControllerRector/CakePHPControllerToSymfonyControllerRectorTest.php b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerToSymfonyControllerRector/CakePHPControllerToSymfonyControllerRectorTest.php index 98bf9a747e31..76c358c19dab 100644 --- a/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerToSymfonyControllerRector/CakePHPControllerToSymfonyControllerRectorTest.php +++ b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerToSymfonyControllerRector/CakePHPControllerToSymfonyControllerRectorTest.php @@ -4,6 +4,8 @@ namespace Rector\CakePHPToSymfony\Tests\Rector\Class_\CakePHPControllerToSymfonyControllerRector; +use Iterator; +use Rector\CakePHPToSymfony\Rector\Class_\CakePHPControllerToSymfonyControllerRector; use Rector\Testing\PHPUnit\AbstractRectorTestCase; final class CakePHPControllerToSymfonyControllerRectorTest extends AbstractRectorTestCase @@ -16,13 +18,13 @@ public function test(string $file): void $this->doTestFile($file); } - public function provideDataForTest(): \Iterator + public function provideDataForTest(): Iterator { return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); } protected function getRectorClass(): string { - return \Rector\CakePHPToSymfony\Rector\Class_\CakePHPControllerToSymfonyControllerRector::class; + return CakePHPControllerToSymfonyControllerRector::class; } } diff --git a/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerToSymfonyControllerRector/Fixture/fixture.php.inc b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerToSymfonyControllerRector/Fixture/fixture.php.inc index a391a8f6fe2c..7562300b7395 100644 --- a/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerToSymfonyControllerRector/Fixture/fixture.php.inc +++ b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerToSymfonyControllerRector/Fixture/fixture.php.inc @@ -6,8 +6,6 @@ class HomepageController extends \AppController { public function index() { - $value = 5; - $this->set('name', $value); } } @@ -19,12 +17,8 @@ namespace Rector\CakePHPToSymfony\Tests\Rector\Class_\CakePHPControllerToSymfony class HomepageController extends \Symfony\Bundle\FrameworkBundle\Controller\AbstractController { - public function index(): \Symfony\Component\HttpFoundation\Response + public function index() { - $value = 5; - return $this->renderResponse('homepage/index.twig', [ - 'name' => $value - ]); } } diff --git a/packages/CodeQuality/config/config.yaml b/packages/CodeQuality/config/config.yaml new file mode 100644 index 000000000000..149bc0265bbb --- /dev/null +++ b/packages/CodeQuality/config/config.yaml @@ -0,0 +1,9 @@ +services: + _defaults: + autowire: true + public: true + + Rector\CodeQuality\: + resource: '../src' + exclude: + - '../src/Rector/**/*Rector.php' diff --git a/packages/CodeQuality/src/CompactConverter.php b/packages/CodeQuality/src/CompactConverter.php new file mode 100644 index 000000000000..1657e8654b3d --- /dev/null +++ b/packages/CodeQuality/src/CompactConverter.php @@ -0,0 +1,43 @@ +valueResolver = $valueResolver; + } + + public function convertToArray(FuncCall $funcCall): Array_ + { + $array = new Array_(); + + foreach ($funcCall->args as $arg) { + /** @var string|null $variableName */ + $variableName = $this->valueResolver->getValue($arg->value); + if (! is_string($variableName)) { + throw new ShouldNotHappenException(); + } + + $array->items[] = new ArrayItem(new Variable($variableName), new String_($variableName)); + } + + return $array; + } +} diff --git a/packages/CodeQuality/src/Rector/FuncCall/CompactToVariablesRector.php b/packages/CodeQuality/src/Rector/FuncCall/CompactToVariablesRector.php index 9d8b3287b1dd..3e551ae4fb2e 100644 --- a/packages/CodeQuality/src/Rector/FuncCall/CompactToVariablesRector.php +++ b/packages/CodeQuality/src/Rector/FuncCall/CompactToVariablesRector.php @@ -5,11 +5,8 @@ namespace Rector\CodeQuality\Rector\FuncCall; use PhpParser\Node; -use PhpParser\Node\Expr\Array_; -use PhpParser\Node\Expr\ArrayItem; use PhpParser\Node\Expr\FuncCall; -use PhpParser\Node\Expr\Variable; -use PhpParser\Node\Scalar\String_; +use Rector\CodeQuality\CompactConverter; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\RectorDefinition; @@ -21,6 +18,16 @@ */ final class CompactToVariablesRector extends AbstractRector { + /** + * @var CompactConverter + */ + private $compactConverter; + + public function __construct(CompactConverter $compactConverter) + { + $this->compactConverter = $compactConverter; + } + public function getDefinition(): RectorDefinition { return new RectorDefinition('Change compact() call to own array', [ @@ -71,13 +78,6 @@ public function refactor(Node $node): ?Node return null; } - $array = new Array_(); - - foreach ($node->args as $arg) { - $variableName = $this->getValue($arg->value); - $array->items[] = new ArrayItem(new Variable($variableName), new String_($variableName)); - } - - return $array; + return $this->compactConverter->convertToArray($node); } } From 22cbd150e8d20a77af0a29c434ac76773d598f2c Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Fri, 17 Jan 2020 20:31:27 +0100 Subject: [PATCH 3/8] [CakePHPToSymfony] Add CakePHPControllerHelperToSymfonyRector --- .../cakephp-24-to-symfony-50.yaml | 1 + docs/AllRectorsOverview.md | 25 ++- ...rActionToSymfonyControllerActionRector.php | 8 +- ...CakePHPControllerHelperToSymfonyRector.php | 142 ++++++++++++++++++ .../Fixture/compact_too.php.inc | 2 +- ...PHPControllerHelperToSymfonyRectorTest.php | 30 ++++ .../Fixture/fixture.php.inc | 31 ++++ 7 files changed, 230 insertions(+), 9 deletions(-) create mode 100644 packages/CakePHPToSymfony/src/Rector/Class_/CakePHPControllerHelperToSymfonyRector.php create mode 100644 packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerHelperToSymfonyRector/CakePHPControllerHelperToSymfonyRectorTest.php create mode 100644 packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerHelperToSymfonyRector/Fixture/fixture.php.inc diff --git a/config/set/cakephp-to-symfony/cakephp-24-to-symfony-50.yaml b/config/set/cakephp-to-symfony/cakephp-24-to-symfony-50.yaml index 79b328081d68..55af14bd3fcc 100644 --- a/config/set/cakephp-to-symfony/cakephp-24-to-symfony-50.yaml +++ b/config/set/cakephp-to-symfony/cakephp-24-to-symfony-50.yaml @@ -1,3 +1,4 @@ services: Rector\CakePHPToSymfony\Rector\Class_\CakePHPControllerToSymfonyControllerRector: null Rector\CakePHPToSymfony\Rector\ClassMethod\CakePHPControllerActionToSymfonyControllerActionRector: null + Rector\CakePHPToSymfony\Rector\Class_\CakePHPControllerHelperToSymfonyRector: null diff --git a/docs/AllRectorsOverview.md b/docs/AllRectorsOverview.md index 55be8114a68c..767313ca3692 100644 --- a/docs/AllRectorsOverview.md +++ b/docs/AllRectorsOverview.md @@ -1,4 +1,4 @@ -# All 434 Rectors Overview +# All 435 Rectors Overview - [Projects](#projects) - [General](#general) @@ -311,6 +311,29 @@ Migrate CakePHP 2.4 Controller action to Symfony 5
+### `CakePHPControllerHelperToSymfonyRector` + +- class: `Rector\CakePHPToSymfony\Rector\Class_\CakePHPControllerHelperToSymfonyRector` + +Migrate CakePHP 2.4 Controller $helpers and $components property to Symfony 5 + +```diff + class HomepageController extends AppController + { +- public $helpers = ['Flash']; +- + public function index() + { +- $this->Flash->success(__('Your post has been saved.')); +- $this->Flash->error(__('Unable to add your post.')); ++ $this->addFlash('success', __('Your post has been saved.')); ++ $this->addFlash('error', __('Unable to add your post.')); + } + } +``` + +
+ ### `CakePHPControllerToSymfonyControllerRector` - class: `Rector\CakePHPToSymfony\Rector\Class_\CakePHPControllerToSymfonyControllerRector` diff --git a/packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector.php b/packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector.php index b3111625f2b4..331811d9d528 100644 --- a/packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector.php +++ b/packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector.php @@ -171,13 +171,7 @@ private function createArrayFromSetValues(array $setValues): Array_ /** @var FuncCall $compactFuncCall */ $compactFuncCall = $setValue[0]->value; - $array = $this->compactConverter->convertToArray($compactFuncCall); - - // ... - dump($array); - - // is it compact? - die; + return $this->compactConverter->convertToArray($compactFuncCall); } } diff --git a/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPControllerHelperToSymfonyRector.php b/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPControllerHelperToSymfonyRector.php new file mode 100644 index 000000000000..3d7ae06f0fc4 --- /dev/null +++ b/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPControllerHelperToSymfonyRector.php @@ -0,0 +1,142 @@ +classManipulator = $classManipulator; + } + + public function getDefinition(): RectorDefinition + { + return new RectorDefinition('Migrate CakePHP 2.4 Controller $helpers and $components property to Symfony 5', [ + new CodeSample( + <<<'PHP' +class HomepageController extends AppController +{ + public $helpers = ['Flash']; + + public function index() + { + $this->Flash->success(__('Your post has been saved.')); + $this->Flash->error(__('Unable to add your post.')); + } +} +PHP +, + <<<'PHP' +class HomepageController extends AppController +{ + public function index() + { + $this->addFlash('success', __('Your post has been saved.')); + $this->addFlash('error', __('Unable to add your post.')); + } +} +PHP + + ), + ]); + } + + /** + * @return string[] + */ + public function getNodeTypes(): array + { + return [Class_::class]; + } + + /** + * @param Class_ $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->isObjectType($node, 'AppController')) { + return null; + } + + $helpersProperty = $this->classManipulator->getProperty($node, 'helpers'); + if ($helpersProperty === null) { + return null; + } + + // remove $helpers completely + $this->removeNode($helpersProperty); + + // replace $this->Flash->.. → $this->addFlash(...) + foreach ($node->getMethods() as $classMethod) { + $this->traverseNodesWithCallable((array) $classMethod->stmts, function (Node $node) { + if (! $this->isThisFlashMethodCall($node)) { + return null; + } + + /** @var MethodCall $node */ + $message = $node->args[0]->value; + + $kind = $this->getName($node->name); + if ($kind === null) { + throw new ShouldNotHappenException(); + } + + $thisVariable = new Variable('this'); + + $args = [new Arg(new String_($kind)), new Arg($message)]; + + return new MethodCall($thisVariable, 'addFlash', $args); + }); + } + + return $node; + } + + private function isThisFlashMethodCall(Node $node): bool + { + if (! $node instanceof MethodCall) { + return false; + } + + if (! $node->var instanceof PropertyFetch) { + return false; + } + + if (! $this->isName($node->var->var, 'this')) { + return false; + } + + if (! $this->isName($node->var->name, 'Flash')) { + return false; + } + + return true; + } +} diff --git a/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/Fixture/compact_too.php.inc b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/Fixture/compact_too.php.inc index 1be9a50b2295..a9e45ab90280 100644 --- a/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/Fixture/compact_too.php.inc +++ b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/Fixture/compact_too.php.inc @@ -22,7 +22,7 @@ class CompactTooController extends \AppController public function index(): \Symfony\Component\HttpFoundation\Response { $value = 5; - return $this->render('homepage/index.twig', ['clientId' => $clientId, 'states' => $states, 'invoiceTemplates' => $invoiceTemplates]); + return $this->render('compacttoo/index.twig', ['clientId' => $clientId, 'states' => $states, 'invoiceTemplates' => $invoiceTemplates]); } } diff --git a/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerHelperToSymfonyRector/CakePHPControllerHelperToSymfonyRectorTest.php b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerHelperToSymfonyRector/CakePHPControllerHelperToSymfonyRectorTest.php new file mode 100644 index 000000000000..185c73a0ba83 --- /dev/null +++ b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerHelperToSymfonyRector/CakePHPControllerHelperToSymfonyRectorTest.php @@ -0,0 +1,30 @@ +doTestFile($file); + } + + public function provideDataForTest(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return CakePHPControllerHelperToSymfonyRector::class; + } +} diff --git a/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerHelperToSymfonyRector/Fixture/fixture.php.inc b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerHelperToSymfonyRector/Fixture/fixture.php.inc new file mode 100644 index 000000000000..6530f12cfb92 --- /dev/null +++ b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerHelperToSymfonyRector/Fixture/fixture.php.inc @@ -0,0 +1,31 @@ +Flash->success(__('Your post has been saved.')); + $this->Flash->error(__('Unable to add your post.')); + } +} + +?> +----- +addFlash('success', __('Your post has been saved.')); + $this->addFlash('error', __('Unable to add your post.')); + } +} + +?> From a60e526722d399fe8800b695aa8883977e4233bc Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Fri, 17 Jan 2020 22:45:46 +0100 Subject: [PATCH 4/8] [CakePHPToSymfony] Add CakePHPControllerComponentToSymfonyRector --- composer.json | 3 +- .../cakephp-24-to-symfony-50.yaml | 1 + docs/AllRectorsOverview.md | 34 ++- .../src/Rector/AbstractCakePHPRector.php | 26 +++ ...rActionToSymfonyControllerActionRector.php | 12 +- ...ePHPControllerComponentToSymfonyRector.php | 193 ++++++++++++++++++ ...CakePHPControllerHelperToSymfonyRector.php | 6 +- ...PHPControllerToSymfonyControllerRector.php | 6 +- ...ControllerComponentToSymfonyRectorTest.php | 30 +++ .../Fixture/fixture.php.inc | 51 +++++ .../Source/Component.php | 8 + .../Node/Manipulator/ClassManipulator.php | 29 +++ .../Command/CheckStaticTypeMappersCommand.php | 2 - 13 files changed, 382 insertions(+), 19 deletions(-) create mode 100644 packages/CakePHPToSymfony/src/Rector/AbstractCakePHPRector.php create mode 100644 packages/CakePHPToSymfony/src/Rector/Class_/CakePHPControllerComponentToSymfonyRector.php create mode 100644 packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerComponentToSymfonyRector/CakePHPControllerComponentToSymfonyRectorTest.php create mode 100644 packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerComponentToSymfonyRector/Fixture/fixture.php.inc create mode 100644 packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerComponentToSymfonyRector/Source/Component.php diff --git a/composer.json b/composer.json index c798c734542e..3875e40457be 100644 --- a/composer.json +++ b/composer.json @@ -190,7 +190,8 @@ "tests/Issues/Issue1243/Source", "packages/Autodiscovery/tests/Rector/FileSystem/MoveInterfacesToContractNamespaceDirectoryRector/Expected", "packages/Autodiscovery/tests/Rector/FileSystem/MoveServicesBySuffixToDirectoryRector/Expected", - "packages/CakePHP/tests/Rector/StaticCall/AppUsesStaticCallToUseStatementRector/Source" + "packages/CakePHP/tests/Rector/StaticCall/AppUsesStaticCallToUseStatementRector/Source", + "packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerComponentToSymfonyRector/Source" ], "files": [ "packages/DeadCode/tests/Rector/MethodCall/RemoveDefaultArgumentValueRector/Source/UserDefined.php", diff --git a/config/set/cakephp-to-symfony/cakephp-24-to-symfony-50.yaml b/config/set/cakephp-to-symfony/cakephp-24-to-symfony-50.yaml index 55af14bd3fcc..ea5723de52a5 100644 --- a/config/set/cakephp-to-symfony/cakephp-24-to-symfony-50.yaml +++ b/config/set/cakephp-to-symfony/cakephp-24-to-symfony-50.yaml @@ -2,3 +2,4 @@ services: Rector\CakePHPToSymfony\Rector\Class_\CakePHPControllerToSymfonyControllerRector: null Rector\CakePHPToSymfony\Rector\ClassMethod\CakePHPControllerActionToSymfonyControllerActionRector: null Rector\CakePHPToSymfony\Rector\Class_\CakePHPControllerHelperToSymfonyRector: null + Rector\CakePHPToSymfony\Rector\Class_\CakePHPControllerComponentToSymfonyRector: null diff --git a/docs/AllRectorsOverview.md b/docs/AllRectorsOverview.md index 767313ca3692..a03b137d6373 100644 --- a/docs/AllRectorsOverview.md +++ b/docs/AllRectorsOverview.md @@ -1,4 +1,4 @@ -# All 435 Rectors Overview +# All 436 Rectors Overview - [Projects](#projects) - [General](#general) @@ -311,6 +311,38 @@ Migrate CakePHP 2.4 Controller action to Symfony 5
+### `CakePHPControllerComponentToSymfonyRector` + +- class: `Rector\CakePHPToSymfony\Rector\Class_\CakePHPControllerComponentToSymfonyRector` + +Migrate CakePHP 2.4 Controller $components property to Symfony 5 + +```diff + class MessagesController extends \AppController + { +- public $components = ['Overview']; ++ private function __construct(OverviewComponent $overviewComponent) ++ { ++ $this->overviewComponent->filter(); ++ } + + public function someAction() + { +- $this->Overview->filter(); ++ $this->overviewComponent->filter(); + } + } + + class OverviewComponent extends \Component + { + public function filter() + { + } + } +``` + +
+ ### `CakePHPControllerHelperToSymfonyRector` - class: `Rector\CakePHPToSymfony\Rector\Class_\CakePHPControllerHelperToSymfonyRector` diff --git a/packages/CakePHPToSymfony/src/Rector/AbstractCakePHPRector.php b/packages/CakePHPToSymfony/src/Rector/AbstractCakePHPRector.php new file mode 100644 index 000000000000..214341ca800e --- /dev/null +++ b/packages/CakePHPToSymfony/src/Rector/AbstractCakePHPRector.php @@ -0,0 +1,26 @@ +isObjectType($node, 'AppController')) { + return true; + } + + $class = $node->getAttribute(AttributeKey::CLASS_NODE); + if ($class !== null) { + return true; + } + + return false; + } +} diff --git a/packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector.php b/packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector.php index 331811d9d528..07f094a9951f 100644 --- a/packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector.php +++ b/packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector.php @@ -16,10 +16,10 @@ use PhpParser\Node\Scalar\String_; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Return_; +use Rector\CakePHPToSymfony\Rector\AbstractCakePHPRector; use Rector\CodeQuality\CompactConverter; use Rector\CodingStyle\Naming\ClassNaming; use Rector\NodeTypeResolver\Node\AttributeKey; -use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\RectorDefinition; @@ -30,7 +30,7 @@ * * @see \Rector\CakePHPToSymfony\Tests\Rector\ClassMethod\CakePHPControllerActionToSymfonyControllerActionRector\CakePHPControllerActionToSymfonyControllerActionRectorTest */ -final class CakePHPControllerActionToSymfonyControllerActionRector extends AbstractRector +final class CakePHPControllerActionToSymfonyControllerActionRector extends AbstractCakePHPRector { /** * @var ClassNaming @@ -95,13 +95,7 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - /** @var Node\Stmt\Class_|null $classNode */ - $classNode = $node->getAttribute(AttributeKey::CLASS_NODE); - if ($classNode === null) { - return null; - } - - if (! $this->isObjectType($classNode, 'AppController')) { + if (! $this->isInCakePHPController($node)) { return null; } diff --git a/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPControllerComponentToSymfonyRector.php b/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPControllerComponentToSymfonyRector.php new file mode 100644 index 000000000000..9cd93e1d5413 --- /dev/null +++ b/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPControllerComponentToSymfonyRector.php @@ -0,0 +1,193 @@ +classManipulator = $classManipulator; + $this->classNaming = $classNaming; + } + + public function getDefinition(): RectorDefinition + { + return new RectorDefinition('Migrate CakePHP 2.4 Controller $components property to Symfony 5', [ + new CodeSample( + <<<'PHP' +class MessagesController extends \AppController +{ + public $components = ['Overview']; + + public function someAction() + { + $this->Overview->filter(); + } +} + +class OverviewComponent extends \Component +{ + public function filter() + { + } +} +PHP +, + <<<'PHP' +class MessagesController extends \AppController +{ + private function __construct(OverviewComponent $overviewComponent) + { + $this->overviewComponent->filter(); + } + + public function someAction() + { + $this->overviewComponent->filter(); + } +} + +class OverviewComponent extends \Component +{ + public function filter() + { + } +} +PHP + + ), + ]); + } + + /** + * @return string[] + */ + public function getNodeTypes(): array + { + return [Class_::class]; + } + + /** + * @param Class_ $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->isInCakePHPController($node)) { + return null; + } + + $componentsProperty = $this->classManipulator->getProperty($node, 'components'); + if ($componentsProperty === null) { + return null; + } + + $defaultValue = $componentsProperty->props[0]->default; + if ($defaultValue === null) { + return null; + } + + $componentNames = $this->getValue($defaultValue); + if (! is_array($componentNames)) { + throw new ShouldNotHappenException(); + } + + $this->removeNode($componentsProperty); + + $componentClasses = $this->matchComponentClass($componentNames); + + $oldProperyNameToNewPropertyName = []; + + foreach ($componentClasses as $componentName => $componentClass) { + $componentClassShortName = $this->classNaming->getShortName($componentClass); + $propertyShortName = lcfirst($componentClassShortName); + $this->addPropertyToClass($node, new ObjectType($componentClass), $propertyShortName); + + $oldProperyNameToNewPropertyName[$componentName] = $propertyShortName; + } + + $this->classManipulator->renamePropertyFetches($node, $oldProperyNameToNewPropertyName); + + return $node; + } + + /** + * @return string[] + */ + private function getComponentClasses(): array + { + if ($this->componentsClasses) { + return $this->componentsClasses; + } + + foreach (get_declared_classes() as $declaredClass) { + if ($declaredClass === 'Component') { + continue; + } + + if (! is_a($declaredClass, 'Component', true)) { + continue; + } + + $this->componentsClasses[] = $declaredClass; + } + + return $this->componentsClasses; + } + + /** + * @param string[] $componentNames + * @return string[] + */ + private function matchComponentClass(array $componentNames): array + { + $componentsClasses = $this->getComponentClasses(); + + $matchedComponentClasses = []; + + foreach ($componentNames as $componentName) { + foreach ($componentsClasses as $componentClass) { + $shortComponentClass = $this->classNaming->getShortName($componentClass); + if (! Strings::startsWith($shortComponentClass, $componentName)) { + continue; + } + + $matchedComponentClasses[$componentName] = $componentClass; + } + } + + return $matchedComponentClasses; + } +} diff --git a/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPControllerHelperToSymfonyRector.php b/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPControllerHelperToSymfonyRector.php index 3d7ae06f0fc4..d12cab8bdd53 100644 --- a/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPControllerHelperToSymfonyRector.php +++ b/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPControllerHelperToSymfonyRector.php @@ -11,9 +11,9 @@ use PhpParser\Node\Expr\Variable; use PhpParser\Node\Scalar\String_; use PhpParser\Node\Stmt\Class_; +use Rector\CakePHPToSymfony\Rector\AbstractCakePHPRector; use Rector\Exception\ShouldNotHappenException; use Rector\PhpParser\Node\Manipulator\ClassManipulator; -use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\RectorDefinition; @@ -24,7 +24,7 @@ * * @see \Rector\CakePHPToSymfony\Tests\Rector\Class_\CakePHPControllerHelperToSymfonyRector\CakePHPControllerHelperToSymfonyRectorTest */ -final class CakePHPControllerHelperToSymfonyRector extends AbstractRector +final class CakePHPControllerHelperToSymfonyRector extends AbstractCakePHPRector { /** * @var ClassManipulator @@ -81,7 +81,7 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - if (! $this->isObjectType($node, 'AppController')) { + if (! $this->isInCakePHPController($node)) { return null; } diff --git a/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPControllerToSymfonyControllerRector.php b/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPControllerToSymfonyControllerRector.php index 59dfac6e4891..0b748a1cc254 100644 --- a/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPControllerToSymfonyControllerRector.php +++ b/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPControllerToSymfonyControllerRector.php @@ -7,7 +7,7 @@ use PhpParser\Node; use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Stmt\Class_; -use Rector\Rector\AbstractRector; +use Rector\CakePHPToSymfony\Rector\AbstractCakePHPRector; use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\RectorDefinition; @@ -18,7 +18,7 @@ * * @see \Rector\CakePHPToSymfony\Tests\Rector\Class_\CakePHPControllerToSymfonyControllerRector\CakePHPControllerToSymfonyControllerRectorTest */ -final class CakePHPControllerToSymfonyControllerRector extends AbstractRector +final class CakePHPControllerToSymfonyControllerRector extends AbstractCakePHPRector { public function getDefinition(): RectorDefinition { @@ -62,7 +62,7 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - if (! $this->isObjectType($node, 'AppController')) { + if (! $this->isInCakePHPController($node)) { return null; } diff --git a/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerComponentToSymfonyRector/CakePHPControllerComponentToSymfonyRectorTest.php b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerComponentToSymfonyRector/CakePHPControllerComponentToSymfonyRectorTest.php new file mode 100644 index 000000000000..bd1a02b4d3fc --- /dev/null +++ b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerComponentToSymfonyRector/CakePHPControllerComponentToSymfonyRectorTest.php @@ -0,0 +1,30 @@ +doTestFile($file); + } + + public function provideDataForTest(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return CakePHPControllerComponentToSymfonyRector::class; + } +} diff --git a/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerComponentToSymfonyRector/Fixture/fixture.php.inc b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerComponentToSymfonyRector/Fixture/fixture.php.inc new file mode 100644 index 000000000000..522db5d8d346 --- /dev/null +++ b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerComponentToSymfonyRector/Fixture/fixture.php.inc @@ -0,0 +1,51 @@ +Overview->filter(); + } +} + +class OverviewComponent extends \Component +{ + public function filter() + { + } +} + +?> +----- +overviewComponent = $overviewComponent; + } + public function someAction() + { + $this->overviewComponent->filter(); + } +} + +class OverviewComponent extends \Component +{ + public function filter() + { + } +} + +?> diff --git a/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerComponentToSymfonyRector/Source/Component.php b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerComponentToSymfonyRector/Source/Component.php new file mode 100644 index 000000000000..f3b370c1fa6e --- /dev/null +++ b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerComponentToSymfonyRector/Source/Component.php @@ -0,0 +1,8 @@ +callableNodeTraverser->traverseNodesWithCallable($class, function (Node $node) use ( + $oldToNewPropertyNames + ) { + if (! $node instanceof Node\Expr\PropertyFetch) { + return null; + } + + if (! $this->nameResolver->isName($node->var, 'this')) { + return null; + } + + foreach ($oldToNewPropertyNames as $oldPropertyName => $newPropertyName) { + if (! $this->nameResolver->isName($node->name, $oldPropertyName)) { + continue; + } + + $node->name = new Identifier($newPropertyName); + } + + return null; + }); + } + private function tryInsertBeforeFirstMethod(Class_ $classNode, Stmt $stmt): bool { foreach ($classNode->stmts as $key => $classStmt) { diff --git a/utils/PHPStanStaticTypeMapperChecker/src/Command/CheckStaticTypeMappersCommand.php b/utils/PHPStanStaticTypeMapperChecker/src/Command/CheckStaticTypeMappersCommand.php index e5ccfb1a9b45..822cc79c94a2 100644 --- a/utils/PHPStanStaticTypeMapperChecker/src/Command/CheckStaticTypeMappersCommand.php +++ b/utils/PHPStanStaticTypeMapperChecker/src/Command/CheckStaticTypeMappersCommand.php @@ -64,8 +64,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->symfonyStyle->error('Some classes are missing nodes'); $this->symfonyStyle->listing($missingNodeClasses); - die; - return Shell::CODE_ERROR; } From aef86dda2f9b1cf281c6e17031c1ddcedb4ef178 Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Sat, 18 Jan 2020 19:21:27 +0100 Subject: [PATCH 5/8] add this->view override support --- packages/CakePHPToSymfony/config/config.yaml | 9 ++ ...rActionToSymfonyControllerActionRector.php | 34 ++---- .../src/Rector/TemplatePathResolver.php | 115 ++++++++++++++++++ .../Fixture/changed_view_property.php.inc | 27 ++++ .../ConstructorPropertyTypeInferer.php | 13 +- .../Manipulator/PropertyFetchManipulator.php | 87 ++++--------- 6 files changed, 194 insertions(+), 91 deletions(-) create mode 100644 packages/CakePHPToSymfony/config/config.yaml create mode 100644 packages/CakePHPToSymfony/src/Rector/TemplatePathResolver.php create mode 100644 packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/Fixture/changed_view_property.php.inc diff --git a/packages/CakePHPToSymfony/config/config.yaml b/packages/CakePHPToSymfony/config/config.yaml new file mode 100644 index 000000000000..8000f0a5e607 --- /dev/null +++ b/packages/CakePHPToSymfony/config/config.yaml @@ -0,0 +1,9 @@ +services: + _defaults: + autowire: true + public: true + + Rector\CakePHPToSymfony\: + resource: '../src' + exclude: + - '../src/Rector/**/*Rector.php' diff --git a/packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector.php b/packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector.php index 07f094a9951f..c36ebce24a08 100644 --- a/packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector.php +++ b/packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector.php @@ -4,7 +4,6 @@ namespace Rector\CakePHPToSymfony\Rector\ClassMethod; -use Nette\Utils\Strings; use PhpParser\Node; use PhpParser\Node\Arg; use PhpParser\Node\Expr\Array_; @@ -17,9 +16,8 @@ use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Return_; use Rector\CakePHPToSymfony\Rector\AbstractCakePHPRector; +use Rector\CakePHPToSymfony\Rector\TemplatePathResolver; use Rector\CodeQuality\CompactConverter; -use Rector\CodingStyle\Naming\ClassNaming; -use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\RectorDefinition; @@ -28,24 +26,26 @@ * @see https://symfony.com/doc/5.0/controller.html * @see https://symfony.com/doc/5.0/controller.html#rendering-templates * + * @see https://stackoverflow.com/a/21647715/1348344 for $this->view + * * @see \Rector\CakePHPToSymfony\Tests\Rector\ClassMethod\CakePHPControllerActionToSymfonyControllerActionRector\CakePHPControllerActionToSymfonyControllerActionRectorTest */ final class CakePHPControllerActionToSymfonyControllerActionRector extends AbstractCakePHPRector { /** - * @var ClassNaming + * @var CompactConverter */ - private $classNaming; + private $compactConverter; /** - * @var CompactConverter + * @var TemplatePathResolver */ - private $compactConverter; + private $templatePathResolver; - public function __construct(ClassNaming $classNaming, CompactConverter $compactConverter) + public function __construct(CompactConverter $compactConverter, TemplatePathResolver $templatePathResolver) { - $this->classNaming = $classNaming; $this->compactConverter = $compactConverter; + $this->templatePathResolver = $templatePathResolver; } public function getDefinition(): RectorDefinition @@ -137,20 +137,6 @@ private function collectAndRemoveSetMethodCallArgs(array $stmts): array return $setMethodCallArgs; } - private function resolveTemplateName(ClassMethod $classMethod): string - { - /** @var string $className */ - $className = $classMethod->getAttribute(AttributeKey::CLASS_NAME); - $shortClassName = $this->classNaming->getShortName($className); - - $shortClassName = Strings::replace($shortClassName, '#Controller$#i'); - $shortClassName = Strings::lower($shortClassName); - - $methodName = $classMethod->getAttribute(AttributeKey::METHOD_NAME); - - return $shortClassName . '/' . $methodName . '.twig'; - } - /** * @param Arg[][] $setValues */ @@ -186,7 +172,7 @@ private function createThisRenderMethodCall(ClassMethod $classMethod): MethodCal $thisVariable = new Variable('this'); $thisRenderMethodCall = new MethodCall($thisVariable, 'render'); - $templateName = $this->resolveTemplateName($classMethod); + $templateName = $this->templatePathResolver->resolveForClassMethod($classMethod); $thisRenderMethodCall->args[] = new Arg(new String_($templateName)); $setValues = $this->collectAndRemoveSetMethodCallArgs((array) $classMethod->stmts); diff --git a/packages/CakePHPToSymfony/src/Rector/TemplatePathResolver.php b/packages/CakePHPToSymfony/src/Rector/TemplatePathResolver.php new file mode 100644 index 000000000000..bd97174e760e --- /dev/null +++ b/packages/CakePHPToSymfony/src/Rector/TemplatePathResolver.php @@ -0,0 +1,115 @@ +callableNodeTraverser = $callableNodeTraverser; + $this->propertyFetchManipulator = $propertyFetchManipulator; + $this->valueResolver = $valueResolver; + $this->classNaming = $classNaming; + $this->nodeRemovingCommander = $nodeRemovingCommander; + } + + public function resolveForClassMethod(ClassMethod $classMethod): string + { + $viewPropertyValue = $this->resolveViewPropertyValue($classMethod); + if ($viewPropertyValue !== null) { + return $viewPropertyValue . '.twig'; + } + + $classAndMethodValue = $this->resolveFromClassAndMethod($classMethod); + + return $classAndMethodValue . '.twig'; + } + + private function resolveViewPropertyValue(ClassMethod $classMethod): ?string + { + $setViewProperty = null; + + $this->callableNodeTraverser->traverseNodesWithCallable($classMethod, function (Node $node) use ( + &$setViewProperty + ) { + if (! $node instanceof Assign) { + return null; + } + + if (! $this->propertyFetchManipulator->isToThisPropertyFetchOfSpecificNameAssign($node, 'view')) { + return null; + } + + $setViewProperty = $node->expr; + + $this->nodeRemovingCommander->addNode($node); + }); + + if ($setViewProperty === null) { + return null; + } + $setViewValue = $this->valueResolver->getValue($setViewProperty); + if (is_string($setViewValue)) { + return $setViewValue; + } + + return null; + } + + private function resolveFromClassAndMethod(ClassMethod $classMethod): string + { + /** @var string $className */ + $className = $classMethod->getAttribute(AttributeKey::CLASS_NAME); + $shortClassName = $this->classNaming->getShortName($className); + + $shortClassName = Strings::replace($shortClassName, '#Controller$#i'); + $shortClassName = Strings::lower($shortClassName); + + $methodName = $classMethod->getAttribute(AttributeKey::METHOD_NAME); + + return $shortClassName . '/' . $methodName; + } +} diff --git a/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/Fixture/changed_view_property.php.inc b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/Fixture/changed_view_property.php.inc new file mode 100644 index 000000000000..5df9518549b2 --- /dev/null +++ b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/Fixture/changed_view_property.php.inc @@ -0,0 +1,27 @@ +view = 'Controller/this_value'; + } +} + +?> +----- +render('Controller/this_value.twig'); + } +} + +?> diff --git a/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/ConstructorPropertyTypeInferer.php b/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/ConstructorPropertyTypeInferer.php index 254f1ff85a14..b5ff421f6ec8 100644 --- a/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/ConstructorPropertyTypeInferer.php +++ b/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/ConstructorPropertyTypeInferer.php @@ -21,6 +21,7 @@ use PHPStan\Type\NullType; use PHPStan\Type\Type; use Rector\NodeTypeResolver\Node\AttributeKey; +use Rector\PhpParser\Node\Manipulator\PropertyFetchManipulator; use Rector\PHPStan\Type\AliasedObjectType; use Rector\PHPStan\Type\FullyQualifiedObjectType; use Rector\TypeDeclaration\Contract\TypeInferer\PropertyTypeInfererInterface; @@ -28,6 +29,16 @@ final class ConstructorPropertyTypeInferer extends AbstractTypeInferer implements PropertyTypeInfererInterface { + /** + * @var PropertyFetchManipulator + */ + private $propertyFetchManipulator; + + public function __construct(PropertyFetchManipulator $propertyFetchManipulator) + { + $this->propertyFetchManipulator = $propertyFetchManipulator; + } + public function inferProperty(Property $property): Type { /** @var Class_|null $class */ @@ -44,7 +55,7 @@ public function inferProperty(Property $property): Type $propertyName = $this->nameResolver->getName($property); - $param = $this->resolveParamForPropertyFetch($classMethod, $propertyName); + $param = $this->propertyFetchManipulator->resolveParamForPropertyFetch($classMethod, $propertyName); if ($param === null) { return new MixedType(); } diff --git a/src/PhpParser/Node/Manipulator/PropertyFetchManipulator.php b/src/PhpParser/Node/Manipulator/PropertyFetchManipulator.php index 24ccd48bb8d5..659059de140c 100644 --- a/src/PhpParser/Node/Manipulator/PropertyFetchManipulator.php +++ b/src/PhpParser/Node/Manipulator/PropertyFetchManipulator.php @@ -5,7 +5,6 @@ namespace Rector\PhpParser\Node\Manipulator; use PhpParser\Node; -use PhpParser\Node\Expr; use PhpParser\Node\Expr\ArrayDimFetch; use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\PropertyFetch; @@ -52,11 +51,6 @@ final class PropertyFetchManipulator */ private $callableNodeTraverser; - /** - * @var AssignManipulator - */ - private $assignManipulator; - public function __construct( NodeTypeResolver $nodeTypeResolver, ReflectionProvider $reflectionProvider, @@ -69,14 +63,6 @@ public function __construct( $this->callableNodeTraverser = $callableNodeTraverser; } - /** - * @required - */ - public function autowirePropertyFetchManipulator(AssignManipulator $assignManipulator): void - { - $this->assignManipulator = $assignManipulator; - } - public function isPropertyToSelf(PropertyFetch $propertyFetch): bool { if (! $this->nameResolver->isName($propertyFetch->var, 'this')) { @@ -204,58 +190,6 @@ public function isLocalProperty(Node $node): bool return $this->nameResolver->isName($node->var, 'this'); } - public function getFirstVariableAssignedToPropertyOfName( - ClassMethod $classMethod, - string $propertyName - ): ?Variable { - $variable = null; - - $this->callableNodeTraverser->traverseNodesWithCallable((array) $classMethod->stmts, function (Node $node) use ( - $propertyName, - &$variable - ): ?int { - if (! $node instanceof Assign) { - return null; - } - - if (! $this->isLocalPropertyOfNames($node->var, [$propertyName])) { - return null; - } - - if (! $node->expr instanceof Variable) { - return null; - } - - $variable = $node->expr; - - return NodeTraverser::STOP_TRAVERSAL; - }); - - return $variable; - } - - /** - * @return Expr[] - */ - public function getExprsAssignedToPropertyName(ClassMethod $classMethod, string $propertyName): array - { - $assignedExprs = []; - - $this->callableNodeTraverser->traverseNodesWithCallable($classMethod, function (Node $node) use ( - $propertyName, - &$assignedExprs - ) { - if (! $this->assignManipulator->isLocalPropertyAssignWithPropertyNames($node, [$propertyName])) { - return null; - } - - /** @var Assign $node */ - $assignedExprs[] = $node->expr; - }); - - return $assignedExprs; - } - /** * In case the property name is different to param name: * @@ -329,6 +263,27 @@ public function matchPropertyFetch(Node $node): ?Node return null; } + /** + * Matches: + * "$this->someValue = $;" + */ + public function isToThisPropertyFetchOfSpecificNameAssign(Node $node, string $propertyName): bool + { + if (! $node instanceof Assign) { + return false; + } + + if (! $node->var instanceof PropertyFetch) { + return false; + } + + if (! $this->nameResolver->isName($node->var->var, 'this')) { + return false; + } + + return $this->nameResolver->isName($node->var->name, $propertyName); + } + private function hasPublicProperty(PropertyFetch $propertyFetch, string $propertyName): bool { $nodeScope = $propertyFetch->getAttribute(AttributeKey::SCOPE); From 0b3a468dfa7bab81abf11d1d9511f30a657c4149 Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Sat, 18 Jan 2020 20:03:05 +0100 Subject: [PATCH 6/8] [CakePHPToSymfony] Add CakePHPControllerRedirectToSymfonyRector --- .../cakephp-24-to-symfony-50.yaml | 2 + ...rActionToSymfonyControllerActionRector.php | 3 + ...kePHPControllerRedirectToSymfonyRector.php | 151 ++++++++++++++++++ ...PControllerRedirectToSymfonyRectorTest.php | 30 ++++ .../Fixture/fixture.php.inc | 27 ++++ .../Fixture/redirect_to_controller.php.inc | 30 ++++ phpstan.neon | 21 +-- 7 files changed, 244 insertions(+), 20 deletions(-) create mode 100644 packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerRedirectToSymfonyRector.php create mode 100644 packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRedirectToSymfonyRector/CakePHPControllerRedirectToSymfonyRectorTest.php create mode 100644 packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRedirectToSymfonyRector/Fixture/fixture.php.inc create mode 100644 packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRedirectToSymfonyRector/Fixture/redirect_to_controller.php.inc diff --git a/config/set/cakephp-to-symfony/cakephp-24-to-symfony-50.yaml b/config/set/cakephp-to-symfony/cakephp-24-to-symfony-50.yaml index ea5723de52a5..51530c132d44 100644 --- a/config/set/cakephp-to-symfony/cakephp-24-to-symfony-50.yaml +++ b/config/set/cakephp-to-symfony/cakephp-24-to-symfony-50.yaml @@ -3,3 +3,5 @@ services: Rector\CakePHPToSymfony\Rector\ClassMethod\CakePHPControllerActionToSymfonyControllerActionRector: null Rector\CakePHPToSymfony\Rector\Class_\CakePHPControllerHelperToSymfonyRector: null Rector\CakePHPToSymfony\Rector\Class_\CakePHPControllerComponentToSymfonyRector: null + Rector\CakePHPToSymfony\Rector\Class_\CakePHPControllerRedirectToSymfonyRector: null + Rector\CakePHPToSymfony\Rector\ClassMethod\CakePHPControllerRedirectToSymfonyRector: null diff --git a/packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector.php b/packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector.php index c36ebce24a08..d68eebd7f8be 100644 --- a/packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector.php +++ b/packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector.php @@ -167,6 +167,9 @@ private function isCompactFuncCall(Node $node): bool return $this->isName($node, 'compact'); } + /** + * Creates "$this->render('...', [...])"; + */ private function createThisRenderMethodCall(ClassMethod $classMethod): MethodCall { $thisVariable = new Variable('this'); diff --git a/packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerRedirectToSymfonyRector.php b/packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerRedirectToSymfonyRector.php new file mode 100644 index 000000000000..772ca5018cc4 --- /dev/null +++ b/packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerRedirectToSymfonyRector.php @@ -0,0 +1,151 @@ +redirect('boom'); + } +} +PHP +, + <<<'PHP' +class RedirectController extends \AppController +{ + public function index() + { + return $this->redirect('boom'); + } +} +PHP + + ), + ]); + } + + /** + * @return string[] + */ + public function getNodeTypes(): array + { + return [ClassMethod::class]; + } + + /** + * @param ClassMethod $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->isInCakePHPController($node)) { + return null; + } + + $this->traverseNodesWithCallable($node, function (Node $node) { + if ($node instanceof Return_) { + $returnedExpr = $node->expr; + if ($returnedExpr === null) { + return null; + } + + return $this->refactorRedirectMethodCall($returnedExpr); + } + + if ($node instanceof Expression) { + return $this->refactorRedirectMethodCall($node); + } + + return null; + }); + + return $node; + } + + /** + * @param Expr|Expression $expr + */ + private function refactorRedirectMethodCall($expr): ?Return_ + { + if ($expr instanceof Expression) { + $expr = $expr->expr; + } + + if (! $expr instanceof MethodCall) { + return null; + } + + if (! $this->isName($expr->var, 'this')) { + return null; + } + + if (! $this->isName($expr->name, 'redirect')) { + return null; + } + + $this->refactorRedirectArgs($expr); + + $parentNode = $expr->getAttribute(AttributeKey::PARENT_NODE); + if ($parentNode instanceof Return_) { + return null; + } + + // add "return" + return new Return_($expr); + } + + private function refactorRedirectArgs(MethodCall $methodCall): void + { + $argumentValue = $methodCall->args[0]->value; + if ($argumentValue instanceof String_) { + return; + } + + // not sure what to do + if (! $argumentValue instanceof Array_) { + return; + } + + $argumentValue = $this->getValue($argumentValue); + + if (! isset($argumentValue['controller']) || ! isset($argumentValue['action'])) { + return; + } + + $composedRouteName = $argumentValue['controller'] . '_' . $argumentValue['action']; + $composedRouteName = RectorStrings::camelCaseToUnderscore($composedRouteName); + + $methodCall->args[0]->value = new String_($composedRouteName); + $methodCall->name = new Identifier('redirectToRoute'); + } +} diff --git a/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRedirectToSymfonyRector/CakePHPControllerRedirectToSymfonyRectorTest.php b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRedirectToSymfonyRector/CakePHPControllerRedirectToSymfonyRectorTest.php new file mode 100644 index 000000000000..94daa5a8a24c --- /dev/null +++ b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRedirectToSymfonyRector/CakePHPControllerRedirectToSymfonyRectorTest.php @@ -0,0 +1,30 @@ +doTestFile($file); + } + + public function provideDataForTest(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return CakePHPControllerRedirectToSymfonyRector::class; + } +} diff --git a/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRedirectToSymfonyRector/Fixture/fixture.php.inc b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRedirectToSymfonyRector/Fixture/fixture.php.inc new file mode 100644 index 000000000000..c5027ea9626f --- /dev/null +++ b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRedirectToSymfonyRector/Fixture/fixture.php.inc @@ -0,0 +1,27 @@ +redirect('boom'); + } +} + +?> +----- +redirect('boom'); + } +} + +?> diff --git a/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRedirectToSymfonyRector/Fixture/redirect_to_controller.php.inc b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRedirectToSymfonyRector/Fixture/redirect_to_controller.php.inc new file mode 100644 index 000000000000..be8591912e68 --- /dev/null +++ b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRedirectToSymfonyRector/Fixture/redirect_to_controller.php.inc @@ -0,0 +1,30 @@ +redirect([ + 'controller' => 'DocumentVersions', + 'action' => 'getFile' + ]); + } +} + +?> +----- +redirectToRoute('document_versions_get_file'); + } +} + +?> diff --git a/phpstan.neon b/phpstan.neon index 59ef8f43c635..d1588bcf4319 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -9,7 +9,7 @@ parameters: level: max # to allow installing with various phsptan versions without reporting old errors here - reportUnmatchedIgnoredErrors: false +# reportUnmatchedIgnoredErrors: false autoload_directories: - stubs @@ -171,8 +171,6 @@ parameters: - '#Parameter \#1 \$sprintfFuncCall of method Rector\\PhpParser\\NodeTransformer\:\:transformSprintfToArray\(\) expects PhpParser\\Node\\Expr\\FuncCall, PhpParser\\Node given#' - '#Method Rector\\BetterPhpDocParser\\Tests\\PhpDocParser\\OrmTagParser\\AbstractPhpDocInfoTest\:\:parseFileAndGetFirstNodeOfType\(\) should return PhpParser\\Node but returns PhpParser\\Node\|null#' - - '#Parameter \#1 \$phpDocTagValueNode of method Rector\\BetterPhpDocParser\\PhpDocInfo\\PhpDocInfo\:\:removeTagValueNodeFromNode\(\) expects PHPStan\\PhpDocParser\\Ast\\PhpDoc\\PhpDocTagValueNode, PHPStan\\PhpDocParser\\Ast\\PhpDoc\\VarTagValueNode\|null given#' - - '#Method Rector\\BetterPhpDocParser\\PhpDocNodeFactory\\JMS\\SerializerTypePhpDocNodeFactory::resolveTypeAnnotation\(\) should return JMS\\Serializer\\Annotation\\Type\|null but returns object\|null#' # known value @@ -216,19 +214,7 @@ parameters: - '#Parameter \#3 \$nodeCallback of method PHPStan\\Analyser\\NodeScopeResolver::processNodes\(\) expects Closure\(PhpParser\\Node, PHPStan\\Analyser\\Scope\): void, Closure\(PhpParser\\Node, PHPStan\\Analyser\\MutatingScope\): void given#' # false positive - - '#Array \(array\) does not accept key#' - - '#Array \(array\) does not accept key#' - '#Comparison operation "<" between 0 and 2 is always true#' - - '#Property Rector\\Compiler\\Process\\SymfonyProcess::\$process type has no value type specified in iterable type Symfony\\Component\\Process\\Process#' - - '#Method Rector\\Compiler\\Process\\SymfonyProcess::getProcess\(\) return type has no value type specified in iterable type Symfony\\Component\\Process\\Process#' - - '#Method Rector\\Compiler\\Contract\\Process\\ProcessInterface::getProcess\(\) return type has no value type specified in iterable type Symfony\\Component\\Process\\Process#' - - # phpstan compiler bug - - '#Parameter \#1 \$docComment of method PhpParser\\Builder\\Property::setDocComment\(\) expects _HumbugBox(.*?)\\PhpParser\\Comment\\Doc\|string, PhpParser\\Comment\\Doc given#' - - - - message: '#Call to function in_array\(\) with arguments PhpParser\\Node\\Expr\\Variable, array\(\) and true will always evaluate to false#' - path: packages/Php56/src/Rector/FunctionLike/AddDefaultValueForUndefinedVariableRector.php # known values - @@ -245,18 +231,13 @@ parameters: - '#Method Rector\\BetterPhpDocParser\\PhpDocNodeFactory\\Gedmo\\(.*?)\:\:createFromNodeAndTokens\(\) should return Rector\\BetterPhpDocParser\\PhpDocNode\\Gedmo\\(.*?)\|null but returns PHPStan\\PhpDocParser\\Ast\\PhpDoc\\PhpDocTagValueNode\|null#' - '#Access to an undefined property PhpParser\\Node\\Identifier\|PhpParser\\Node\\Name\|PhpParser\\Node\\NullableType\:\:\$type#' - - '#Call to an undefined method PhpParser\\Node\\Identifier\|PhpParser\\Node\\Name\|PhpParser\\Node\\NullableType\:\:toString\(\)#' - '#Parameter \#1 \$expected of method PHPUnit\\Framework\\Assert\:\:assertInstanceOf\(\) expects class\-string, string given#' - '#Unable to resolve the template type ExpectedType in call to method PHPUnit\\Framework\\Assert\:\:assertInstanceOf\(\)#' - '#Right side of \|\| is always false#' - - '#Method Rector\\NodeContainer\\ParsedNodesByType\:\:findNewNodesByClass\(\) should return array but returns array#' - # fix Symplify 7.2 later - '#Method (.*?) returns bool type, so the name should start with is/has/was#' - - '#Property Rector\\BetterPhpDocParser\\PhpDocNode\\Gedmo\\BlameableTagValueNode\:\:\$value has no typehint specified#' - # known value - "#^Parameter \\#1 \\$variable of class Rector\\\\Php70\\\\ValueObject\\\\VariableAssignPair constructor expects PhpParser\\\\Node\\\\Expr\\\\ArrayDimFetch\\|PhpParser\\\\Node\\\\Expr\\\\PropertyFetch\\|PhpParser\\\\Node\\\\Expr\\\\StaticPropertyFetch\\|PhpParser\\\\Node\\\\Expr\\\\Variable, PhpParser\\\\Node\\\\Expr given\\.$#" - '#Cannot cast \(array\)\|string\|true to string#' From 70031e0b500379fbf5a734a2a22c04b6a4456c53 Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Sat, 18 Jan 2020 21:57:35 +0100 Subject: [PATCH 7/8] cleanup fixed phpstan reports --- .../cakephp-24-to-symfony-50.yaml | 1 - docs/AllRectorsOverview.md | 21 +++++++++++++++- phpstan.neon | 25 +++---------------- 3 files changed, 23 insertions(+), 24 deletions(-) diff --git a/config/set/cakephp-to-symfony/cakephp-24-to-symfony-50.yaml b/config/set/cakephp-to-symfony/cakephp-24-to-symfony-50.yaml index 51530c132d44..5f3bb2d3d690 100644 --- a/config/set/cakephp-to-symfony/cakephp-24-to-symfony-50.yaml +++ b/config/set/cakephp-to-symfony/cakephp-24-to-symfony-50.yaml @@ -3,5 +3,4 @@ services: Rector\CakePHPToSymfony\Rector\ClassMethod\CakePHPControllerActionToSymfonyControllerActionRector: null Rector\CakePHPToSymfony\Rector\Class_\CakePHPControllerHelperToSymfonyRector: null Rector\CakePHPToSymfony\Rector\Class_\CakePHPControllerComponentToSymfonyRector: null - Rector\CakePHPToSymfony\Rector\Class_\CakePHPControllerRedirectToSymfonyRector: null Rector\CakePHPToSymfony\Rector\ClassMethod\CakePHPControllerRedirectToSymfonyRector: null diff --git a/docs/AllRectorsOverview.md b/docs/AllRectorsOverview.md index a03b137d6373..e0984468ef72 100644 --- a/docs/AllRectorsOverview.md +++ b/docs/AllRectorsOverview.md @@ -1,4 +1,4 @@ -# All 436 Rectors Overview +# All 437 Rectors Overview - [Projects](#projects) - [General](#general) @@ -366,6 +366,25 @@ Migrate CakePHP 2.4 Controller $helpers and $components property to Symfony 5
+### `CakePHPControllerRedirectToSymfonyRector` + +- class: `Rector\CakePHPToSymfony\Rector\ClassMethod\CakePHPControllerRedirectToSymfonyRector` + +Migrate CakePHP 2.4 Controller redirect() to Symfony 5 + +```diff + class RedirectController extends \AppController + { + public function index() + { +- $this->redirect('boom'); ++ return $this->redirect('boom'); + } + } +``` + +
+ ### `CakePHPControllerToSymfonyControllerRector` - class: `Rector\CakePHPToSymfony\Rector\Class_\CakePHPControllerToSymfonyControllerRector` diff --git a/phpstan.neon b/phpstan.neon index d1588bcf4319..d5d69eaa9b90 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -9,7 +9,7 @@ parameters: level: max # to allow installing with various phsptan versions without reporting old errors here -# reportUnmatchedIgnoredErrors: false + reportUnmatchedIgnoredErrors: false autoload_directories: - stubs @@ -43,13 +43,9 @@ parameters: # false positive # - '#Call to function method_exists\(\) with string and (.*?) will always evaluate to false#' - '#PHPDoc tag \@param for parameter \$node with type float is incompatible with native type PhpParser\\Node#' - - '#Access to an undefined property PhpParser\\Node\\Stmt\\ClassLike\:\:\$extends#' # misuse of interface and class - '#Parameter \#1 (.*?) expects Symfony\\Component\\DependencyInjection\\ContainerBuilder, Symfony\\Component\\DependencyInjection\\ContainerInterface given#' - - '#Method Rector\\Symfony\\Bridge\\DefaultAnalyzedSymfonyApplicationContainer::getContainer\(\) should return Symfony\\Component\\DependencyInjection\\ContainerBuilder but returns Symfony\\Component\\DependencyInjection\\Container#' - - - '#Property Rector\\DependencyInjection\\Loader\\RectorServiceParametersShifter::\$serviceKeywords \(array\) does not accept ReflectionProperty#' - '#Strict comparison using === between string and null will always evaluate to false#' # false positive - type is set by annotation above @@ -112,7 +108,6 @@ parameters: # false positive 0.11.5 - '#Unreachable statement \- code above always terminates#' - - '#Method Rector\\NodeTypeResolver\\NodeVisitor\\(.*?)\:\:enterNode\(\) should return int\|PhpParser\\Node\|void\|null but return statement is missing#' - '#Negated boolean expression is always true#' - '#Strict comparison using \=\=\= between PhpParser\\Node and null will always evaluate to false#' @@ -133,29 +128,21 @@ parameters: - '#Cannot cast array\|bool\|string\|null to string#' - '#Method Rector\\Legacy\\Rector\\ClassMethod\\ChangeSingletonToServiceRector\:\:matchStaticPropertyFetchAndGetSingletonMethodName\(\) should return array\|null but returns array#' - - '#Parameter \#1 \$rule of method Rector\\Configuration\\Configuration\:\:setRule\(\) expects string\|null, array\|bool\|string\|null given#' - - '#Empty catch block\. If you are sure this is meant to be empty, please add a "// @ignoreException" comment in the catch block#' - - '#Method Rector\\Rector\\AbstractRector\:\:wrapToArg\(\) should return array but returns array#' - '#Parameter \#2 \$currentNode of method Rector\\CodingStyle\\Rector\\String_\\ManualJsonStringToJsonEncodeArrayRector\:\:matchNextExpressionAssignConcatToSameVariable\(\) expects PhpParser\\Node\\Expr\\Assign\|PhpParser\\Node\\Expr\\AssignOp\\Concat, PhpParser\\Node given#' - '#Method Rector\\FileSystemRector\\Rector\\AbstractFileSystemRector\:\:wrapToArg\(\) should return array but returns array#' # array is callable - - '#Parameter \#2 \$listener of method Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcher\:\:getListenerPriority\(\) expects callable\(\)\: mixed, array given#' - - '#Parameter \#1 \$kernelClass of method Rector\\Symfony\\Bridge\\DependencyInjection\\SymfonyContainerFactory\:\:createFromKernelClass\(\) expects string, string\|null given#' - '#If condition is always true#' - '#Ternary operator condition is always true#' - - '#Method Rector\\Symfony\\Bridge\\DefaultAnalyzedSymfonyApplicationContainer\:\:getService\(\) should return object but returns object\|null#' - '#Call to function property_exists\(\) with string and (.*?) will always evaluate to false#' - '#Access to an undefined property PhpParser\\Node\\FunctionLike\|PhpParser\\Node\\Stmt\\ClassLike\:\:\$stmts#' - '#Property Rector\\TypeDeclaration\\TypeInferer\\(.*?)\:\:\$(.*?)TypeInferers \(array\) does not accept array#' # sense-less errors - - '#In method "Rector\\Rector\\Property\\InjectAnnotationClassRector\:\:resolveJMSDIInjectType", caught "Throwable" must be rethrown\. Either catch a more specific exception or add a "throw" clause in the "catch" block to propagate the exception\. More info\: http\://bit\.ly/failloud#' - - '#Instanceof between PHPStan\\Type\\Type and PHPStan\\Type\\VoidType will always evaluate to false#' - '#Parameter \#1 \$functionLike of method Rector\\NodeTypeResolver\\PhpDoc\\NodeAnalyzer\\DocBlockManipulator\:\:getParamTypesByName\(\) expects PhpParser\\Node\\Expr\\Closure\|PhpParser\\Node\\Stmt\\ClassMethod\|PhpParser\\Node\\Stmt\\Function_, PhpParser\\Node\\FunctionLike given#' # PHP 7.4 1_000 support @@ -176,7 +163,6 @@ parameters: # known value - '#Method Rector\\StrictCodeQuality\\Rector\\Stmt\\VarInlineAnnotationToAssertRector\:\:findVariableByName\(\) should return PhpParser\\Node\\Expr\\Variable\|null but returns PhpParser\\Node\|null#' - - '#In method "Rector\\BetterPhpDocParser\\AnnotationReader\\NodeAnnotationReader\:\:createPropertyReflectionFromPropertyNode", caught "Throwable" must be rethrown\. Either catch a more specific exception or add a "throw" clause in the "catch" block to propagate the exception\. More info\: http\://bit\.ly/failloud#' - '#Method Rector\\PhpParser\\Node\\Manipulator\\MethodCallManipulator\:\:findAssignToVariableName\(\) should return PhpParser\\Node\\Expr\\Assign\|null but returns PhpParser\\Node\|null#' - '#Method Rector\\PhpParser\\Node\\Manipulator\\MethodCallManipulator\:\:findMethodCallsIncludingChain\(\) should return array but returns array#' - '#Method Rector\\NodeTypeResolver\\PHPStan\\Type\\TypeFactory\:\:createUnionOrSingleType\(\) should return PHPStan\\Type\\MixedType\|PHPStan\\Type\\UnionType but returns PHPStan\\Type\\Type#' @@ -216,21 +202,14 @@ parameters: # false positive - '#Comparison operation "<" between 0 and 2 is always true#' - # known values - - - message: '#Method Rector\\Rector\\Property\\InjectAnnotationClassRector\:\:resolveJMSDIInjectType\(\) should return PHPStan\\Type\\Type but returns PHPStan\\Type\\Type\|null#' - path: src/Rector/Property/InjectAnnotationClassRector.php - - message: '#Method Rector\\Symfony\\Rector\\FrameworkBundle\\AbstractToConstructorInjectionRector\:\:getServiceTypeFromMethodCallArgument\(\) should return PHPStan\\Type\\Type but returns PHPStan\\Type\\Type\|null#' path: packages/Symfony/src/Rector/FrameworkBundle/AbstractToConstructorInjectionRector.php - '#Parameter \#1 \$error_handler of function set_error_handler expects \(callable\(int, string, string, int, array\)\: bool\)\|null, Closure\(int, string\)\: void given#' - - '#Parameter \#1 \$source of method Rector\\Scan\\ErrorScanner\:\:scanSource\(\) expects array, array\|string\|null given#' - '#Method Rector\\BetterPhpDocParser\\PhpDocNodeFactory\\Gedmo\\(.*?)\:\:createFromNodeAndTokens\(\) should return Rector\\BetterPhpDocParser\\PhpDocNode\\Gedmo\\(.*?)\|null but returns PHPStan\\PhpDocParser\\Ast\\PhpDoc\\PhpDocTagValueNode\|null#' - - '#Access to an undefined property PhpParser\\Node\\Identifier\|PhpParser\\Node\\Name\|PhpParser\\Node\\NullableType\:\:\$type#' - '#Parameter \#1 \$expected of method PHPUnit\\Framework\\Assert\:\:assertInstanceOf\(\) expects class\-string, string given#' - '#Unable to resolve the template type ExpectedType in call to method PHPUnit\\Framework\\Assert\:\:assertInstanceOf\(\)#' - '#Right side of \|\| is always false#' @@ -241,3 +220,5 @@ parameters: # known value - "#^Parameter \\#1 \\$variable of class Rector\\\\Php70\\\\ValueObject\\\\VariableAssignPair constructor expects PhpParser\\\\Node\\\\Expr\\\\ArrayDimFetch\\|PhpParser\\\\Node\\\\Expr\\\\PropertyFetch\\|PhpParser\\\\Node\\\\Expr\\\\StaticPropertyFetch\\|PhpParser\\\\Node\\\\Expr\\\\Variable, PhpParser\\\\Node\\\\Expr given\\.$#" - '#Cannot cast \(array\)\|string\|true to string#' + + - '#In method "Rector\\BetterPhpDocParser\\AnnotationReader\\NodeAnnotationReader\:\:createPropertyReflectionFromPropertyNode", caught "Throwable" must be rethrown\. Either catch a more specific exception or add a "throw" clause in the "catch" block to propagate the exception\. More info\: http\://bit\.ly/failloud#' From 37c342b65459931ed4492b80eee941b1c5d29fe3 Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Sat, 18 Jan 2020 22:13:12 +0100 Subject: [PATCH 8/8] [CakePHPToSymfony] Add CakePHPControllerRenderToSymfonyRector --- .../cakephp-24-to-symfony-50.yaml | 1 + docs/AllRectorsOverview.md | 25 +- ...rActionToSymfonyControllerActionRector.php | 110 --------- ...CakePHPControllerRenderToSymfonyRector.php | 215 ++++++++++++++++++ .../TemplateMethodCallManipulator.php | 130 +++++++++++ .../src/Rector/TemplatePathResolver.php | 23 +- .../Fixture/fixture.php.inc | 2 - ...PHPControllerRenderToSymfonyRectorTest.php | 30 +++ .../Fixture/changed_view_property.php.inc | 8 +- .../Fixture/compact_too.php.inc | 8 +- .../Fixture/fixture.php.inc | 27 +++ .../Fixture/set.inc | 27 +++ phpstan.neon | 1 + 13 files changed, 473 insertions(+), 134 deletions(-) create mode 100644 packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerRenderToSymfonyRector.php create mode 100644 packages/CakePHPToSymfony/src/Rector/Template/TemplateMethodCallManipulator.php create mode 100644 packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRenderToSymfonyRector/CakePHPControllerRenderToSymfonyRectorTest.php rename packages/CakePHPToSymfony/tests/Rector/ClassMethod/{CakePHPControllerActionToSymfonyControllerActionRector => CakePHPControllerRenderToSymfonyRector}/Fixture/changed_view_property.php.inc (60%) rename packages/CakePHPToSymfony/tests/Rector/ClassMethod/{CakePHPControllerActionToSymfonyControllerActionRector => CakePHPControllerRenderToSymfonyRector}/Fixture/compact_too.php.inc (69%) create mode 100644 packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRenderToSymfonyRector/Fixture/fixture.php.inc create mode 100644 packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRenderToSymfonyRector/Fixture/set.inc diff --git a/config/set/cakephp-to-symfony/cakephp-24-to-symfony-50.yaml b/config/set/cakephp-to-symfony/cakephp-24-to-symfony-50.yaml index 5f3bb2d3d690..462974897a24 100644 --- a/config/set/cakephp-to-symfony/cakephp-24-to-symfony-50.yaml +++ b/config/set/cakephp-to-symfony/cakephp-24-to-symfony-50.yaml @@ -4,3 +4,4 @@ services: Rector\CakePHPToSymfony\Rector\Class_\CakePHPControllerHelperToSymfonyRector: null Rector\CakePHPToSymfony\Rector\Class_\CakePHPControllerComponentToSymfonyRector: null Rector\CakePHPToSymfony\Rector\ClassMethod\CakePHPControllerRedirectToSymfonyRector: null + Rector\CakePHPToSymfony\Rector\ClassMethod\CakePHPControllerRenderToSymfonyRector: null diff --git a/docs/AllRectorsOverview.md b/docs/AllRectorsOverview.md index e0984468ef72..5d02b4da380e 100644 --- a/docs/AllRectorsOverview.md +++ b/docs/AllRectorsOverview.md @@ -1,4 +1,4 @@ -# All 437 Rectors Overview +# All 438 Rectors Overview - [Projects](#projects) - [General](#general) @@ -301,10 +301,6 @@ Migrate CakePHP 2.4 Controller action to Symfony 5 + public function index(): Response { $value = 5; -- $this->set('name', $value); -+ return $this->renderResponse('homepage/index.twig', [ -+ 'name' => $value -+ ]); } } ``` @@ -385,6 +381,25 @@ Migrate CakePHP 2.4 Controller redirect() to Symfony 5
+### `CakePHPControllerRenderToSymfonyRector` + +- class: `Rector\CakePHPToSymfony\Rector\ClassMethod\CakePHPControllerRenderToSymfonyRector` + +Migrate CakePHP 2.4 Controller render() to Symfony 5 + +```diff + class RedirectController extends \AppController + { + public function index() + { +- $this->render('custom_file'); ++ return $this->render('redirect/custom_file.twig'); + } + } +``` + +
+ ### `CakePHPControllerToSymfonyControllerRector` - class: `Rector\CakePHPToSymfony\Rector\Class_\CakePHPControllerToSymfonyControllerRector` diff --git a/packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector.php b/packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector.php index d68eebd7f8be..b50ec8425eb3 100644 --- a/packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector.php +++ b/packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector.php @@ -5,19 +5,9 @@ namespace Rector\CakePHPToSymfony\Rector\ClassMethod; use PhpParser\Node; -use PhpParser\Node\Arg; -use PhpParser\Node\Expr\Array_; -use PhpParser\Node\Expr\ArrayItem; -use PhpParser\Node\Expr\FuncCall; -use PhpParser\Node\Expr\MethodCall; -use PhpParser\Node\Expr\Variable; use PhpParser\Node\Name\FullyQualified; -use PhpParser\Node\Scalar\String_; use PhpParser\Node\Stmt\ClassMethod; -use PhpParser\Node\Stmt\Return_; use Rector\CakePHPToSymfony\Rector\AbstractCakePHPRector; -use Rector\CakePHPToSymfony\Rector\TemplatePathResolver; -use Rector\CodeQuality\CompactConverter; use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\RectorDefinition; @@ -32,22 +22,6 @@ */ final class CakePHPControllerActionToSymfonyControllerActionRector extends AbstractCakePHPRector { - /** - * @var CompactConverter - */ - private $compactConverter; - - /** - * @var TemplatePathResolver - */ - private $templatePathResolver; - - public function __construct(CompactConverter $compactConverter, TemplatePathResolver $templatePathResolver) - { - $this->compactConverter = $compactConverter; - $this->templatePathResolver = $templatePathResolver; - } - public function getDefinition(): RectorDefinition { return new RectorDefinition('Migrate CakePHP 2.4 Controller action to Symfony 5', [ @@ -58,7 +32,6 @@ class HomepageController extends \AppController public function index() { $value = 5; - $this->set('name', $value); } } PHP @@ -71,9 +44,6 @@ class HomepageController extends \AppController public function index(): Response { $value = 5; - return $this->renderResponse('homepage/index.twig', [ - 'name' => $value - ]); } } PHP @@ -103,88 +73,8 @@ public function refactor(Node $node): ?Node return null; } - $methodCall = $this->createThisRenderMethodCall($node); - $return = new Return_($methodCall); - $node->stmts[] = $return; - $node->returnType = new FullyQualified('Symfony\Component\HttpFoundation\Response'); return $node; } - - /** - * @return Arg[][] - */ - private function collectAndRemoveSetMethodCallArgs(array $stmts): array - { - $setMethodCallArgs = []; - - $this->traverseNodesWithCallable($stmts, function (Node $node) use (&$setMethodCallArgs) { - if (! $node instanceof MethodCall) { - return null; - } - - if (! $this->isName($node->name, 'set')) { - return null; - } - - $setMethodCallArgs[] = $node->args; - $this->removeNode($node); - - return null; - }); - - return $setMethodCallArgs; - } - - /** - * @param Arg[][] $setValues - */ - private function createArrayFromSetValues(array $setValues): Array_ - { - $arrayItems = []; - - foreach ($setValues as $setValue) { - if (count($setValue) > 1) { - $arrayItems[] = new ArrayItem($setValue[1]->value, $setValue[0]->value); - } elseif ($this->isCompactFuncCall($setValue[0]->value)) { - /** @var FuncCall $compactFuncCall */ - $compactFuncCall = $setValue[0]->value; - - return $this->compactConverter->convertToArray($compactFuncCall); - } - } - - return new Array_($arrayItems); - } - - private function isCompactFuncCall(Node $node): bool - { - if (! $node instanceof FuncCall) { - return false; - } - - return $this->isName($node, 'compact'); - } - - /** - * Creates "$this->render('...', [...])"; - */ - private function createThisRenderMethodCall(ClassMethod $classMethod): MethodCall - { - $thisVariable = new Variable('this'); - $thisRenderMethodCall = new MethodCall($thisVariable, 'render'); - - $templateName = $this->templatePathResolver->resolveForClassMethod($classMethod); - $thisRenderMethodCall->args[] = new Arg(new String_($templateName)); - - $setValues = $this->collectAndRemoveSetMethodCallArgs((array) $classMethod->stmts); - - if ($setValues !== []) { - $parametersArray = $this->createArrayFromSetValues($setValues); - $thisRenderMethodCall->args[] = new Arg($parametersArray); - } - - return $thisRenderMethodCall; - } } diff --git a/packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerRenderToSymfonyRector.php b/packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerRenderToSymfonyRector.php new file mode 100644 index 000000000000..0e6ae1b9dbe7 --- /dev/null +++ b/packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerRenderToSymfonyRector.php @@ -0,0 +1,215 @@ +templatePathResolver = $templatePathResolver; + $this->compactConverter = $compactConverter; + $this->templateMethodCallManipulator = $templateMethodCallManipulator; + } + + public function getDefinition(): RectorDefinition + { + return new RectorDefinition('Migrate CakePHP 2.4 Controller render() to Symfony 5', [ + new CodeSample( + <<<'PHP' +class RedirectController extends \AppController +{ + public function index() + { + $this->render('custom_file'); + } +} +PHP +, + <<<'PHP' +class RedirectController extends \AppController +{ + public function index() + { + return $this->render('redirect/custom_file.twig'); + } +} +PHP + + ), + ]); + } + + /** + * @return string[] + */ + public function getNodeTypes(): array + { + return [ClassMethod::class]; + } + + /** + * @param ClassMethod $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->isInCakePHPController($node)) { + return null; + } + + if ($this->hasRenderMethodCall($node)) { + $this->templateMethodCallManipulator->refactorExistingRenderMethodCall($node); + return null; + } + + $this->completeImplicitRenderMethodCall($node); + + return $node; + } + + /** + * Creates "$this->render('...', [...])"; + */ + private function createThisRenderMethodCall(ClassMethod $classMethod): MethodCall + { + $thisVariable = new Variable('this'); + $thisRenderMethodCall = new MethodCall($thisVariable, 'render'); + + $templateName = $this->templatePathResolver->resolveForClassMethod($classMethod); + $thisRenderMethodCall->args[] = new Arg(new String_($templateName)); + + $setValues = $this->collectAndRemoveSetMethodCallArgs((array) $classMethod->stmts); + + if ($setValues !== []) { + $parametersArray = $this->createArrayFromSetValues($setValues); + $thisRenderMethodCall->args[] = new Arg($parametersArray); + } + + return $thisRenderMethodCall; + } + + /** + * @return Arg[][] + */ + private function collectAndRemoveSetMethodCallArgs(array $stmts): array + { + $setMethodCallArgs = []; + + $this->traverseNodesWithCallable($stmts, function (Node $node) use (&$setMethodCallArgs) { + if (! $node instanceof MethodCall) { + return null; + } + + if (! $this->isName($node->name, 'set')) { + return null; + } + + $setMethodCallArgs[] = $node->args; + $this->removeNode($node); + + return null; + }); + + return $setMethodCallArgs; + } + + /** + * @param Arg[][] $setValues + */ + private function createArrayFromSetValues(array $setValues): Array_ + { + $arrayItems = []; + + foreach ($setValues as $setValue) { + if (count($setValue) > 1) { + $arrayItems[] = new ArrayItem($setValue[1]->value, $setValue[0]->value); + } elseif ($this->isCompactFuncCall($setValue[0]->value)) { + /** @var FuncCall $compactFuncCall */ + $compactFuncCall = $setValue[0]->value; + + return $this->compactConverter->convertToArray($compactFuncCall); + } + } + + return new Array_($arrayItems); + } + + private function isCompactFuncCall(Node $node): bool + { + if (! $node instanceof FuncCall) { + return false; + } + + return $this->isName($node, 'compact'); + } + + private function hasRenderMethodCall(ClassMethod $classMethod): bool + { + return (bool) $this->betterNodeFinder->findFirst($classMethod, function (Node $node) { + return $this->isThisRenderMethodCall($node); + }); + } + + private function completeImplicitRenderMethodCall(ClassMethod $classMethod): void + { + $methodCall = $this->createThisRenderMethodCall($classMethod); + $return = new Return_($methodCall); + + $classMethod->stmts[] = $return; + } + + private function isThisRenderMethodCall(Node $node): bool + { + if (! $node instanceof MethodCall) { + return false; + } + + if (! $this->isName($node->var, 'this')) { + return false; + } + + return $this->isName($node->name, 'render'); + } +} diff --git a/packages/CakePHPToSymfony/src/Rector/Template/TemplateMethodCallManipulator.php b/packages/CakePHPToSymfony/src/Rector/Template/TemplateMethodCallManipulator.php new file mode 100644 index 000000000000..357f4d048aa3 --- /dev/null +++ b/packages/CakePHPToSymfony/src/Rector/Template/TemplateMethodCallManipulator.php @@ -0,0 +1,130 @@ +valueResolver = $valueResolver; + $this->templatePathResolver = $templatePathResolver; + $this->callableNodeTraverser = $callableNodeTraverser; + $this->nameResolver = $nameResolver; + } + + public function refactorExistingRenderMethodCall(ClassMethod $classMethod): void + { + $controllerNamePart = $this->templatePathResolver->resolveClassNameTemplatePart($classMethod); + + $this->callableNodeTraverser->traverseNodesWithCallable($classMethod, function (Node $node) use ( + $controllerNamePart + ) { + $renderMethodCall = $this->matchThisRenderMethodCallBareOrInReturn($node); + if ($renderMethodCall === null) { + return null; + } + + return $this->refactorRenderTemplateName($renderMethodCall, $controllerNamePart); + }); + } + + private function refactorRenderTemplateName(Node $node, string $controllerNamePart): ?Return_ + { + /** @var MethodCall $node */ + $renderArgumentValue = $this->valueResolver->getValue($node->args[0]->value); + + /** @var string|mixed $renderArgumentValue */ + if (! is_string($renderArgumentValue)) { + return null; + } + + if (Strings::contains($renderArgumentValue, '/')) { + $templateName = $renderArgumentValue . '.twig'; + } else { + // add explicit controller + $templateName = $controllerNamePart . '/' . $renderArgumentValue . '.twig'; + } + + $node->args[0]->value = new String_($templateName); + + return new Return_($node); + } + + private function isThisRenderMethodCall(Node $node): bool + { + if (! $node instanceof MethodCall) { + return false; + } + + if (! $this->nameResolver->isName($node->var, 'this')) { + return false; + } + + return $this->nameResolver->isName($node->name, 'render'); + } + + private function matchThisRenderMethodCallBareOrInReturn(Node $node): ?MethodCall + { + if ($node instanceof Return_) { + $nodeExpr = $node->expr; + if ($nodeExpr === null) { + return null; + } + + if (! $this->isThisRenderMethodCall($nodeExpr)) { + return null; + } + + /** @var MethodCall $nodeExpr */ + return $nodeExpr; + } + + if ($node instanceof Expression) { + if (! $this->isThisRenderMethodCall($node->expr)) { + return null; + } + + return $node->expr; + } + + return null; + } +} diff --git a/packages/CakePHPToSymfony/src/Rector/TemplatePathResolver.php b/packages/CakePHPToSymfony/src/Rector/TemplatePathResolver.php index bd97174e760e..52ce1962fc73 100644 --- a/packages/CakePHPToSymfony/src/Rector/TemplatePathResolver.php +++ b/packages/CakePHPToSymfony/src/Rector/TemplatePathResolver.php @@ -60,6 +60,7 @@ public function resolveForClassMethod(ClassMethod $classMethod): string { $viewPropertyValue = $this->resolveViewPropertyValue($classMethod); if ($viewPropertyValue !== null) { + $viewPropertyValue = Strings::lower($viewPropertyValue); return $viewPropertyValue . '.twig'; } @@ -68,6 +69,17 @@ public function resolveForClassMethod(ClassMethod $classMethod): string return $classAndMethodValue . '.twig'; } + public function resolveClassNameTemplatePart(ClassMethod $classMethod): string + { + /** @var string $className */ + $className = $classMethod->getAttribute(AttributeKey::CLASS_NAME); + $shortClassName = $this->classNaming->getShortName($className); + + $shortClassName = Strings::replace($shortClassName, '#Controller$#i'); + + return Strings::lower($shortClassName); + } + private function resolveViewPropertyValue(ClassMethod $classMethod): ?string { $setViewProperty = null; @@ -91,9 +103,10 @@ private function resolveViewPropertyValue(ClassMethod $classMethod): ?string if ($setViewProperty === null) { return null; } + $setViewValue = $this->valueResolver->getValue($setViewProperty); if (is_string($setViewValue)) { - return $setViewValue; + return Strings::lower($setViewValue); } return null; @@ -101,13 +114,7 @@ private function resolveViewPropertyValue(ClassMethod $classMethod): ?string private function resolveFromClassAndMethod(ClassMethod $classMethod): string { - /** @var string $className */ - $className = $classMethod->getAttribute(AttributeKey::CLASS_NAME); - $shortClassName = $this->classNaming->getShortName($className); - - $shortClassName = Strings::replace($shortClassName, '#Controller$#i'); - $shortClassName = Strings::lower($shortClassName); - + $shortClassName = $this->resolveClassNameTemplatePart($classMethod); $methodName = $classMethod->getAttribute(AttributeKey::METHOD_NAME); return $shortClassName . '/' . $methodName; diff --git a/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/Fixture/fixture.php.inc b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/Fixture/fixture.php.inc index cf0d3bd4d35f..f0ac934fabb6 100644 --- a/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/Fixture/fixture.php.inc +++ b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/Fixture/fixture.php.inc @@ -7,7 +7,6 @@ class HomepageController extends \AppController public function index() { $value = 5; - $this->set('name', $value); } } @@ -22,7 +21,6 @@ class HomepageController extends \AppController public function index(): \Symfony\Component\HttpFoundation\Response { $value = 5; - return $this->render('homepage/index.twig', ['name' => $value]); } } diff --git a/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRenderToSymfonyRector/CakePHPControllerRenderToSymfonyRectorTest.php b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRenderToSymfonyRector/CakePHPControllerRenderToSymfonyRectorTest.php new file mode 100644 index 000000000000..5b0908361c7a --- /dev/null +++ b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRenderToSymfonyRector/CakePHPControllerRenderToSymfonyRectorTest.php @@ -0,0 +1,30 @@ +doTestFile($file); + } + + public function provideDataForTest(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return CakePHPControllerRenderToSymfonyRector::class; + } +} diff --git a/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/Fixture/changed_view_property.php.inc b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRenderToSymfonyRector/Fixture/changed_view_property.php.inc similarity index 60% rename from packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/Fixture/changed_view_property.php.inc rename to packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRenderToSymfonyRector/Fixture/changed_view_property.php.inc index 5df9518549b2..5740ce2772a6 100644 --- a/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/Fixture/changed_view_property.php.inc +++ b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRenderToSymfonyRector/Fixture/changed_view_property.php.inc @@ -1,6 +1,6 @@ render('Controller/this_value.twig'); + return $this->render('controller/this_value.twig'); } } diff --git a/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/Fixture/compact_too.php.inc b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRenderToSymfonyRector/Fixture/compact_too.php.inc similarity index 69% rename from packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/Fixture/compact_too.php.inc rename to packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRenderToSymfonyRector/Fixture/compact_too.php.inc index a9e45ab90280..1c635cddd030 100644 --- a/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/Fixture/compact_too.php.inc +++ b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRenderToSymfonyRector/Fixture/compact_too.php.inc @@ -1,12 +1,11 @@ set(compact('clientId', 'states', 'invoiceTemplates')); } } @@ -15,13 +14,12 @@ class CompactTooController extends \AppController ----- render('compacttoo/index.twig', ['clientId' => $clientId, 'states' => $states, 'invoiceTemplates' => $invoiceTemplates]); } } diff --git a/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRenderToSymfonyRector/Fixture/fixture.php.inc b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRenderToSymfonyRector/Fixture/fixture.php.inc new file mode 100644 index 000000000000..13e924726fce --- /dev/null +++ b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRenderToSymfonyRector/Fixture/fixture.php.inc @@ -0,0 +1,27 @@ +render('custom_file'); + } +} + +?> +----- +render('redirect/custom_file.twig'); + } +} + +?> diff --git a/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRenderToSymfonyRector/Fixture/set.inc b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRenderToSymfonyRector/Fixture/set.inc new file mode 100644 index 000000000000..f565987bedad --- /dev/null +++ b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRenderToSymfonyRector/Fixture/set.inc @@ -0,0 +1,27 @@ +set('name', 5); + } +} + +?> +----- +render('homepage/index.twig', ['name' => 5]); + } +} + +?> diff --git a/phpstan.neon b/phpstan.neon index d5d69eaa9b90..76bdd250188d 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -222,3 +222,4 @@ parameters: - '#Cannot cast \(array\)\|string\|true to string#' - '#In method "Rector\\BetterPhpDocParser\\AnnotationReader\\NodeAnnotationReader\:\:createPropertyReflectionFromPropertyNode", caught "Throwable" must be rethrown\. Either catch a more specific exception or add a "throw" clause in the "catch" block to propagate the exception\. More info\: http\://bit\.ly/failloud#' + - '#Method Rector\\CakePHPToSymfony\\Rector\\Template\\TemplateMethodCallManipulator\:\:matchThisRenderMethodCallBareOrInReturn\(\) should return PhpParser\\Node\\Expr\\MethodCall\|null but returns PhpParser\\Node\\Expr#'