diff --git a/composer.json b/composer.json
index c88bd0e32861..3875e40457be 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",
@@ -188,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
new file mode 100644
index 000000000000..462974897a24
--- /dev/null
+++ b/config/set/cakephp-to-symfony/cakephp-24-to-symfony-50.yaml
@@ -0,0 +1,7 @@
+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
+ Rector\CakePHPToSymfony\Rector\ClassMethod\CakePHPControllerRedirectToSymfonyRector: null
+ Rector\CakePHPToSymfony\Rector\ClassMethod\CakePHPControllerRenderToSymfonyRector: null
diff --git a/docs/AllRectorsOverview.md b/docs/AllRectorsOverview.md
index d92366c482d7..5d02b4da380e 100644
--- a/docs/AllRectorsOverview.md
+++ b/docs/AllRectorsOverview.md
@@ -1,4 +1,4 @@
-# All 432 Rectors Overview
+# All 438 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,144 @@ 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;
+ }
+ }
+```
+
+
+
+### `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`
+
+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.'));
+ }
+ }
+```
+
+
+
+### `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');
+ }
+ }
+```
+
+
+
+### `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`
+
+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 +8863,7 @@ services:
Rector\Rector\Visibility\ChangeMethodVisibilityRector:
$methodToVisibilityByClass:
FrameworkClass:
- someMethod: protected
+ someMethod: protected
```
↓
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/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
new file mode 100644
index 000000000000..b50ec8425eb3
--- /dev/null
+++ b/packages/CakePHPToSymfony/src/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector.php
@@ -0,0 +1,80 @@
+view
+ *
+ * @see \Rector\CakePHPToSymfony\Tests\Rector\ClassMethod\CakePHPControllerActionToSymfonyControllerActionRector\CakePHPControllerActionToSymfonyControllerActionRectorTest
+ */
+final class CakePHPControllerActionToSymfonyControllerActionRector extends AbstractCakePHPRector
+{
+ 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;
+ }
+}
+PHP
+,
+ <<<'PHP'
+use Symfony\Component\HttpFoundation\Response;
+
+class HomepageController extends \AppController
+{
+ public function index(): Response
+ {
+ $value = 5;
+ }
+}
+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 (! $node->isPublic()) {
+ return null;
+ }
+
+ $node->returnType = new FullyQualified('Symfony\Component\HttpFoundation\Response');
+
+ return $node;
+ }
+}
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/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/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
new file mode 100644
index 000000000000..d12cab8bdd53
--- /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->isInCakePHPController($node)) {
+ 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/src/Rector/Class_/CakePHPControllerToSymfonyControllerRector.php b/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPControllerToSymfonyControllerRector.php
new file mode 100644
index 000000000000..0b748a1cc254
--- /dev/null
+++ b/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPControllerToSymfonyControllerRector.php
@@ -0,0 +1,73 @@
+isInCakePHPController($node)) {
+ return null;
+ }
+
+ $node->extends = new FullyQualified('Symfony\Bundle\FrameworkBundle\Controller\AbstractController');
+
+ return $node;
+ }
+}
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
new file mode 100644
index 000000000000..52ce1962fc73
--- /dev/null
+++ b/packages/CakePHPToSymfony/src/Rector/TemplatePathResolver.php
@@ -0,0 +1,122 @@
+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) {
+ $viewPropertyValue = Strings::lower($viewPropertyValue);
+ return $viewPropertyValue . '.twig';
+ }
+
+ $classAndMethodValue = $this->resolveFromClassAndMethod($classMethod);
+
+ 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;
+
+ $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 Strings::lower($setViewValue);
+ }
+
+ return null;
+ }
+
+ private function resolveFromClassAndMethod(ClassMethod $classMethod): string
+ {
+ $shortClassName = $this->resolveClassNameTemplatePart($classMethod);
+ $methodName = $classMethod->getAttribute(AttributeKey::METHOD_NAME);
+
+ return $shortClassName . '/' . $methodName;
+ }
+}
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/fixture.php.inc b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/Fixture/fixture.php.inc
new file mode 100644
index 000000000000..f0ac934fabb6
--- /dev/null
+++ b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerActionToSymfonyControllerActionRector/Fixture/fixture.php.inc
@@ -0,0 +1,27 @@
+
+-----
+
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/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/CakePHPControllerRenderToSymfonyRector/Fixture/changed_view_property.php.inc b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRenderToSymfonyRector/Fixture/changed_view_property.php.inc
new file mode 100644
index 000000000000..5740ce2772a6
--- /dev/null
+++ b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRenderToSymfonyRector/Fixture/changed_view_property.php.inc
@@ -0,0 +1,27 @@
+view = 'Controller/this_value';
+ }
+}
+
+?>
+-----
+render('controller/this_value.twig');
+ }
+}
+
+?>
diff --git a/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRenderToSymfonyRector/Fixture/compact_too.php.inc b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRenderToSymfonyRector/Fixture/compact_too.php.inc
new file mode 100644
index 000000000000..1c635cddd030
--- /dev/null
+++ b/packages/CakePHPToSymfony/tests/Rector/ClassMethod/CakePHPControllerRenderToSymfonyRector/Fixture/compact_too.php.inc
@@ -0,0 +1,27 @@
+set(compact('clientId', 'states', 'invoiceTemplates'));
+ }
+}
+
+?>
+-----
+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/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 @@
+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.'));
+ }
+}
+
+?>
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..76c358c19dab
--- /dev/null
+++ b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerToSymfonyControllerRector/CakePHPControllerToSymfonyControllerRectorTest.php
@@ -0,0 +1,30 @@
+doTestFile($file);
+ }
+
+ public function provideDataForTest(): Iterator
+ {
+ return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
+ }
+
+ protected function getRectorClass(): string
+ {
+ 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
new file mode 100644
index 000000000000..7562300b7395
--- /dev/null
+++ b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPControllerToSymfonyControllerRector/Fixture/fixture.php.inc
@@ -0,0 +1,25 @@
+
+-----
+
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);
}
}
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/phpstan.neon b/phpstan.neon
index 59ef8f43c635..76bdd250188d 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -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
@@ -171,14 +158,11 @@ 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
- '#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