From 10f343c89471e11f19c954f5e03d4ffdb3484202 Mon Sep 17 00:00:00 2001 From: DenisD Date: Wed, 4 Mar 2026 17:56:44 +0300 Subject: [PATCH 1/3] Skip classes with Symfony 7.1 Workflow listener attributes EventListenerToEventSubscriberRector now skips classes that use any of the 7 workflow listener attributes introduced in Symfony 7.1, not just AsEventListener. Fixes rectorphp/rector#9685 --- ...stener_attribute_exists_on_methods.php.inc | 13 ++++++++++ ...stener_attribute_exists_on_methods.php.inc | 13 ++++++++++ ...stener_attribute_exists_on_methods.php.inc | 13 ++++++++++ ...stener_attribute_exists_on_methods.php.inc | 13 ++++++++++ ...listener_attribute_exists_on_class.php.inc | 13 ++++++++++ ...stener_attribute_exists_on_methods.php.inc | 13 ++++++++++ ...stener_attribute_exists_on_methods.php.inc | 18 +++++++++++++ .../EventListenerToEventSubscriberRector.php | 25 ++++++++++++++++--- src/Enum/SymfonyAttribute.php | 15 +++++++++++ 9 files changed, 132 insertions(+), 4 deletions(-) create mode 100644 rules-tests/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/skip_when_as_announce_listener_attribute_exists_on_methods.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/skip_when_as_completed_listener_attribute_exists_on_methods.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/skip_when_as_enter_listener_attribute_exists_on_methods.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/skip_when_as_entered_listener_attribute_exists_on_methods.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/skip_when_as_guard_listener_attribute_exists_on_class.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/skip_when_as_leave_listener_attribute_exists_on_methods.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/skip_when_as_transition_listener_attribute_exists_on_methods.php.inc diff --git a/rules-tests/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/skip_when_as_announce_listener_attribute_exists_on_methods.php.inc b/rules-tests/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/skip_when_as_announce_listener_attribute_exists_on_methods.php.inc new file mode 100644 index 000000000..ba86b0513 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/skip_when_as_announce_listener_attribute_exists_on_methods.php.inc @@ -0,0 +1,13 @@ +phpAttributeAnalyzer->hasPhpAttribute($class, SymfonyAttribute::AS_EVENT_LISTENER)) { - return true; + $listenerAttributes = [ + SymfonyAttribute::AS_EVENT_LISTENER, + // Symfony Workflow attributes (Symfony 7.1+) + SymfonyAttribute::AS_ANNOUNCE_LISTENER, + SymfonyAttribute::AS_COMPLETED_LISTENER, + SymfonyAttribute::AS_ENTER_LISTENER, + SymfonyAttribute::AS_ENTERED_LISTENER, + SymfonyAttribute::AS_GUARD_LISTENER, + SymfonyAttribute::AS_LEAVE_LISTENER, + SymfonyAttribute::AS_TRANSITION_LISTENER, + ]; + + foreach ($listenerAttributes as $attribute) { + if ($this->phpAttributeAnalyzer->hasPhpAttribute($class, $attribute)) { + return true; + } } foreach ($class->getMethods() as $classMethod) { @@ -180,8 +195,10 @@ private function hasAsListenerAttribute(Class_ $class): bool continue; } - if ($this->phpAttributeAnalyzer->hasPhpAttribute($classMethod, SymfonyAttribute::AS_EVENT_LISTENER)) { - return true; + foreach ($listenerAttributes as $attribute) { + if ($this->phpAttributeAnalyzer->hasPhpAttribute($classMethod, $attribute)) { + return true; + } } } diff --git a/src/Enum/SymfonyAttribute.php b/src/Enum/SymfonyAttribute.php index 6ee0b0d40..7c434c566 100644 --- a/src/Enum/SymfonyAttribute.php +++ b/src/Enum/SymfonyAttribute.php @@ -16,6 +16,21 @@ final class SymfonyAttribute public const string AS_EVENT_LISTENER = 'Symfony\Component\EventDispatcher\Attribute\AsEventListener'; + // Workflow listener attributes (Symfony 7.1+) + public const string AS_ANNOUNCE_LISTENER = 'Symfony\Component\Workflow\Attribute\AsAnnounceListener'; + + public const string AS_COMPLETED_LISTENER = 'Symfony\Component\Workflow\Attribute\AsCompletedListener'; + + public const string AS_ENTER_LISTENER = 'Symfony\Component\Workflow\Attribute\AsEnterListener'; + + public const string AS_ENTERED_LISTENER = 'Symfony\Component\Workflow\Attribute\AsEnteredListener'; + + public const string AS_GUARD_LISTENER = 'Symfony\Component\Workflow\Attribute\AsGuardListener'; + + public const string AS_LEAVE_LISTENER = 'Symfony\Component\Workflow\Attribute\AsLeaveListener'; + + public const string AS_TRANSITION_LISTENER = 'Symfony\Component\Workflow\Attribute\AsTransitionListener'; + public const string ROUTE = 'Symfony\Component\Routing\Attribute\Route'; public const string IS_GRANTED = 'Symfony\Component\Security\Http\Attribute\IsGranted'; From 1517c5d15bbbc614f9a96678cabc8cc963a492c4 Mon Sep 17 00:00:00 2001 From: DenisD Date: Thu, 5 Mar 2026 17:21:58 +0300 Subject: [PATCH 2/3] extract listener attributes to class constant --- .../EventListenerToEventSubscriberRector.php | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/rules/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector.php b/rules/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector.php index 77608c12b..9eb20c86c 100644 --- a/rules/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector.php +++ b/rules/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector.php @@ -32,6 +32,21 @@ final class EventListenerToEventSubscriberRector extends AbstractRector */ private const string LISTENER_MATCH_REGEX = '#^(.*?)(Listener)?$#'; + /** + * @var string[] + */ + private const array LISTENER_ATTRIBUTES = [ + SymfonyAttribute::AS_EVENT_LISTENER, + // Symfony Workflow attributes (Symfony 7.1+) + SymfonyAttribute::AS_ANNOUNCE_LISTENER, + SymfonyAttribute::AS_COMPLETED_LISTENER, + SymfonyAttribute::AS_ENTER_LISTENER, + SymfonyAttribute::AS_ENTERED_LISTENER, + SymfonyAttribute::AS_GUARD_LISTENER, + SymfonyAttribute::AS_LEAVE_LISTENER, + SymfonyAttribute::AS_TRANSITION_LISTENER, + ]; + /** * @var EventNameToClassAndConstant[] */ @@ -172,19 +187,7 @@ private function changeListenerToSubscriberWithMethods(Class_ $class, array $eve */ private function hasAsListenerAttribute(Class_ $class): bool { - $listenerAttributes = [ - SymfonyAttribute::AS_EVENT_LISTENER, - // Symfony Workflow attributes (Symfony 7.1+) - SymfonyAttribute::AS_ANNOUNCE_LISTENER, - SymfonyAttribute::AS_COMPLETED_LISTENER, - SymfonyAttribute::AS_ENTER_LISTENER, - SymfonyAttribute::AS_ENTERED_LISTENER, - SymfonyAttribute::AS_GUARD_LISTENER, - SymfonyAttribute::AS_LEAVE_LISTENER, - SymfonyAttribute::AS_TRANSITION_LISTENER, - ]; - - foreach ($listenerAttributes as $attribute) { + foreach (self::LISTENER_ATTRIBUTES as $attribute) { if ($this->phpAttributeAnalyzer->hasPhpAttribute($class, $attribute)) { return true; } From f2325fb3fdd35c659ae711776cdf296cbd826659 Mon Sep 17 00:00:00 2001 From: DenisD Date: Thu, 5 Mar 2026 20:18:26 +0300 Subject: [PATCH 3/3] fix undefined $listenerAttributes, use self::LISTENER_ATTRIBUTES --- .../Rector/Class_/EventListenerToEventSubscriberRector.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector.php b/rules/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector.php index 9eb20c86c..535078598 100644 --- a/rules/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector.php +++ b/rules/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector.php @@ -198,7 +198,7 @@ private function hasAsListenerAttribute(Class_ $class): bool continue; } - foreach ($listenerAttributes as $attribute) { + foreach (self::LISTENER_ATTRIBUTES as $attribute) { if ($this->phpAttributeAnalyzer->hasPhpAttribute($classMethod, $attribute)) { return true; }