From d73a5b5e468d682ddeefe35fce558d04e1384c19 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Tue, 4 Aug 2026 12:24:48 +0200 Subject: [PATCH 1/2] [CodeQuality] Skip type-guarded classes in ControllerMethodInjectionToConstructorRector --- .../non_guarded_controller.php.inc | 44 +++++++++++++++++++ .../skip_type_guarded_controller.php.inc | 18 ++++++++ .../Source/AbstractCustomController.php | 11 +++++ .../TypeGuardedTest.php | 28 ++++++++++++ .../config/type_guarded_rule.php | 11 +++++ ...llerMethodInjectionToConstructorRector.php | 5 +++ 6 files changed, 117 insertions(+) create mode 100644 rules-tests/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector/FixtureTypeGuarded/non_guarded_controller.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector/FixtureTypeGuarded/skip_type_guarded_controller.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector/Source/AbstractCustomController.php create mode 100644 rules-tests/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector/TypeGuardedTest.php create mode 100644 rules-tests/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector/config/type_guarded_rule.php diff --git a/rules-tests/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector/FixtureTypeGuarded/non_guarded_controller.php.inc b/rules-tests/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector/FixtureTypeGuarded/non_guarded_controller.php.inc new file mode 100644 index 000000000..0d5388d33 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector/FixtureTypeGuarded/non_guarded_controller.php.inc @@ -0,0 +1,44 @@ +log('level', 'value'); + } +} + +?> +----- +logger->log('level', 'value'); + } +} + +?> diff --git a/rules-tests/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector/FixtureTypeGuarded/skip_type_guarded_controller.php.inc b/rules-tests/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector/FixtureTypeGuarded/skip_type_guarded_controller.php.inc new file mode 100644 index 000000000..14d7946a1 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector/FixtureTypeGuarded/skip_type_guarded_controller.php.inc @@ -0,0 +1,18 @@ +log('level', 'value'); + } +} diff --git a/rules-tests/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector/Source/AbstractCustomController.php b/rules-tests/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector/Source/AbstractCustomController.php new file mode 100644 index 000000000..24349aaeb --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector/Source/AbstractCustomController.php @@ -0,0 +1,11 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/FixtureTypeGuarded'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/type_guarded_rule.php'; + } +} diff --git a/rules-tests/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector/config/type_guarded_rule.php b/rules-tests/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector/config/type_guarded_rule.php new file mode 100644 index 000000000..fd4d9746a --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector/config/type_guarded_rule.php @@ -0,0 +1,11 @@ +withRules([ControllerMethodInjectionToConstructorRector::class]) + ->withTypeGuardedClasses([AbstractCustomController::class]); diff --git a/rules/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector.php b/rules/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector.php index d45796faf..1ac95d66d 100644 --- a/rules/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector.php +++ b/rules/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector.php @@ -177,6 +177,11 @@ public function refactor(Node $node): ?Node return null; } + // removing action params would break child classes of user-guarded classes + if ($this->parentClassMethodTypeOverrideGuard->isTypeGuardedClass($node)) { + return null; + } + $propertyMetadatas = []; $constructParamVariables = []; From ee3fb3510319a4c2ab51ff08d4083d40b6664249 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Tue, 4 Aug 2026 12:26:09 +0200 Subject: [PATCH 2/2] [CodeQuality] Name autowire method autowire() to stay unique under inheritance --- ...ent_with_private_promoted_property.php.inc | 2 +- ...rent_constructor_multiple_services.php.inc | 2 +- .../parent_with_autowire_method.php.inc | 2 +- ...llerMethodInjectionToConstructorRector.php | 27 ++++++------------- 4 files changed, 11 insertions(+), 22 deletions(-) diff --git a/rules-tests/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector/Fixture/child_of_parent_with_private_promoted_property.php.inc b/rules-tests/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector/Fixture/child_of_parent_with_private_promoted_property.php.inc index a8f935aa6..cd135c182 100644 --- a/rules-tests/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector/Fixture/child_of_parent_with_private_promoted_property.php.inc +++ b/rules-tests/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector/Fixture/child_of_parent_with_private_promoted_property.php.inc @@ -38,7 +38,7 @@ final class ChildOfParentWithPrivatePromotedProperty extends ParentControllerWit $this->logger->log('level', 'value'); } #[\Symfony\Contracts\Service\Attribute\Required] - public function autowire(\Psr\Log\LoggerInterface $logger): void + public function autowireChildOfParentWithPrivatePromotedProperty(\Psr\Log\LoggerInterface $logger): void { $this->logger = $logger; } diff --git a/rules-tests/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector/Fixture/parent_constructor_multiple_services.php.inc b/rules-tests/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector/Fixture/parent_constructor_multiple_services.php.inc index de28db64a..bf34dff0e 100644 --- a/rules-tests/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector/Fixture/parent_constructor_multiple_services.php.inc +++ b/rules-tests/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector/Fixture/parent_constructor_multiple_services.php.inc @@ -41,7 +41,7 @@ final class ParentConstructorMultipleServices extends ParentControllerWithPrivat $this->logger->log('level', $this->translator->trans('value')); } #[\Symfony\Contracts\Service\Attribute\Required] - public function autowire(\Psr\Log\LoggerInterface $logger, \Symfony\Contracts\Translation\TranslatorInterface $translator): void + public function autowireParentConstructorMultipleServices(\Psr\Log\LoggerInterface $logger, \Symfony\Contracts\Translation\TranslatorInterface $translator): void { $this->logger = $logger; $this->translator = $translator; diff --git a/rules-tests/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector/Fixture/parent_with_autowire_method.php.inc b/rules-tests/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector/Fixture/parent_with_autowire_method.php.inc index b0736d5e9..94d0c19aa 100644 --- a/rules-tests/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector/Fixture/parent_with_autowire_method.php.inc +++ b/rules-tests/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector/Fixture/parent_with_autowire_method.php.inc @@ -38,7 +38,7 @@ final class ParentWithAutowireMethod extends ParentControllerWithAutowireMethod $this->logger->log('level', 'value'); } #[\Symfony\Contracts\Service\Attribute\Required] - public function autowireServices(\Psr\Log\LoggerInterface $logger): void + public function autowireParentWithAutowireMethod(\Psr\Log\LoggerInterface $logger): void { $this->logger = $logger; } diff --git a/rules/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector.php b/rules/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector.php index 1ac95d66d..f2f6e226e 100644 --- a/rules/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector.php +++ b/rules/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector.php @@ -53,12 +53,7 @@ final class ControllerMethodInjectionToConstructorRector extends AbstractRector */ private const array COMMON_ENTITY_CONTAINS_SUBNAMESPACES = ["\\Entity\\", "\\Document\\", "\\Model\\"]; - private const string AUTOWIRE_METHOD_NAME = 'autowire'; - - /** - * Used when a parent class already defines autowire(), to avoid overriding it - */ - private const string FALLBACK_AUTOWIRE_METHOD_NAME = 'autowireServices'; + private const string AUTOWIRE_METHOD_NAME_PREFIX = 'autowire'; public function __construct( private readonly ControllerAnalyzer $controllerAnalyzer, @@ -75,7 +70,7 @@ public function __construct( public function getRuleDefinition(): RuleDefinition { return new RuleDefinition( - 'Change Symfony controller method injection to direct constructor dependency, to separate params and services clearly. If a parent class has a constructor, use #[Required] autowire() method instead, to avoid repeating all parent params', + 'Change Symfony controller method injection to direct constructor dependency, to separate params and services clearly. If a parent class has a constructor, use #[Required] autowire() method instead, to avoid repeating all parent params', [ new CodeSample( <<<'CODE_SAMPLE' @@ -145,7 +140,7 @@ public function someAction() } #[Required] - public function autowire(SomeService $someService): void + public function autowireSomeController(SomeService $someService): void { $this->someService = $someService; } @@ -500,20 +495,14 @@ private function addRequiredAutowireClassMethod(Class_ $class, array $propertyMe } } + /** + * Suffix with the short class name, to keep the method unique in case of inheritance + */ private function resolveAutowireMethodName(Class_ $class): string { - $classReflection = $this->reflectionResolver->resolveClassReflection($class); - if (! $classReflection instanceof ClassReflection) { - return self::AUTOWIRE_METHOD_NAME; - } - - foreach ($classReflection->getParents() as $parentClassReflection) { - if ($parentClassReflection->hasNativeMethod(self::AUTOWIRE_METHOD_NAME)) { - return self::FALLBACK_AUTOWIRE_METHOD_NAME; - } - } + $shortClassName = $class->name instanceof Identifier ? $class->name->toString() : ''; - return self::AUTOWIRE_METHOD_NAME; + return self::AUTOWIRE_METHOD_NAME_PREFIX . ucfirst($shortClassName); } private function hasParentConstructor(Class_ $class): bool