diff --git a/config/sets/symfony/symfony-code-quality.php b/config/sets/symfony/symfony-code-quality.php index d43c20a5d..b122b817f 100644 --- a/config/sets/symfony/symfony-code-quality.php +++ b/config/sets/symfony/symfony-code-quality.php @@ -2,6 +2,7 @@ declare(strict_types=1); +use Rector\Symfony\Symfony62\Rector\Class_\SecurityAttributeToIsGrantedAttributeRector; use Rector\Config\RectorConfig; use Rector\Symfony\CodeQuality\Rector\BinaryOp\RequestIsMainRector; use Rector\Symfony\CodeQuality\Rector\BinaryOp\ResponseStatusCodeRector; @@ -38,5 +39,8 @@ // routing InlineClassRoutePrefixRector::class, + + // narrow attributes + SecurityAttributeToIsGrantedAttributeRector::class, ]); }; diff --git a/rules/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector.php b/rules/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector.php index cae412d02..58b6410ba 100644 --- a/rules/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector.php +++ b/rules/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector.php @@ -170,7 +170,7 @@ private function changeListenerToSubscriberWithMethods(Class_ $class, array $eve */ private function hasAsListenerAttribute(Class_ $class): bool { - if ($this->phpAttributeAnalyzer->hasPhpAttribute($class, SymfonyAttribute::EVENT_LISTENER_ATTRIBUTE)) { + if ($this->phpAttributeAnalyzer->hasPhpAttribute($class, SymfonyAttribute::AS_EVENT_LISTENER)) { return true; } @@ -181,7 +181,7 @@ private function hasAsListenerAttribute(Class_ $class): bool if ($this->phpAttributeAnalyzer->hasPhpAttribute( $classMethod, - SymfonyAttribute::EVENT_LISTENER_ATTRIBUTE + SymfonyAttribute::AS_EVENT_LISTENER )) { return true; } diff --git a/rules/Symfony62/Rector/Class_/SecurityAttributeToIsGrantedAttributeRector.php b/rules/Symfony62/Rector/Class_/SecurityAttributeToIsGrantedAttributeRector.php index 3f3748a22..6676790fc 100644 --- a/rules/Symfony62/Rector/Class_/SecurityAttributeToIsGrantedAttributeRector.php +++ b/rules/Symfony62/Rector/Class_/SecurityAttributeToIsGrantedAttributeRector.php @@ -14,7 +14,10 @@ use PhpParser\Node\Scalar\String_; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; +use PHPStan\Reflection\ReflectionProvider; use Rector\Rector\AbstractRector; +use Rector\Symfony\Enum\SensioAttribute; +use Rector\Symfony\Enum\SymfonyAttribute; use Rector\ValueObject\PhpVersionFeature; use Rector\VersionBonding\Contract\MinPhpVersionInterface; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; @@ -28,15 +31,6 @@ */ final class SecurityAttributeToIsGrantedAttributeRector extends AbstractRector implements MinPhpVersionInterface { - /** - * @var string - */ - private const SECURITY_ATTRIBUTE = 'Sensio\Bundle\FrameworkExtraBundle\Configuration\Security'; - - /** - * @var string - */ - private const IS_GRANTED_ATTRIBUTE = 'Symfony\Component\Security\Http\Attribute\IsGranted'; /** * @var string @@ -50,6 +44,11 @@ final class SecurityAttributeToIsGrantedAttributeRector extends AbstractRector i */ private const IS_GRANTED_AND_SUBJECT_REGEX = '#^is_granted\((\"|\')(?[\w]+)(\"|\'),\s+(?\w+)\)$#'; + public function __construct( + private readonly ReflectionProvider $reflectionProvider + ) { + } + public function provideMinPhpVersion(): int { return PhpVersionFeature::ATTRIBUTES; @@ -113,15 +112,22 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { + if (! $this->hasSymfonySecurityAttribute()) { + return null; + } + $hasChanged = false; foreach ($node->attrGroups as $attrGroup) { foreach ($attrGroup->attrs as $attribute) { - if (! $this->isName($attribute->name, self::SECURITY_ATTRIBUTE)) { + if (! $this->isName($attribute->name, SensioAttribute::SECURITY)) { continue; } - $attribute->name = new FullyQualified(self::IS_GRANTED_ATTRIBUTE); + // 1. resolve closest existing name of IsGranted + $isGrantedName = $this->resolveIsGrantedAttributeName(); + + $attribute->name = new FullyQualified($isGrantedName); $firstArg = $attribute->args[0]; $firstArg->name = new Identifier('attribute'); @@ -179,4 +185,26 @@ private function wrapToNewExpression(Expr $expr): New_|String_ return new New_(new FullyQualified('Symfony\Component\ExpressionLanguage\Expression'), $args); } + + private function resolveIsGrantedAttributeName(): string + { + if ($this->reflectionProvider->hasClass(SymfonyAttribute::IS_GRANTED)) { + return SymfonyAttribute::IS_GRANTED; + } + + // fallback to sensio, if available + return SensioAttribute::IS_GRANTED; + } + + private function hasSymfonySecurityAttribute(): bool + { + // run only if the sensio attribute is available + if (! $this->reflectionProvider->hasClass(SensioAttribute::SECURITY)) { + return false; + } + + // must be attribute, not just annotation + $securityClassReflection = $this->reflectionProvider->getClass(SensioAttribute::SECURITY); + return $securityClassReflection->isAttributeClass(); + } } diff --git a/src/Enum/SensioAttribute.php b/src/Enum/SensioAttribute.php index 86d57361c..033216dde 100644 --- a/src/Enum/SensioAttribute.php +++ b/src/Enum/SensioAttribute.php @@ -25,4 +25,14 @@ final class SensioAttribute * @var string */ public const TEMPLATE = 'Sensio\Bundle\FrameworkExtraBundle\Configuration\Template'; + + /** + * @var string + */ + public const IS_GRANTED = 'Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted'; + + /** + * @var string + */ + public const SECURITY = 'Sensio\Bundle\FrameworkExtraBundle\Configuration\Security'; } diff --git a/src/Enum/SymfonyAttribute.php b/src/Enum/SymfonyAttribute.php index 9d2767712..e4f79afb3 100644 --- a/src/Enum/SymfonyAttribute.php +++ b/src/Enum/SymfonyAttribute.php @@ -29,10 +29,15 @@ final class SymfonyAttribute /** * @var string */ - public const EVENT_LISTENER_ATTRIBUTE = 'Symfony\Component\EventDispatcher\Attribute\AsEventListener'; + public const AS_EVENT_LISTENER = 'Symfony\Component\EventDispatcher\Attribute\AsEventListener'; /** * @var string */ public const ROUTE = 'Symfony\Component\Routing\Attribute\Route'; + + /** + * @var string + */ + public const IS_GRANTED = 'Symfony\Component\Security\Http\Attribute\IsGranted'; } diff --git a/stubs/Symfony/Bundle/FrameworkExtraBundle/Configuration/Security.php b/stubs/Symfony/Bundle/FrameworkExtraBundle/Configuration/Security.php index ebb0fddd1..46838f4e1 100644 --- a/stubs/Symfony/Bundle/FrameworkExtraBundle/Configuration/Security.php +++ b/stubs/Symfony/Bundle/FrameworkExtraBundle/Configuration/Security.php @@ -2,6 +2,7 @@ namespace Sensio\Bundle\FrameworkExtraBundle\Configuration; +#[\Attribute] class Security { }