[Symfony 5.2] Add LogoutHandlerToLogoutEventSubscriberRector (#5337)

This commit is contained in:
Tomas Votruba 2021-01-28 15:36:46 +01:00 committed by GitHub
parent a54819aba8
commit e0db712dec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 597 additions and 98 deletions

View File

@ -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",

View File

@ -147,6 +147,6 @@ return static function (ContainerConfigurator $containerConfigurator): void {
// @see https://github.com/symfony/symfony/pull/35858
RenameStringRector::STRING_CHANGES => [
'ROLE_PREVIOUS_ADMIN' => 'IS_IMPERSONATOR',
]
],
]]);
};

View File

@ -127,7 +127,7 @@ final class TemplateVariablesFactory
{
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

View File

@ -30,7 +30,7 @@ private $classTypeToMethodName = [];
{
return new RuleDefinition('Change $service->arg(...) to $service->call(...)', [
new ConfiguredCodeSample(
<<<'PHP'
<<<'CODE_SAMPLE'
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
@ -39,9 +39,9 @@ return static function (ContainerConfigurator $containerConfigurator): void {
$services->set(SomeClass::class)
->arg('$key', 'value');
}
PHP
CODE_SAMPLE
,
<<<'PHP'
<<<'CODE_SAMPLE'
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
@ -52,7 +52,7 @@ return static function (ContainerConfigurator $containerConfigurator): void {
'$key' => 'value'
]]);
}
PHP
CODE_SAMPLE
,
[self::CLASS_TYPE_TO_METHOD_NAME => ['SomeClass' => 'configure']]
)

View File

@ -30,7 +30,7 @@ private $classTypeToMethodName = [];
{
return new RuleDefinition('Change $service->arg(...) to $service->call(...)', [
new ConfiguredCodeSample(
<<<'PHP'
<<<'CODE_SAMPLE'
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
@ -39,9 +39,9 @@ return static function (ContainerConfigurator $containerConfigurator): void {
$services->set(SomeClass::class)
->arg('$key', 'value');
}
PHP
CODE_SAMPLE
,
<<<'PHP'
<<<'CODE_SAMPLE'
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
@ -52,7 +52,7 @@ return static function (ContainerConfigurator $containerConfigurator): void {
'$key' => 'value'
]]);
}
PHP
CODE_SAMPLE
,
[self::CLASS_TYPE_TO_METHOD_NAME => ['SomeClass' => 'configure']]
)

View File

@ -19,7 +19,7 @@ final class TestRector extends AbstractRector
{
return new RuleDefinition('Description', [
new CodeSample(
<<<'PHP'
<<<'CODE_SAMPLE'
class SomeClass
{
public function run()
@ -27,10 +27,10 @@ class SomeClass
$this->something();
}
}
PHP
CODE_SAMPLE
,
<<<'PHP'
<<<'CODE_SAMPLE'
class SomeClass
{
public function run()
@ -38,7 +38,7 @@ class SomeClass
$this->somethingElse();
}
}
PHP
CODE_SAMPLE
)
]);

View File

@ -154,8 +154,11 @@ CODE_SAMPLE
);
}
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);

View File

@ -116,16 +116,7 @@ final class ListeningClassMethodArgumentManipulator
}
}
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 @@ final class ListeningClassMethodArgumentManipulator
});
}
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,

View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace Rector\SymfonyCodeQuality\NodeFactory;
use PhpParser\Node;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Scalar\String_;
use Rector\Core\PhpParser\Node\NodeFactory;
use Rector\SymfonyCodeQuality\ValueObject\EventNameToClassAndConstant;
final class EventReferenceFactory
{
/**
* @var NodeFactory
*/
private $nodeFactory;
public function __construct(NodeFactory $nodeFactory)
{
$this->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);
}
}

View File

