Skip to content

Commit bd79952

Browse files
authored
[code-quality] Skip enum in ControllerMethodInjectionToConstructorRector (#893)
* extract ParamConverterClassesResolver * skip enum in ControllerMethodInjectionToConstructorRector * skip user interface
1 parent 48cf807 commit bd79952

5 files changed

Lines changed: 142 additions & 64 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Symfony\Tests\CodeQuality\Rector\Class_\ControllerMethodInjectionToConstructorRector\Fixture;
6+
7+
use Rector\Symfony\Tests\CodeQuality\Rector\Class_\ControllerMethodInjectionToConstructorRector\Source\SomeEnum;
8+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
9+
use Symfony\Component\Routing\Annotation\Route;
10+
11+
final class SkipEnum extends AbstractController
12+
{
13+
#[Route('/some-action', name: 'some_action')]
14+
public function someAction(SomeEnum $someEnum)
15+
{
16+
}
17+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Symfony\Tests\CodeQuality\Rector\Class_\ControllerMethodInjectionToConstructorRector\Fixture;
6+
7+
use Rector\Symfony\Tests\CodeQuality\Rector\Class_\ControllerMethodInjectionToConstructorRector\Source\SomeEnum;
8+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
9+
use Symfony\Component\Routing\Annotation\Route;
10+
use Symfony\Component\Security\Core\User\UserInterface;
11+
12+
final class SkipUser extends AbstractController
13+
{
14+
#[Route('/some-action', name: 'some_action')]
15+
public function someAction(UserInterface $user)
16+
{
17+
}
18+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace Rector\Symfony\Tests\CodeQuality\Rector\Class_\ControllerMethodInjectionToConstructorRector\Source;
4+
5+
enum SomeEnum
6+
{
7+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Symfony\CodeQuality\NodeAnalyzer;
6+
7+
use PhpParser\Node\Identifier;
8+
use PhpParser\Node\Stmt\ClassMethod;
9+
use Rector\Doctrine\NodeAnalyzer\AttributeFinder;
10+
use Rector\PhpParser\Node\Value\ValueResolver;
11+
use Rector\Symfony\Enum\SensioAttribute;
12+
13+
final class ParamConverterClassesResolver
14+
{
15+
public function __construct(
16+
private AttributeFinder $attributeFinder,
17+
private ValueResolver $valueResolver,
18+
) {
19+
}
20+
21+
/**
22+
* @return string[]
23+
*/
24+
public function resolveEntityClasses(ClassMethod $classMethod): array
25+
{
26+
$entityClasses = [];
27+
28+
$paramConverterAttributes = $this->attributeFinder->findManyByClass(
29+
$classMethod,
30+
SensioAttribute::PARAM_CONVERTER
31+
);
32+
foreach ($paramConverterAttributes as $paramConverterAttribute) {
33+
foreach ($paramConverterAttribute->args as $arg) {
34+
if (! $arg->name instanceof Identifier) {
35+
continue;
36+
}
37+
38+
if ($arg->name->toString() !== 'class') {
39+
continue;
40+
}
41+
42+
$entityClass = $this->valueResolver->getValue($arg->value);
43+
if (! is_string($entityClass)) {
44+
continue;
45+
}
46+
47+
$entityClasses[] = $entityClass;
48+
}
49+
}
50+
51+
return $entityClasses;
52+
}
53+
}

rules/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector.php

Lines changed: 47 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,18 @@
88
use PhpParser\Node\Expr\Closure;
99
use PhpParser\Node\Expr\PropertyFetch;
1010
use PhpParser\Node\Expr\Variable;
11-
use PhpParser\Node\Identifier;
1211
use PhpParser\Node\Name\FullyQualified;
1312
use PhpParser\Node\Stmt\Class_;
1413
use PhpParser\Node\Stmt\ClassMethod;
15-
use Rector\Doctrine\NodeAnalyzer\AttributeFinder;
14+
use PHPStan\Type\ObjectType;
1615
use Rector\NodeManipulator\ClassDependencyManipulator;
1716
use Rector\NodeTypeResolver\Node\AttributeKey;
18-
use Rector\PhpParser\Node\Value\ValueResolver;
1917
use Rector\PostRector\ValueObject\PropertyMetadata;
2018
use Rector\Rector\AbstractRector;
2119
use Rector\StaticTypeMapper\StaticTypeMapper;
2220
use Rector\Symfony\Bridge\NodeAnalyzer\ControllerMethodAnalyzer;
21+
use Rector\Symfony\CodeQuality\NodeAnalyzer\ParamConverterClassesResolver;
2322
use Rector\Symfony\Enum\FosClass;
24-
use Rector\Symfony\Enum\SensioAttribute;
2523
use Rector\Symfony\Enum\SymfonyClass;
2624
use Rector\Symfony\TypeAnalyzer\ControllerAnalyzer;
2725
use Rector\ValueObject\MethodName;
@@ -44,8 +42,7 @@ public function __construct(
4442
private readonly ControllerMethodAnalyzer $controllerMethodAnalyzer,
4543
private readonly ClassDependencyManipulator $classDependencyManipulator,
4644
private readonly StaticTypeMapper $staticTypeMapper,
47-
private readonly AttributeFinder $attributeFinder,
48-
private readonly ValueResolver $valueResolver,
45+
private readonly ParamConverterClassesResolver $paramConverterClassesResolver,
4946
private readonly ParentClassMethodTypeOverrideGuard $parentClassMethodTypeOverrideGuard
5047
) {
5148
}
@@ -126,7 +123,7 @@ public function refactor(Node $node): ?Node
126123
continue;
127124
}
128125

129-
$entityClasses = $this->resolveParamConverterEntityClasses($classMethod);
126+
$entityClasses = $this->paramConverterClassesResolver->resolveEntityClasses($classMethod);
130127

131128
foreach ($classMethod->getParams() as $key => $param) {
132129
// skip scalar and empty values, as not services
@@ -139,12 +136,11 @@ public function refactor(Node $node): ?Node
139136
continue;
140137
}
141138

142-
// request is allowed
143-
if ($this->isNames($param->type, [SymfonyClass::REQUEST, FosClass::PARAM_FETCHER])) {
144-
continue;
145-
}
146-
147-
if ($this->isNames($param->type, $entityClasses)) {
139+
// skip allowed known objectsallowed
140+
if ($this->isNames(
141+
$param->type,
142+
[SymfonyClass::USER_INTERFACE, SymfonyClass::REQUEST, FosClass::PARAM_FETCHER, ...$entityClasses]
143+
)) {
148144
continue;
149145
}
150146

@@ -154,10 +150,15 @@ public function refactor(Node $node): ?Node
154150
}
155151
}
156152

157-
// @todo allow parameter converter
158-
unset($classMethod->params[$key]);
159-
160153
$paramType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($param->type);
154+
155+
if ($paramType instanceof ObjectType) {
156+
if ($paramType->isEnum()->yes()) {
157+
continue;
158+
}
159+
}
160+
161+
unset($classMethod->params[$key]);
161162
$propertyMetadatas[] = new PropertyMetadata($this->getName($param->var), $paramType);
162163
}
163164
}
@@ -182,39 +183,9 @@ public function refactor(Node $node): ?Node
182183
continue;
183184
}
184185

