Skip to content

Commit 29a1abf

Browse files
authored
Fill listener method name, based on kernel.x event name in EventListenerToEventSubscriberRectory (#694)
* add test fixture for event without method * Fill listener method name, based on kernel.x event name
1 parent 917a527 commit 29a1abf

File tree

7 files changed

+79
-49
lines changed

7 files changed

+79
-49
lines changed

rules-tests/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/some_listener.php.inc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class SomeEventSubscriber implements \Symfony\Component\EventDispatcher\EventSub
2525
*/
2626
public static function getSubscribedEvents(): array
2727
{
28-
return ['some_event' => 'methodToBeCalled'];
28+
return ['some_event' => 'methodToBeCalled', \Symfony\Component\HttpKernel\KernelEvents::EXCEPTION => 'onKernelException'];
2929
}
3030
}
3131

rules-tests/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector/config/listener_services.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<services>
44
<service id="first_listener" class="Rector\Symfony\Tests\CodeQuality\Rector\Class_\EventListenerToEventSubscriberRector\Fixture\SomeListener">
55
<tag name="kernel.event_listener" event="some_event" method="methodToBeCalled"/>
6+
<tag name="kernel.event_listener" event="kernel.exception"/>
67
</service>
78

89
<service id="second_listener" class="Rector\Symfony\Tests\CodeQuality\Rector\Class_\EventListenerToEventSubscriberRector\Fixture\WithPriorityListener">

rules/CodeQuality/Rector/Class_/EventListenerToEventSubscriberRector.php

