Skip to content

Commit

Permalink
Fill listener method name, based on kernel.x event name in EventListe…
Browse files Browse the repository at this point in the history
…nerToEventSubscriberRectory (#694)

* add test fixture for event without method

* Fill listener method name, based on kernel.x event name
  • Loading branch information
TomasVotruba authored Dec 8, 2024
1 parent 917a527 commit 29a1abf
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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'];
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<services>
<service id="first_listener" class="Rector\Symfony\Tests\CodeQuality\Rector\Class_\EventListenerToEventSubscriberRector\Fixture\SomeListener">
<tag name="kernel.event_listener" event="some_event" method="methodToBeCalled"/>
<tag name="kernel.event_listener" event="kernel.exception"/>
</service>

<service id="second_listener" class="Rector\Symfony\Tests\CodeQuality\Rector\Class_\EventListenerToEventSubscriberRector\Fixture\WithPriorityListener">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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'),
];
}

Expand Down Expand Up @@ -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;
}

Expand All @@ -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);

Expand All @@ -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;
}

Expand All @@ -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);
}
}
10 changes: 10 additions & 0 deletions src/ApplicationMetadata/ListenerServiceDefinitionProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/Enum/SymfonyAttribute.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';
}
15 changes: 15 additions & 0 deletions src/Enum/SymfonyClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';
}
11 changes: 8 additions & 3 deletions src/ValueObject/Tag/EventListenerTag.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
) {
}

Expand Down Expand Up @@ -46,4 +46,9 @@ public function getData(): array
'event' => $this->event,
];
}

public function changeMethod(string $methodName): void
{
$this->method = $methodName;
}
}

0 comments on commit 29a1abf

Please sign in to comment.