185-
// replace param use with property fetch
186-
$this->traverseNodesWithCallable((array) $classMethod->stmts, function (Node $node) use (
187-
$paramNamesToReplace
188-
): Closure|null|PropertyFetch {
189-
if ($node instanceof Closure) {
190-
foreach ($node->uses as $key => $closureUse) {
191-
if ($this->isNames($closureUse->var, $paramNamesToReplace)) {
192-
unset($node->uses[$key]);
193-
}
194-
}
195-
196-
return $node;
197-
}
198-
199-
if (! $node instanceof Variable) {
200-
return null;
201-
}
202-
203-
if (! $this->isNames($node, $paramNamesToReplace)) {
204-
return null;
205-
}
206-
207-
if ($node->getAttribute(AttributeKey::IS_BEING_ASSIGNED) === true) {
208-
return null;
209-
}
210-
211-
$propertyName = $this->getName($node);
212-
return new PropertyFetch(new Variable('this'), $propertyName);
213-
});
186+
$this->replaceParamUseWithPropertyFetch($classMethod, $paramNamesToReplace);
214187
}
215188

216-
// 2. replace in method bodies
217-
218189
return $node;
219190
}
220191

@@ -232,29 +203,41 @@ private function shouldSkipClassMethod(ClassMethod $classMethod): bool
232203
}
233204

234205
/**
235-
* @return string[]
206+
* @param string[] $paramNamesToReplace
236207
*/
237-
private function resolveParamConverterEntityClasses(ClassMethod $classMethod): array
208+
private function replaceParamUseWithPropertyFetch(ClassMethod $classMethod, array $paramNamesToReplace): void
238209
{
239-
$entityClasses = [];
210+
if ($classMethod->stmts === null) {
211+
return;
212+
}
240213

241-
$paramConverterAttributes = $this->attributeFinder->findManyByClass(
242-
$classMethod,
243-
SensioAttribute::PARAM_CONVERTER
244-
);
245-
foreach ($paramConverterAttributes as $paramConverterAttribute) {
246-
foreach ($paramConverterAttribute->args as $arg) {
247-
if ($arg->name instanceof Identifier && $this->isName($arg->name, 'class')) {
248-
$entityClass = $this->valueResolver->getValue($arg->value);
249-
if (! is_string($entityClass)) {
250-
continue;
214+
$this->traverseNodesWithCallable($classMethod->stmts, function (Node $node) use (
215+
$paramNamesToReplace
216+
): Closure|null|PropertyFetch {
217+
if ($node instanceof Closure) {
218+
foreach ($node->uses as $key => $closureUse) {
219+
if ($this->isNames($closureUse->var, $paramNamesToReplace)) {
220+
unset($node->uses[$key]);
251221
}
252-
253-
$entityClasses[] = $entityClass;
254222
}
223+
224+
return $node;
225+
}
226+
227+
if (! $node instanceof Variable) {
228+
return null;
229+
}
230+
231+
if (! $this->isNames($node, $paramNamesToReplace)) {
232+
return null;
233+
}
234+
235+
if ($node->getAttribute(AttributeKey::IS_BEING_ASSIGNED) === true) {
236+
return null;
255237
}
256-
}
257238

258-
return $entityClasses;
239+
$propertyName = $this->getName($node);
240+
return new PropertyFetch(new Variable('this'), $propertyName);
241+
});
259242
}
260243
}

0 commit comments

Comments
 (0)