From 88856a3acf71079ec90562f2ede917c3b1861781 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sun, 8 Dec 2024 18:42:24 +0100 Subject: [PATCH 1/2] add test fixture for event without method --- .../Fixture/some_listener.php.inc | 2 +- .../config/listener_services.xml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/rules-tests/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/some_listener.php.inc b/rules-tests/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/some_listener.php.inc index 64a15497..4a722ff8 100644 --- a/rules-tests/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/some_listener.php.inc +++ b/rules-tests/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/some_listener.php.inc @@ -25,7 +25,7 @@ class SomeEventSubscriber implements \Symfony\Component\EventDispatcher\EventSub */ public static function getSubscribedEvents(): array { - return ['some_event' => 'methodToBeCalled']; + return ['some_event' => 'methodToBeCalled', \Symfony\Component\HttpKernel\KernelEvents::EXCEPTION => 'onKernelException']; } } diff --git a/rules-tests/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector/config/listener_services.xml b/rules-tests/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector/config/listener_services.xml index 52b1fa60..5d988ce1 100644 --- a/rules-tests/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector/config/listener_services.xml +++ b/rules-tests/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector/config/listener_services.xml @@ -3,6 +3,7 @@ + From f3edef9382740ae7240dd04313ca891bb7856834 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sun, 8 Dec 2024 18:43:34 +0100 Subject: [PATCH 2/2] Fill listener method name, based on kernel.x event name --- .../EventListenerToEventSubscriberRector.php | 84 +++++++++---------- .../ListenerServiceDefinitionProvider.php | 10 +++ src/Enum/SymfonyAttribute.php | 5 ++ src/Enum/SymfonyClass.php | 15 ++++ src/ValueObject/Tag/EventListenerTag.php | 11 ++- 5 files changed, 77 insertions(+), 48 deletions(-) diff --git a/rules/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector.php b/rules/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector.php index 5e78c8e2..cae412d0 100644 --- a/rules/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector.php +++ b/rules/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector.php @@ -12,6 +12,8 @@ use Rector\Php80\NodeAnalyzer\PhpAttributeAnalyzer; use Rector\Rector\AbstractRector; use Rector\Symfony\ApplicationMetadata\ListenerServiceDefinitionProvider; +use Rector\Symfony\Enum\SymfonyAttribute; +use Rector\Symfony\Enum\SymfonyClass; use Rector\Symfony\NodeAnalyzer\ClassAnalyzer; use Rector\Symfony\NodeFactory\GetSubscribedEventsClassMethodFactory; use Rector\Symfony\ValueObject\EventNameToClassAndConstant; @@ -24,26 +26,6 @@ */ final class EventListenerToEventSubscriberRector extends AbstractRector { - /** - * @var string - */ - private const EVENT_SUBSCRIBER_INTERFACE = 'Symfony\Component\EventDispatcher\EventSubscriberInterface'; - - /** - * @var string - */ - private const EVENT_LISTENER_ATTRIBUTE = 'Symfony\Component\EventDispatcher\Attribute\AsEventListener'; - - /** - * @var string - */ - private const KERNEL_EVENTS_CLASS = 'Symfony\Component\HttpKernel\KernelEvents'; - - /** - * @var string - */ - private const CONSOLE_EVENTS_CLASS = 'Symfony\Component\Console\ConsoleEvents'; - /** * @var string * @changelog https://regex101.com/r/qiHZ4T/1 @@ -63,22 +45,26 @@ public function __construct( ) { $this->eventNamesToClassConstants = [ // kernel events - new EventNameToClassAndConstant('kernel.request', self::KERNEL_EVENTS_CLASS, 'REQUEST'), - new EventNameToClassAndConstant('kernel.exception', self::KERNEL_EVENTS_CLASS, 'EXCEPTION'), - new EventNameToClassAndConstant('kernel.view', self::KERNEL_EVENTS_CLASS, 'VIEW'), - new EventNameToClassAndConstant('kernel.controller', self::KERNEL_EVENTS_CLASS, 'CONTROLLER'), + new EventNameToClassAndConstant('kernel.request', SymfonyClass::KERNEL_EVENTS_CLASS, 'REQUEST'), + new EventNameToClassAndConstant('kernel.exception', SymfonyClass::KERNEL_EVENTS_CLASS, 'EXCEPTION'), + new EventNameToClassAndConstant('kernel.view', SymfonyClass::KERNEL_EVENTS_CLASS, 'VIEW'), + new EventNameToClassAndConstant('kernel.controller', SymfonyClass::KERNEL_EVENTS_CLASS, 'CONTROLLER'), new EventNameToClassAndConstant( 'kernel.controller_arguments', - self::KERNEL_EVENTS_CLASS, + SymfonyClass::KERNEL_EVENTS_CLASS, 'CONTROLLER_ARGUMENTS' ), - new EventNameToClassAndConstant('kernel.response', self::KERNEL_EVENTS_CLASS, 'RESPONSE'), - new EventNameToClassAndConstant('kernel.terminate', self::KERNEL_EVENTS_CLASS, 'TERMINATE'), - new EventNameToClassAndConstant('kernel.finish_request', self::KERNEL_EVENTS_CLASS, 'FINISH_REQUEST'), + new EventNameToClassAndConstant('kernel.response', SymfonyClass::KERNEL_EVENTS_CLASS, 'RESPONSE'), + new EventNameToClassAndConstant('kernel.terminate', SymfonyClass::KERNEL_EVENTS_CLASS, 'TERMINATE'), + new EventNameToClassAndConstant( + 'kernel.finish_request', + SymfonyClass::KERNEL_EVENTS_CLASS, + 'FINISH_REQUEST' + ), // console events - new EventNameToClassAndConstant('console.command', self::CONSOLE_EVENTS_CLASS, 'COMMAND'), - new EventNameToClassAndConstant('console.terminate', self::CONSOLE_EVENTS_CLASS, 'TERMINATE'), - new EventNameToClassAndConstant('console.error', self::CONSOLE_EVENTS_CLASS, 'ERROR'), + new EventNameToClassAndConstant('console.command', SymfonyClass::CONSOLE_EVENTS_CLASS, 'COMMAND'), + new EventNameToClassAndConstant('console.terminate', SymfonyClass::CONSOLE_EVENTS_CLASS, 'TERMINATE'), + new EventNameToClassAndConstant('console.error', SymfonyClass::CONSOLE_EVENTS_CLASS, 'ERROR'), ]; } @@ -139,17 +125,7 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - // anonymous class - if (! $node->name instanceof Identifier) { - return null; - } - - // is already a subscriber - if ($this->classAnalyzer->hasImplements($node, 'Symfony\Component\EventDispatcher\EventSubscriberInterface')) { - return null; - } - - if ($this->hasAsListenerAttribute($node)) { + if ($this->shouldSkipClass($node)) { return null; } @@ -173,7 +149,7 @@ public function refactor(Node $node): ?Node */ private function changeListenerToSubscriberWithMethods(Class_ $class, array $eventsToMethods): void { - $class->implements[] = new FullyQualified(self::EVENT_SUBSCRIBER_INTERFACE); + $class->implements[] = new FullyQualified(SymfonyClass::EVENT_SUBSCRIBER_INTERFACE); $classShortName = $this->nodeNameResolver->getShortName($class); @@ -194,7 +170,7 @@ private function changeListenerToSubscriberWithMethods(Class_ $class, array $eve */ private function hasAsListenerAttribute(Class_ $class): bool { - if ($this->phpAttributeAnalyzer->hasPhpAttribute($class, self::EVENT_LISTENER_ATTRIBUTE)) { + if ($this->phpAttributeAnalyzer->hasPhpAttribute($class, SymfonyAttribute::EVENT_LISTENER_ATTRIBUTE)) { return true; } @@ -203,11 +179,29 @@ private function hasAsListenerAttribute(Class_ $class): bool continue; } - if ($this->phpAttributeAnalyzer->hasPhpAttribute($classMethod, self::EVENT_LISTENER_ATTRIBUTE)) { + if ($this->phpAttributeAnalyzer->hasPhpAttribute( + $classMethod, + SymfonyAttribute::EVENT_LISTENER_ATTRIBUTE + )) { return true; } } return false; } + + private function shouldSkipClass(Class_ $class): bool + { + // anonymous class + if ($class->isAnonymous()) { + return true; + } + + // is already a subscriber + if ($this->classAnalyzer->hasImplements($class, SymfonyClass::EVENT_SUBSCRIBER_INTERFACE)) { + return true; + } + + return $this->hasAsListenerAttribute($class); + } } diff --git a/src/ApplicationMetadata/ListenerServiceDefinitionProvider.php b/src/ApplicationMetadata/ListenerServiceDefinitionProvider.php index 0b84ccb8..8da9a2ae 100644 --- a/src/ApplicationMetadata/ListenerServiceDefinitionProvider.php +++ b/src/ApplicationMetadata/ListenerServiceDefinitionProvider.php @@ -53,6 +53,16 @@ public function extract(): array } $eventName = $tag->getEvent(); + + if ($tag->getMethod() === '') { + // fill method based on the event + if (str_starts_with($tag->getEvent(), 'kernel.')) { + [, $event] = explode('.', $tag->getEvent()); + $methodName = 'onKernel' . ucfirst($event); + $tag->changeMethod($methodName); + } + } + $this->listenerClassesToEvents[$eventListener->getClass()][$eventName][] = $eventListener; } } diff --git a/src/Enum/SymfonyAttribute.php b/src/Enum/SymfonyAttribute.php index 59012b01..6e4404f5 100644 --- a/src/Enum/SymfonyAttribute.php +++ b/src/Enum/SymfonyAttribute.php @@ -10,4 +10,9 @@ final class SymfonyAttribute * @var string */ public const AUTOWIRE = 'Symfony\Component\DependencyInjection\Attribute\Autowire'; + + /** + * @var string + */ + public const EVENT_LISTENER_ATTRIBUTE = 'Symfony\Component\EventDispatcher\Attribute\AsEventListener'; } diff --git a/src/Enum/SymfonyClass.php b/src/Enum/SymfonyClass.php index 99cdc46a..1c845e65 100644 --- a/src/Enum/SymfonyClass.php +++ b/src/Enum/SymfonyClass.php @@ -45,4 +45,19 @@ final class SymfonyClass * @var string */ public const SERIALIZER_INTERFACE = 'JMS\Serializer\SerializerInterface'; + + /** + * @var string + */ + public const KERNEL_EVENTS_CLASS = 'Symfony\Component\HttpKernel\KernelEvents'; + + /** + * @var string + */ + public const CONSOLE_EVENTS_CLASS = 'Symfony\Component\Console\ConsoleEvents'; + + /** + * @var string + */ + public const EVENT_SUBSCRIBER_INTERFACE = 'Symfony\Component\EventDispatcher\EventSubscriberInterface'; } diff --git a/src/ValueObject/Tag/EventListenerTag.php b/src/ValueObject/Tag/EventListenerTag.php index 0ccc0006..e3ed6e90 100644 --- a/src/ValueObject/Tag/EventListenerTag.php +++ b/src/ValueObject/Tag/EventListenerTag.php @@ -6,12 +6,12 @@ use Rector\Symfony\Contract\Tag\TagInterface; -final readonly class EventListenerTag implements TagInterface +final class EventListenerTag implements TagInterface { public function __construct( - private string $event, + private readonly string $event, private string $method, - private int $priority + private readonly int $priority ) { } @@ -46,4 +46,9 @@ public function getData(): array 'event' => $this->event, ]; } + + public function changeMethod(string $methodName): void + { + $this->method = $methodName; + } }