@ -4,7 +4,6 @@ declare(strict_types=1);
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\ClassMethod;
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\ServiceDefinition;
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 EventReferenceToMethodName[] $eventReferencesToMethodNames
*/
public function create(array $eventReferencesToMethodNames): ClassMethod
{
$getSubscribersClassMethod = $this->createClassMethod();
$eventsToMethodsArray = new Array_();
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<string, ServiceDefinition[]> $eventsToMethods
* @param EventNameToClassAndConstant[] $eventNamesToClassConstants
*/
public function createFromEventsToMethods(array $eventsToMethods, array $eventNamesToClassConstants): ClassMethod
{
$getSubscribersClassMethod = $this->nodeFactory->createPublicMethod('getSubscribedEvents');
public function createFromServiceDefinitionsAndEventsToMethods(
array $eventsToMethods,
array $eventNamesToClassConstants
): ClassMethod {
$getSubscribersClassMethod = $this->createClassMethod();
$eventsToMethodsArray = new Array_();
$this->visibilityManipulator->makeStatic($getSubscribersClassMethod);
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,29 +149,36 @@ final class GetSubscriberEventsClassMethodFactory
return $getSubscribersClassMethod;
}
/**
* @param EventNameToClassAndConstant[] $eventNamesToClassConstants
* @return String_|ClassConstFetch
*/
private function createEventName(string $eventName, array $eventNamesToClassConstants): Node
private function createClassMethod(): ClassMethod
{
if (class_exists($eventName)) {
return $this->nodeFactory->createClassConstReference($eventName);
$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);
}
// is string a that could be caught in constant, e.g. KernelEvents?
foreach ($eventNamesToClassConstants as $eventNameToClassConstant) {
if ($eventNameToClassConstant->getEventName() !== $eventName) {
continue;
}
return new ArrayItem(new String_($methodName), $expr);
}
return $this->nodeFactory->createClassConstFetch(
$eventNameToClassConstant->getEventClass(),
$eventNameToClassConstant->getEventConstant()
);
private function decorateClassMethodWithReturnType(ClassMethod $classMethod): void
{
if ($this->phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::SCALAR_TYPES)) {
$classMethod->returnType = new Identifier('array');
}
return new String_($eventName);
$returnType = new ArrayType(new StringType(), new MixedType(true));
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($classMethod);
$this->phpDocTypeChanger->changeReturnType($phpDocInfo, $returnType);
}
/**
@ -142,30 +191,13 @@ final class GetSubscriberEventsClassMethodFactory
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,25 +224,38 @@ final class GetSubscriberEventsClassMethodFactory
}
$eventItems[] = $this->createEventItem($tag);
$alreadyUsedTags[] = $tag;
}
}
$multipleMethodsArray = new Array_($eventItems);
$eventsToMethodsArray->items[] = new ArrayItem($multipleMethodsArray, $expr);
}
private function decorateClassMethodWithReturnType(ClassMethod $classMethod): void
private function resolveMethodName(ServiceDefinition $serviceDefinition, string $eventName): ?string
{
if ($this->phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::SCALAR_TYPES)) {
$classMethod->returnType = new Identifier('array');
/** @var EventListenerTag[]|Tag[] $eventTags */
$eventTags = $serviceDefinition->getTags();
foreach ($eventTags as $eventTag) {
if ($eventTag instanceof EventListenerTag && $eventTag->getEvent() === $eventName) {
return $eventTag->getMethod();
}
}
$returnType = new ArrayType(new MixedType(), new MixedType(true));
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($classMethod);
$this->phpDocTypeChanger->changeReturnType($phpDocInfo, $returnType);
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;
}
/**

View File

@ -12,7 +12,7 @@ use PhpParser\Node\Stmt\Class_;
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 $getSubscribedEventsClassMethodFactory
) {
$this->eventNamesToClassConstants = [
// kernel events
@ -82,7 +82,7 @@ final class EventListenerToEventSubscriberRector extends AbstractRector
new EventNameToClassAndConstant('console.error', self::CONSOLE_EVENTS_CLASS, 'ERROR'),
];
$this->listenerServiceDefinitionProvider = $listenerServiceDefinitionProvider;
$this->getSubscriberEventsClassMethodFactory = $getSubscriberEventsClassMethodFactory;
$this->getSubscribedEventsClassMethodFactory = $getSubscribedEventsClassMethodFactory;
}
public function getRuleDefinition(): RuleDefinition
@ -191,7 +191,7 @@ CODE_SAMPLE
$class->name = new Identifier($classShortName . 'EventSubscriber');
$classMethod = $this->getSubscriberEventsClassMethodFactory->createFromEventsToMethods(
$classMethod = $this->getSubscribedEventsClassMethodFactory->createFromServiceDefinitionsAndEventsToMethods(
$eventsToMethods,
$this->eventNamesToClassConstants
);

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Rector\SymfonyCodeQuality\ValueObject;
use PhpParser\Node\Expr\ClassConstFetch;
final class EventReferenceToMethodName
{
/**
* @var ClassConstFetch
*/
private $classConstFetch;
/**
* @var string
*/
private $methodName;
public function __construct(ClassConstFetch $classConstFetch, string $methodName)
{
$this->classConstFetch = $classConstFetch;
$this->methodName = $methodName;
}
public function getClassConstFetch(): ClassConstFetch
{
return $this->classConstFetch;
}
public function getMethodName(): string
{
return $this->methodName;
}
}

View File

