From 3701e9d5148b35081c5d9f1b194e208825b86877 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sat, 25 Oct 2025 13:43:31 +0200 Subject: [PATCH 1/2] [config] Add RemoveConstructorAutowireServiceRector --- config/sets/symfony/configs.php | 2 + .../Fixture/avoid_different_type.php.inc | 20 ++ .../Fixture/skip_no_defaults_autowire.php.inc | 17 ++ .../Fixture/skip_no_service_type.php.inc | 19 ++ .../Fixture/some_class.php.inc | 42 ++++ ...veConstructorAutowireServiceRectorTest.php | 28 +++ .../Source/AnotherClassWithoutConstructor.php | 11 + .../Source/DifferentType.php | 7 + .../Source/PassedAsDependency.php | 7 + .../config/configured_rule.php | 10 + ...RemoveConstructorAutowireServiceRector.php | 212 ++++++++++++++++++ .../ServiceArgsToServiceNamedArgRector.php | 12 +- ...rviceTagsToDefaultsAutoconfigureRector.php | 2 +- .../ConstructorReflectionTypesResolver.php | 58 +++++ .../FuncCall/ReplaceServiceArgumentRector.php | 3 +- src/Enum/SymfonyClass.php | 10 + src/Enum/SymfonyFunction.php | 13 ++ .../SymfonyPhpClosureDetector.php | 7 +- 18 files changed, 467 insertions(+), 13 deletions(-) create mode 100644 rules-tests/Configs/Rector/Closure/RemoveConstructorAutowireServiceRector/Fixture/avoid_different_type.php.inc create mode 100644 rules-tests/Configs/Rector/Closure/RemoveConstructorAutowireServiceRector/Fixture/skip_no_defaults_autowire.php.inc create mode 100644 rules-tests/Configs/Rector/Closure/RemoveConstructorAutowireServiceRector/Fixture/skip_no_service_type.php.inc create mode 100644 rules-tests/Configs/Rector/Closure/RemoveConstructorAutowireServiceRector/Fixture/some_class.php.inc create mode 100644 rules-tests/Configs/Rector/Closure/RemoveConstructorAutowireServiceRector/RemoveConstructorAutowireServiceRectorTest.php create mode 100644 rules-tests/Configs/Rector/Closure/RemoveConstructorAutowireServiceRector/Source/AnotherClassWithoutConstructor.php create mode 100644 rules-tests/Configs/Rector/Closure/RemoveConstructorAutowireServiceRector/Source/DifferentType.php create mode 100644 rules-tests/Configs/Rector/Closure/RemoveConstructorAutowireServiceRector/Source/PassedAsDependency.php create mode 100644 rules-tests/Configs/Rector/Closure/RemoveConstructorAutowireServiceRector/config/configured_rule.php create mode 100644 rules/Configs/Rector/Closure/RemoveConstructorAutowireServiceRector.php create mode 100644 rules/Configs/Rector/Reflection/ConstructorReflectionTypesResolver.php create mode 100644 src/Enum/SymfonyFunction.php diff --git a/config/sets/symfony/configs.php b/config/sets/symfony/configs.php index a802c8310..656ab3506 100644 --- a/config/sets/symfony/configs.php +++ b/config/sets/symfony/configs.php @@ -4,6 +4,7 @@ use Rector\Config\RectorConfig; use Rector\Symfony\Configs\Rector\Closure\MergeServiceNameTypeRector; +use Rector\Symfony\Configs\Rector\Closure\RemoveConstructorAutowireServiceRector; use Rector\Symfony\Configs\Rector\Closure\ServiceArgsToServiceNamedArgRector; use Rector\Symfony\Configs\Rector\Closure\ServiceSetStringNameToClassNameRector; use Rector\Symfony\Configs\Rector\Closure\ServiceSettersToSettersAutodiscoveryRector; @@ -16,5 +17,6 @@ ServiceSetStringNameToClassNameRector::class, ServiceSettersToSettersAutodiscoveryRector::class, ServiceTagsToDefaultsAutoconfigureRector::class, + RemoveConstructorAutowireServiceRector::class, ]); }; diff --git a/rules-tests/Configs/Rector/Closure/RemoveConstructorAutowireServiceRector/Fixture/avoid_different_type.php.inc b/rules-tests/Configs/Rector/Closure/RemoveConstructorAutowireServiceRector/Fixture/avoid_different_type.php.inc new file mode 100644 index 000000000..5fd774cc2 --- /dev/null +++ b/rules-tests/Configs/Rector/Closure/RemoveConstructorAutowireServiceRector/Fixture/avoid_different_type.php.inc @@ -0,0 +1,20 @@ +services(); + + $services->defaults()->autowire(); + + $services->set(AnotherClassWithoutConstructor::class) + ->arg('$passedAsDependency', service(DifferentType::class)); +}; diff --git a/rules-tests/Configs/Rector/Closure/RemoveConstructorAutowireServiceRector/Fixture/skip_no_defaults_autowire.php.inc b/rules-tests/Configs/Rector/Closure/RemoveConstructorAutowireServiceRector/Fixture/skip_no_defaults_autowire.php.inc new file mode 100644 index 000000000..2bf552af5 --- /dev/null +++ b/rules-tests/Configs/Rector/Closure/RemoveConstructorAutowireServiceRector/Fixture/skip_no_defaults_autowire.php.inc @@ -0,0 +1,17 @@ +services(); + + $services->set(AnotherClassWithoutConstructor::class) + ->arg('$passedAsDependency', service(PassedAsDependency::class)); +}; diff --git a/rules-tests/Configs/Rector/Closure/RemoveConstructorAutowireServiceRector/Fixture/skip_no_service_type.php.inc b/rules-tests/Configs/Rector/Closure/RemoveConstructorAutowireServiceRector/Fixture/skip_no_service_type.php.inc new file mode 100644 index 000000000..6bf741044 --- /dev/null +++ b/rules-tests/Configs/Rector/Closure/RemoveConstructorAutowireServiceRector/Fixture/skip_no_service_type.php.inc @@ -0,0 +1,19 @@ +services(); + + $services->defaults()->autowire(); + + $services->set(AnotherClassWithoutConstructor::class) + ->arg('$passedAsDependency', 123); +}; diff --git a/rules-tests/Configs/Rector/Closure/RemoveConstructorAutowireServiceRector/Fixture/some_class.php.inc b/rules-tests/Configs/Rector/Closure/RemoveConstructorAutowireServiceRector/Fixture/some_class.php.inc new file mode 100644 index 000000000..97bb65ede --- /dev/null +++ b/rules-tests/Configs/Rector/Closure/RemoveConstructorAutowireServiceRector/Fixture/some_class.php.inc @@ -0,0 +1,42 @@ +services(); + + $services->defaults()->autowire(); + + $services->set(AnotherClassWithoutConstructor::class) + ->arg('$passedAsDependency', service(PassedAsDependency::class)); +}; + +?> +----- +services(); + + $services->defaults()->autowire(); + + $services->set(AnotherClassWithoutConstructor::class); +}; + +?> diff --git a/rules-tests/Configs/Rector/Closure/RemoveConstructorAutowireServiceRector/RemoveConstructorAutowireServiceRectorTest.php b/rules-tests/Configs/Rector/Closure/RemoveConstructorAutowireServiceRector/RemoveConstructorAutowireServiceRectorTest.php new file mode 100644 index 000000000..96540a638 --- /dev/null +++ b/rules-tests/Configs/Rector/Closure/RemoveConstructorAutowireServiceRector/RemoveConstructorAutowireServiceRectorTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/rules-tests/Configs/Rector/Closure/RemoveConstructorAutowireServiceRector/Source/AnotherClassWithoutConstructor.php b/rules-tests/Configs/Rector/Closure/RemoveConstructorAutowireServiceRector/Source/AnotherClassWithoutConstructor.php new file mode 100644 index 000000000..350372e40 --- /dev/null +++ b/rules-tests/Configs/Rector/Closure/RemoveConstructorAutowireServiceRector/Source/AnotherClassWithoutConstructor.php @@ -0,0 +1,11 @@ +rule(RemoveConstructorAutowireServiceRector::class); +}; diff --git a/rules/Configs/Rector/Closure/RemoveConstructorAutowireServiceRector.php b/rules/Configs/Rector/Closure/RemoveConstructorAutowireServiceRector.php new file mode 100644 index 000000000..7f8372a7d --- /dev/null +++ b/rules/Configs/Rector/Closure/RemoveConstructorAutowireServiceRector.php @@ -0,0 +1,212 @@ +services(); + + $services->defaults() + ->autowire(); + + $services->set(\App\SomeClass::class) + ->arg('$someService', ref(\App\SomeService::class)); +}; + +final class SomeClass +{ + public function __construct(private SomeService $someService) + { + } +} +CODE_SAMPLE + + , + <<<'CODE_SAMPLE' +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; + +return static function (ContainerConfigurator $containerConfigurator): void { + $services = $containerConfigurator->services(); + + $services->defaults() + ->autowire(); + + $services->set(\App\SomeClass::class); +}; + +final class SomeClass +{ + public function __construct(private SomeService $someService) + { + } +} +CODE_SAMPLE + ), + + ] + ); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [Closure::class]; + } + + /** + * @param Closure $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->symfonyPhpClosureDetector->detect($node)) { + return null; + } + + // services must be autowired, so we can remove explicit service passing + if (! $this->symfonyPhpClosureDetector->hasDefaultsConfigured($node, 'autowire')) { + return null; + } + + $hasChanged = false; + + $this->traverseNodesWithCallable($node->getStmts(), function (Node $node) use (&$hasChanged): ?Expr { + if (! $node instanceof MethodCall) { + return null; + } + + if (! $this->isName($node->name, 'arg')) { + return null; + } + + $serviceClass = $this->matchSetServicesClass($node); + if (! is_string($serviceClass)) { + return null; + } + + $constructorTypesByParameterName = $this->constructorReflectionTypesResolver->resolve($serviceClass); + if ($constructorTypesByParameterName === null) { + return null; + } + + $argName = $node->getArgs()[0] + ->value; + $serviceArgExpr = $node->getArgs()[1] + ->value; + + if (! $argName instanceof String_) { + return null; + } + + $bareParameterName = ltrim($argName->value, '$'); + $knownParameterType = $constructorTypesByParameterName[$bareParameterName] ?? null; + if (! $knownParameterType instanceof ObjectType) { + return null; + } + + if (! $this->isParameterTypeMatchingPassedArgExprClass($serviceArgExpr, $knownParameterType)) { + return null; + } + + $hasChanged = true; + + return $node->var; + }); + + if ($hasChanged) { + return $node; + } + + return null; + } + + private function isSetServices(MethodCall $methodCall): bool + { + if (! $this->isName($methodCall->name, 'set')) { + return false; + } + + return $this->isObjectType($methodCall->var, new ObjectType(SymfonyClass::SERVICES_CONFIGURATOR)); + } + + /** + * $services->set(); + */ + private function matchSetServicesClass(MethodCall $methodCall): ?string + { + while ($methodCall instanceof MethodCall) { + if ($this->isSetServices($methodCall)) { + break; + } + + $methodCall = $methodCall->var; + } + + /** @var MethodCall $methodCall */ + $firstArg = $methodCall->getArgs()[0]; + if (! $firstArg->value instanceof ClassConstFetch) { + return null; + } + + return $this->valueResolver->getValue($firstArg->value); + } + + private function isParameterTypeMatchingPassedArgExprClass( + Expr $serviceArgExpr, + ObjectType $objectType + ): bool { + if (! $serviceArgExpr instanceof FuncCall) { + return false; + } + + if (! $this->isName($serviceArgExpr->name, SymfonyFunction::SERVICE)) { + return false; + } + + $dependencyServiceExpr = $serviceArgExpr->getArgs()[0] + ->value; + $dependencyService = $this->valueResolver->getValue($dependencyServiceExpr); + + return $dependencyService === $objectType->getClassName(); + } +} diff --git a/rules/Configs/Rector/Closure/ServiceArgsToServiceNamedArgRector.php b/rules/Configs/Rector/Closure/ServiceArgsToServiceNamedArgRector.php index 1e0436518..83a19577b 100644 --- a/rules/Configs/Rector/Closure/ServiceArgsToServiceNamedArgRector.php +++ b/rules/Configs/Rector/Closure/ServiceArgsToServiceNamedArgRector.php @@ -14,11 +14,11 @@ use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Scalar\String_; use PHPStan\Reflection\ParametersAcceptorSelector; -use PHPStan\Reflection\Php\PhpParameterReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\ObjectType; use Rector\PhpParser\Node\Value\ValueResolver; use Rector\Rector\AbstractRector; +use Rector\Symfony\Enum\SymfonyClass; use Rector\Symfony\NodeAnalyzer\SymfonyPhpClosureDetector; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -162,9 +162,8 @@ private function resolveConstructorParameterNames(string $serviceClass): array $extendedMethodReflection->getVariants() ); - foreach ($extendedParametersAcceptor->getParameters() as $parameterReflectionWithPhpDoc) { - /** @var PhpParameterReflection $parameterReflectionWithPhpDoc */ - $constructorParameterNames[] = '$' . $parameterReflectionWithPhpDoc->getName(); + foreach ($extendedParametersAcceptor->getParameters() as $extendedParameterReflection) { + $constructorParameterNames[] = '$' . $extendedParameterReflection->getName(); } return $constructorParameterNames; @@ -176,10 +175,7 @@ private function isServiceArgsMethodCall(MethodCall $methodCall): bool return false; } - return $this->isObjectType( - $methodCall->var, - new ObjectType('Symfony\Component\DependencyInjection\Loader\Configurator\ServiceConfigurator') - ); + return $this->isObjectType($methodCall->var, new ObjectType(SymfonyClass::SERVICE_CONFIGURATOR)); } private function createArgMethodCall( diff --git a/rules/Configs/Rector/Closure/ServiceTagsToDefaultsAutoconfigureRector.php b/rules/Configs/Rector/Closure/ServiceTagsToDefaultsAutoconfigureRector.php index dfe40d8f9..6fadf1262 100644 --- a/rules/Configs/Rector/Closure/ServiceTagsToDefaultsAutoconfigureRector.php +++ b/rules/Configs/Rector/Closure/ServiceTagsToDefaultsAutoconfigureRector.php @@ -94,7 +94,7 @@ public function refactor(Node $node): ?Node return null; } - $hasDefaultsAutoconfigure = $this->symfonyPhpClosureDetector->hasDefaultsAutoconfigure($node); + $hasDefaultsAutoconfigure = $this->symfonyPhpClosureDetector->hasDefaultsConfigured($node, 'autoconfigure'); $hasChanged = false; diff --git a/rules/Configs/Rector/Reflection/ConstructorReflectionTypesResolver.php b/rules/Configs/Rector/Reflection/ConstructorReflectionTypesResolver.php new file mode 100644 index 000000000..b4831ad45 --- /dev/null +++ b/rules/Configs/Rector/Reflection/ConstructorReflectionTypesResolver.php @@ -0,0 +1,58 @@ +|null + */ + public function resolve(string $serviceClass): ?array + { + if (! $this->reflectionProvider->hasClass($serviceClass)) { + return null; + } + + $constructorReflection = $this->reflectionResolver->resolveMethodReflection( + $serviceClass, + MethodName::CONSTRUCT, + null + ); + + if (! $constructorReflection instanceof MethodReflection) { + return null; + } + + return $this->resolveMethodReflectionParameterTypes($constructorReflection); + } + + /** + * @return array + */ + private function resolveMethodReflectionParameterTypes(MethodReflection $methodReflection): array + { + $extendedParametersAcceptor = ParametersAcceptorSelector::combineAcceptors($methodReflection->getVariants()); + $constructorTypes = []; + + foreach ($extendedParametersAcceptor->getParameters() as $extendedParameterReflection) { + $constructorTypes[$extendedParameterReflection->getName()] = $extendedParameterReflection->getType(); + } + + return $constructorTypes; + } +} diff --git a/rules/Symfony60/Rector/FuncCall/ReplaceServiceArgumentRector.php b/rules/Symfony60/Rector/FuncCall/ReplaceServiceArgumentRector.php index c4fc7a367..8200e2f95 100644 --- a/rules/Symfony60/Rector/FuncCall/ReplaceServiceArgumentRector.php +++ b/rules/Symfony60/Rector/FuncCall/ReplaceServiceArgumentRector.php @@ -12,6 +12,7 @@ use Rector\PhpParser\Node\Value\ValueResolver; use Rector\Rector\AbstractRector; use Rector\Symfony\ValueObject\ReplaceServiceArgument; +use Symplify\PHPStanRules\Enum\SymfonyFunctionName; use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; use Webmozart\Assert\Assert; @@ -68,7 +69,7 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?FuncCall { - if (! $this->isName($node, 'Symfony\Component\DependencyInjection\Loader\Configurator\service')) { + if (! $this->isName($node->name, SymfonyFunctionName::SERVICE)) { return null; } diff --git a/src/Enum/SymfonyClass.php b/src/Enum/SymfonyClass.php index 32ddd141a..50bbcbbe5 100644 --- a/src/Enum/SymfonyClass.php +++ b/src/Enum/SymfonyClass.php @@ -66,6 +66,11 @@ final class SymfonyClass */ public const TRANSLATOR_INTERFACE = 'Symfony\Contracts\Translation\TranslatorInterface'; + /** + * @var string + */ + public const SERVICE_CONFIGURATOR = 'Symfony\Component\DependencyInjection\Loader\Configurator\ServiceConfigurator'; + /** * @var string */ @@ -160,4 +165,9 @@ final class SymfonyClass * @var string */ public const SYMFONY_VALIDATOR_CONSTRAINTS_COLLECTION = 'Symfony\Component\Validator\Constraints\Collection'; + + /** + * @var string + */ + public const SERVICES_CONFIGURATOR = 'Symfony\Component\DependencyInjection\Loader\Configurator\ServicesConfigurator'; } diff --git a/src/Enum/SymfonyFunction.php b/src/Enum/SymfonyFunction.php new file mode 100644 index 000000000..a38c059bd --- /dev/null +++ b/src/Enum/SymfonyFunction.php @@ -0,0 +1,13 @@ +nodeNameResolver->isName($firstParam->type, SymfonyClass::CONTAINER_CONFIGURATOR); } - public function hasDefaultsAutoconfigure(Closure $closure): bool + public function hasDefaultsConfigured(Closure $closure, string $desiredMethodName): bool { $hasDefaultsAutoconfigure = false; // has defaults autoconfigure? $this->simpleCallableNodeTraverser->traverseNodesWithCallable($closure, function (Node $node) use ( - &$hasDefaultsAutoconfigure + &$hasDefaultsAutoconfigure, + $desiredMethodName ): ?int { if (! $node instanceof MethodCall) { return null; } - if (! $this->nodeNameResolver->isName($node->name, 'autoconfigure')) { + if (! $this->nodeNameResolver->isName($node->name, $desiredMethodName)) { return null; } From 3587b7ccebec819837edc08c93dc37d8ff55db9f Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sat, 25 Oct 2025 16:37:43 +0200 Subject: [PATCH 2/2] decouple class to strings --- composer.json | 11 ++++++----- phpstan.neon | 12 ++++++------ .../ArgumentValueResolverToValueResolverRector.php | 9 ++++----- src/Enum/SymfonyClass.php | 10 ++++++++++ 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/composer.json b/composer.json index 3c3d03478..a423b1f93 100644 --- a/composer.json +++ b/composer.json @@ -12,20 +12,21 @@ "phpstan/extension-installer": "^1.4", "phpstan/phpstan": "^2.1.31", "phpstan/phpstan-webmozart-assert": "^2.0", - "phpunit/phpunit": "^11.4", + "phpunit/phpunit": "^11.5", + "rector/jack": "^0.2.9", "rector/rector-src": "dev-main", - "rector/type-perfect": "^2.0", + "rector/type-perfect": "^2.1", "symfony/config": "^6.4", "symfony/dependency-injection": "^6.4", - "symfony/http-kernel": "^6.4", + "symfony/http-kernel": "^7.0", "symfony/routing": "^6.4", "symfony/security-core": "^6.4", "symfony/security-http": "^6.4", "symfony/validator": "^6.4", "symfony/web-link": "^6.4", "symplify/phpstan-extensions": "^12.0", - "symplify/phpstan-rules": "^14.6", - "symplify/vendor-patches": "^11.3", + "symplify/phpstan-rules": "^14.8", + "symplify/vendor-patches": "^11.5", "tomasvotruba/class-leak": "^2.0", "tomasvotruba/type-coverage": "^2.0", "tomasvotruba/unused-public": "^2.0", diff --git a/phpstan.neon b/phpstan.neon index 878dd3e38..db3940abb 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -39,16 +39,10 @@ parameters: # pointless check - '#expects class-string, string given#' - - '#Calling PHPStan\\Reflection\\Php\\PhpParameterReflection\:\:getName\(\) is not covered by backward compatibility promise\. The method might change in a minor PHPStan version#' - # false positive - '#but class PhpParser\\Node\\Stmt\\Expression is not generic#' - '#Access to an undefined property Rector\\Contract\\PhpParser\\Node\\StmtsAwareInterface\:\:\$stmts#' - # false positive - - '#Parameters should have "PhpParser\\Node\\Expr\\Closure" types as the only types passed to this method#' - - '#Parameter 1 should use "PHPStan\\BetterReflection\\Reflection\\Adapter\\ReflectionMethod" type as the only type passed to this method#' - # more advanced usage, but not always working # see https://github.com/rectorphp/rector-src/actions/runs/11798721617/job/32865546672?pr=6422#step:5:110 - '#Doing instanceof PHPStan\\Type\\.+ is error\-prone and deprecated#' @@ -59,3 +53,9 @@ parameters: # avoid notice on run on php 8.3 - identifier: typeCoverage.constantTypeCoverage + + # false postitive + - + identifier: typePerfect.narrowPublicClassMethodParamType + path: src/NodeAnalyzer/SymfonyPhpClosureDetector.php + - '#Parameter 1 should use "PHPStan\\BetterReflection\\Reflection\\Adapter\\ReflectionMethod" type as the only type passed to this method#' diff --git a/rules/Symfony62/Rector/ClassMethod/ClassMethod/ArgumentValueResolverToValueResolverRector.php b/rules/Symfony62/Rector/ClassMethod/ClassMethod/ArgumentValueResolverToValueResolverRector.php index d23773209..b333a7459 100644 --- a/rules/Symfony62/Rector/ClassMethod/ClassMethod/ArgumentValueResolverToValueResolverRector.php +++ b/rules/Symfony62/Rector/ClassMethod/ClassMethod/ArgumentValueResolverToValueResolverRector.php @@ -17,8 +17,7 @@ use PhpParser\Node\Stmt\If_; use PhpParser\Node\Stmt\Return_; use Rector\Rector\AbstractRector; -use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; -use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; +use Rector\Symfony\Enum\SymfonyClass; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -63,7 +62,7 @@ public function refactor(Node $node): ?Node public function getRuleDefinition(): RuleDefinition { return new RuleDefinition( - 'Replaces ArgumentValueResolverInterface by ValueResolverInterface', + 'Replaces ArgumentValueResolverInterface by ValueResolverInterface with supports logic moved to resolve() method', [ new CodeSample( <<<'CODE_SAMPLE' @@ -100,8 +99,8 @@ private function shouldRefactorClass(Class_ $class): bool { // Check if the class implements ArgumentValueResolverInterface foreach ($class->implements as $key => $interface) { - if ($interface->toString() === ArgumentValueResolverInterface::class) { - $class->implements[$key] = new FullyQualified(ValueResolverInterface::class); + if ($interface->toString() === SymfonyClass::ARGUMENT_RESOLVER_INTERFACE) { + $class->implements[$key] = new FullyQualified(SymfonyClass::VALUE_RESOLVER_INTERFACE); return true; } } diff --git a/src/Enum/SymfonyClass.php b/src/Enum/SymfonyClass.php index 50bbcbbe5..d47860b78 100644 --- a/src/Enum/SymfonyClass.php +++ b/src/Enum/SymfonyClass.php @@ -170,4 +170,14 @@ final class SymfonyClass * @var string */ public const SERVICES_CONFIGURATOR = 'Symfony\Component\DependencyInjection\Loader\Configurator\ServicesConfigurator'; + + /** + * @var string + */ + public const ARGUMENT_RESOLVER_INTERFACE = 'Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface'; + + /** + * @var string + */ + public const VALUE_RESOLVER_INTERFACE = 'Symfony\Component\HttpKernel\Controller\ValueResolverInterface'; }