diff --git a/config/set/symfony51.php b/config/set/symfony51.php index e02a854494c..2a5ecad9701 100644 --- a/config/set/symfony51.php +++ b/config/set/symfony51.php @@ -10,6 +10,8 @@ use Rector\Renaming\Rector\Name\RenameClassRector; use Rector\Renaming\Rector\String_\RenameStringRector; use Rector\Renaming\ValueObject\MethodCallRename; use Rector\Renaming\ValueObject\RenameClassAndConstFetch; +use Rector\Symfony5\Rector\Class_\LogoutHandlerToLogoutEventSubscriberRector; +use Rector\Symfony5\Rector\Class_\LogoutSuccessHandlerToLogoutEventSubscriberRector; use Rector\Transform\Rector\New_\NewArgToMethodCallRector; use Rector\Transform\Rector\StaticCall\StaticCallToNewRector; use Rector\Transform\ValueObject\NewArgToMethodCall; @@ -24,6 +26,10 @@ use Symplify\SymfonyPhpConfig\ValueObjectInliner; return static function (ContainerConfigurator $containerConfigurator): void { $services = $containerConfigurator->services(); + // see https://github.com/symfony/symfony/pull/36243 + $services->set(LogoutHandlerToLogoutEventSubscriberRector::class); + $services->set(LogoutSuccessHandlerToLogoutEventSubscriberRector::class); + $services->set(RenameClassRector::class) ->call('configure', [[ RenameClassRector::OLD_TO_NEW_CLASSES => [ diff --git a/rules/early-return/tests/Rector/Return_/ReturnBinaryAndToEarlyReturnRector/Fixture/not_identical.php.inc b/rules/early-return/tests/Rector/Return_/ReturnBinaryAndToEarlyReturnRector/Fixture/some_not_identical.php.inc similarity index 89% rename from rules/early-return/tests/Rector/Return_/ReturnBinaryAndToEarlyReturnRector/Fixture/not_identical.php.inc rename to rules/early-return/tests/Rector/Return_/ReturnBinaryAndToEarlyReturnRector/Fixture/some_not_identical.php.inc index 785a2644ba1..2c69d79f187 100644 --- a/rules/early-return/tests/Rector/Return_/ReturnBinaryAndToEarlyReturnRector/Fixture/not_identical.php.inc +++ b/rules/early-return/tests/Rector/Return_/ReturnBinaryAndToEarlyReturnRector/Fixture/some_not_identical.php.inc @@ -2,7 +2,7 @@ namespace Rector\EarlyReturn\Tests\Rector\Return_\ReturnBinaryAndToEarlyReturnRector\Fixture; -class NotIdentical +final class SomeNotIdentical { public function accept($something, $somethingelse) { @@ -16,7 +16,7 @@ class NotIdentical namespace Rector\EarlyReturn\Tests\Rector\Return_\ReturnBinaryAndToEarlyReturnRector\Fixture; -class NotIdentical +final class SomeNotIdentical { public function accept($something, $somethingelse) { diff --git a/rules/symfony-code-quality/src/Contract/EventReferenceToMethodNameInterface.php b/rules/symfony-code-quality/src/Contract/EventReferenceToMethodNameInterface.php new file mode 100644 index 00000000000..804e707908c --- /dev/null +++ b/rules/symfony-code-quality/src/Contract/EventReferenceToMethodNameInterface.php @@ -0,0 +1,14 @@ +getPriority() : null; + $eventsToMethodsArray->items[] = $this->createArrayItemFromMethodAndPriority( - null, + $priority, $eventReferencesToMethodName->getMethodName(), $eventReferencesToMethodName->getClassConstFetch() ); diff --git a/rules/symfony-code-quality/src/ValueObject/EventReferenceToMethodName.php b/rules/symfony-code-quality/src/ValueObject/EventReferenceToMethodName.php index 46cea57668b..36c206d2999 100644 --- a/rules/symfony-code-quality/src/ValueObject/EventReferenceToMethodName.php +++ b/rules/symfony-code-quality/src/ValueObject/EventReferenceToMethodName.php @@ -5,8 +5,9 @@ declare(strict_types=1); namespace Rector\SymfonyCodeQuality\ValueObject; use PhpParser\Node\Expr\ClassConstFetch; +use Rector\SymfonyCodeQuality\Contract\EventReferenceToMethodNameInterface; -final class EventReferenceToMethodName +final class EventReferenceToMethodName implements EventReferenceToMethodNameInterface { /** * @var ClassConstFetch diff --git a/rules/symfony-code-quality/src/ValueObject/EventReferenceToMethodNameWithPriority.php b/rules/symfony-code-quality/src/ValueObject/EventReferenceToMethodNameWithPriority.php new file mode 100644 index 00000000000..34323beb078 --- /dev/null +++ b/rules/symfony-code-quality/src/ValueObject/EventReferenceToMethodNameWithPriority.php @@ -0,0 +1,48 @@ +classConstFetch = $classConstFetch; + $this->methodName = $methodName; + $this->priority = $priority; + } + + public function getClassConstFetch(): ClassConstFetch + { + return $this->classConstFetch; + } + + public function getMethodName(): string + { + return $this->methodName; + } + + public function getPriority(): int + { + return $this->priority; + } +} diff --git a/rules/symfony5/src/NodeFactory/BareLogoutClassMethodFactory.php b/rules/symfony5/src/NodeFactory/BareLogoutClassMethodFactory.php new file mode 100644 index 00000000000..3c7385ca618 --- /dev/null +++ b/rules/symfony5/src/NodeFactory/BareLogoutClassMethodFactory.php @@ -0,0 +1,55 @@ +nodeFactory = $nodeFactory; + $this->phpVersionProvider = $phpVersionProvider; + } + + public function create(): ClassMethod + { + $classMethod = $this->nodeFactory->createPublicMethod('onLogout'); + + $variable = new Variable('logoutEvent'); + $classMethod->params[] = $this->createLogoutEventParam($variable); + + if ($this->phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::VOID_TYPE)) { + $classMethod->returnType = new Identifier('void'); + } + + return $classMethod; + } + + private function createLogoutEventParam(Variable $variable): Param + { + $param = new Param($variable); + $param->type = new FullyQualified('Symfony\Component\Security\Http\Event\LogoutEvent'); + + return $param; + } +} diff --git a/rules/symfony5/src/NodeFactory/OnLogoutClassMethodFactory.php b/rules/symfony5/src/NodeFactory/OnLogoutClassMethodFactory.php index 4d915cf37b4..01b3e547277 100644 --- a/rules/symfony5/src/NodeFactory/OnLogoutClassMethodFactory.php +++ b/rules/symfony5/src/NodeFactory/OnLogoutClassMethodFactory.php @@ -7,14 +7,10 @@ namespace Rector\Symfony5\NodeFactory; use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\Variable; -use PhpParser\Node\Identifier; -use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Param; +use PhpParser\Node\Stmt; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Expression; -use Rector\Core\Php\PhpVersionProvider; -use Rector\Core\PhpParser\Node\NodeFactory; -use Rector\Core\ValueObject\PhpVersionFeature; use Rector\NetteKdyby\NodeManipulator\ListeningClassMethodArgumentManipulator; use Rector\NodeNameResolver\NodeNameResolver; @@ -29,16 +25,6 @@ final class OnLogoutClassMethodFactory 'token' => 'getToken', ]; - /** - * @var NodeFactory - */ - private $nodeFactory; - - /** - * @var PhpVersionProvider - */ - private $phpVersionProvider; - /** * @var ListeningClassMethodArgumentManipulator */ @@ -49,25 +35,45 @@ final class OnLogoutClassMethodFactory */ private $nodeNameResolver; + /** + * @var BareLogoutClassMethodFactory + */ + private $bareLogoutClassMethodFactory; + public function __construct( - NodeFactory $nodeFactory, - PhpVersionProvider $phpVersionProvider, ListeningClassMethodArgumentManipulator $listeningClassMethodArgumentManipulator, - NodeNameResolver $nodeNameResolver + NodeNameResolver $nodeNameResolver, + BareLogoutClassMethodFactory $bareLogoutClassMethodFactory ) { - $this->nodeFactory = $nodeFactory; - $this->phpVersionProvider = $phpVersionProvider; $this->listeningClassMethodArgumentManipulator = $listeningClassMethodArgumentManipulator; $this->nodeNameResolver = $nodeNameResolver; + $this->bareLogoutClassMethodFactory = $bareLogoutClassMethodFactory; } public function createFromLogoutClassMethod(ClassMethod $logoutClassMethod): ClassMethod { - $classMethod = $this->nodeFactory->createPublicMethod('onLogout'); + $classMethod = $this->bareLogoutClassMethodFactory->create(); - $logoutEventVariable = new Variable('logoutEvent'); - $classMethod->params[] = $this->createLogoutEventParam($logoutEventVariable); + $assignStmts = $this->createAssignStmtFromOldClassMethod($logoutClassMethod); + $classMethod->stmts = array_merge($assignStmts, (array) $logoutClassMethod->stmts); + return $classMethod; + } + + /** + * @return Stmt[] + */ + private function createAssignStmtFromOldClassMethod(ClassMethod $onLogoutSuccessClassMethod): array + { + $usedParams = $this->resolveUsedParams($onLogoutSuccessClassMethod); + return $this->createAssignStmts($usedParams); + } + + /** + * @return Param[] + */ + private function resolveUsedParams(ClassMethod $logoutClassMethod): array + { $usedParams = []; foreach ($logoutClassMethod->params as $oldParam) { if (! $this->listeningClassMethodArgumentManipulator->isParamUsedInClassMethodBody( @@ -79,30 +85,17 @@ final class OnLogoutClassMethodFactory $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; + return $usedParams; } /** * @param Param[] $params * @return Expression[] */ - private function createAssignStmts(array $params, Variable $logoutEventVariable): array + private function createAssignStmts(array $params): array { + $logoutEventVariable = new Variable('logoutEvent'); + $assignStmts = []; foreach ($params as $param) { foreach (self::PARAMETER_TO_GETTER_NAMES as $parameterName => $getterName) { diff --git a/rules/symfony5/src/NodeFactory/OnSuccessLogoutClassMethodFactory.php b/rules/symfony5/src/NodeFactory/OnSuccessLogoutClassMethodFactory.php new file mode 100644 index 00000000000..0168c248040 --- /dev/null +++ b/rules/symfony5/src/NodeFactory/OnSuccessLogoutClassMethodFactory.php @@ -0,0 +1,111 @@ +nodeFactory = $nodeFactory; + $this->nodeNameResolver = $nodeNameResolver; + $this->simpleCallableNodeTraverser = $simpleCallableNodeTraverser; + $this->bareLogoutClassMethodFactory = $bareLogoutClassMethodFactory; + } + + public function createFromOnLogoutSuccessClassMethod(ClassMethod $onLogoutSuccessClassMethod): ClassMethod + { + $classMethod = $this->bareLogoutClassMethodFactory->create(); + + $getResponseMethodCall = new MethodCall(new Variable(self::LOGOUT_EVENT), 'getResponse'); + $notIdentical = new NotIdentical($getResponseMethodCall, $this->nodeFactory->createNull()); + + $if = new If_($notIdentical); + $if->stmts[] = new Return_(); + + // replace `return $response;` with `$logoutEvent->setResponse($response)` + $this->replaceReturnResponseWithSetResponse($onLogoutSuccessClassMethod); + $this->replaceRequestWithGetRequest($onLogoutSuccessClassMethod); + + $oldClassStmts = (array) $onLogoutSuccessClassMethod->stmts; + $classStmts = array_merge([$if], $oldClassStmts); + $classMethod->stmts = $classStmts; + + return $classMethod; + } + + private function replaceReturnResponseWithSetResponse(ClassMethod $classMethod): void + { + $this->simpleCallableNodeTraverser->traverseNodesWithCallable($classMethod, function (Node $node): ?Expression { + if (! $node instanceof Return_) { + return null; + } + + if ($node->expr === null) { + return null; + } + + $args = $this->nodeFactory->createArgs([$node->expr]); + $methodCall = new MethodCall(new Variable(self::LOGOUT_EVENT), 'setResponse', $args); + + return new Expression($methodCall); + }); + } + + private function replaceRequestWithGetRequest(ClassMethod $classMethod): void + { + $this->simpleCallableNodeTraverser->traverseNodesWithCallable($classMethod, function (Node $node): ?MethodCall { + if (! $node instanceof Variable) { + return null; + } + + if (! $this->nodeNameResolver->isName($node, 'request')) { + return null; + } + + return new MethodCall(new Variable(self::LOGOUT_EVENT), 'getRequest'); + }); + } +} diff --git a/rules/symfony5/src/Rector/Class_/LogoutHandlerToLogoutEventSubscriberRector.php b/rules/symfony5/src/Rector/Class_/LogoutHandlerToLogoutEventSubscriberRector.php index a25325aeb37..7d0f13ee2d8 100644 --- a/rules/symfony5/src/Rector/Class_/LogoutHandlerToLogoutEventSubscriberRector.php +++ b/rules/symfony5/src/Rector/Class_/LogoutHandlerToLogoutEventSubscriberRector.php @@ -47,7 +47,7 @@ final class LogoutHandlerToLogoutEventSubscriberRector extends AbstractRector public function getRuleDefinition(): RuleDefinition { - return new RuleDefinition('Change logout handler to an event listener that listens to LogoutEent', [ + return new RuleDefinition('Change logout handler to an event listener that listens to LogoutEvent', [ new CodeSample( <<<'CODE_SAMPLE' use Symfony\Component\Security\Http\Logout\LogoutHandlerInterface; diff --git a/rules/symfony5/src/Rector/Class_/LogoutSuccessHandlerToLogoutEventSubscriberRector.php b/rules/symfony5/src/Rector/Class_/LogoutSuccessHandlerToLogoutEventSubscriberRector.php new file mode 100644 index 00000000000..8daea85616e --- /dev/null +++ b/rules/symfony5/src/Rector/Class_/LogoutSuccessHandlerToLogoutEventSubscriberRector.php @@ -0,0 +1,171 @@ +getSubscribedEventsClassMethodFactory = $getSubscribedEventsClassMethodFactory; + $this->onSuccessLogoutClassMethodFactory = $onSuccessLogoutClassMethodFactory; + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition('Change logout success handler to an event listener that listens to LogoutEvent', [ + new CodeSample( + <<<'CODE_SAMPLE' +use Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + +final class SomeLogoutHandler implements LogoutSuccessHandlerInterface +{ + /** + * @var HttpUtils + */ + private $httpUtils; + + public function __construct(HttpUtils $httpUtils) + { + $this->httpUtils = $httpUtils; + } + + public function onLogoutSuccess(Request $request) + { + $response = $this->httpUtils->createRedirectResponse($request, 'some_url'); + return $response; + } +} +CODE_SAMPLE + + , + <<<'CODE_SAMPLE' +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Security\Http\Event\LogoutEvent; + +final class SomeLogoutHandler implements EventSubscriberInterface +{ + /** + * @var HttpUtils + */ + private $httpUtils; + + public function onLogout(LogoutEvent $logoutEvent): void + { + if ($logoutEvent->getResponse() !== null) { + return; + } + + $response = $this->httpUtils->createRedirectResponse($logoutEvent->getRequest(), 'some_url'); + $logoutEvent->setResponse($response); + } + + /** + * @return array + */ + public static function getSubscribedEvents(): array + { + return [ + LogoutEvent::class => [['onLogout', 64]], + ]; + } +} +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_SUCCESS_HANDLER_TYPE)) { + return null; + } + + $this->refactorImplements($node); + + // 2. refactor logout() class method to onLogout() + $onLogoutSuccessClassMethod = $node->getMethod('onLogoutSuccess'); + if (! $onLogoutSuccessClassMethod instanceof ClassMethod) { + return null; + } + + $node->stmts[] = $this->onSuccessLogoutClassMethodFactory->createFromOnLogoutSuccessClassMethod( + $onLogoutSuccessClassMethod + ); + + // 3. add getSubscribedEvents() class method + $classConstFetch = $this->createClassConstReference('Symfony\Component\Security\Http\Event\LogoutEvent'); + + $eventReferencesToMethodNames = [new EventReferenceToMethodNameWithPriority($classConstFetch, 'onLogout', 64)]; + $getSubscribedEventsClassMethod = $this->getSubscribedEventsClassMethodFactory->create( + $eventReferencesToMethodNames + ); + $node->stmts[] = $getSubscribedEventsClassMethod; + + $this->removeNode($onLogoutSuccessClassMethod); + + 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_SUCCESS_HANDLER_TYPE)) { + continue; + } + + unset($class->implements[$key]); + } + } +} diff --git a/rules/symfony5/tests/Rector/Class_/LogoutHandlerToLogoutEventSubscriberRector/Fixture/logout_handle_without_any_usage.php.inc b/rules/symfony5/tests/Rector/Class_/LogoutHandlerToLogoutEventSubscriberRector/Fixture/logout_handle_without_any_usage.php.inc new file mode 100644 index 00000000000..1dd5c2ad771 --- /dev/null +++ b/rules/symfony5/tests/Rector/Class_/LogoutHandlerToLogoutEventSubscriberRector/Fixture/logout_handle_without_any_usage.php.inc @@ -0,0 +1,53 @@ +value = 1000; + } +} + +?> +----- +value = 1000; + } + /** + * @return array + */ + public static function getSubscribedEvents(): array + { + return [\Symfony\Component\Security\Http\Event\LogoutEvent::class => 'onLogout']; + } +} + +?> diff --git a/rules/symfony5/tests/Rector/Class_/LogoutSuccessHandlerToLogoutEventSubscriberRector/Fixture/simple_logout_success_handler.php.inc b/rules/symfony5/tests/Rector/Class_/LogoutSuccessHandlerToLogoutEventSubscriberRector/Fixture/simple_logout_success_handler.php.inc new file mode 100644 index 00000000000..af83e8f9b25 --- /dev/null +++ b/rules/symfony5/tests/Rector/Class_/LogoutSuccessHandlerToLogoutEventSubscriberRector/Fixture/simple_logout_success_handler.php.inc @@ -0,0 +1,64 @@ +httpUtils = $httpUtils; + } + + public function onLogoutSuccess(Request $request) + { + $response = $this->httpUtils->createRedirectResponse($request, 'some_url'); + return $response; + } +} + +?> +----- +httpUtils = $httpUtils; + } + public function onLogout(\Symfony\Component\Security\Http\Event\LogoutEvent $logoutEvent): void + { + if ($logoutEvent->getResponse() !== null) { + return; + } + $response = $this->httpUtils->createRedirectResponse($logoutEvent->getRequest(), 'some_url'); + $logoutEvent->setResponse($response); + } + /** + * @return array + */ + public static function getSubscribedEvents(): array + { + return [\Symfony\Component\Security\Http\Event\LogoutEvent::class => ['onLogout', 64]]; + } +} + +?> diff --git a/rules/symfony5/tests/Rector/Class_/LogoutSuccessHandlerToLogoutEventSubscriberRector/LogoutSuccessHandlerToLogoutEventSubscriberRectorTest.php b/rules/symfony5/tests/Rector/Class_/LogoutSuccessHandlerToLogoutEventSubscriberRector/LogoutSuccessHandlerToLogoutEventSubscriberRectorTest.php new file mode 100644 index 00000000000..bf69f5f4298 --- /dev/null +++ b/rules/symfony5/tests/Rector/Class_/LogoutSuccessHandlerToLogoutEventSubscriberRector/LogoutSuccessHandlerToLogoutEventSubscriberRectorTest.php @@ -0,0 +1,31 @@ +doTestFileInfo($fileInfo); + } + + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return LogoutSuccessHandlerToLogoutEventSubscriberRector::class; + } +}