Updated Rector to commit 34cb81be395d8baaa91ef420e2c005fcf1da5824

34cb81be39 [automated] Apply Coding Standard (#6804)
This commit is contained in:
Tomas Votruba 2025-03-23 00:39:02 +00:00
parent 0ba96421af
commit 408ce49cdf
19 changed files with 448 additions and 29 deletions

View File

@ -3,8 +3,8 @@
declare (strict_types=1);
namespace Rector\DeadCode\Rector\FuncCall;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Expr\FuncCall;
use Rector\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;

View File

@ -19,12 +19,12 @@ final class VersionResolver
* @api
* @var string
*/
public const PACKAGE_VERSION = '9939bd2a7e9d3c231cf13eb32f2bccf71c8ee031';
public const PACKAGE_VERSION = '34cb81be395d8baaa91ef420e2c005fcf1da5824';
/**
* @api
* @var string
*/
public const RELEASE_DATE = '2025-03-21 16:44:07';
public const RELEASE_DATE = '2025-03-23 00:36:33';
/**
* @var int
*/

View File

@ -2284,6 +2284,7 @@ return array(
'Rector\\Symfony\\DependencyInjection\\Rector\\Trait_\\TraitGetByTypeToInjectRector' => $vendorDir . '/rector/rector-symfony/rules/DependencyInjection/Rector/Trait_/TraitGetByTypeToInjectRector.php',
'Rector\\Symfony\\DependencyInjection\\ThisGetTypeMatcher' => $vendorDir . '/rector/rector-symfony/rules/DependencyInjection/ThisGetTypeMatcher.php',
'Rector\\Symfony\\DowngradeSymfony70\\Rector\\Class_\\DowngradeSymfonyCommandAttributeRector' => $vendorDir . '/rector/rector-symfony/rules/DowngradeSymfony70/Rector/Class_/DowngradeSymfonyCommandAttributeRector.php',
'Rector\\Symfony\\Enum\\CommandMethodName' => $vendorDir . '/rector/rector-symfony/src/Enum/CommandMethodName.php',
'Rector\\Symfony\\Enum\\FosAnnotation' => $vendorDir . '/rector/rector-symfony/src/Enum/FosAnnotation.php',
'Rector\\Symfony\\Enum\\SensioAttribute' => $vendorDir . '/rector/rector-symfony/src/Enum/SensioAttribute.php',
'Rector\\Symfony\\Enum\\SymfonyAnnotation' => $vendorDir . '/rector/rector-symfony/src/Enum/SymfonyAnnotation.php',
@ -2399,6 +2400,11 @@ return array(
'Rector\\Symfony\\Symfony62\\Rector\\MethodCall\\SimplifyFormRenderingRector' => $vendorDir . '/rector/rector-symfony/rules/Symfony62/Rector/MethodCall/SimplifyFormRenderingRector.php',
'Rector\\Symfony\\Symfony63\\Rector\\Class_\\ParamAndEnvAttributeRector' => $vendorDir . '/rector/rector-symfony/rules/Symfony63/Rector/Class_/ParamAndEnvAttributeRector.php',
'Rector\\Symfony\\Symfony63\\Rector\\Class_\\SignalableCommandInterfaceReturnTypeRector' => $vendorDir . '/rector/rector-symfony/rules/Symfony63/Rector/Class_/SignalableCommandInterfaceReturnTypeRector.php',
'Rector\\Symfony\\Symfony73\\NodeAnalyzer\\CommandArgumentsAndOptionsResolver' => $vendorDir . '/rector/rector-symfony/rules/Symfony73/NodeAnalyzer/CommandArgumentsAndOptionsResolver.php',
'Rector\\Symfony\\Symfony73\\NodeFactory\\CommandInvokeParamsFactory' => $vendorDir . '/rector/rector-symfony/rules/Symfony73/NodeFactory/CommandInvokeParamsFactory.php',
'Rector\\Symfony\\Symfony73\\Rector\\Class_\\InvokableCommandRector' => $vendorDir . '/rector/rector-symfony/rules/Symfony73/Rector/Class_/InvokableCommandRector.php',
'Rector\\Symfony\\Symfony73\\ValueObject\\CommandArgument' => $vendorDir . '/rector/rector-symfony/rules/Symfony73/ValueObject/CommandArgument.php',
'Rector\\Symfony\\Symfony73\\ValueObject\\CommandOption' => $vendorDir . '/rector/rector-symfony/rules/Symfony73/ValueObject/CommandOption.php',
'Rector\\Symfony\\Twig134\\Rector\\Return_\\SimpleFunctionAndFilterRector' => $vendorDir . '/rector/rector-symfony/rules/Twig134/Rector/Return_/SimpleFunctionAndFilterRector.php',
'Rector\\Symfony\\TypeAnalyzer\\ArrayUnionResponseTypeAnalyzer' => $vendorDir . '/rector/rector-symfony/src/TypeAnalyzer/ArrayUnionResponseTypeAnalyzer.php',
'Rector\\Symfony\\TypeAnalyzer\\ContainerAwareAnalyzer' => $vendorDir . '/rector/rector-symfony/src/TypeAnalyzer/ContainerAwareAnalyzer.php',

View File

@ -2503,6 +2503,7 @@ class ComposerStaticInit82c6a73a31b80aa1be38da0fc895c7e2
'Rector\\Symfony\\DependencyInjection\\Rector\\Trait_\\TraitGetByTypeToInjectRector' => __DIR__ . '/..' . '/rector/rector-symfony/rules/DependencyInjection/Rector/Trait_/TraitGetByTypeToInjectRector.php',
'Rector\\Symfony\\DependencyInjection\\ThisGetTypeMatcher' => __DIR__ . '/..' . '/rector/rector-symfony/rules/DependencyInjection/ThisGetTypeMatcher.php',
'Rector\\Symfony\\DowngradeSymfony70\\Rector\\Class_\\DowngradeSymfonyCommandAttributeRector' => __DIR__ . '/..' . '/rector/rector-symfony/rules/DowngradeSymfony70/Rector/Class_/DowngradeSymfonyCommandAttributeRector.php',
'Rector\\Symfony\\Enum\\CommandMethodName' => __DIR__ . '/..' . '/rector/rector-symfony/src/Enum/CommandMethodName.php',
'Rector\\Symfony\\Enum\\FosAnnotation' => __DIR__ . '/..' . '/rector/rector-symfony/src/Enum/FosAnnotation.php',
'Rector\\Symfony\\Enum\\SensioAttribute' => __DIR__ . '/..' . '/rector/rector-symfony/src/Enum/SensioAttribute.php',
'Rector\\Symfony\\Enum\\SymfonyAnnotation' => __DIR__ . '/..' . '/rector/rector-symfony/src/Enum/SymfonyAnnotation.php',
@ -2618,6 +2619,11 @@ class ComposerStaticInit82c6a73a31b80aa1be38da0fc895c7e2
'Rector\\Symfony\\Symfony62\\Rector\\MethodCall\\SimplifyFormRenderingRector' => __DIR__ . '/..' . '/rector/rector-symfony/rules/Symfony62/Rector/MethodCall/SimplifyFormRenderingRector.php',
'Rector\\Symfony\\Symfony63\\Rector\\Class_\\ParamAndEnvAttributeRector' => __DIR__ . '/..' . '/rector/rector-symfony/rules/Symfony63/Rector/Class_/ParamAndEnvAttributeRector.php',
'Rector\\Symfony\\Symfony63\\Rector\\Class_\\SignalableCommandInterfaceReturnTypeRector' => __DIR__ . '/..' . '/rector/rector-symfony/rules/Symfony63/Rector/Class_/SignalableCommandInterfaceReturnTypeRector.php',
'Rector\\Symfony\\Symfony73\\NodeAnalyzer\\CommandArgumentsAndOptionsResolver' => __DIR__ . '/..' . '/rector/rector-symfony/rules/Symfony73/NodeAnalyzer/CommandArgumentsAndOptionsResolver.php',
'Rector\\Symfony\\Symfony73\\NodeFactory\\CommandInvokeParamsFactory' => __DIR__ . '/..' . '/rector/rector-symfony/rules/Symfony73/NodeFactory/CommandInvokeParamsFactory.php',
'Rector\\Symfony\\Symfony73\\Rector\\Class_\\InvokableCommandRector' => __DIR__ . '/..' . '/rector/rector-symfony/rules/Symfony73/Rector/Class_/InvokableCommandRector.php',
'Rector\\Symfony\\Symfony73\\ValueObject\\CommandArgument' => __DIR__ . '/..' . '/rector/rector-symfony/rules/Symfony73/ValueObject/CommandArgument.php',
'Rector\\Symfony\\Symfony73\\ValueObject\\CommandOption' => __DIR__ . '/..' . '/rector/rector-symfony/rules/Symfony73/ValueObject/CommandOption.php',
'Rector\\Symfony\\Twig134\\Rector\\Return_\\SimpleFunctionAndFilterRector' => __DIR__ . '/..' . '/rector/rector-symfony/rules/Twig134/Rector/Return_/SimpleFunctionAndFilterRector.php',
'Rector\\Symfony\\TypeAnalyzer\\ArrayUnionResponseTypeAnalyzer' => __DIR__ . '/..' . '/rector/rector-symfony/src/TypeAnalyzer/ArrayUnionResponseTypeAnalyzer.php',
'Rector\\Symfony\\TypeAnalyzer\\ContainerAwareAnalyzer' => __DIR__ . '/..' . '/rector/rector-symfony/src/TypeAnalyzer/ContainerAwareAnalyzer.php',

View File

@ -1864,12 +1864,12 @@
"source": {
"type": "git",
"url": "https:\/\/github.com\/rectorphp\/rector-symfony.git",
"reference": "d4e5a0f5ca731659f5d7761c3133e176fa78cdef"
"reference": "00c1f259413b81b5075df372d56446f3e41d7891"
},
"dist": {
"type": "zip",
"url": "https:\/\/api.github.com\/repos\/rectorphp\/rector-symfony\/zipball\/d4e5a0f5ca731659f5d7761c3133e176fa78cdef",
"reference": "d4e5a0f5ca731659f5d7761c3133e176fa78cdef",
"url": "https:\/\/api.github.com\/repos\/rectorphp\/rector-symfony\/zipball\/00c1f259413b81b5075df372d56446f3e41d7891",
"reference": "00c1f259413b81b5075df372d56446f3e41d7891",
"shasum": ""
},
"require": {
@ -1877,24 +1877,27 @@
"php": ">=8.2"
},
"require-dev": {
"phpecs\/phpecs": "^2.0.1",
"phpstan\/extension-installer": "^1.4",
"phpstan\/phpstan": "^2.1.8",
"phpstan\/phpstan-webmozart-assert": "^2.0",
"phpunit\/phpunit": "^11.4",
"rector\/rector-src": "dev-main",
"rector\/type-perfect": "^2.0",
"symfony\/config": "^6.4",
"symfony\/dependency-injection": "^6.4",
"symfony\/http-kernel": "~6.3",
"symfony\/http-kernel": "^6.4",
"symfony\/routing": "^6.4",
"symfony\/security-core": "^6.4",
"symfony\/security-http": "^6.4",
"symfony\/validator": "^6.4",
"symplify\/easy-coding-standard": "^12.3",
"symplify\/vendor-patches": "^11.3",
"tomasvotruba\/class-leak": "^1.0",
"tomasvotruba\/class-leak": "^2.0",
"tomasvotruba\/type-coverage": "^2.0",
"tomasvotruba\/unused-public": "^2.0",
"tracy\/tracy": "^2.10"
},
"time": "2025-03-11T10:18:32+00:00",
"time": "2025-03-21T10:27:25+00:00",
"default-branch": true,
"type": "rector-extension",
"extra": {

File diff suppressed because one or more lines are too long

View File

@ -9,7 +9,7 @@ namespace Rector\RectorInstaller;
*/
final class GeneratedConfig
{
public const EXTENSIONS = array('rector/rector-doctrine' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-doctrine', 'relative_install_path' => '../../rector-doctrine', 'extra' => NULL, 'version' => 'dev-main 1da0aa0'), 'rector/rector-downgrade-php' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-downgrade-php', 'relative_install_path' => '../../rector-downgrade-php', 'extra' => NULL, 'version' => 'dev-main b55c3bc'), 'rector/rector-phpunit' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-phpunit', 'relative_install_path' => '../../rector-phpunit', 'extra' => NULL, 'version' => 'dev-main 8350a12'), 'rector/rector-symfony' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-symfony', 'relative_install_path' => '../../rector-symfony', 'extra' => NULL, 'version' => 'dev-main d4e5a0f'));
public const EXTENSIONS = array('rector/rector-doctrine' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-doctrine', 'relative_install_path' => '../../rector-doctrine', 'extra' => NULL, 'version' => 'dev-main 1da0aa0'), 'rector/rector-downgrade-php' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-downgrade-php', 'relative_install_path' => '../../rector-downgrade-php', 'extra' => NULL, 'version' => 'dev-main b55c3bc'), 'rector/rector-phpunit' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-phpunit', 'relative_install_path' => '../../rector-phpunit', 'extra' => NULL, 'version' => 'dev-main 8350a12'), 'rector/rector-symfony' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-symfony', 'relative_install_path' => '../../rector-symfony', 'extra' => NULL, 'version' => 'dev-main 00c1f25'));
private function __construct()
{
}

View File

@ -8,21 +8,24 @@
"ext-xml": "*"
},
"require-dev": {
"phpecs\/phpecs": "^2.0.1",
"phpstan\/extension-installer": "^1.4",
"phpstan\/phpstan": "^2.1.8",
"phpstan\/phpstan-webmozart-assert": "^2.0",
"phpunit\/phpunit": "^11.4",
"rector\/rector-src": "dev-main",
"rector\/type-perfect": "^2.0",
"symfony\/config": "^6.4",
"symfony\/dependency-injection": "^6.4",
"symfony\/http-kernel": "~6.3",
"symfony\/http-kernel": "^6.4",
"symfony\/routing": "^6.4",
"symfony\/security-core": "^6.4",
"symfony\/security-http": "^6.4",
"symfony\/validator": "^6.4",
"symplify\/easy-coding-standard": "^12.3",
"symplify\/vendor-patches": "^11.3",
"tomasvotruba\/class-leak": "^1.0",
"tomasvotruba\/class-leak": "^2.0",
"tomasvotruba\/type-coverage": "^2.0",
"tomasvotruba\/unused-public": "^2.0",
"tracy\/tracy": "^2.10"
},
"autoload": {

View File

@ -0,0 +1,9 @@
<?php
declare (strict_types=1);
namespace RectorPrefix202503;
use Rector\Config\RectorConfig;
use Rector\Symfony\Symfony73\Rector\Class_\InvokableCommandRector;
// @see https://github.com/symfony/symfony/blame/7.3/UPGRADE-7.3.md
return RectorConfig::configure()->withRules([InvokableCommandRector::class]);

View File

@ -18,13 +18,14 @@ use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\ObjectType;
use Rector\PhpAttribute\NodeFactory\PhpAttributeGroupFactory;
use Rector\Rector\AbstractRector;
use Rector\Symfony\Enum\SymfonyAnnotation;
use Rector\Symfony\Enum\SymfonyAttribute;
use Rector\Symfony\Enum\SymfonyClass;
use Rector\ValueObject\PhpVersionFeature;
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @changelog https://symfony.com/doc/current/console.html#registering-the-command
* @see https://symfony.com/doc/current/console.html#registering-the-command
*
* @see \Rector\Symfony\Tests\Symfony61\Rector\Class_\CommandConfigureToAttributeRector\CommandConfigureToAttributeRectorTest
*/
@ -92,10 +93,10 @@ CODE_SAMPLE
if ($node->isAbstract()) {
return null;
}
if (!$this->reflectionProvider->hasClass(SymfonyAnnotation::AS_COMMAND)) {
if (!$this->reflectionProvider->hasClass(SymfonyAttribute::AS_COMMAND)) {
return null;
}
if (!$this->isObjectType($node, new ObjectType('Symfony\\Component\\Console\\Command\\Command'))) {
if (!$this->isObjectType($node, new ObjectType(SymfonyClass::COMMAND))) {
return null;
}
$configureClassMethod = $node->getMethod('configure');
@ -107,7 +108,7 @@ CODE_SAMPLE
$attributeArgs = [];
foreach ($node->attrGroups as $attrGroup) {
foreach ($attrGroup->attrs as $attribute) {
if (!$this->nodeNameResolver->isName($attribute->name, SymfonyAnnotation::AS_COMMAND)) {
if (!$this->nodeNameResolver->isName($attribute->name, SymfonyAttribute::AS_COMMAND)) {
continue;
}
$asCommandAttribute = $attribute;
@ -122,7 +123,7 @@ CODE_SAMPLE
}
}
if (!$asCommandAttribute instanceof Attribute) {
$asCommandAttributeGroup = $this->phpAttributeGroupFactory->createFromClass(SymfonyAnnotation::AS_COMMAND);
$asCommandAttributeGroup = $this->phpAttributeGroupFactory->createFromClass(SymfonyAttribute::AS_COMMAND);
$asCommandAttribute = $asCommandAttributeGroup->attrs[0];
$node->attrGroups[] = $asCommandAttributeGroup;
}

View File

@ -16,7 +16,7 @@ use PHPStan\Type\ObjectType;
use Rector\Doctrine\NodeAnalyzer\AttributeFinder;
use Rector\PhpAttribute\NodeFactory\PhpAttributeGroupFactory;
use Rector\Rector\AbstractRector;
use Rector\Symfony\Enum\SymfonyAnnotation;
use Rector\Symfony\Enum\SymfonyAttribute;
use Rector\Symfony\Enum\SymfonyClass;
use Rector\ValueObject\PhpVersionFeature;
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
@ -91,7 +91,7 @@ CODE_SAMPLE
return null;
}
// does attribute already exist?
if (!$this->reflectionProvider->hasClass(SymfonyAnnotation::AS_COMMAND)) {
if (!$this->reflectionProvider->hasClass(SymfonyAttribute::AS_COMMAND)) {
return null;
}
$defaultNameExpr = $this->resolvePropertyExpr($node, 'defaultName');
@ -99,7 +99,7 @@ CODE_SAMPLE
return null;
}
$defaultDescriptionExpr = $this->resolvePropertyExpr($node, 'defaultDescription');
$existingAsCommandAttribute = $this->attributeFinder->findAttributeByClass($node, SymfonyAnnotation::AS_COMMAND);
$existingAsCommandAttribute = $this->attributeFinder->findAttributeByClass($node, SymfonyAttribute::AS_COMMAND);
$attributeArgs = $this->createAttributeArgs($defaultNameExpr, $defaultDescriptionExpr);
// already has attribute, only add "name" and optionally "description"
if ($existingAsCommandAttribute instanceof Attribute) {
@ -115,7 +115,7 @@ CODE_SAMPLE
private function createAttributeGroupAsCommand(array $args) : AttributeGroup
{
Assert::allIsInstanceOf($args, Arg::class);
$attributeGroup = $this->phpAttributeGroupFactory->createFromClass(SymfonyAnnotation::AS_COMMAND);
$attributeGroup = $this->phpAttributeGroupFactory->createFromClass(SymfonyAttribute::AS_COMMAND);
$attributeGroup->attrs[0]->args = $args;
return $attributeGroup;
}

View File

@ -0,0 +1,73 @@
<?php
declare (strict_types=1);
namespace Rector\Symfony\Symfony73\NodeAnalyzer;
use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Identifier;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\NodeFinder;
use Rector\Exception\ShouldNotHappenException;
use Rector\Symfony\Symfony73\ValueObject\CommandArgument;
use Rector\Symfony\Symfony73\ValueObject\CommandOption;
final class CommandArgumentsAndOptionsResolver
{
/**
* @return CommandArgument[]
*/
public function collectCommandArguments(ClassMethod $configureClassMethod) : array
{
$addArgumentMethodCalls = $this->findMethodCallsByName($configureClassMethod, 'addArgument');
$commandArguments = [];
foreach ($addArgumentMethodCalls as $addArgumentMethodCall) {
// @todo extract name, type and requirements
$addArgumentArgs = $addArgumentMethodCall->getArgs();
$nameArgValue = $addArgumentArgs[0]->value;
if (!$nameArgValue instanceof String_) {
// we need string value, otherwise param will not have a name
throw new ShouldNotHappenException('Argument name is required');
}
$optionName = $nameArgValue->value;
$commandArguments[] = new CommandArgument($optionName);
}
return $commandArguments;
}
/**
* @return CommandOption[]
*/
public function collectCommandOptions(ClassMethod $configureClassMethod) : array
{
$addOptionMethodCalls = $this->findMethodCallsByName($configureClassMethod, 'addOption');
$commandOptionMetadatas = [];
foreach ($addOptionMethodCalls as $addOptionMethodCall) {
// @todo extract name, type and requirements
$addOptionArgs = $addOptionMethodCall->getArgs();
$nameArgValue = $addOptionArgs[0]->value;
if (!$nameArgValue instanceof String_) {
// we need string value, otherwise param will not have a name
throw new ShouldNotHappenException('Option name is required');
}
$optionName = $nameArgValue->value;
$commandOptionMetadatas[] = new CommandOption($optionName);
}
return $commandOptionMetadatas;
}
/**
* @return MethodCall[]
*/
private function findMethodCallsByName(ClassMethod $classMethod, string $desiredMethodName) : array
{
$nodeFinder = new NodeFinder();
return $nodeFinder->find($classMethod, function (Node $node) use($desiredMethodName) : bool {
if (!$node instanceof MethodCall) {
return \false;
}
if (!$node->name instanceof Identifier) {
return \false;
}
return $node->name->toString() === $desiredMethodName;
});
}
}

View File

@ -0,0 +1,60 @@
<?php
declare (strict_types=1);
namespace Rector\Symfony\Symfony73\NodeFactory;
use PhpParser\Node\Attribute;
use PhpParser\Node\AttributeGroup;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Param;
use Rector\Symfony\Enum\SymfonyAttribute;
use Rector\Symfony\Symfony73\ValueObject\CommandArgument;
use Rector\Symfony\Symfony73\ValueObject\CommandOption;
final class CommandInvokeParamsFactory
{
/**
* @param CommandArgument[] $commandArguments
* @param CommandOption[] $commandOptions
* @return Param[]
*/
public function createParams(array $commandArguments, array $commandOptions) : array
{
$argumentParams = $this->createArgumentParams($commandArguments);
$optionParams = $this->createOptionParams($commandOptions);
return \array_merge($argumentParams, $optionParams);
}
/**
* @param CommandArgument[] $commandArguments
* @return Param[]
*/
private function createArgumentParams(array $commandArguments) : array
{
$argumentParams = [];
foreach ($commandArguments as $commandArgument) {
$argumentParam = new Param(new Variable($commandArgument->getName()));
$argumentParam->type = new Identifier('string');
// @todo fill type or default value
// @todo default string, multiple values array
$argumentParam->attrGroups[] = new AttributeGroup([new Attribute(new FullyQualified(SymfonyAttribute::COMMAND_ARGUMENT))]);
$argumentParams[] = $argumentParam;
}
return $argumentParams;
}
/**
* @param CommandOption[] $commandOptions
* @return Param[]
*/
private function createOptionParams(array $commandOptions) : array
{
$optionParams = [];
foreach ($commandOptions as $commandOption) {
$optionParam = new Param(new Variable($commandOption->getName()));
// @todo fill type or default value
$optionParam->attrGroups[] = new AttributeGroup([new Attribute(new FullyQualified(SymfonyAttribute::COMMAND_OPTION))]);
$optionParams[] = $optionParam;
}
return $optionParams;
}
}

View File

@ -0,0 +1,198 @@
<?php
declare (strict_types=1);
namespace Rector\Symfony\Symfony73\Rector\Class_;
use PhpParser\Node;
use PhpParser\Node\Attribute;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use Rector\Doctrine\NodeAnalyzer\AttributeFinder;
use Rector\Exception\ShouldNotHappenException;
use Rector\Rector\AbstractRector;
use Rector\Symfony\Enum\CommandMethodName;
use Rector\Symfony\Enum\SymfonyAttribute;
use Rector\Symfony\Enum\SymfonyClass;
use Rector\Symfony\Symfony73\NodeAnalyzer\CommandArgumentsAndOptionsResolver;
use Rector\Symfony\Symfony73\NodeFactory\CommandInvokeParamsFactory;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see https://github.com/symfony/symfony-docs/issues/20553
* @see https://github.com/symfony/symfony/pull/59340
*
* @see \Rector\Symfony\Tests\Symfony73\Rector\Class_\InvokableCommandRector\InvokableCommandRectorTest
*/
final class InvokableCommandRector extends AbstractRector
{
/**
* @readonly
*/
private AttributeFinder $attributeFinder;
/**
* @readonly
*/
private CommandArgumentsAndOptionsResolver $commandArgumentsAndOptionsResolver;
/**
* @readonly
*/
private CommandInvokeParamsFactory $commandInvokeParamsFactory;
public function __construct(AttributeFinder $attributeFinder, CommandArgumentsAndOptionsResolver $commandArgumentsAndOptionsResolver, CommandInvokeParamsFactory $commandInvokeParamsFactory)
{
$this->attributeFinder = $attributeFinder;
$this->commandArgumentsAndOptionsResolver = $commandArgumentsAndOptionsResolver;
$this->commandInvokeParamsFactory = $commandInvokeParamsFactory;
}
public function getRuleDefinition() : RuleDefinition
{
return new RuleDefinition('Change Symfony Command with execute() + configure() to __invoke() with attributes', [new CodeSample(<<<'CODE_SAMPLE'
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
#[AsCommand(name: 'some_name')]
final class SomeCommand extends Command
{
public function configure()
{
$this->addArgument('argument', InputArgument::REQUIRED, 'Argument description');
$this->addOption('option', 'o', InputOption::VALUE_NONE, 'Option description');
}
public function execute(InputInterface $input, OutputInterface $output)
{
$someArgument = $input->getArgument('argument');
$someOption = $input->getOption('option');
// ...
return 1;
}
}
CODE_SAMPLE
, <<<'CODE_SAMPLE'
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Command\Argument;
use Symfony\Component\Console\Command\Option;
final class SomeCommand
{
public function __invoke(
#[Argument]
string $argument,
#[Option]
bool $option = false,
) {
$someArgument = $argument;
$someOption = $option;
// ...
return 1;
}
}
CODE_SAMPLE
)]);
}
public function getNodeTypes() : array
{
return [Class_::class];
}
/**
* @param Class_ $node
*/
public function refactor(Node $node) : ?Class_
{
if (!$node->extends instanceof Name) {
return null;
}
// handle only direct child classes, to keep safe
if (!$this->isName($node->extends, SymfonyClass::COMMAND)) {
return null;
}
if ($this->isComplexCommand($node)) {
return null;
}
// as command attribute is required, its handled by previous symfony versions
// @todo possibly to add it here to handle multiple cases
if (!$this->attributeFinder->findAttributeByClass($node, SymfonyAttribute::AS_COMMAND) instanceof Attribute) {
return null;
}
// 1. fetch configure method to get arguments and options metadata
$configureClassMethod = $node->getMethod(CommandMethodName::CONFIGURE);
if (!$configureClassMethod instanceof ClassMethod) {
return null;
}
// 2. rename execute to __invoke
$executeClassMethod = $node->getMethod(CommandMethodName::EXECUTE);
if (!$executeClassMethod instanceof ClassMethod) {
return null;
}
$executeClassMethod->name = new Identifier('__invoke');
// 3. create arguments and options parameters
// @todo
$commandArguments = $this->commandArgumentsAndOptionsResolver->collectCommandArguments($configureClassMethod);
$commandOptions = $this->commandArgumentsAndOptionsResolver->collectCommandOptions($configureClassMethod);
// 4. remove configure() method
$this->removeConfigureClassMethod($node);
// 5. decorate __invoke method with attributes
$invokeParams = $this->commandInvokeParamsFactory->createParams($commandArguments, $commandOptions);
$executeClassMethod->params = $invokeParams;
// 6. remove parent class
$node->extends = null;
// 7. replace input->getArgument() and input->getOption() calls with direct variable access
$this->replaceInputArgumentOptionFetchWithVariables($executeClassMethod);
return $node;
}
/**
* Skip commands with interact() or initialize() methods as modify the argument/option values
*/
private function isComplexCommand(Class_ $class) : bool
{
if ($class->getMethod(CommandMethodName::INTERACT) instanceof ClassMethod) {
return \true;
}
return $class->getMethod(CommandMethodName::INITIALIZE) instanceof ClassMethod;
}
private function removeConfigureClassMethod(Class_ $class) : void
{
foreach ($class->stmts as $key => $stmt) {
if (!$stmt instanceof ClassMethod) {
continue;
}
if (!$this->isName($stmt->name, CommandMethodName::CONFIGURE)) {
continue;
}
unset($class->stmts[$key]);
return;
}
}
private function replaceInputArgumentOptionFetchWithVariables(ClassMethod $executeClassMethod) : void
{
$this->traverseNodesWithCallable($executeClassMethod->stmts, function (Node $node) : ?Variable {
if (!$node instanceof MethodCall) {
return null;
}
if (!$this->isName($node->var, 'input')) {
return null;
}
if (!$this->isNames($node->name, ['getOption', 'getArgument'])) {
return null;
}
$firstArgValue = $node->getArgs()[0]->value;
if (!$firstArgValue instanceof String_) {
// unable to resolve argument/option name
throw new ShouldNotHappenException();
}
return new Variable($firstArgValue->value);
});
}
}

View File

@ -0,0 +1,20 @@
<?php
declare (strict_types=1);
namespace Rector\Symfony\Symfony73\ValueObject;
final class CommandArgument
{
/**
* @readonly
*/
private string $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function getName() : string
{
return $this->name;
}
}

View File

@ -0,0 +1,20 @@
<?php
declare (strict_types=1);
namespace Rector\Symfony\Symfony73\ValueObject;
final class CommandOption
{
/**
* @readonly
*/
private string $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function getName() : string
{
return $this->name;
}
}

View File

@ -0,0 +1,12 @@
<?php
declare (strict_types=1);
namespace Rector\Symfony\Enum;
final class CommandMethodName
{
public const CONFIGURE = 'configure';
public const EXECUTE = 'execute';
public const INTERACT = 'interact';
public const INITIALIZE = 'initialize';
}

View File

@ -21,8 +21,4 @@ final class SymfonyAnnotation
* @var string
*/
public const TEMPLATE = 'Sensio\\Bundle\\FrameworkExtraBundle\\Configuration\\Template';
/**
* @var string
*/
public const AS_COMMAND = 'Symfony\\Component\\Console\\Attribute\\AsCommand';
}

View File

@ -9,6 +9,18 @@ final class SymfonyAttribute
* @var string
*/
public const AUTOWIRE = 'Symfony\\Component\\DependencyInjection\\Attribute\\Autowire';
/**
* @var string
*/
public const AS_COMMAND = 'Symfony\\Component\\Console\\Attribute\\AsCommand';
/**
* @var string
*/
public const COMMAND_OPTION = 'Symfony\\Component\\Console\\Attribute\\Command\\Option';
/**
* @var string
*/
public const COMMAND_ARGUMENT = 'Symfony\\Component\\Console\\Attribute\\Command\\Argument';
/**
* @var string
*/