Skip to content

Commit

Permalink
[Symfony 5.1] Add LogoutHandlerToLogoutEventSubscriberRector
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasVotruba committed Jan 28, 2021
1 parent c32d4d8 commit b6e145b
Show file tree
Hide file tree
Showing 11 changed files with 444 additions and 29 deletions.
2 changes: 2 additions & 0 deletions config/set/symfony51.php
Expand Up @@ -11,6 +11,7 @@
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;
Expand All @@ -27,6 +28,7 @@

// see https://github.com/symfony/symfony/pull/36243
$services->set(LogoutHandlerToLogoutEventSubscriberRector::class);
$services->set(LogoutSuccessHandlerToLogoutEventSubscriberRector::class);

$services->set(RenameClassRector::class)
->call('configure', [[
Expand Down
Expand Up @@ -2,7 +2,7 @@

namespace Rector\EarlyReturn\Tests\Rector\Return_\ReturnBinaryAndToEarlyReturnRector\Fixture;

class NotIdentical
final class SomeNotIdentical
{
public function accept($something, $somethingelse)
{
Expand All @@ -16,7 +16,7 @@ class NotIdentical

namespace Rector\EarlyReturn\Tests\Rector\Return_\ReturnBinaryAndToEarlyReturnRector\Fixture;

class NotIdentical
final class SomeNotIdentical
{
public function accept($something, $somethingelse)
{
Expand Down
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace Rector\SymfonyCodeQuality\Contract;

use PhpParser\Node\Expr\ClassConstFetch;

interface EventReferenceToMethodNameInterface
{
public function getClassConstFetch(): ClassConstFetch;

public function getMethodName(): string;
}
Expand Up @@ -26,8 +26,9 @@
use Rector\Symfony\ValueObject\ServiceDefinition;
use Rector\Symfony\ValueObject\Tag;
use Rector\Symfony\ValueObject\Tag\EventListenerTag;
use Rector\SymfonyCodeQuality\Contract\EventReferenceToMethodNameInterface;
use Rector\SymfonyCodeQuality\ValueObject\EventNameToClassAndConstant;
use Rector\SymfonyCodeQuality\ValueObject\EventReferenceToMethodName;
use Rector\SymfonyCodeQuality\ValueObject\EventReferenceToMethodNameWithPriority;

final class GetSubscribedEventsClassMethodFactory
{
Expand Down Expand Up @@ -83,7 +84,7 @@ public function __construct(
}

/**
* @param EventReferenceToMethodName[] $eventReferencesToMethodNames
* @param EventReferenceToMethodNameInterface[] $eventReferencesToMethodNames
*/
public function create(array $eventReferencesToMethodNames): ClassMethod
{
Expand All @@ -92,8 +93,10 @@ public function create(array $eventReferencesToMethodNames): ClassMethod
$eventsToMethodsArray = new Array_();

foreach ($eventReferencesToMethodNames as $eventReferencesToMethodName) {
$priority = $eventReferencesToMethodName instanceof EventReferenceToMethodNameWithPriority ? $eventReferencesToMethodName->getPriority() : null;

$eventsToMethodsArray->items[] = $this->createArrayItemFromMethodAndPriority(
null,
$priority,
$eventReferencesToMethodName->getMethodName(),
$eventReferencesToMethodName->getClassConstFetch()
);
Expand Down
Expand Up @@ -5,8 +5,9 @@
namespace Rector\SymfonyCodeQuality\ValueObject;

use PhpParser\Node\Expr\ClassConstFetch;
use Rector\SymfonyCodeQuality\Contract\EventReferenceToMethodNameInterface;

final class EventReferenceToMethodName
final class EventReferenceToMethodName implements EventReferenceToMethodNameInterface
{
/**
* @var ClassConstFetch
Expand Down
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace Rector\SymfonyCodeQuality\ValueObject;

use PhpParser\Node\Expr\ClassConstFetch;
use Rector\SymfonyCodeQuality\Contract\EventReferenceToMethodNameInterface;

final class EventReferenceToMethodNameWithPriority implements EventReferenceToMethodNameInterface
{
/**
* @var ClassConstFetch
*/
private $classConstFetch;

/**
* @var string
*/
private $methodName;

/**
* @var int
*/
private $priority;

public function __construct(ClassConstFetch $classConstFetch, string $methodName, int $priority)
{
$this->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;
}
}
128 changes: 106 additions & 22 deletions rules/symfony5/src/NodeFactory/OnLogoutClassMethodFactory.php
Expand Up @@ -5,18 +5,24 @@
namespace Rector\Symfony5\NodeFactory;

use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\BinaryOp\NotIdentical;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\If_;
use PhpParser\Node\Stmt\Return_;
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;
use Symplify\Astral\NodeTraverser\SimpleCallableNodeTraverser;

final class OnLogoutClassMethodFactory
{
Expand Down Expand Up @@ -49,60 +55,86 @@ final class OnLogoutClassMethodFactory
*/
private $nodeNameResolver;

/**
* @var SimpleCallableNodeTraverser
*/
private $simpleCallableNodeTraverser;

public function __construct(
NodeFactory $nodeFactory,
PhpVersionProvider $phpVersionProvider,
ListeningClassMethodArgumentManipulator $listeningClassMethodArgumentManipulator,
NodeNameResolver $nodeNameResolver
NodeNameResolver $nodeNameResolver,
SimpleCallableNodeTraverser $simpleCallableNodeTraverser
) {
$this->nodeFactory = $nodeFactory;
$this->phpVersionProvider = $phpVersionProvider;
$this->listeningClassMethodArgumentManipulator = $listeningClassMethodArgumentManipulator;
$this->nodeNameResolver = $nodeNameResolver;
$this->simpleCallableNodeTraverser = $simpleCallableNodeTraverser;
}

public function createFromLogoutClassMethod(ClassMethod $logoutClassMethod): ClassMethod
public function createFromOnLogoutSuccessClassMethod(ClassMethod $onLogoutSuccessClassMethod): ClassMethod
{
$classMethod = $this->nodeFactory->createPublicMethod('onLogout');
$classMethod = $this->createOnLogoutClassMethod();

$logoutEventVariable = new Variable('logoutEvent');
$classMethod->params[] = $this->createLogoutEventParam($logoutEventVariable);
$notIdentical = new NotIdentical(new MethodCall(new Variable('logoutEvent'), 'getResponse'), new ConstFetch(
new Name('null')
));
$if = new If_($notIdentical);
$if->stmts[] = new Return_();

$usedParams = [];
foreach ($logoutClassMethod->params as $oldParam) {
if (! $this->listeningClassMethodArgumentManipulator->isParamUsedInClassMethodBody(
$logoutClassMethod,
$oldParam
)) {
continue;
}
// replace `return $response;` with `$logoutEvent->setResponse($response)`
$this->replaceReturnResponseWithSetResponse($onLogoutSuccessClassMethod);
$this->replaceRequestWithGetRequest($onLogoutSuccessClassMethod);

$usedParams[] = $oldParam;
}
$oldClassStmts = (array) $onLogoutSuccessClassMethod->stmts;
$classStmts = array_merge([$if], $oldClassStmts);
$classMethod->stmts = $classStmts;

if ($this->phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::VOID_TYPE)) {
$classMethod->returnType = new Identifier('void');
}
return $classMethod;
}

$assignStmts = $this->createAssignStmts($usedParams, $logoutEventVariable);
public function createFromLogoutClassMethod(ClassMethod $logoutClassMethod): ClassMethod
{
$classMethod = $this->createOnLogoutClassMethod();

$assignStmts = $this->createAssignStmtFromOldClassMethod($logoutClassMethod);
$classMethod->stmts = array_merge($assignStmts, (array) $logoutClassMethod->stmts);

return $classMethod;
}

private function createLogoutEventParam(Variable $logoutEventVariable): Param
private function createLogoutEventParam(Variable $variable): Param
{
$param = new Param($logoutEventVariable);
$param = new Param($variable);
$param->type = new FullyQualified('Symfony\Component\Security\Http\Event\LogoutEvent');

return $param;
}

private function createOnLogoutClassMethod(): 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;
}

/**
* @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) {
Expand All @@ -117,4 +149,56 @@ private function createAssignStmts(array $params, Variable $logoutEventVariable)

return $assignStmts;
}

private function resolveUsedParams(ClassMethod $logoutClassMethod): array
{
$usedParams = [];
foreach ($logoutClassMethod->params as $oldParam) {
if (! $this->listeningClassMethodArgumentManipulator->isParamUsedInClassMethodBody(
$logoutClassMethod,
$oldParam
)) {
continue;
}

$usedParams[] = $oldParam;
}
return $usedParams;
}

private function createAssignStmtFromOldClassMethod(ClassMethod $onLogoutSuccessClassMethod): array
{
$usedParams = $this->resolveUsedParams($onLogoutSuccessClassMethod);
return $this->createAssignStmts($usedParams);
}

private function replaceReturnResponseWithSetResponse(ClassMethod $classMethod): void
{
$this->simpleCallableNodeTraverser->traverseNodesWithCallable($classMethod, function (\PhpParser\Node $node) {
if (! $node instanceof Return_) {
return null;
}

if ($node->expr === null) {
return null;
}

return new MethodCall(new Variable('logoutEvent'), 'setResponse', [$node->expr]);
});
}

private function replaceRequestWithGetRequest(ClassMethod $classMethod): void
{
$this->simpleCallableNodeTraverser->traverseNodesWithCallable($classMethod, function (\PhpParser\Node $node) {
if (! $node instanceof Variable) {
return null;
}

if (! $this->nodeNameResolver->isName($node, 'request')) {
return null;
}

return new MethodCall(new Variable('logoutEvent'), 'getRequest');
});
}
}
Expand Up @@ -47,7 +47,7 @@ public function __construct(

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;
Expand Down

0 comments on commit b6e145b

Please sign in to comment.