diff --git a/composer.json b/composer.json index a1535eac633a..4c8a76c41c2a 100644 --- a/composer.json +++ b/composer.json @@ -65,6 +65,8 @@ "phpstan/phpstan-nette": "^0.12.12", "phpunit/phpunit": "^9.5", "sebastian/diff": "^4.0.4", + "symfony/security-core": "^5.2", + "symfony/security-http": "^5.2", "symplify/changelog-linker": "^9.0.34", "symplify/coding-standard": "^9.0.34", "symplify/easy-coding-standard": "^9.0.34", diff --git a/config/set/symfony51.php b/config/set/symfony51.php index ce3b3dccfe4f..e02a854494c8 100644 --- a/config/set/symfony51.php +++ b/config/set/symfony51.php @@ -147,6 +147,6 @@ // @see https://github.com/symfony/symfony/pull/35858 RenameStringRector::STRING_CHANGES => [ 'ROLE_PREVIOUS_ADMIN' => 'IS_IMPERSONATOR', - ] + ], ]]); }; diff --git a/packages/rector-generator/src/TemplateVariablesFactory.php b/packages/rector-generator/src/TemplateVariablesFactory.php index 657e1f42d2bb..4ccae1a878d3 100644 --- a/packages/rector-generator/src/TemplateVariablesFactory.php +++ b/packages/rector-generator/src/TemplateVariablesFactory.php @@ -127,7 +127,7 @@ private function createCodeForDefinition(string $code): string { if (Strings::contains($code, PHP_EOL)) { // multi lines - return sprintf("<<<'PHP'%s%s%sPHP%s", PHP_EOL, $code, PHP_EOL, PHP_EOL); + return sprintf("<<<'CODE_SAMPLE'%s%s%sCODE_SAMPLE%s", PHP_EOL, $code, PHP_EOL, PHP_EOL); } // single line diff --git a/rules/dead-code/src/Rector/Class_/RemoveUselessJustForSakeInterfaceRector.php b/rules/dead-code/src/Rector/Class_/RemoveUselessJustForSakeInterfaceRector.php index 6a4c3e6f8a77..753e97133fd0 100644 --- a/rules/dead-code/src/Rector/Class_/RemoveUselessJustForSakeInterfaceRector.php +++ b/rules/dead-code/src/Rector/Class_/RemoveUselessJustForSakeInterfaceRector.php @@ -154,8 +154,11 @@ function (string $className) use ($interfaceName): bool { ); } - private function removeOrReplaceImplementedInterface(string $implementedInterfaceName, Class_ $class, int $key): void - { + private function removeOrReplaceImplementedInterface( + string $implementedInterfaceName, + Class_ $class, + int $key + ): void { $parentInterface = $this->getParentInterfaceIfFound($implementedInterfaceName); if ($parentInterface !== null) { $class->implements[$key] = new FullyQualified($parentInterface); diff --git a/rules/nette-kdyby/src/NodeManipulator/ListeningClassMethodArgumentManipulator.php b/rules/nette-kdyby/src/NodeManipulator/ListeningClassMethodArgumentManipulator.php index cc2bdbfc7aa7..ffa8364d1a5f 100644 --- a/rules/nette-kdyby/src/NodeManipulator/ListeningClassMethodArgumentManipulator.php +++ b/rules/nette-kdyby/src/NodeManipulator/ListeningClassMethodArgumentManipulator.php @@ -116,16 +116,7 @@ public function change(array $classMethodsByEventClass, ?EventAndListenerTree $e } } - private function changeClassParamToEventClass(string $eventClass, ClassMethod $classMethod): void - { - $paramName = $this->classNaming->getVariableName($eventClass); - $eventVariable = new Variable($paramName); - - $param = new Param($eventVariable, null, new FullyQualified($eventClass)); - $classMethod->params = [$param]; - } - - private function isParamUsedInClassMethodBody(ClassMethod $classMethod, Param $param): bool + public function isParamUsedInClassMethodBody(ClassMethod $classMethod, Param $param): bool { return (bool) $this->betterNodeFinder->findFirst((array) $classMethod->stmts, function (Node $node) use ( $param @@ -138,6 +129,15 @@ private function isParamUsedInClassMethodBody(ClassMethod $classMethod, Param $p }); } + private function changeClassParamToEventClass(string $eventClass, ClassMethod $classMethod): void + { + $paramName = $this->classNaming->getVariableName($eventClass); + $eventVariable = new Variable($paramName); + + $param = new Param($eventVariable, null, new FullyQualified($eventClass)); + $classMethod->params = [$param]; + } + private function createEventGetterToVariableMethodCall( string $eventClass, Param $param, diff --git a/rules/symfony-code-quality/src/NodeFactory/EventReferenceFactory.php b/rules/symfony-code-quality/src/NodeFactory/EventReferenceFactory.php new file mode 100644 index 000000000000..be285769349c --- /dev/null +++ b/rules/symfony-code-quality/src/NodeFactory/EventReferenceFactory.php @@ -0,0 +1,49 @@ +nodeFactory = $nodeFactory; + } + + /** + * @param EventNameToClassAndConstant[] $eventNamesToClassConstants + * @return String_|ClassConstFetch + */ + public function createEventName(string $eventName, array $eventNamesToClassConstants): Node + { + if (class_exists($eventName)) { + return $this->nodeFactory->createClassConstReference($eventName); + } + + // is string a that could be caught in constant, e.g. KernelEvents? + foreach ($eventNamesToClassConstants as $eventNameToClassConstant) { + if ($eventNameToClassConstant->getEventName() !== $eventName) { + continue; + } + + return $this->nodeFactory->createClassConstFetch( + $eventNameToClassConstant->getEventClass(), + $eventNameToClassConstant->getEventConstant() + ); + } + + return new String_($eventName); + } +} diff --git a/rules/symfony-code-quality/src/NodeFactory/GetSubscriberEventsClassMethodFactory.php b/rules/symfony-code-quality/src/NodeFactory/GetSubscribedEventsClassMethodFactory.php similarity index 64% rename from rules/symfony-code-quality/src/NodeFactory/GetSubscriberEventsClassMethodFactory.php rename to rules/symfony-code-quality/src/NodeFactory/GetSubscribedEventsClassMethodFactory.php index 780a2f367a8c..a62485859b37 100644 --- a/rules/symfony-code-quality/src/NodeFactory/GetSubscriberEventsClassMethodFactory.php +++ b/rules/symfony-code-quality/src/NodeFactory/GetSubscribedEventsClassMethodFactory.php @@ -4,7 +4,6 @@ namespace Rector\SymfonyCodeQuality\NodeFactory; -use PhpParser\Node; use PhpParser\Node\Expr; use PhpParser\Node\Expr\Array_; use PhpParser\Node\Expr\ArrayItem; @@ -16,6 +15,7 @@ use PhpParser\Node\Stmt\Return_; use PHPStan\Type\ArrayType; use PHPStan\Type\MixedType; +use PHPStan\Type\StringType; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger; use Rector\Core\Php\PhpVersionProvider; @@ -27,9 +27,15 @@ use Rector\Symfony\ValueObject\Tag; use Rector\Symfony\ValueObject\Tag\EventListenerTag; use Rector\SymfonyCodeQuality\ValueObject\EventNameToClassAndConstant; +use Rector\SymfonyCodeQuality\ValueObject\EventReferenceToMethodName; -final class GetSubscriberEventsClassMethodFactory +final class GetSubscribedEventsClassMethodFactory { + /** + * @var string + */ + private const GET_SUBSCRIBED_EVENTS_METHOD_NAME = 'getSubscribedEvents'; + /** * @var NodeFactory */ @@ -55,34 +61,70 @@ final class GetSubscriberEventsClassMethodFactory */ private $phpDocTypeChanger; + /** + * @var EventReferenceFactory + */ + private $eventReferenceFactory; + public function __construct( NodeFactory $nodeFactory, VisibilityManipulator $visibilityManipulator, PhpVersionProvider $phpVersionProvider, PhpDocInfoFactory $phpDocInfoFactory, - PhpDocTypeChanger $phpDocTypeChanger + PhpDocTypeChanger $phpDocTypeChanger, + EventReferenceFactory $eventReferenceFactory ) { $this->nodeFactory = $nodeFactory; $this->visibilityManipulator = $visibilityManipulator; $this->phpVersionProvider = $phpVersionProvider; $this->phpDocInfoFactory = $phpDocInfoFactory; $this->phpDocTypeChanger = $phpDocTypeChanger; + $this->eventReferenceFactory = $eventReferenceFactory; } /** - * @param array $eventsToMethods - * @param EventNameToClassAndConstant[] $eventNamesToClassConstants + * @param EventReferenceToMethodName[] $eventReferencesToMethodNames */ - public function createFromEventsToMethods(array $eventsToMethods, array $eventNamesToClassConstants): ClassMethod + public function create(array $eventReferencesToMethodNames): ClassMethod { - $getSubscribersClassMethod = $this->nodeFactory->createPublicMethod('getSubscribedEvents'); + $getSubscribersClassMethod = $this->createClassMethod(); $eventsToMethodsArray = new Array_(); - $this->visibilityManipulator->makeStatic($getSubscribersClassMethod); + foreach ($eventReferencesToMethodNames as $eventReferencesToMethodName) { + $eventsToMethodsArray->items[] = $this->createArrayItemFromMethodAndPriority( + null, + $eventReferencesToMethodName->getMethodName(), + $eventReferencesToMethodName->getClassConstFetch() + ); + } + + $getSubscribersClassMethod->stmts[] = new Return_($eventsToMethodsArray); + + $this->decorateClassMethodWithReturnType($getSubscribersClassMethod); + + return $getSubscribersClassMethod; + } + + /** + * @param array $eventsToMethods + * @param EventNameToClassAndConstant[] $eventNamesToClassConstants + */ + public function createFromServiceDefinitionsAndEventsToMethods( + array $eventsToMethods, + array $eventNamesToClassConstants + ): ClassMethod { + $getSubscribersClassMethod = $this->createClassMethod(); + + $eventsToMethodsArray = new Array_(); foreach ($eventsToMethods as $eventName => $methodNamesWithPriorities) { - $eventNameExpr = $this->createEventName($eventName, $eventNamesToClassConstants); + $eventNameExpr = $this->eventReferenceFactory->createEventName($eventName, $eventNamesToClassConstants); + + // just method name, without a priority + if (! is_array($methodNamesWithPriorities)) { + $methodNamesWithPriorities = [$methodNamesWithPriorities]; + } if (count($methodNamesWithPriorities) === 1) { $this->createSingleMethod( @@ -107,31 +149,6 @@ public function createFromEventsToMethods(array $eventsToMethods, array $eventNa return $getSubscribersClassMethod; } - /** - * @param EventNameToClassAndConstant[] $eventNamesToClassConstants - * @return String_|ClassConstFetch - */ - private function createEventName(string $eventName, array $eventNamesToClassConstants): Node - { - if (class_exists($eventName)) { - return $this->nodeFactory->createClassConstReference($eventName); - } - - // is string a that could be caught in constant, e.g. KernelEvents? - foreach ($eventNamesToClassConstants as $eventNameToClassConstant) { - if ($eventNameToClassConstant->getEventName() !== $eventName) { - continue; - } - - return $this->nodeFactory->createClassConstFetch( - $eventNameToClassConstant->getEventClass(), - $eventNameToClassConstant->getEventConstant() - ); - } - - return new String_($eventName); - } - /** * @param ClassConstFetch|String_ $expr * @param ServiceDefinition[] $methodNamesWithPriorities @@ -142,30 +159,13 @@ private function createSingleMethod( Expr $expr, Array_ $eventsToMethodsArray ): void { - - /** @var EventListenerTag[]|Tag[] $eventTags */ - $eventTags = $methodNamesWithPriorities[0]->getTags(); - foreach ($eventTags as $eventTag) { - if ($eventTag instanceof EventListenerTag && $eventTag->getEvent() === $eventName) { - $methodName = $eventTag->getMethod(); - $priority = $eventTag->getPriority(); - break; - } - } - - if (! isset($methodName, $priority)) { + $methodName = $this->resolveMethodName($methodNamesWithPriorities[0], $eventName); + $priority = $this->resolvePriority($methodNamesWithPriorities[0], $eventName); + if ($methodName === null) { return; } - if ($priority !== 0) { - $methodNameWithPriorityArray = new Array_(); - $methodNameWithPriorityArray->items[] = new ArrayItem(new String_($methodName)); - $methodNameWithPriorityArray->items[] = new ArrayItem(new LNumber((int) $priority)); - - $eventsToMethodsArray->items[] = new ArrayItem($methodNameWithPriorityArray, $expr); - } else { - $eventsToMethodsArray->items[] = new ArrayItem(new String_($methodName), $expr); - } + $eventsToMethodsArray->items[] = $this->createArrayItemFromMethodAndPriority($priority, $methodName, $expr); } /** @@ -192,13 +192,11 @@ private function createMultipleMethods( } $eventItems[] = $this->createEventItem($tag); - $alreadyUsedTags[] = $tag; } } $multipleMethodsArray = new Array_($eventItems); - $eventsToMethodsArray->items[] = new ArrayItem($multipleMethodsArray, $expr); } @@ -208,7 +206,7 @@ private function decorateClassMethodWithReturnType(ClassMethod $classMethod): vo $classMethod->returnType = new Identifier('array'); } - $returnType = new ArrayType(new MixedType(), new MixedType(true)); + $returnType = new ArrayType(new StringType(), new MixedType(true)); $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($classMethod); $this->phpDocTypeChanger->changeReturnType($phpDocInfo, $returnType); } @@ -237,4 +235,51 @@ private function createEventItem(EventListenerTag $eventListenerTag): ArrayItem return new ArrayItem(new String_($eventListenerTag->getMethod())); } + + private function resolveMethodName(ServiceDefinition $serviceDefinition, string $eventName): ?string + { + /** @var EventListenerTag[]|Tag[] $eventTags */ + $eventTags = $serviceDefinition->getTags(); + foreach ($eventTags as $eventTag) { + if ($eventTag instanceof EventListenerTag && $eventTag->getEvent() === $eventName) { + return $eventTag->getMethod(); + } + } + + return null; + } + + private function resolvePriority(ServiceDefinition $serviceDefinition, string $eventName): ?int + { + /** @var EventListenerTag[]|Tag[] $eventTags */ + $eventTags = $serviceDefinition->getTags(); + foreach ($eventTags as $eventTag) { + if ($eventTag instanceof EventListenerTag && $eventTag->getEvent() === $eventName) { + return $eventTag->getPriority(); + } + } + + return null; + } + + private function createClassMethod(): ClassMethod + { + $classMethod = $this->nodeFactory->createPublicMethod(self::GET_SUBSCRIBED_EVENTS_METHOD_NAME); + $this->visibilityManipulator->makeStatic($classMethod); + + return $classMethod; + } + + private function createArrayItemFromMethodAndPriority(?int $priority, string $methodName, Expr $expr): ArrayItem + { + if ($priority !== null && $priority !== 0) { + $methodNameWithPriorityArray = new Array_(); + $methodNameWithPriorityArray->items[] = new ArrayItem(new String_($methodName)); + $methodNameWithPriorityArray->items[] = new ArrayItem(new LNumber((int) $priority)); + + return new ArrayItem($methodNameWithPriorityArray, $expr); + } + + return new ArrayItem(new String_($methodName), $expr); + } } diff --git a/rules/symfony-code-quality/src/Rector/Class_/EventListenerToEventSubscriberRector.php b/rules/symfony-code-quality/src/Rector/Class_/EventListenerToEventSubscriberRector.php index ad41fcb93d70..a4f1352739e6 100644 --- a/rules/symfony-code-quality/src/Rector/Class_/EventListenerToEventSubscriberRector.php +++ b/rules/symfony-code-quality/src/Rector/Class_/EventListenerToEventSubscriberRector.php @@ -12,7 +12,7 @@ use Rector\Core\Rector\AbstractRector; use Rector\Symfony\ValueObject\ServiceDefinition; use Rector\SymfonyCodeQuality\ApplicationMetadata\ListenerServiceDefinitionProvider; -use Rector\SymfonyCodeQuality\NodeFactory\GetSubscriberEventsClassMethodFactory; +use Rector\SymfonyCodeQuality\NodeFactory\GetSubscribedEventsClassMethodFactory; use Rector\SymfonyCodeQuality\ValueObject\EventNameToClassAndConstant; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -54,13 +54,13 @@ final class EventListenerToEventSubscriberRector extends AbstractRector private $listenerServiceDefinitionProvider; /** - * @var GetSubscriberEventsClassMethodFactory + * @var GetSubscribedEventsClassMethodFactory */ - private $getSubscriberEventsClassMethodFactory; + private $getSubscribedEventsClassMethodFactory; public function __construct( ListenerServiceDefinitionProvider $listenerServiceDefinitionProvider, - GetSubscriberEventsClassMethodFactory $getSubscriberEventsClassMethodFactory + GetSubscribedEventsClassMethodFactory $getSubscriberEventsClassMethodFactory ) { $this->eventNamesToClassConstants = [ // kernel events @@ -82,7 +82,7 @@ public function __construct( new EventNameToClassAndConstant('console.error', self::CONSOLE_EVENTS_CLASS, 'ERROR'), ]; $this->listenerServiceDefinitionProvider = $listenerServiceDefinitionProvider; - $this->getSubscriberEventsClassMethodFactory = $getSubscriberEventsClassMethodFactory; + $this->getSubscribedEventsClassMethodFactory = $getSubscriberEventsClassMethodFactory; } public function getRuleDefinition(): RuleDefinition @@ -191,7 +191,7 @@ private function changeListenerToSubscriberWithMethods(Class_ $class, array $eve $class->name = new Identifier($classShortName . 'EventSubscriber'); - $classMethod = $this->getSubscriberEventsClassMethodFactory->createFromEventsToMethods( + $classMethod = $this->getSubscribedEventsClassMethodFactory->createFromServiceDefinitionsAndEventsToMethods( $eventsToMethods, $this->eventNamesToClassConstants ); diff --git a/rules/symfony-code-quality/src/ValueObject/EventReferenceToMethodName.php b/rules/symfony-code-quality/src/ValueObject/EventReferenceToMethodName.php new file mode 100644 index 000000000000..46cea57668b8 --- /dev/null +++ b/rules/symfony-code-quality/src/ValueObject/EventReferenceToMethodName.php @@ -0,0 +1,36 @@ +classConstFetch = $classConstFetch; + $this->methodName = $methodName; + } + + public function getClassConstFetch(): ClassConstFetch + { + return $this->classConstFetch; + } + + public function getMethodName(): string + { + return $this->methodName; + } +} diff --git a/rules/symfony5/config/config.php b/rules/symfony5/config/config.php new file mode 100644 index 000000000000..0dfdce123696 --- /dev/null +++ b/rules/symfony5/config/config.php @@ -0,0 +1,17 @@ +services(); + + $services->defaults() + ->public() + ->autowire() + ->autoconfigure(); + + $services->load('Rector\Symfony5\\', __DIR__ . '/../src') + ->exclude([__DIR__ . '/../src/Rector']); +}; diff --git a/rules/symfony5/src/NodeFactory/OnLogoutClassMethodFactory.php b/rules/symfony5/src/NodeFactory/OnLogoutClassMethodFactory.php new file mode 100644 index 000000000000..4d915cf37b4e --- /dev/null +++ b/rules/symfony5/src/NodeFactory/OnLogoutClassMethodFactory.php @@ -0,0 +1,120 @@ + + */ + private const PARAMETER_TO_GETTER_NAMES = [ + 'request' => 'getRequest', + 'response' => 'getResponse', + 'token' => 'getToken', + ]; + + /** + * @var NodeFactory + */ + private $nodeFactory; + + /** + * @var PhpVersionProvider + */ + private $phpVersionProvider; + + /** + * @var ListeningClassMethodArgumentManipulator + */ + private $listeningClassMethodArgumentManipulator; + + /** + * @var NodeNameResolver + */ + private $nodeNameResolver; + + public function __construct( + NodeFactory $nodeFactory, + PhpVersionProvider $phpVersionProvider, + ListeningClassMethodArgumentManipulator $listeningClassMethodArgumentManipulator, + NodeNameResolver $nodeNameResolver + ) { + $this->nodeFactory = $nodeFactory; + $this->phpVersionProvider = $phpVersionProvider; + $this->listeningClassMethodArgumentManipulator = $listeningClassMethodArgumentManipulator; + $this->nodeNameResolver = $nodeNameResolver; + } + + public function createFromLogoutClassMethod(ClassMethod $logoutClassMethod): ClassMethod + { + $classMethod = $this->nodeFactory->createPublicMethod('onLogout'); + + $logoutEventVariable = new Variable('logoutEvent'); + $classMethod->params[] = $this->createLogoutEventParam($logoutEventVariable); + + $usedParams = []; + foreach ($logoutClassMethod->params as $oldParam) { + if (! $this->listeningClassMethodArgumentManipulator->isParamUsedInClassMethodBody( + $logoutClassMethod, + $oldParam + )) { + continue; + } + + $usedParams[] = $oldParam; + } + + if ($this->phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::VOID_TYPE)) { + $classMethod->returnType = new Identifier('void'); + } + + $assignStmts = $this->createAssignStmts($usedParams, $logoutEventVariable); + $classMethod->stmts = array_merge($assignStmts, (array) $logoutClassMethod->stmts); + + return $classMethod; + } + + private function createLogoutEventParam(Variable $logoutEventVariable): Param + { + $param = new Param($logoutEventVariable); + $param->type = new FullyQualified('Symfony\Component\Security\Http\Event\LogoutEvent'); + return $param; + } + + /** + * @param Param[] $params + * @return Expression[] + */ + private function createAssignStmts(array $params, Variable $logoutEventVariable): array + { + $assignStmts = []; + foreach ($params as $param) { + foreach (self::PARAMETER_TO_GETTER_NAMES as $parameterName => $getterName) { + if (! $this->nodeNameResolver->isName($param, $parameterName)) { + continue; + } + + $assign = new Assign($param->var, new MethodCall($logoutEventVariable, $getterName)); + $assignStmts[] = new Expression($assign); + } + } + + return $assignStmts; + } +} diff --git a/rules/symfony5/src/Rector/Class_/LogoutHandlerToLogoutEventSubscriberRector.php b/rules/symfony5/src/Rector/Class_/LogoutHandlerToLogoutEventSubscriberRector.php new file mode 100644 index 000000000000..c4b5e716587a --- /dev/null +++ b/rules/symfony5/src/Rector/Class_/LogoutHandlerToLogoutEventSubscriberRector.php @@ -0,0 +1,147 @@ +onLogoutClassMethodFactory = $onLogoutClassMethodFactory; + $this->getSubscriberEventsClassMethodFactory = $getSubscriberEventsClassMethodFactory; + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition('Change logout handler to an event listener that listens to LogoutEent', [ + new CodeSample( + <<<'CODE_SAMPLE' +use Symfony\Component\Security\Http\Logout\LogoutHandlerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + +final class SomeLogoutHandler implements LogoutHandlerInterface +{ + public function logout(Request $request, Response $response, TokenInterface $token) + { + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Security\Http\Event\LogoutEvent; + +final class SomeLogoutHandler implements EventSubscriberInterface +{ + public function onLogout(LogoutEvent $logoutEvent): void + { + $request = $logoutEvent->getRequest(); + $response = $logoutEvent->getResponse(); + $token = $logoutEvent->getToken(); + } + + /** + * @return array + */ + public static function getSubscribedEvents(): array + { + return [ + LogoutEvent::class => ['onLogout'], + ]; + } +} +CODE_SAMPLE + + ), + ]); + } + + /** + * @return string[] + */ + public function getNodeTypes(): array + { + return [Class_::class]; + } + + /** + * @param Class_ $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->isObjectType($node, self::LOGOUT_HANDLER_TYPE)) { + return null; + } + + $this->refactorImplements($node); + + // 2. refactor logout() class method to onLogout() + $logoutClassMethod = $node->getMethod('logout'); + if (! $logoutClassMethod instanceof ClassMethod) { + return null; + } + + $node->stmts[] = $this->onLogoutClassMethodFactory->createFromLogoutClassMethod($logoutClassMethod); + $this->removeNode($logoutClassMethod); + + // 3. add getSubscribedEvents() class method + $classConstFetch = $this->createClassConstReference('Symfony\Component\Security\Http\Event\LogoutEvent'); + + $eventReferencesToMethodNames = [new EventReferenceToMethodName($classConstFetch, 'onLogout')]; + $getSubscribedEventsClassMethod = $this->getSubscriberEventsClassMethodFactory->create( + $eventReferencesToMethodNames + ); + $node->stmts[] = $getSubscribedEventsClassMethod; + + return $node; + } + + private function refactorImplements(Class_ $class): void + { + $class->implements[] = new FullyQualified('Symfony\Component\EventDispatcher\EventSubscriberInterface'); + + foreach ($class->implements as $key => $implement) { + if (! $this->isName($implement, self::LOGOUT_HANDLER_TYPE)) { + continue; + } + + unset($class->implements[$key]); + } + } +} diff --git a/rules/symfony5/tests/Rector/Class_/LogoutHandlerToLogoutEventSubscriberRector/Fixture/some_logout_handler.php.inc b/rules/symfony5/tests/Rector/Class_/LogoutHandlerToLogoutEventSubscriberRector/Fixture/some_logout_handler.php.inc new file mode 100644 index 000000000000..be9fc8ba0b63 --- /dev/null +++ b/rules/symfony5/tests/Rector/Class_/LogoutHandlerToLogoutEventSubscriberRector/Fixture/some_logout_handler.php.inc @@ -0,0 +1,48 @@ + +----- +getRequest(); + $token = $logoutEvent->getToken(); + $result = $request; + $nextResult = $token; + } + /** + * @return array + */ + public static function getSubscribedEvents(): array + { + return [\Symfony\Component\Security\Http\Event\LogoutEvent::class => 'onLogout']; + } +} + +?> diff --git a/rules/symfony5/tests/Rector/Class_/LogoutHandlerToLogoutEventSubscriberRector/LogoutHandlerToLogoutEventSubscriberRectorTest.php b/rules/symfony5/tests/Rector/Class_/LogoutHandlerToLogoutEventSubscriberRector/LogoutHandlerToLogoutEventSubscriberRectorTest.php new file mode 100644 index 000000000000..d25421f37daa --- /dev/null +++ b/rules/symfony5/tests/Rector/Class_/LogoutHandlerToLogoutEventSubscriberRector/LogoutHandlerToLogoutEventSubscriberRectorTest.php @@ -0,0 +1,29 @@ +doTestFileInfo($fileInfo); + } + + public function provideData(): \Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return LogoutHandlerToLogoutEventSubscriberRector::class; + } +}