@ -37,7 +37,7 @@ class MultipleMethodsEventSubscriber implements \Symfony\Component\EventDispatch
{
}
/**
* @return mixed[]
* @return array<string, mixed>
*/
public static function getSubscribedEvents(): array
{

View File

@ -29,7 +29,7 @@ class MultipleListenersCalledOnceEventSubscriber implements \Symfony\Component\E
{
}
/**
* @return mixed[]
* @return array<string, mixed>
*/
public static function getSubscribedEvents(): array
{

View File

@ -29,7 +29,7 @@ class MultipleMethodsWithNonKernelEventSubscriber implements \Symfony\Component\
{
}
/**
* @return mixed[]
* @return array<string, mixed>
*/
public static function getSubscribedEvents(): array
{

View File

@ -21,7 +21,7 @@ class SomeEventSubscriber implements \Symfony\Component\EventDispatcher\EventSub
{
}
/**
* @return mixed[]
* @return array<string, mixed>
*/
public static function getSubscribedEvents(): array
{

View File

@ -21,7 +21,7 @@ class WithPriorityEventSubscriber implements \Symfony\Component\EventDispatcher\
{
}
/**
* @return mixed[]
* @return array<string, mixed>
*/
public static function getSubscribedEvents(): array
{

View File

@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->defaults()
->public()
->autowire()
->autoconfigure();
$services->load('Rector\Symfony5\\', __DIR__ . '/../src')
->exclude([__DIR__ . '/../src/Rector']);
};

View File

@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
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\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;
final class OnLogoutClassMethodFactory
{
/**
* @var array<string, string>
*/
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;
}
}

View File

@ -0,0 +1,147 @@
<?php
declare(strict_types=1);
namespace Rector\Symfony5\Rector\Class_;
use PhpParser\Node;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use Rector\Core\Rector\AbstractRector;
use Rector\Symfony5\NodeFactory\OnLogoutClassMethodFactory;
use Rector\SymfonyCodeQuality\NodeFactory\GetSubscribedEventsClassMethodFactory;
use Rector\SymfonyCodeQuality\ValueObject\EventReferenceToMethodName;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see https://github.com/symfony/symfony/pull/36243
*
* @see \Rector\Symfony5\Tests\Rector\Class_\LogoutHandlerToLogoutEventSubscriberRector\LogoutHandlerToLogoutEventSubscriberRectorTest
*/
final class LogoutHandlerToLogoutEventSubscriberRector extends AbstractRector
{
/**
* @var string
*/
private const LOGOUT_HANDLER_TYPE = 'Symfony\Component\Security\Http\Logout\LogoutHandlerInterface';
/**
* @var OnLogoutClassMethodFactory
*/
private $onLogoutClassMethodFactory;
/**
* @var GetSubscribedEventsClassMethodFactory
*/
private $getSubscribedEventsClassMethodFactory;
public function __construct(
OnLogoutClassMethodFactory $onLogoutClassMethodFactory,
GetSubscribedEventsClassMethodFactory $getSubscribedEventsClassMethodFactory
) {
$this->onLogoutClassMethodFactory = $onLogoutClassMethodFactory;
$this->getSubscribedEventsClassMethodFactory = $getSubscribedEventsClassMethodFactory;
}
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<string, string[]>
*/
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->getSubscribedEventsClassMethodFactory->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]);
}
}
}

View File

@ -108,7 +108,7 @@ CODE_SAMPLE
private function shouldSkip(MethodCall $methodCall): bool
{
if (! $this->isObjectType($methodCall, 'Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor')) {
if (! $this->isObjectType($methodCall->var, 'Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor')) {
return true;
}
@ -155,16 +155,17 @@ CODE_SAMPLE
private function prepareEnableMagicMethodsExtractionFlags(bool $enableMagicCallExtractionValue): BitwiseOr
{
$classConstFetch = $this->createClassConstFetch(
$magicGetClassConstFetch = $this->createClassConstFetch(
'Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor',
'MAGIC_GET'
);
$magicSet = $this->createClassConstFetch(
$magicSetClassConstFetch = $this->createClassConstFetch(
'Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor',
'MAGIC_SET'
);
if (! $enableMagicCallExtractionValue) {
return new BitwiseOr($classConstFetch, $magicSet);
return new BitwiseOr($magicGetClassConstFetch, $magicSetClassConstFetch);
}
return new BitwiseOr(
@ -173,9 +174,9 @@ CODE_SAMPLE
'Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor',
'MAGIC_CALL'
),
$classConstFetch,
$magicGetClassConstFetch,
),
$magicSet,
$magicSetClassConstFetch,
);
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace Rector\Symfony5\Tests\Rector\Class_\LogoutHandlerToLogoutEventSubscriberRector\Fixture;
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)
{
$result = $request;
$nextResult = $token;
}
}
?>
-----
<?php
namespace Rector\Symfony5\Tests\Rector\Class_\LogoutHandlerToLogoutEventSubscriberRector\Fixture;
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 \Symfony\Component\EventDispatcher\EventSubscriberInterface
{
public function onLogout(\Symfony\Component\Security\Http\Event\LogoutEvent $logoutEvent): void
{
$request = $logoutEvent->getRequest();
$token = $logoutEvent->getToken();
$result = $request;
$nextResult = $token;
}
/**
* @return array<string, mixed>
*/
public static function getSubscribedEvents(): array
{
return [\Symfony\Component\Security\Http\Event\LogoutEvent::class => 'onLogout'];
}
}
?>

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Rector\Symfony5\Tests\Rector\Class_\LogoutHandlerToLogoutEventSubscriberRector;
use Iterator;
use Rector\Symfony5\Rector\Class_\LogoutHandlerToLogoutEventSubscriberRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
final class LogoutHandlerToLogoutEventSubscriberRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
protected function getRectorClass(): string
{
return LogoutHandlerToLogoutEventSubscriberRector::class;
}
}