rector/vendor/rector/rector-symfony/src/Rector/Class_/EventListenerToEventSubscriberRector.php
Tomas Votruba 8afa5f961e Updated Rector to commit 074b1781b3fa7c7e23470c57ab85c0bd5fe0bfd2
074b1781b3 Remove RemoveUnusedVariableAssignRector, as could be breaking code in order of excution and hard to do reliable (#3793)
2023-05-11 07:56:32 +00:00

163 lines
6.3 KiB
PHP

<?php
declare (strict_types=1);
namespace Rector\Symfony\Rector\Class_;
use RectorPrefix202305\Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt\Class_;
use Rector\Core\Rector\AbstractRector;
use Rector\Symfony\ApplicationMetadata\ListenerServiceDefinitionProvider;
use Rector\Symfony\NodeAnalyzer\ClassAnalyzer;
use Rector\Symfony\NodeFactory\GetSubscribedEventsClassMethodFactory;
use Rector\Symfony\ValueObject\EventNameToClassAndConstant;
use Rector\Symfony\ValueObject\ServiceDefinition;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \Rector\Symfony\Tests\Rector\Class_\EventListenerToEventSubscriberRector\EventListenerToEventSubscriberRectorTest
*/
final class EventListenerToEventSubscriberRector extends AbstractRector
{
/**
* @var string
*/
private const EVENT_SUBSCRIBER_INTERFACE = 'Symfony\\Component\\EventDispatcher\\EventSubscriberInterface';
/**
* @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
*/
private const LISTENER_MATCH_REGEX = '#^(.*?)(Listener)?$#';
/**
* @var EventNameToClassAndConstant[]
*/
private $eventNamesToClassConstants = [];
/**
* @readonly
* @var \Rector\Symfony\ApplicationMetadata\ListenerServiceDefinitionProvider
*/
private $listenerServiceDefinitionProvider;
/**
* @readonly
* @var \Rector\Symfony\NodeFactory\GetSubscribedEventsClassMethodFactory
*/
private $getSubscribedEventsClassMethodFactory;
/**
* @readonly
* @var \Rector\Symfony\NodeAnalyzer\ClassAnalyzer
*/
private $classAnalyzer;
public function __construct(ListenerServiceDefinitionProvider $listenerServiceDefinitionProvider, GetSubscribedEventsClassMethodFactory $getSubscribedEventsClassMethodFactory, ClassAnalyzer $classAnalyzer)
{
$this->listenerServiceDefinitionProvider = $listenerServiceDefinitionProvider;
$this->getSubscribedEventsClassMethodFactory = $getSubscribedEventsClassMethodFactory;
$this->classAnalyzer = $classAnalyzer;
$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.controller_arguments', self::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'),
// 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'),
];
}
public function getRuleDefinition() : RuleDefinition
{
return new RuleDefinition('Change Symfony Event listener class to Event Subscriber based on configuration in service.yaml file', [new CodeSample(<<<'CODE_SAMPLE'
class SomeListener
{
public function methodToBeCalled()
{
}
}
// in config.yaml
services:
SomeListener:
tags:
- { name: kernel.event_listener, event: 'some_event', method: 'methodToBeCalled' }
CODE_SAMPLE
, <<<'CODE_SAMPLE'
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class SomeEventSubscriber implements EventSubscriberInterface
{
/**
* @return string[]
*/
public static function getSubscribedEvents(): array
{
return ['some_event' => 'methodToBeCalled'];
}
public function methodToBeCalled()
{
}
}
CODE_SAMPLE
)]);
}
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes() : array
{
return [Class_::class];
}
/**
* @param Class_ $node
*/
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;
}
// there must be event dispatcher in the application
$listenerClassesToEventsToMethods = $this->listenerServiceDefinitionProvider->extract();
if ($listenerClassesToEventsToMethods === []) {
return null;
}
$className = $this->getName($node);
if (!isset($listenerClassesToEventsToMethods[$className])) {
return null;
}
$this->changeListenerToSubscriberWithMethods($node, $listenerClassesToEventsToMethods[$className]);
return $node;
}
/**
* @param array<string, ServiceDefinition[]> $eventsToMethods
*/
private function changeListenerToSubscriberWithMethods(Class_ $class, array $eventsToMethods) : void
{
$class->implements[] = new FullyQualified(self::EVENT_SUBSCRIBER_INTERFACE);
$classShortName = $this->nodeNameResolver->getShortName($class);
// remove suffix
$classShortName = Strings::replace($classShortName, self::LISTENER_MATCH_REGEX, '$1');
$class->name = new Identifier($classShortName . 'EventSubscriber');
$classMethod = $this->getSubscribedEventsClassMethodFactory->createFromServiceDefinitionsAndEventsToMethods($eventsToMethods, $this->eventNamesToClassConstants);
$class->stmts[] = $classMethod;
}
}