diff --git a/.phpstorm.meta.php b/.phpstorm.meta.php index 6fc16b1bdbf6..8c06d1c2aa0d 100644 --- a/.phpstorm.meta.php +++ b/.phpstorm.meta.php @@ -40,6 +40,7 @@ \Rector\NodeTypeResolver\Node\AttributeKey::IS_UNREACHABLE, \Rector\NodeTypeResolver\Node\AttributeKey::PHP_DOC_INFO, \Rector\NodeTypeResolver\Node\AttributeKey::KIND, + \Rector\NodeTypeResolver\Node\AttributeKey::CLASS_SHORT_NAME, ); expectedArguments( @@ -65,4 +66,5 @@ \Rector\NodeTypeResolver\Node\AttributeKey::IS_UNREACHABLE, \Rector\NodeTypeResolver\Node\AttributeKey::PHP_DOC_INFO, \Rector\NodeTypeResolver\Node\AttributeKey::KIND, + \Rector\NodeTypeResolver\Node\AttributeKey::CLASS_SHORT_NAME, ); diff --git a/config/set/cakephp-to-symfony/cakephp-24-to-symfony-51.yaml b/config/set/cakephp-to-symfony/cakephp-24-to-symfony-51.yaml index bb279a82188e..265928e5335c 100644 --- a/config/set/cakephp-to-symfony/cakephp-24-to-symfony-51.yaml +++ b/config/set/cakephp-to-symfony/cakephp-24-to-symfony-51.yaml @@ -17,3 +17,4 @@ services: Rector\CakePHPToSymfony\Rector\Class_\CakePHPModelToDoctrineEntityRector: null Rector\CakePHPToSymfony\Rector\Class_\CakePHPModelToDoctrineRepositoryRector: null Rector\CakePHPToSymfony\Rector\Class_\CakePHPImplicitRouteToExplicitRouteAnnotationRector: null + Rector\CakePHPToSymfony\Rector\Class_\CakePHPBeforeFilterToRequestEventSubscriberRector: null diff --git a/docs/AllRectorsOverview.md b/docs/AllRectorsOverview.md index 8acf24cc6bd1..ee9889f7de46 100644 --- a/docs/AllRectorsOverview.md +++ b/docs/AllRectorsOverview.md @@ -1,4 +1,4 @@ -# All 445 Rectors Overview +# All 446 Rectors Overview - [Projects](#projects) - [General](#general) @@ -286,6 +286,24 @@ services: ## CakePHPToSymfony +### `CakePHPBeforeFilterToRequestEventSubscriberRector` + +- class: `Rector\CakePHPToSymfony\Rector\Class_\CakePHPBeforeFilterToRequestEventSubscriberRector` + +Migrate CakePHP beforeFilter() method from controller to Event Subscriber before request + +```diff + class SuperadminController extends \AppController + { +- public function beforeFilter() +- { +- // something +- } + } +``` + +
+ ### `CakePHPControllerActionToSymfonyControllerActionRector` - class: `Rector\CakePHPToSymfony\Rector\ClassMethod\CakePHPControllerActionToSymfonyControllerActionRector` @@ -4178,24 +4196,15 @@ Change Form that extends Control to Controller and decoupled FormType + $form->handleRequest($request); - $form->onSuccess[] = [$this, 'processForm']; +- } +- +- public function processForm(Form $form) +- { +- // process me + if ($form->isSuccess() && $form->isValid()) { + // process me + } } -+} - -- public function processForm(Form $form) -- { -- // process me -- } -+class SomeFormType extends \Symfony\Component\Form\AbstractType -+{ -+ public function buildForm(\Symfony\Component\Form\FormBuilderInterface $formBuilder, array $options) -+ { -+ $formBuilder->add('name', \Symfony\Component\Form\Extension\Core\Type\TextType::class, [ -+ 'label' => 'Your name' -+ ]); -+ } } ``` diff --git a/packages/CakePHPToSymfony/src/NodeFactory/EventSubscriberClassFactory.php b/packages/CakePHPToSymfony/src/NodeFactory/EventSubscriberClassFactory.php new file mode 100644 index 000000000000..22cd47c2c6b0 --- /dev/null +++ b/packages/CakePHPToSymfony/src/NodeFactory/EventSubscriberClassFactory.php @@ -0,0 +1,104 @@ +builderFactory = $builderFactory; + } + + /** + * @return Namespace_|Class_ + */ + public function createEventSubscriberClass(Class_ $class, ClassMethod $onBeforeFilterClassMethod): Node + { + $eventSubscriberClassName = $this->createEventSubscriberClassName($class); + + $classBuilder = $this->builderFactory->class($eventSubscriberClassName); + $classBuilder->implement(new FullyQualified('Symfony\Component\EventDispatcher\EventSubscriberInterface')); + $classBuilder->makeFinal(); + + $classBuilder->addStmt($this->createGetSubscribedEventsClassMethod()); + $classBuilder->addStmt($this->createOnKernelRequestClassMethod($onBeforeFilterClassMethod)); + + $eventSubscriberClass = $classBuilder->getNode(); + + /** @var string|null $namespaceName */ + $namespaceName = $class->getAttribute(AttributeKey::NAMESPACE_NAME); + if ($namespaceName === null) { + return $eventSubscriberClass; + } + + $namespace = new Namespace_(new Name($namespaceName)); + $namespace->stmts[] = $eventSubscriberClass; + + return $namespace; + } + + public function resolveEventSubscriberFilePath(Class_ $class): string + { + /** @var SmartFileInfo $fileInfo */ + $fileInfo = $class->getAttribute(AttributeKey::FILE_INFO); + $eventSubscriberClassName = $this->createEventSubscriberClassName($class); + return dirname($fileInfo->getRealPath()) . DIRECTORY_SEPARATOR . $eventSubscriberClassName . '.php'; + } + + public function createEventSubscriberClassName(Class_ $class): string + { + $className = $class->getAttribute(AttributeKey::CLASS_SHORT_NAME); + + return $className . 'EventSubscriber'; + } + + private function createGetSubscribedEventsClassMethod(): ClassMethod + { + $classMethodBuilder = $this->builderFactory->method('getSubscribedEvents'); + $classMethodBuilder->makePublic(); + $classMethodBuilder->makeStatic(); + + $eventConstant = new ClassConstFetch(new FullyQualified( + 'Symfony\Component\HttpKernel\KernelEvents' + ), 'REQUEST'); + $arrayItem = new ArrayItem(new String_('onKernelRequest'), $eventConstant); + $eventsToMethodsArray = new Array_([$arrayItem]); + + $return = new Return_($eventsToMethodsArray); + $classMethodBuilder->addStmt($return); + $classMethodBuilder->setReturnType('array'); + + return $classMethodBuilder->getNode(); + } + + private function createOnKernelRequestClassMethod(ClassMethod $onBeforeFilterClassMethod): ClassMethod + { + $classMethodBuilder = $this->builderFactory->method('onKernelRequest'); + $classMethodBuilder->addStmts((array) $onBeforeFilterClassMethod->stmts); + $classMethodBuilder->makePublic(); + + return $classMethodBuilder->getNode(); + } +} diff --git a/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPBeforeFilterToRequestEventSubscriberRector.php b/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPBeforeFilterToRequestEventSubscriberRector.php new file mode 100644 index 000000000000..f0150b44ba57 --- /dev/null +++ b/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPBeforeFilterToRequestEventSubscriberRector.php @@ -0,0 +1,97 @@ +eventSubscriberClassFactory = $eventSubscriberClassFactory; + } + + public function getDefinition(): RectorDefinition + { + return new RectorDefinition( + 'Migrate CakePHP beforeFilter() method from controller to Event Subscriber before request', + [ + new CodeSample( + <<<'PHP' +class SuperadminController extends \AppController +{ + public function beforeFilter() + { + // something + } +} +PHP +, + <<<'PHP' +class SuperadminController extends \AppController +{ +} +PHP + + ), + ] + ); + } + + /** + * @return string[] + */ + public function getNodeTypes(): array + { + return [Class_::class]; + } + + /** + * @param Class_ $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->isInCakePHPController($node)) { + return null; + } + + $beforeFilterClassMethod = $node->getMethod('beforeFilter'); + if ($beforeFilterClassMethod === null) { + return null; + } + + $this->removeNode($beforeFilterClassMethod); + + // create event subscriber with name... + $eventSubscriberClass = $this->eventSubscriberClassFactory->createEventSubscriberClass( + $node, + $beforeFilterClassMethod + ); + $eventSubscriberFilePath = $this->eventSubscriberClassFactory->resolveEventSubscriberFilePath($node); + + // @todo make temporary + $content = 'print($eventSubscriberClass) . PHP_EOL; + FileSystem::write($eventSubscriberFilePath, $content); + + return null; + } +} diff --git a/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPImplicitRouteToExplicitRouteAnnotationRector.php b/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPImplicitRouteToExplicitRouteAnnotationRector.php index 879587e4c414..9ac70e207bf2 100644 --- a/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPImplicitRouteToExplicitRouteAnnotationRector.php +++ b/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPImplicitRouteToExplicitRouteAnnotationRector.php @@ -86,9 +86,8 @@ public function refactor(Node $node): ?Node continue; } - /** @var string $className */ - $className = $node->getAttribute(AttributeKey::CLASS_NAME); - $shortClassName = $this->getShortName($className); + /** @var string $shortClassName */ + $shortClassName = $node->getAttribute(AttributeKey::CLASS_SHORT_NAME); $methodName = $this->getName($classMethod); diff --git a/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPModelToDoctrineRepositoryRector.php b/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPModelToDoctrineRepositoryRector.php index ce75ca5d9c60..be9340b1176a 100644 --- a/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPModelToDoctrineRepositoryRector.php +++ b/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPModelToDoctrineRepositoryRector.php @@ -260,6 +260,9 @@ private function inRepositoryMethod(ClassMethod $classMethod): bool private function getRepositoryShortClassName(Class_ $class): string { - return $this->getShortName($class->name) . 'Repository'; + /** @var string $classShortName */ + $classShortName = $class->getAttribute(AttributeKey::CLASS_SHORT_NAME); + + return $classShortName . 'Repository'; } } diff --git a/packages/CakePHPToSymfony/src/TemplatePathResolver.php b/packages/CakePHPToSymfony/src/TemplatePathResolver.php index ffd641217042..5b4fc1500000 100644 --- a/packages/CakePHPToSymfony/src/TemplatePathResolver.php +++ b/packages/CakePHPToSymfony/src/TemplatePathResolver.php @@ -8,7 +8,6 @@ use PhpParser\Node; use PhpParser\Node\Expr\Assign; use PhpParser\Node\Stmt\ClassMethod; -use Rector\CodingStyle\Naming\ClassNaming; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\PhpParser\Node\Commander\NodeRemovingCommander; use Rector\PhpParser\Node\Manipulator\PropertyFetchManipulator; @@ -32,11 +31,6 @@ final class TemplatePathResolver */ private $valueResolver; - /** - * @var ClassNaming - */ - private $classNaming; - /** * @var NodeRemovingCommander */ @@ -46,13 +40,11 @@ public function __construct( CallableNodeTraverser $callableNodeTraverser, PropertyFetchManipulator $propertyFetchManipulator, ValueResolver $valueResolver, - ClassNaming $classNaming, NodeRemovingCommander $nodeRemovingCommander ) { $this->callableNodeTraverser = $callableNodeTraverser; $this->propertyFetchManipulator = $propertyFetchManipulator; $this->valueResolver = $valueResolver; - $this->classNaming = $classNaming; $this->nodeRemovingCommander = $nodeRemovingCommander; } @@ -71,10 +63,8 @@ public function resolveForClassMethod(ClassMethod $classMethod): string public function resolveClassNameTemplatePart(ClassMethod $classMethod): string { - /** @var string $className */ - $className = $classMethod->getAttribute(AttributeKey::CLASS_NAME); - $shortClassName = $this->classNaming->getShortName($className); - + /** @var string $shortClassName */ + $shortClassName = $classMethod->getAttribute(AttributeKey::CLASS_SHORT_NAME); $shortClassName = Strings::replace($shortClassName, '#Controller$#i'); return Strings::lower($shortClassName); diff --git a/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPBeforeFilterToRequestEventSubscriberRector/CakePHPBeforeFilterToRequestEventSubscriberRectorTest.php b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPBeforeFilterToRequestEventSubscriberRector/CakePHPBeforeFilterToRequestEventSubscriberRectorExtraTest.php similarity index 51% rename from packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPBeforeFilterToRequestEventSubscriberRector/CakePHPBeforeFilterToRequestEventSubscriberRectorTest.php rename to packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPBeforeFilterToRequestEventSubscriberRector/CakePHPBeforeFilterToRequestEventSubscriberRectorExtraTest.php index 957e9b15c492..cbabad4e2373 100644 --- a/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPBeforeFilterToRequestEventSubscriberRector/CakePHPBeforeFilterToRequestEventSubscriberRectorTest.php +++ b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPBeforeFilterToRequestEventSubscriberRector/CakePHPBeforeFilterToRequestEventSubscriberRectorExtraTest.php @@ -8,19 +8,24 @@ use Rector\CakePHPToSymfony\Rector\Class_\CakePHPBeforeFilterToRequestEventSubscriberRector; use Rector\Testing\PHPUnit\AbstractRectorTestCase; -final class CakePHPBeforeFilterToRequestEventSubscriberRectorTest extends AbstractRectorTestCase +final class CakePHPBeforeFilterToRequestEventSubscriberRectorExtraTest extends AbstractRectorTestCase { /** * @dataProvider provideData() */ - public function test(string $file): void + public function test(string $inputFile, string $expectedExtraFileName, string $expectedExtraContentFilePath): void { - $this->doTestFile($file); + $this->doTestFile($inputFile); + $this->doTestExtraFile($expectedExtraFileName, $expectedExtraContentFilePath); } public function provideData(): Iterator { - return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + yield [ + __DIR__ . '/Fixture/fixture.php.inc', + 'SuperadminControllerEventSubscriber.php', + __DIR__ . '/Source/extra_file.php', + ]; } protected function getRectorClass(): string diff --git a/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPBeforeFilterToRequestEventSubscriberRector/Fixture/fixture.php.inc b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPBeforeFilterToRequestEventSubscriberRector/Fixture/fixture.php.inc new file mode 100644 index 000000000000..2630a5114c42 --- /dev/null +++ b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPBeforeFilterToRequestEventSubscriberRector/Fixture/fixture.php.inc @@ -0,0 +1,23 @@ + +----- + diff --git a/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPBeforeFilterToRequestEventSubscriberRector/Source/extra_file.php b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPBeforeFilterToRequestEventSubscriberRector/Source/extra_file.php new file mode 100644 index 000000000000..4108cac2c15a --- /dev/null +++ b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPBeforeFilterToRequestEventSubscriberRector/Source/extra_file.php @@ -0,0 +1,14 @@ + 'onKernelRequest']; + } + public function onKernelRequest() + { + // something + } +} diff --git a/packages/CodeQuality/src/Rector/Class_/CompleteDynamicPropertiesRector.php b/packages/CodeQuality/src/Rector/Class_/CompleteDynamicPropertiesRector.php index 989bd1e7d343..137da42fd042 100644 --- a/packages/CodeQuality/src/Rector/Class_/CompleteDynamicPropertiesRector.php +++ b/packages/CodeQuality/src/Rector/Class_/CompleteDynamicPropertiesRector.php @@ -105,15 +105,16 @@ public function refactor(Node $node): ?Node // special case for Laravel Collection macro magic $fetchedLocalPropertyNameToTypes = $this->resolveFetchedLocalPropertyNameToType($node); - $propertyNames = $this->getClassPropertyNames($node); - - $fetchedLocalPropertyNames = array_keys($fetchedLocalPropertyNameToTypes); - $propertiesToComplete = array_diff($fetchedLocalPropertyNames, $propertyNames); + $propertiesToComplete = $this->resolvePropertiesToComplete($node, $fetchedLocalPropertyNameToTypes); + if ($propertiesToComplete === []) { + return null; + } // remove other properties that are accessible from this scope /** @var string $class */ $class = $this->getName($node); foreach ($propertiesToComplete as $key => $propertyToComplete) { + /** @var string $propertyToComplete */ if (! property_exists($class, $propertyToComplete)) { continue; } @@ -253,4 +254,17 @@ private function resolvePropertyFetchType(Node $node): Type return new MixedType(); } + + /** + * @param Type[] $fetchedLocalPropertyNameToTypes + * @return string[] + */ + private function resolvePropertiesToComplete(Class_ $class, array $fetchedLocalPropertyNameToTypes): array + { + $propertyNames = $this->getClassPropertyNames($class); + + $fetchedLocalPropertyNames = array_keys($fetchedLocalPropertyNameToTypes); + + return array_diff($fetchedLocalPropertyNames, $propertyNames); + } } diff --git a/packages/CodingStyle/src/Imports/ShortNameResolver.php b/packages/CodingStyle/src/Imports/ShortNameResolver.php index f3e69c42a9d2..f02a2c973f4c 100644 --- a/packages/CodingStyle/src/Imports/ShortNameResolver.php +++ b/packages/CodingStyle/src/Imports/ShortNameResolver.php @@ -10,9 +10,7 @@ use PhpParser\Node\Name; use PhpParser\Node\Stmt\ClassLike; use PhpParser\Node\Stmt\Namespace_; -use Rector\CodingStyle\Naming\ClassNaming; use Rector\NodeTypeResolver\Node\AttributeKey; -use Rector\PhpParser\Node\Resolver\NameResolver; use Rector\PhpParser\NodeTraverser\CallableNodeTraverser; final class ShortNameResolver @@ -27,24 +25,9 @@ final class ShortNameResolver */ private $callableNodeTraverser; - /** - * @var NameResolver - */ - private $nameResolver; - - /** - * @var ClassNaming - */ - private $classNaming; - - public function __construct( - CallableNodeTraverser $callableNodeTraverser, - NameResolver $nameResolver, - ClassNaming $classNaming - ) { + public function __construct(CallableNodeTraverser $callableNodeTraverser) + { $this->callableNodeTraverser = $callableNodeTraverser; - $this->nameResolver = $nameResolver; - $this->classNaming = $classNaming; } /** @@ -96,9 +79,9 @@ public function resolveShortClassLikeNamesForNode(Node $node): array return null; } - /** @var string $classLikeName */ - $classLikeName = $this->nameResolver->getName($node->name); - $shortClassLikeNames[] = $this->classNaming->getShortName($classLikeName); + /** @var string $classShortName */ + $classShortName = $node->getAttribute(AttributeKey::CLASS_SHORT_NAME); + $shortClassLikeNames[] = $classShortName; }); return array_unique($shortClassLikeNames); diff --git a/packages/CodingStyle/src/Rector/Class_/AddArrayDefaultToArrayPropertyRector.php b/packages/CodingStyle/src/Rector/Class_/AddArrayDefaultToArrayPropertyRector.php index 289b8a65363d..cfcad6873468 100644 --- a/packages/CodingStyle/src/Rector/Class_/AddArrayDefaultToArrayPropertyRector.php +++ b/packages/CodingStyle/src/Rector/Class_/AddArrayDefaultToArrayPropertyRector.php @@ -92,6 +92,9 @@ public function getNodeTypes(): array public function refactor(Node $node): ?Node { $changedProperties = $this->collectPropertyNamesWithMissingDefaultArray($node); + if ($changedProperties === []) { + return null; + } $this->completeDefaultArrayToPropertyNames($node, $changedProperties); diff --git a/packages/NetteToSymfony/src/Rector/Assign/FormControlToControllerAndFormTypeRector.php b/packages/NetteToSymfony/src/Rector/Assign/FormControlToControllerAndFormTypeRector.php index f74264f3b68d..f369cccc2413 100644 --- a/packages/NetteToSymfony/src/Rector/Assign/FormControlToControllerAndFormTypeRector.php +++ b/packages/NetteToSymfony/src/Rector/Assign/FormControlToControllerAndFormTypeRector.php @@ -25,7 +25,6 @@ use PhpParser\Node\Stmt\Expression; use PhpParser\Node\Stmt\If_; use PhpParser\Node\Stmt\Namespace_; -use Rector\CodingStyle\Naming\ClassNaming; use Rector\NetteToSymfony\Collector\CollectOnFormVariableMethodCallsCollector; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\Rector\AbstractRector; @@ -48,17 +47,10 @@ final class FormControlToControllerAndFormTypeRector extends AbstractRector */ private $collectOnFormVariableMethodCallsCollector; - /** - * @var ClassNaming - */ - private $classNaming; - public function __construct( - CollectOnFormVariableMethodCallsCollector $collectOnFormVariableMethodCallsCollector, - ClassNaming $classNaming + CollectOnFormVariableMethodCallsCollector $collectOnFormVariableMethodCallsCollector ) { $this->collectOnFormVariableMethodCallsCollector = $collectOnFormVariableMethodCallsCollector; - $this->classNaming = $classNaming; } public function getDefinition(): RectorDefinition @@ -199,13 +191,9 @@ private function dumpFormController(Class_ $node, Class_ $formTypeClass): void return; } - /** @var string $className */ - $className = $this->getName($node); - /** @var string $namespaceName */ - $namespaceName = $this->classNaming->getNamespace($className); - // @todo make name dynamic -// $formControllerClassName = $namespace . '\\SomeFormController'; + $namespaceName = $node->getAttribute(AttributeKey::NAMESPACE_NAME); + $formControllerClass = new Class_('SomeFormController'); $formControllerClass->extends = new FullyQualified( 'Symfony\Bundle\FrameworkBundle\Controller\AbstractController' diff --git a/packages/NodeTypeResolver/src/Node/AttributeKey.php b/packages/NodeTypeResolver/src/Node/AttributeKey.php index c764bbced673..e78d8b222e49 100644 --- a/packages/NodeTypeResolver/src/Node/AttributeKey.php +++ b/packages/NodeTypeResolver/src/Node/AttributeKey.php @@ -39,6 +39,11 @@ final class AttributeKey */ public const CLASS_NAME = 'className'; + /** + * @var string + */ + public const CLASS_SHORT_NAME = 'class_short_name'; + /** * @todo split Class node, interface node and trait node, to be compatible with other SpecificNode|null, values * @var string diff --git a/packages/NodeTypeResolver/src/NodeVisitor/FunctionMethodAndClassNodeVisitor.php b/packages/NodeTypeResolver/src/NodeVisitor/FunctionMethodAndClassNodeVisitor.php index c775ba4dcb2c..e1876ad421ad 100644 --- a/packages/NodeTypeResolver/src/NodeVisitor/FunctionMethodAndClassNodeVisitor.php +++ b/packages/NodeTypeResolver/src/NodeVisitor/FunctionMethodAndClassNodeVisitor.php @@ -11,6 +11,7 @@ use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Function_; use PhpParser\NodeVisitorAbstract; +use Rector\CodingStyle\Naming\ClassNaming; use Rector\NodeTypeResolver\Node\AttributeKey; final class FunctionMethodAndClassNodeVisitor extends NodeVisitorAbstract @@ -25,6 +26,11 @@ final class FunctionMethodAndClassNodeVisitor extends NodeVisitorAbstract */ private $className; + /** + * @var string|null + */ + private $classShortName; + /** * @var ClassLike[]|null[] */ @@ -50,6 +56,16 @@ final class FunctionMethodAndClassNodeVisitor extends NodeVisitorAbstract */ private $functionNode; + /** + * @var ClassNaming + */ + private $classNaming; + + public function __construct(ClassNaming $classNaming) + { + $this->classNaming = $classNaming; + } + /** * @param Node[] $nodes * @return Node[]|null @@ -98,6 +114,7 @@ private function processClass(Node $node): void $node->setAttribute(AttributeKey::CLASS_NODE, $this->classNode); $node->setAttribute(AttributeKey::CLASS_NAME, $this->className); + $node->setAttribute(AttributeKey::CLASS_SHORT_NAME, $this->classShortName); if ($this->classNode instanceof Class_) { $this->setParentClassName($this->classNode, $node); @@ -133,8 +150,10 @@ private function setClassNodeAndName(?ClassLike $classLike): void $this->className = null; } elseif (isset($classLike->namespacedName)) { $this->className = $classLike->namespacedName->toString(); + $this->classShortName = $this->classNaming->getShortName($this->className); } else { $this->className = (string) $classLike->name; + $this->classShortName = $this->classNaming->getShortName($this->className); } } diff --git a/packages/NodeTypeResolver/tests/NodeVisitor/FunctionMethodAndClassNodeVisitor/FunctionMethodAndClassNodeVisitorTest.php b/packages/NodeTypeResolver/tests/NodeVisitor/FunctionMethodAndClassNodeVisitor/FunctionMethodAndClassNodeVisitorTest.php index 9153643f409c..3d016e552539 100644 --- a/packages/NodeTypeResolver/tests/NodeVisitor/FunctionMethodAndClassNodeVisitor/FunctionMethodAndClassNodeVisitorTest.php +++ b/packages/NodeTypeResolver/tests/NodeVisitor/FunctionMethodAndClassNodeVisitor/FunctionMethodAndClassNodeVisitorTest.php @@ -8,8 +8,10 @@ use PhpParser\Node; use PhpParser\NodeTraverser; use PhpParser\NodeVisitor\NameResolver; +use Rector\CodingStyle\Naming\ClassNaming; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\NodeTypeResolver\NodeVisitor\FunctionMethodAndClassNodeVisitor; +use Rector\PhpParser\Node\Resolver\NameResolver as RectorNameResolver; use Rector\Testing\PHPUnit\AbstractNodeVisitorTestCase; final class FunctionMethodAndClassNodeVisitorTest extends AbstractNodeVisitorTestCase @@ -34,7 +36,7 @@ protected function visitNodes(array $nodes): void { $nodeTraverser = new NodeTraverser(); $nodeTraverser->addVisitor(new NameResolver()); - $nodeTraverser->addVisitor(new FunctionMethodAndClassNodeVisitor()); + $nodeTraverser->addVisitor(new FunctionMethodAndClassNodeVisitor(new ClassNaming(new RectorNameResolver()))); $nodeTraverser->traverse($nodes); } diff --git a/packages/SymfonyCodeQuality/src/Rector/Class_/EventListenerToEventSubscriberRector.php b/packages/SymfonyCodeQuality/src/Rector/Class_/EventListenerToEventSubscriberRector.php index c426a02ed1d8..f82a13cbebca 100644 --- a/packages/SymfonyCodeQuality/src/Rector/Class_/EventListenerToEventSubscriberRector.php +++ b/packages/SymfonyCodeQuality/src/Rector/Class_/EventListenerToEventSubscriberRector.php @@ -22,6 +22,7 @@ use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\RectorDefinition; +use Rector\Symfony\Contract\Tag\TagInterface; use Rector\Symfony\ServiceMapProvider; use Rector\Symfony\ValueObject\ServiceDefinition; use Rector\Symfony\ValueObject\Tag\EventListenerTag; @@ -326,15 +327,7 @@ private function createMultipleMethods( foreach ($methodNamesWithPriorities as $methodNamesWithPriority) { foreach ($methodNamesWithPriority->getTags() as $tag) { - if (! $tag instanceof EventListenerTag) { - continue; - } - - if ($eventName !== $tag->getEvent()) { - continue; - } - - if (in_array($tag, $alreadyUsedTags, true)) { + if ($this->shouldSkip($eventName, $tag, $alreadyUsedTags)) { continue; } @@ -371,4 +364,20 @@ private function createEventItem(EventListenerTag $eventListenerTag): ArrayItem return new ArrayItem(new String_($eventListenerTag->getMethod())); } + + /** + * @param TagInterface[] $alreadyUsedTags + */ + private function shouldSkip(string $eventName, TagInterface $tag, array $alreadyUsedTags): bool + { + if (! $tag instanceof EventListenerTag) { + return true; + } + + if ($eventName !== $tag->getEvent()) { + return true; + } + + return in_array($tag, $alreadyUsedTags, true); + } } diff --git a/phpstan.neon b/phpstan.neon index af9a67f3ccb6..91ffe00cb873 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -236,3 +236,4 @@ parameters: - '#Ternary operator condition is always false#' - '#Parameter \#1 \$tagValueNode of method Rector\\BetterPhpDocParser\\PhpDocInfo\\PhpDocInfo\:\:addTagValueNodeWithShortName\(\) expects Rector\\BetterPhpDocParser\\PhpDocNode\\AbstractTagValueNode, PHPStan\\PhpDocParser\\Ast\\PhpDoc\\PhpDocTagNode\|Rector\\BetterPhpDocParser\\PhpDocNode\\Doctrine\\Property_\\JoinColumnTagValueNode given#' + - '#Parameter \#1 \$eventListenerTag of method Rector\\SymfonyCodeQuality\\Rector\\Class_\\EventListenerToEventSubscriberRector\:\:createEventItem\(\) expects Rector\\Symfony\\ValueObject\\Tag\\EventListenerTag, Rector\\Symfony\\Contract\\Tag\\TagInterface given#'