From 100bc1511f9af89b5749e68594e949c777c181a8 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Thu, 11 Dec 2025 23:54:20 +0100 Subject: [PATCH] create ClassMethod directly in InvokableCommandInputAttributeRector, to keep newline per attribute --- rector.php | 2 +- .../option_with_array_type.php.inc | 9 +- .../Fixture/name_from_constant.php.inc | 1 - .../Fixture/name_with_hyphen.php.inc | 9 +- .../Fixture/option_name_constant.php.inc | 13 ++- .../Fixture/some_command.php.inc | 11 +-- .../some_command_with_method_chaining.php.inc | 11 +-- .../some_command_with_set_help.php.inc | 11 +-- .../Fixture/with_optional_argument.php.inc | 11 +-- .../ParamConverterClassesResolver.php | 2 +- ...llerMethodInjectionToConstructorRector.php | 6 +- .../InvokableCommandInputAttributeRector.php | 89 ++++++++++--------- 12 files changed, 93 insertions(+), 82 deletions(-) diff --git a/rector.php b/rector.php index 488b75034..91c897172 100644 --- a/rector.php +++ b/rector.php @@ -23,7 +23,7 @@ '*/Source/*', '*/Source*/*', '*/tests/*/Fixture*/Expected/*', - StringClassNameToClassConstantRector::class => [__DIR__ . '/config'], + StringClassNameToClassConstantRector::class => [__DIR__ . '/config', __DIR__ . '/src/Enum'], UseClassKeywordForClassNameResolutionRector::class => [__DIR__ . '/config'], RenameForeachValueVariableToMatchMethodCallReturnTypeRector::class => [ diff --git a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/DefaultValue/option_with_array_type.php.inc b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/DefaultValue/option_with_array_type.php.inc index 13297fc55..9ce6f0c21 100644 --- a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/DefaultValue/option_with_array_type.php.inc +++ b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/DefaultValue/option_with_array_type.php.inc @@ -42,9 +42,12 @@ use Symfony\Component\Console\Output\OutputInterface; )] class OptionWithOptionalValue { - public function __invoke(#[\Symfony\Component\Console\Attribute\Option(name: 'some-array', mode: InputOption::VALUE_IS_ARRAY)] - array $someArray = ['third value'], #[\Symfony\Component\Console\Attribute\Option(name: 'no-default-array', mode: InputOption::VALUE_IS_ARRAY)] - array $noDefaultArray): int + public function __invoke( + #[\Symfony\Component\Console\Attribute\Option(name: 'some-array', mode: InputOption::VALUE_IS_ARRAY)] + array $someArray = ['third value'], + #[\Symfony\Component\Console\Attribute\Option(name: 'no-default-array', mode: InputOption::VALUE_IS_ARRAY)] + array $noDefaultArray + ): int { } } diff --git a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/name_from_constant.php.inc b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/name_from_constant.php.inc index e15147451..98b3f6bd8 100644 --- a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/name_from_constant.php.inc +++ b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/name_from_constant.php.inc @@ -51,7 +51,6 @@ use Symfony\Component\Console\Output\OutputInterface; class NameFromConstant { private const string ARGUMENT_NAME = 'name'; - public function __invoke( #[\Symfony\Component\Console\Attribute\Argument(name: self::ARGUMENT_NAME, description: 'The name of the person to greet.')] ?string $name, diff --git a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/name_with_hyphen.php.inc b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/name_with_hyphen.php.inc index bb9577376..d7f728d4a 100644 --- a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/name_with_hyphen.php.inc +++ b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/name_with_hyphen.php.inc @@ -43,9 +43,12 @@ use Symfony\Component\Console\Output\OutputInterface; )] class NameWithHyphen { - public function __invoke(#[\Symfony\Component\Console\Attribute\Argument(name: 'argument-with-hyphen', description: 'Argument description')] - ?string $argumentWithHyphen, #[\Symfony\Component\Console\Attribute\Option(name: 'option-with-hyphen')] - $optionWithHyphen): int + public function __invoke( + #[\Symfony\Component\Console\Attribute\Argument(name: 'argument-with-hyphen', description: 'Argument description')] + ?string $argumentWithHyphen, + #[\Symfony\Component\Console\Attribute\Option(name: 'option-with-hyphen')] + $optionWithHyphen + ): int { $argument = $argument_with_hyphen; $option = $option_with_hyphen; diff --git a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/option_name_constant.php.inc b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/option_name_constant.php.inc index 3c1ca26fd..37c154506 100644 --- a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/option_name_constant.php.inc +++ b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/option_name_constant.php.inc @@ -53,17 +53,16 @@ final class OptionNameConstant private const ARGUMENT_NAME = 'some-argument'; private const OPTION_NAME = 'some-option'; - - public function __invoke(#[\Symfony\Component\Console\Attribute\Argument(name: self::ARGUMENT_NAME)] - ?string $someArgument, #[\Symfony\Component\Console\Attribute\Option(name: self::OPTION_NAME)] - $someOption): int + public function __invoke( + #[\Symfony\Component\Console\Attribute\Argument(name: self::ARGUMENT_NAME)] + ?string $someArgument, + #[\Symfony\Component\Console\Attribute\Option(name: self::OPTION_NAME)] + $someOption + ): int { $someArgument = $some_argument; - $someOption = $some_option; - // ... - return 1; } } diff --git a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/some_command.php.inc b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/some_command.php.inc index 3e48572db..9450342f1 100644 --- a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/some_command.php.inc +++ b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/some_command.php.inc @@ -45,15 +45,16 @@ use Symfony\Component\Console\Input\InputOption; #[AsCommand(name: 'some_name')] final class SomeCommand { - public function __invoke(#[\Symfony\Component\Console\Attribute\Argument(name: 'argument', description: 'Argument description')] - string $argument, #[\Symfony\Component\Console\Attribute\Option(name: 'option', shortcut: 'o', mode: InputOption::VALUE_NONE, description: 'Option description')] - bool $option = false): int + public function __invoke( + #[\Symfony\Component\Console\Attribute\Argument(name: 'argument', description: 'Argument description')] + string $argument, + #[\Symfony\Component\Console\Attribute\Option(name: 'option', shortcut: 'o', mode: InputOption::VALUE_NONE, description: 'Option description')] + bool $option = false + ): int { $someArgument = $argument; $someOption = $option; - // ... - return 1; } } diff --git a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/some_command_with_method_chaining.php.inc b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/some_command_with_method_chaining.php.inc index 249388936..c3556dd5b 100644 --- a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/some_command_with_method_chaining.php.inc +++ b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/some_command_with_method_chaining.php.inc @@ -46,15 +46,16 @@ use Symfony\Component\Console\Input\InputOption; #[AsCommand(name: 'some_name')] final class SomeCommandWithMethodChaining { - public function __invoke(#[\Symfony\Component\Console\Attribute\Argument(name: 'argument', description: 'Argument description')] - string $argument, #[\Symfony\Component\Console\Attribute\Option(name: 'option', shortcut: 'o', mode: InputOption::VALUE_NONE, description: 'Option description')] - bool $option = false): int + public function __invoke( + #[\Symfony\Component\Console\Attribute\Argument(name: 'argument', description: 'Argument description')] + string $argument, + #[\Symfony\Component\Console\Attribute\Option(name: 'option', shortcut: 'o', mode: InputOption::VALUE_NONE, description: 'Option description')] + bool $option = false + ): int { $someArgument = $argument; $someOption = $option; - // ... - return 1; } } diff --git a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/some_command_with_set_help.php.inc b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/some_command_with_set_help.php.inc index a78a933dc..567c022d0 100644 --- a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/some_command_with_set_help.php.inc +++ b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/some_command_with_set_help.php.inc @@ -51,15 +51,16 @@ final class SomeCommandWithSetHelp $this->setHelp('argument'); } - public function __invoke(#[\Symfony\Component\Console\Attribute\Argument(name: 'argument', description: 'Argument description')] - string $argument, #[\Symfony\Component\Console\Attribute\Option(name: 'option', shortcut: 'o', mode: InputOption::VALUE_NONE, description: 'Option description')] - bool $option = false): int + public function __invoke( + #[\Symfony\Component\Console\Attribute\Argument(name: 'argument', description: 'Argument description')] + string $argument, + #[\Symfony\Component\Console\Attribute\Option(name: 'option', shortcut: 'o', mode: InputOption::VALUE_NONE, description: 'Option description')] + bool $option = false + ): int { $someArgument = $argument; $someOption = $option; - // ... - return 1; } } diff --git a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/with_optional_argument.php.inc b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/with_optional_argument.php.inc index b23d6a78a..8d38cbbc3 100644 --- a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/with_optional_argument.php.inc +++ b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/with_optional_argument.php.inc @@ -45,15 +45,16 @@ use Symfony\Component\Console\Input\InputOption; #[AsCommand(name: 'some_name')] final class WithOptionalArgument { - public function __invoke(#[\Symfony\Component\Console\Attribute\Argument(name: 'argument', description: 'Argument description')] - ?string $argument, #[\Symfony\Component\Console\Attribute\Option(name: 'option', shortcut: 'o', mode: InputOption::VALUE_NONE, description: 'Option description')] - bool $option = false): int + public function __invoke( + #[\Symfony\Component\Console\Attribute\Argument(name: 'argument', description: 'Argument description')] + ?string $argument, + #[\Symfony\Component\Console\Attribute\Option(name: 'option', shortcut: 'o', mode: InputOption::VALUE_NONE, description: 'Option description')] + bool $option = false + ): int { $someArgument = $argument; $someOption = $option; - // ... - return 1; } } diff --git a/rules/CodeQuality/NodeAnalyzer/ParamConverterClassesResolver.php b/rules/CodeQuality/NodeAnalyzer/ParamConverterClassesResolver.php index ab050923f..c6a81031f 100644 --- a/rules/CodeQuality/NodeAnalyzer/ParamConverterClassesResolver.php +++ b/rules/CodeQuality/NodeAnalyzer/ParamConverterClassesResolver.php @@ -10,7 +10,7 @@ use Rector\PhpParser\Node\Value\ValueResolver; use Rector\Symfony\Enum\SensioAttribute; -final class ParamConverterClassesResolver +final readonly class ParamConverterClassesResolver { public function __construct( private AttributeFinder $attributeFinder, diff --git a/rules/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector.php b/rules/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector.php index 33a0f057d..ec3df3c0b 100644 --- a/rules/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector.php +++ b/rules/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector.php @@ -4,6 +4,7 @@ namespace Rector\Symfony\CodeQuality\Rector\Class_; +use Exception; use PhpParser\Node; use PhpParser\Node\Expr\Closure; use PhpParser\Node\Expr\PropertyFetch; @@ -26,6 +27,7 @@ use Rector\VendorLocker\ParentClassMethodTypeOverrideGuard; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; +use Throwable; /** * @see \Rector\Symfony\Tests\CodeQuality\Rector\Class_\ControllerMethodInjectionToConstructorRector\ControllerMethodInjectionToConstructorRectorTest @@ -154,8 +156,8 @@ public function refactor(Node $node): ?Node SymfonyClass::REQUEST, FosClass::PARAM_FETCHER, SymfonyClass::UUID, - \Throwable::class, - \Exception::class, + Throwable::class, + Exception::class, ...$entityClasses, ] )) { diff --git a/rules/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector.php b/rules/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector.php index 876c7fedb..bc3a416cc 100644 --- a/rules/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector.php +++ b/rules/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector.php @@ -4,16 +4,17 @@ namespace Rector\Symfony\Symfony73\Rector\Class_; +use PhpParser\Modifiers; use PhpParser\Node; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\Variable; use PhpParser\Node\Identifier; use PhpParser\Node\Name; +use PhpParser\Node\Param; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Expression; use Rector\Doctrine\NodeAnalyzer\AttributeFinder; -use Rector\Privatization\NodeManipulator\VisibilityManipulator; use Rector\Rector\AbstractRector; use Rector\Symfony\Enum\CommandMethodName; use Rector\Symfony\Enum\SymfonyAttribute; @@ -46,7 +47,6 @@ public function __construct( private readonly CommandOptionsResolver $commandOptionsResolver, private readonly CommandInvokeParamsFactory $commandInvokeParamsFactory, private readonly ConsoleOptionAndArgumentMethodCallVariableReplacer $consoleOptionAndArgumentMethodCallVariableReplacer, - private readonly VisibilityManipulator $visibilityManipulator, private readonly OutputInputSymfonyStyleReplacer $outputInputSymfonyStyleReplacer, private readonly CommandUnusedInputOutputRemover $commandUnusedInputOutputRemover ) { @@ -141,48 +141,42 @@ public function refactor(Node $node): ?Class_ return null; } - $executeClassMethod = $node->getMethod(CommandMethodName::EXECUTE); - if (! $executeClassMethod instanceof ClassMethod) { - return null; - } + foreach ($node->stmts as $key => $classStmt) { + if (! $classStmt instanceof ClassMethod) { + continue; + } - // 1. rename execute to __invoke - $executeClassMethod->name = new Identifier(MethodName::INVOKE); - $this->visibilityManipulator->makePublic($executeClassMethod); + if (! $this->isName($classStmt, CommandMethodName::EXECUTE)) { + continue; + } - // 2. fetch configure method to get arguments and options metadata - $configureClassMethod = $node->getMethod(CommandMethodName::CONFIGURE); + $executeClassMethod = $classStmt; - if ($configureClassMethod instanceof ClassMethod) { - // 3. create arguments and options parameters - $commandArguments = $this->commandArgumentsResolver->resolve($configureClassMethod); - $commandOptions = $this->commandOptionsResolver->resolve($configureClassMethod); + // 1. rename execute to __invoke + $invokeClassMethod = new ClassMethod(MethodName::INVOKE); + $invokeClassMethod->flags |= Modifiers::PUBLIC; + $invokeClassMethod->returnType = new Identifier('int'); + $invokeClassMethod->stmts = $classStmt->stmts; - // 4. remove configure() method - $this->removeConfigureClassMethodIfNotUseful($node); + $invokeParams = $this->createInvokeParams($node); - // 5. decorate __invoke method with attributes - $invokeParams = $this->commandInvokeParamsFactory->createParams($commandArguments, $commandOptions); - } else { - $invokeParams = []; - } + $invokeClassMethod->params = array_merge($invokeParams, [$executeClassMethod->params[1]]); - $executeClassMethod->params = array_merge($invokeParams, [$executeClassMethod->params[1]]); + // 6. remove parent class + $node->extends = null; - // 6. remove parent class - $node->extends = null; + // 7. replace input->getArgument() and input->getOption() calls with direct variable access + $this->consoleOptionAndArgumentMethodCallVariableReplacer->replace($invokeClassMethod); - $this->removeOverrideAttributeAsDifferentMethod($executeClassMethod); + $this->outputInputSymfonyStyleReplacer->replace($invokeClassMethod); + $this->commandUnusedInputOutputRemover->remove($invokeClassMethod); - if ($configureClassMethod instanceof ClassMethod) { - // 7. replace input->getArgument() and input->getOption() calls with direct variable access - $this->consoleOptionAndArgumentMethodCallVariableReplacer->replace($executeClassMethod); - } + $node->stmts[$key] = $invokeClassMethod; - $this->outputInputSymfonyStyleReplacer->replace($executeClassMethod); - $this->commandUnusedInputOutputRemover->remove($executeClassMethod); + return $node; + } - return $node; + return null; } /** @@ -256,19 +250,26 @@ private function isFluentArgumentOptionChain(MethodCall $methodCall): bool return $current instanceof Variable && $this->isName($current, 'this'); } - private function removeOverrideAttributeAsDifferentMethod(ClassMethod $executeClassMethod): void + /** + * @return Param[] + */ + private function createInvokeParams(Class_ $class): array { - foreach ($executeClassMethod->attrGroups as $attrGroupKey => $attrGroup) { - foreach ($attrGroup->attrs as $attributeKey => $attr) { - if ($this->isName($attr->name, 'Override')) { - unset($attrGroup->attrs[$attributeKey]); - } - } + // 1. fetch configure method to get arguments and options metadata + $configureClassMethod = $class->getMethod(CommandMethodName::CONFIGURE); - // is attribute empty? remove whole group - if ($attrGroup->attrs === []) { - unset($executeClassMethod->attrGroups[$attrGroupKey]); - } + if ($configureClassMethod instanceof ClassMethod) { + // 2. create arguments and options parameters + $commandArguments = $this->commandArgumentsResolver->resolve($configureClassMethod); + $commandOptions = $this->commandOptionsResolver->resolve($configureClassMethod); + + // 3. remove configure() method + $this->removeConfigureClassMethodIfNotUseful($class); + + // 4. decorate __invoke method with attributes + return $this->commandInvokeParamsFactory->createParams($commandArguments, $commandOptions); } + + return []; } }