Lines changed: 39 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
use Rector\Php80\NodeAnalyzer\PhpAttributeAnalyzer;
1313
use Rector\Rector\AbstractRector;
1414
use Rector\Symfony\ApplicationMetadata\ListenerServiceDefinitionProvider;
15+
use Rector\Symfony\Enum\SymfonyAttribute;
16+
use Rector\Symfony\Enum\SymfonyClass;
1517
use Rector\Symfony\NodeAnalyzer\ClassAnalyzer;
1618
use Rector\Symfony\NodeFactory\GetSubscribedEventsClassMethodFactory;
1719
use Rector\Symfony\ValueObject\EventNameToClassAndConstant;
@@ -24,26 +26,6 @@
2426
*/
2527
final class EventListenerToEventSubscriberRector extends AbstractRector
2628
{
27-
/**
28-
* @var string
29-
*/
30-
private const EVENT_SUBSCRIBER_INTERFACE = 'Symfony\Component\EventDispatcher\EventSubscriberInterface';
31-
32-
/**
33-
* @var string
34-
*/
35-
private const EVENT_LISTENER_ATTRIBUTE = 'Symfony\Component\EventDispatcher\Attribute\AsEventListener';
36-
37-
/**
38-
* @var string
39-
*/
40-
private const KERNEL_EVENTS_CLASS = 'Symfony\Component\HttpKernel\KernelEvents';
41-
42-
/**
43-
* @var string
44-
*/
45-
private const CONSOLE_EVENTS_CLASS = 'Symfony\Component\Console\ConsoleEvents';
46-
4729
/**
4830
* @var string
4931
* @changelog https://regex101.com/r/qiHZ4T/1
@@ -63,22 +45,26 @@ public function __construct(
6345
) {
6446
$this->eventNamesToClassConstants = [
6547
// kernel events
66-
new EventNameToClassAndConstant('kernel.request', self::KERNEL_EVENTS_CLASS, 'REQUEST'),
67-
new EventNameToClassAndConstant('kernel.exception', self::KERNEL_EVENTS_CLASS, 'EXCEPTION'),
68-
new EventNameToClassAndConstant('kernel.view', self::KERNEL_EVENTS_CLASS, 'VIEW'),
69-
new EventNameToClassAndConstant('kernel.controller', self::KERNEL_EVENTS_CLASS, 'CONTROLLER'),
48+
new EventNameToClassAndConstant('kernel.request', SymfonyClass::KERNEL_EVENTS_CLASS, 'REQUEST'),
49+
new EventNameToClassAndConstant('kernel.exception', SymfonyClass::KERNEL_EVENTS_CLASS, 'EXCEPTION'),
50+
new EventNameToClassAndConstant('kernel.view', SymfonyClass::KERNEL_EVENTS_CLASS, 'VIEW'),
51+
new EventNameToClassAndConstant('kernel.controller', SymfonyClass::KERNEL_EVENTS_CLASS, 'CONTROLLER'),
7052
new EventNameToClassAndConstant(
7153
'kernel.controller_arguments',
72-
self::KERNEL_EVENTS_CLASS,
54+
SymfonyClass::KERNEL_EVENTS_CLASS,
7355
'CONTROLLER_ARGUMENTS'
7456
),
75-
new EventNameToClassAndConstant('kernel.response', self::KERNEL_EVENTS_CLASS, 'RESPONSE'),
76-
new EventNameToClassAndConstant('kernel.terminate', self::KERNEL_EVENTS_CLASS, 'TERMINATE'),
77-
new EventNameToClassAndConstant('kernel.finish_request', self::KERNEL_EVENTS_CLASS, 'FINISH_REQUEST'),
57+
new EventNameToClassAndConstant('kernel.response', SymfonyClass::KERNEL_EVENTS_CLASS, 'RESPONSE'),
58+
new EventNameToClassAndConstant('kernel.terminate', SymfonyClass::KERNEL_EVENTS_CLASS, 'TERMINATE'),
59+
new EventNameToClassAndConstant(
60+
'kernel.finish_request',
61+
SymfonyClass::KERNEL_EVENTS_CLASS,
62+
'FINISH_REQUEST'
63+
),
7864
// console events
79-
new EventNameToClassAndConstant('console.command', self::CONSOLE_EVENTS_CLASS, 'COMMAND'),
80-
new EventNameToClassAndConstant('console.terminate', self::CONSOLE_EVENTS_CLASS, 'TERMINATE'),
81-
new EventNameToClassAndConstant('console.error', self::CONSOLE_EVENTS_CLASS, 'ERROR'),
65+
new EventNameToClassAndConstant('console.command', SymfonyClass::CONSOLE_EVENTS_CLASS, 'COMMAND'),
66+
new EventNameToClassAndConstant('console.terminate', SymfonyClass::CONSOLE_EVENTS_CLASS, 'TERMINATE'),
67+
new EventNameToClassAndConstant('console.error', SymfonyClass::CONSOLE_EVENTS_CLASS, 'ERROR'),
8268
];
8369
}
8470

@@ -139,17 +125,7 @@ public function getNodeTypes(): array
139125
*/
140126
public function refactor(Node $node): ?Node
141127
{
142-
// anonymous class
143-
if (! $node->name instanceof Identifier) {
144-
return null;
145-
}
146-
147-
// is already a subscriber
148-
if ($this->classAnalyzer->hasImplements($node, 'Symfony\Component\EventDispatcher\EventSubscriberInterface')) {
149-
return null;
150-
}
151-
152-
if ($this->hasAsListenerAttribute($node)) {
128+
if ($this->shouldSkipClass($node)) {
153129
return null;
154130
}
155131

@@ -173,7 +149,7 @@ public function refactor(Node $node): ?Node
173149
*/
174150
private function changeListenerToSubscriberWithMethods(Class_ $class, array $eventsToMethods): void
175151
{
176-
$class->implements[] = new FullyQualified(self::EVENT_SUBSCRIBER_INTERFACE);
152+
$class->implements[] = new FullyQualified(SymfonyClass::EVENT_SUBSCRIBER_INTERFACE);
177153

178154
$classShortName = $this->nodeNameResolver->getShortName($class);
179155

@@ -194,7 +170,7 @@ private function changeListenerToSubscriberWithMethods(Class_ $class, array $eve
194170
*/
195171
private function hasAsListenerAttribute(Class_ $class): bool
196172
{
197-
if ($this->phpAttributeAnalyzer->hasPhpAttribute($class, self::EVENT_LISTENER_ATTRIBUTE)) {
173+
if ($this->phpAttributeAnalyzer->hasPhpAttribute($class, SymfonyAttribute::EVENT_LISTENER_ATTRIBUTE)) {
198174
return true;
199175
}
200176

@@ -203,11 +179,29 @@ private function hasAsListenerAttribute(Class_ $class): bool
203179
continue;
204180
}
205181

206-
if ($this->phpAttributeAnalyzer->hasPhpAttribute($classMethod, self::EVENT_LISTENER_ATTRIBUTE)) {
182+
if ($this->phpAttributeAnalyzer->hasPhpAttribute(
183+
$classMethod,
184+
SymfonyAttribute::EVENT_LISTENER_ATTRIBUTE
185+
)) {
207186
return true;
208187
}
209188
}
210189

211190
return false;
212191
}
192+
193+
private function shouldSkipClass(Class_ $class): bool
194+
{
195+
// anonymous class
196+
if ($class->isAnonymous()) {
197+
return true;
198+
}
199+
200+
// is already a subscriber
201+
if ($this->classAnalyzer->hasImplements($class, SymfonyClass::EVENT_SUBSCRIBER_INTERFACE)) {
202+
return true;
203+
}
204+
205+
return $this->hasAsListenerAttribute($class);
206+
}
213207
}

src/ApplicationMetadata/ListenerServiceDefinitionProvider.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,16 @@ public function extract(): array
5353
}
5454

5555
$eventName = $tag->getEvent();
56+
57+
if ($tag->getMethod() === '') {
58+
// fill method based on the event
59+
if (str_starts_with($tag->getEvent(), 'kernel.')) {
60+
[, $event] = explode('.', $tag->getEvent());
61+
$methodName = 'onKernel' . ucfirst($event);
62+
$tag->changeMethod($methodName);
63+
}
64+
}
65+
5666
$this->listenerClassesToEvents[$eventListener->getClass()][$eventName][] = $eventListener;
5767
}
5868
}

src/Enum/SymfonyAttribute.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,9 @@ final class SymfonyAttribute
1010
* @var string
1111
*/
1212
public const AUTOWIRE = 'Symfony\Component\DependencyInjection\Attribute\Autowire';
13+
14+
/**
15+
* @var string
16+
*/
17+
public const EVENT_LISTENER_ATTRIBUTE = 'Symfony\Component\EventDispatcher\Attribute\AsEventListener';
1318
}

