From e0db712decce7cfd1a4944f5f9d895e93f32ba08 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Thu, 28 Jan 2021 15:36:46 +0100 Subject: [PATCH] [Symfony 5.2] Add LogoutHandlerToLogoutEventSubscriberRector (#5337) --- composer.json | 2 + config/set/symfony51.php | 2 +- .../src/TemplateVariablesFactory.php | 2 +- .../src/Rector/MethodCall/WhateverRector.php | 8 +- .../src/Rector/MethodCall/WhateverRector.php | 8 +- .../src/Rector/Arg/TestRector.php | 8 +- ...emoveUselessJustForSakeInterfaceRector.php | 7 +- ...isteningClassMethodArgumentManipulator.php | 20 +-- .../src/NodeFactory/EventReferenceFactory.php | 49 ++++++ ...GetSubscribedEventsClassMethodFactory.php} | 155 +++++++++++------- .../EventListenerToEventSubscriberRector.php | 12 +- .../EventReferenceToMethodName.php | 36 ++++ .../Fixture/multiple_listeners.php.inc | 2 +- .../multiple_listeners_called_once.php.inc | 2 +- ...multiple_listeners_with_non_kernel.php.inc | 2 +- .../Fixture/some_listener.php.inc | 2 +- .../Fixture/with_priority_listener.php.inc | 2 +- rules/symfony5/config/config.php | 17 ++ .../OnLogoutClassMethodFactory.php | 120 ++++++++++++++ ...utHandlerToLogoutEventSubscriberRector.php | 147 +++++++++++++++++ ...xtractorEnableMagicCallExtractorRector.php | 13 +- .../Fixture/some_logout_handler.php.inc | 48 ++++++ ...ndlerToLogoutEventSubscriberRectorTest.php | 31 ++++ 23 files changed, 597 insertions(+), 98 deletions(-) create mode 100644 rules/symfony-code-quality/src/NodeFactory/EventReferenceFactory.php rename rules/symfony-code-quality/src/NodeFactory/{GetSubscriberEventsClassMethodFactory.php => GetSubscribedEventsClassMethodFactory.php} (64%) create mode 100644 rules/symfony-code-quality/src/ValueObject/EventReferenceToMethodName.php create mode 100644 rules/symfony5/config/config.php create mode 100644 rules/symfony5/src/NodeFactory/OnLogoutClassMethodFactory.php create mode 100644 rules/symfony5/src/Rector/Class_/LogoutHandlerToLogoutEventSubscriberRector.php create mode 100644 rules/symfony5/tests/Rector/Class_/LogoutHandlerToLogoutEventSubscriberRector/Fixture/some_logout_handler.php.inc create mode 100644 rules/symfony5/tests/Rector/Class_/LogoutHandlerToLogoutEventSubscriberRector/LogoutHandlerToLogoutEventSubscriberRectorTest.php diff --git a/composer.json b/composer.json index b115a92a1d7b..c8ce636b62d4 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/packages/rector-generator/tests/RectorGenerator/Fixture/expected/rules/moderate-package/src/Rector/MethodCall/WhateverRector.php b/packages/rector-generator/tests/RectorGenerator/Fixture/expected/rules/moderate-package/src/Rector/MethodCall/WhateverRector.php index 6961feb6784d..091f9029265e 100644 --- a/packages/rector-generator/tests/RectorGenerator/Fixture/expected/rules/moderate-package/src/Rector/MethodCall/WhateverRector.php +++ b/packages/rector-generator/tests/RectorGenerator/Fixture/expected/rules/moderate-package/src/Rector/MethodCall/WhateverRector.php @@ -30,7 +30,7 @@ public function getRuleDefinition(): RuleDefinition { 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 @@ public function getRuleDefinition(): RuleDefinition $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 @@ public function getRuleDefinition(): RuleDefinition '$key' => 'value' ]]); } -PHP +CODE_SAMPLE , [self::CLASS_TYPE_TO_METHOD_NAME => ['SomeClass' => 'configure']] ) diff --git a/packages/rector-generator/tests/RectorGenerator/Fixture/expected_3rd_party/utils/rector/src/Rector/MethodCall/WhateverRector.php b/packages/rector-generator/tests/RectorGenerator/Fixture/expected_3rd_party/utils/rector/src/Rector/MethodCall/WhateverRector.php index 8fc0f746ed96..ff0c78c1822b 100644 --- a/packages/rector-generator/tests/RectorGenerator/Fixture/expected_3rd_party/utils/rector/src/Rector/MethodCall/WhateverRector.php +++ b/packages/rector-generator/tests/RectorGenerator/Fixture/expected_3rd_party/utils/rector/src/Rector/MethodCall/WhateverRector.php @@ -30,7 +30,7 @@ public function getRuleDefinition(): RuleDefinition { 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 @@ public function getRuleDefinition(): RuleDefinition $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 @@ public function getRuleDefinition(): RuleDefinition '$key' => 'value' ]]); } -PHP +CODE_SAMPLE , [self::CLASS_TYPE_TO_METHOD_NAME => ['SomeClass' => 'configure']] ) diff --git a/packages/rector-generator/tests/ValueObjectFactory/Fixture/expected_interactive/rules/test-package-name/src/Rector/Arg/TestRector.php b/packages/rector-generator/tests/ValueObjectFactory/Fixture/expected_interactive/rules/test-package-name/src/Rector/Arg/TestRector.php index 6c0b251d97b4..16c257389b0b 100644 --- a/packages/rector-generator/tests/ValueObjectFactory/Fixture/expected_interactive/rules/test-package-name/src/Rector/Arg/TestRector.php +++ b/packages/rector-generator/tests/ValueObjectFactory/Fixture/expected_interactive/rules/test-package-name/src/Rector/Arg/TestRector.php @@ -19,7 +19,7 @@ public function getRuleDefinition(): RuleDefinition { return new RuleDefinition('Description', [ new CodeSample( - <<<'PHP' + <<<'CODE_SAMPLE' class SomeClass { public function run() @@ -27,10 +27,10 @@ public function run() $this->something(); } } -PHP +CODE_SAMPLE , - <<<'PHP' + <<<'CODE_SAMPLE' class SomeClass { public function run() @@ -38,7 +38,7 @@ public function run() $this->somethingElse(); } } -PHP +CODE_SAMPLE ) ]); 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..e941f1d42e2b 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,29 +149,36 @@ public function createFromEventsToMethods(array $eventsToMethods, array $eventNa 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 @@ 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,25 +224,38 @@ private function createMultipleMethods( } $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; } /** diff --git a/rules/symfony-code-quality/src/Rector/Class_/EventListenerToEventSubscriberRector.php b/rules/symfony-code-quality/src/Rector/Class_/EventListenerToEventSubscriberRector.php index ad41fcb93d70..586b1b580eb2 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 $getSubscribedEventsClassMethodFactory ) { $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 = $getSubscribedEventsClassMethodFactory; } 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/symfony-code-quality/tests/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/multiple_listeners.php.inc b/rules/symfony-code-quality/tests/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/multiple_listeners.php.inc index 5a24b7affef0..1b295da18fa5 100644 --- a/rules/symfony-code-quality/tests/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/multiple_listeners.php.inc +++ b/rules/symfony-code-quality/tests/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/multiple_listeners.php.inc @@ -37,7 +37,7 @@ class MultipleMethodsEventSubscriber implements \Symfony\Component\EventDispatch { } /** - * @return mixed[] + * @return array */ public static function getSubscribedEvents(): array { diff --git a/rules/symfony-code-quality/tests/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/multiple_listeners_called_once.php.inc b/rules/symfony-code-quality/tests/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/multiple_listeners_called_once.php.inc index f203bb0eacb0..0f4fb6967dad 100644 --- a/rules/symfony-code-quality/tests/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/multiple_listeners_called_once.php.inc +++ b/rules/symfony-code-quality/tests/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/multiple_listeners_called_once.php.inc @@ -29,7 +29,7 @@ class MultipleListenersCalledOnceEventSubscriber implements \Symfony\Component\E { } /** - * @return mixed[] + * @return array */ public static function getSubscribedEvents(): array { diff --git a/rules/symfony-code-quality/tests/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/multiple_listeners_with_non_kernel.php.inc b/rules/symfony-code-quality/tests/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/multiple_listeners_with_non_kernel.php.inc index ed2529e9def0..c2fbc9c7a6ba 100644 --- a/rules/symfony-code-quality/tests/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/multiple_listeners_with_non_kernel.php.inc +++ b/rules/symfony-code-quality/tests/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/multiple_listeners_with_non_kernel.php.inc @@ -29,7 +29,7 @@ class MultipleMethodsWithNonKernelEventSubscriber implements \Symfony\Component\ { } /** - * @return mixed[] + * @return array */ public static function getSubscribedEvents(): array { diff --git a/rules/symfony-code-quality/tests/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/some_listener.php.inc b/rules/symfony-code-quality/tests/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/some_listener.php.inc index a918e42c38dc..4471d8b3f89a 100644 --- a/rules/symfony-code-quality/tests/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/some_listener.php.inc +++ b/rules/symfony-code-quality/tests/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/some_listener.php.inc @@ -21,7 +21,7 @@ class SomeEventSubscriber implements \Symfony\Component\EventDispatcher\EventSub { } /** - * @return mixed[] + * @return array */ public static function getSubscribedEvents(): array { diff --git a/rules/symfony-code-quality/tests/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/with_priority_listener.php.inc b/rules/symfony-code-quality/tests/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/with_priority_listener.php.inc index 6d169a66b126..5738832c8d81 100644 --- a/rules/symfony-code-quality/tests/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/with_priority_listener.php.inc +++ b/rules/symfony-code-quality/tests/Rector/Class_/EventListenerToEventSubscriberRector/Fixture/with_priority_listener.php.inc @@ -21,7 +21,7 @@ class WithPriorityEventSubscriber implements \Symfony\Component\EventDispatcher\ { } /** - * @return mixed[] + * @return array */ public static function getSubscribedEvents(): array { 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..a25325aeb37f --- /dev/null +++ b/rules/symfony5/src/Rector/Class_/LogoutHandlerToLogoutEventSubscriberRector.php @@ -0,0 +1,147 @@ +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 + */ + 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]); + } + } +} diff --git a/rules/symfony5/src/Rector/MethodCall/ReflectionExtractorEnableMagicCallExtractorRector.php b/rules/symfony5/src/Rector/MethodCall/ReflectionExtractorEnableMagicCallExtractorRector.php index 2f2434d626ce..b484514f9c04 100644 --- a/rules/symfony5/src/Rector/MethodCall/ReflectionExtractorEnableMagicCallExtractorRector.php +++ b/rules/symfony5/src/Rector/MethodCall/ReflectionExtractorEnableMagicCallExtractorRector.php @@ -108,7 +108,7 @@ public function refactor(Node $node): ?Node 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 @@ private function getContextOptionValue(MethodCall $methodCall): ?bool 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 @@ private function prepareEnableMagicMethodsExtractionFlags(bool $enableMagicCallE 'Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor', 'MAGIC_CALL' ), - $classConstFetch, + $magicGetClassConstFetch, ), - $magicSet, + $magicSetClassConstFetch, ); } } 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..a8c95a36e567 --- /dev/null +++ b/rules/symfony5/tests/Rector/Class_/LogoutHandlerToLogoutEventSubscriberRector/LogoutHandlerToLogoutEventSubscriberRectorTest.php @@ -0,0 +1,31 @@ +doTestFileInfo($fileInfo); + } + + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return LogoutHandlerToLogoutEventSubscriberRector::class; + } +}