1414use PhpParser \Node \Scalar \String_ ;
1515use PhpParser \Node \Stmt \Class_ ;
1616use PhpParser \Node \Stmt \ClassMethod ;
17+ use PHPStan \Reflection \ReflectionProvider ;
1718use Rector \Rector \AbstractRector ;
19+ use Rector \Symfony \Enum \SensioAttribute ;
20+ use Rector \Symfony \Enum \SymfonyAttribute ;
1821use Rector \ValueObject \PhpVersionFeature ;
1922use Rector \VersionBonding \Contract \MinPhpVersionInterface ;
2023use Symplify \RuleDocGenerator \ValueObject \CodeSample \CodeSample ;
2831 */
2932final class SecurityAttributeToIsGrantedAttributeRector extends AbstractRector implements MinPhpVersionInterface
3033{
31- /**
32- * @var string
33- */
34- private const SECURITY_ATTRIBUTE = 'Sensio\Bundle\FrameworkExtraBundle\Configuration\Security ' ;
35-
36- /**
37- * @var string
38- */
39- private const IS_GRANTED_ATTRIBUTE = 'Symfony\Component\Security\Http\Attribute\IsGranted ' ;
4034
4135 /**
4236 * @var string
@@ -50,6 +44,11 @@ final class SecurityAttributeToIsGrantedAttributeRector extends AbstractRector i
5044 */
5145 private const IS_GRANTED_AND_SUBJECT_REGEX = '#^is_granted\((\"| \')(?<role>[\w]+)(\"| \'),\s+(?<subject>\w+)\)$# ' ;
5246
47+ public function __construct (
48+ private readonly ReflectionProvider $ reflectionProvider
49+ ) {
50+ }
51+
5352 public function provideMinPhpVersion (): int
5453 {
5554 return PhpVersionFeature::ATTRIBUTES ;
@@ -113,15 +112,22 @@ public function getNodeTypes(): array
113112 */
114113 public function refactor (Node $ node ): ?Node
115114 {
115+ if (! $ this ->hasSymfonySecurityAttribute ()) {
116+ return null ;
117+ }
118+
116119 $ hasChanged = false ;
117120
118121 foreach ($ node ->attrGroups as $ attrGroup ) {
119122 foreach ($ attrGroup ->attrs as $ attribute ) {
120- if (! $ this ->isName ($ attribute ->name , self :: SECURITY_ATTRIBUTE )) {
123+ if (! $ this ->isName ($ attribute ->name , SensioAttribute:: SECURITY )) {
121124 continue ;
122125 }
123126
124- $ attribute ->name = new FullyQualified (self ::IS_GRANTED_ATTRIBUTE );
127+ // 1. resolve closest existing name of IsGranted
128+ $ isGrantedName = $ this ->resolveIsGrantedAttributeName ();
129+
130+ $ attribute ->name = new FullyQualified ($ isGrantedName );
125131
126132 $ firstArg = $ attribute ->args [0 ];
127133 $ firstArg ->name = new Identifier ('attribute ' );
@@ -179,4 +185,26 @@ private function wrapToNewExpression(Expr $expr): New_|String_
179185
180186 return new New_ (new FullyQualified ('Symfony\Component\ExpressionLanguage\Expression ' ), $ args );
181187 }
188+
189+ private function resolveIsGrantedAttributeName (): string
190+ {
191+ if ($ this ->reflectionProvider ->hasClass (SymfonyAttribute::IS_GRANTED )) {
192+ return SymfonyAttribute::IS_GRANTED ;
193+ }
194+
195+ // fallback to sensio, if available
196+ return SensioAttribute::IS_GRANTED ;
197+ }
198+
199+ private function hasSymfonySecurityAttribute (): bool
200+ {
201+ // run only if the sensio attribute is available
202+ if (! $ this ->reflectionProvider ->hasClass (SensioAttribute::SECURITY )) {
203+ return false ;
204+ }
205+
206+ // must be attribute, not just annotation
207+ $ securityClassReflection = $ this ->reflectionProvider ->getClass (SensioAttribute::SECURITY );
208+ return $ securityClassReflection ->isAttributeClass ();
209+ }
182210}
0 commit comments