src/Enum/SymfonyClass.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,19 @@ final class SymfonyClass
4545
* @var string
4646
*/
4747
public const SERIALIZER_INTERFACE = 'JMS\Serializer\SerializerInterface';
48+
49+
/**
50+
* @var string
51+
*/
52+
public const KERNEL_EVENTS_CLASS = 'Symfony\Component\HttpKernel\KernelEvents';
53+
54+
/**
55+
* @var string
56+
*/
57+
public const CONSOLE_EVENTS_CLASS = 'Symfony\Component\Console\ConsoleEvents';
58+
59+
/**
60+
* @var string
61+
*/
62+
public const EVENT_SUBSCRIBER_INTERFACE = 'Symfony\Component\EventDispatcher\EventSubscriberInterface';
4863
}

src/ValueObject/Tag/EventListenerTag.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66

77
use Rector\Symfony\Contract\Tag\TagInterface;
88

9-
final readonly class EventListenerTag implements TagInterface
9+
final class EventListenerTag implements TagInterface
1010
{
1111
public function __construct(
12-
private string $event,
12+
private readonly string $event,
1313
private string $method,
14-
private int $priority
14+
private readonly int $priority
1515
) {
1616
}
1717

@@ -46,4 +46,9 @@ public function getData(): array
4646
'event' => $this->event,
4747
];
4848
}
49+
50+
public function changeMethod(string $methodName): void
51+
{
52+
$this->method = $methodName;
53+
}
4954
}

0 commit comments

Comments
 (0)