diff --git a/rules/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector.php b/rules/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector.php index 7949bd4c2..7252c38e3 100644 --- a/rules/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector.php +++ b/rules/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector.php @@ -13,6 +13,7 @@ use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; use PHPStan\Type\ObjectType; +use Rector\Contract\Rector\ConfigurableRectorInterface; use Rector\NodeManipulator\ClassDependencyManipulator; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\PostRector\ValueObject\PropertyMetadata; @@ -32,13 +33,18 @@ /** * @see \Rector\Symfony\Tests\CodeQuality\Rector\Class_\ControllerMethodInjectionToConstructorRector\ControllerMethodInjectionToConstructorRectorTest */ -final class ControllerMethodInjectionToConstructorRector extends AbstractRector +final class ControllerMethodInjectionToConstructorRector extends AbstractRector implements ConfigurableRectorInterface { /** * @var string[] */ private const COMMON_ENTITY_CONTAINS_SUBNAMESPACES = ["\\Entity", "\\Document", "\\Model"]; + /** + * @var string[] + */ + private array $skipAllowedKnownObjects = []; + public function __construct( private readonly ControllerAnalyzer $controllerAnalyzer, private readonly ControllerMethodAnalyzer $controllerMethodAnalyzer, @@ -159,6 +165,7 @@ public function refactor(Node $node): ?Node Throwable::class, Exception::class, ...$entityClasses, + ...$this->skipAllowedKnownObjects, ] )) { continue; @@ -275,4 +282,9 @@ private function replaceParamUseWithPropertyFetch(ClassMethod $classMethod, arra return new PropertyFetch(new Variable('this'), $propertyName); }); } + + public function configure(array $configuration): void + { + $this->skipAllowedKnownObjects = $configuration; + } } diff --git a/src/Enum/SymfonyAttribute.php b/src/Enum/SymfonyAttribute.php index 8471bfb4b..e7ca9bb19 100644 --- a/src/Enum/SymfonyAttribute.php +++ b/src/Enum/SymfonyAttribute.php @@ -45,4 +45,8 @@ final class SymfonyAttribute * @var string */ public const REQUIRED = 'Symfony\Contracts\Service\Attribute\Required'; + /** + * @var string + */ + public const AS_CONTROLLER = 'Symfony\Component\HttpKernel\Attribute\AsController'; } diff --git a/src/TypeAnalyzer/ControllerAnalyzer.php b/src/TypeAnalyzer/ControllerAnalyzer.php index 4081c83b4..da1b217a6 100644 --- a/src/TypeAnalyzer/ControllerAnalyzer.php +++ b/src/TypeAnalyzer/ControllerAnalyzer.php @@ -5,7 +5,6 @@ namespace Rector\Symfony\TypeAnalyzer; use PhpParser\Node; -use PhpParser\Node\Expr; use PhpParser\Node\Stmt\Class_; use PHPStan\Analyser\Scope; use PHPStan\Reflection\ClassReflection; @@ -13,10 +12,12 @@ use PHPStan\Type\ThisType; use PHPStan\Type\TypeWithClassName; use Rector\NodeTypeResolver\Node\AttributeKey; +use Rector\Php80\NodeAnalyzer\PhpAttributeAnalyzer; use Rector\Reflection\ReflectionResolver; +use Rector\Symfony\Enum\SymfonyAttribute; use Rector\Symfony\Enum\SymfonyClass; -final readonly class ControllerAnalyzer +final class ControllerAnalyzer { public function __construct( private ReflectionResolver $reflectionResolver, @@ -73,6 +74,12 @@ private function isControllerClassReflection(ClassReflection $classReflection): return true; } + foreach ($classReflection->getAttributes() as $attribute) { + if ($attribute->getName() === SymfonyAttribute::AS_CONTROLLER) { + return true; + } + } + return $classReflection->is(SymfonyClass::ABSTRACT_CONTROLLER); } @@ -82,7 +89,6 @@ private function isControllerClass(Class_ $class): bool if (! $classReflection instanceof ClassReflection) { return false; } - return $this->isControllerClassReflection($classReflection); } }