diff --git a/phpstan.neon b/phpstan.neon index c80f18d8ced..cc0d5fc44da 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -335,3 +335,5 @@ parameters: - '#Parameter \#1 \$expr of method Rector\\MagicDisclosure\\Matcher\\ClassNameTypeMatcher\:\:doesExprMatchNames\(\) expects PhpParser\\Node\\Expr, PhpParser\\Node\\Expr\|null given#' - '#Parameter \#1 \$objectType of method Rector\\Naming\\Naming\\PropertyNaming\:\:fqnToVariableName\(\) expects PHPStan\\Type\\ObjectType\|string, PHPStan\\Type\\Type given#' - '#Method Rector\\Core\\PhpParser\\Node\\NodeFactory\:\:createConcat\(\) should return PhpParser\\Node\\Expr\\BinaryOp\\Concat\|null but returns PhpParser\\Node\\Expr#' + - '#Method Rector\\Core\\PhpParser\\Node\\BetterNodeFinder\:\:findFirstNonAnonymousClass\(\) should return PhpParser\\Node\\Stmt\\Class_\|null but returns PhpParser\\Node\|null#' + diff --git a/rules/sensio/src/BundleClassResolver.php b/rules/sensio/src/BundleClassResolver.php new file mode 100644 index 00000000000..8295b03f50c --- /dev/null +++ b/rules/sensio/src/BundleClassResolver.php @@ -0,0 +1,113 @@ +parser = $parser; + $this->betterNodeFinder = $betterNodeFinder; + $this->classNaming = $classNaming; + $this->nodeNameResolver = $nodeNameResolver; + } + + public function resolveShortBundleClassFromControllerClass(string $class): ?string + { + // resolve bundle from existing ones + $classReflection = new ReflectionClass($class); + + $fileName = $classReflection->getFileName(); + if (! $fileName) { + return null; + } + + $controllerDirectory = dirname($fileName); + + $rootFolder = getenv('SystemDrive', true) . DIRECTORY_SEPARATOR; + + // traverse up, un-till first bundle class appears + $bundleFiles = []; + while ($bundleFiles === [] && $controllerDirectory !== $rootFolder) { + $bundleFiles = (array) glob($controllerDirectory . '/**Bundle.php'); + $controllerDirectory = dirname($controllerDirectory); + } + + if (count($bundleFiles) === 0) { + return null; + } + + /** @var string $bundleFile */ + $bundleFile = $bundleFiles[0]; + + $bundleClassName = $this->resolveClassNameFromFilePath($bundleFile); + if ($bundleClassName !== null) { + return $this->classNaming->getShortName($bundleClassName); + } + + return null; + } + + private function resolveClassNameFromFilePath(string $filePath): ?string + { + $fileInfo = new SmartFileInfo($filePath); + $nodes = $this->parser->parseFileInfo($fileInfo); + + $this->addFullyQualifiedNamesToNodes($nodes); + + $class = $this->betterNodeFinder->findFirstNonAnonymousClass($nodes); + if ($class === null) { + return null; + } + + return $this->nodeNameResolver->getName($class); + } + + /** + * @param Node[] $nodes + */ + private function addFullyQualifiedNamesToNodes(array $nodes): void + { + $nodeTraverser = new NodeTraverser(); + $nameResolverNodeVisitor = new NameResolver(); + $nodeTraverser->addVisitor($nameResolverNodeVisitor); + + $nodeTraverser->traverse($nodes); + } +} diff --git a/rules/sensio/src/Helper/TemplateGuesser.php b/rules/sensio/src/Helper/TemplateGuesser.php index 3fd68ff048f..c10e8e34df6 100644 --- a/rules/sensio/src/Helper/TemplateGuesser.php +++ b/rules/sensio/src/Helper/TemplateGuesser.php @@ -9,9 +9,10 @@ use PhpParser\Node\Stmt\ClassMethod; use Rector\Core\Exception\ShouldNotHappenException; use Rector\NodeNameResolver\NodeNameResolver; use Rector\NodeTypeResolver\Node\AttributeKey; +use Rector\Sensio\BundleClassResolver; /** - * @see \Rector\Sensio\Tests\Rector\FrameworkExtraBundle\TemplateAnnotationRector\TemplateAnnotationRectorTest + * @see \Rector\Sensio\Tests\Rector\FrameworkExtraBundle\TemplateAnnotationToThisRenderRector\TemplateAnnotationToThisRenderRectorTest */ final class TemplateGuesser { @@ -20,9 +21,15 @@ final class TemplateGuesser */ private $nodeNameResolver; - public function __construct(NodeNameResolver $nodeNameResolver) + /** + * @var BundleClassResolver + */ + private $bundleClassResolver; + + public function __construct(NodeNameResolver $nodeNameResolver, BundleClassResolver $bundleClassResolver) { $this->nodeNameResolver = $nodeNameResolver; + $this->bundleClassResolver = $bundleClassResolver; } public function resolveFromClassMethodNode(ClassMethod $classMethod): string @@ -50,17 +57,24 @@ final class TemplateGuesser */ private function resolve(string $namespace, string $class, string $method): string { - $bundle = Strings::match($namespace, '#(?[\w]*Bundle)#')['bundle'] ?? ''; - $bundle = Strings::replace($bundle, '#Bundle$#'); - $bundle = $bundle !== '' ? '@' . $bundle . '/' : ''; + $bundle = $this->resolveBundle($class, $namespace); + $controller = $this->resolveController($class); - $controller = $this->resolveControllerVersion5($class); $action = Strings::replace($method, '#Action$#'); - return sprintf('%s%s%s.html.twig', $bundle, $controller, $action); + $fullPath = ''; + if ($bundle !== '') { + $fullPath .= $bundle . '/'; + } + + if ($controller !== '') { + $fullPath .= $controller . '/'; + } + + return $fullPath . $action . '.html.twig'; } - private function resolveControllerVersion5(string $class): string + private function resolveController(string $class): string { $match = Strings::match($class, '#Controller\\\(.+)Controller$#'); if (! $match) { @@ -68,9 +82,18 @@ final class TemplateGuesser } $controller = Strings::replace($match[1], '#([a-z\d])([A-Z])#', '\\1_\\2'); - $controller = strtolower($controller); - $controller = str_replace('\\', '/', $controller); + return str_replace('\\', '/', $controller); + } - return $controller !== '' ? $controller . '/' : ''; + private function resolveBundle(string $class, string $namespace): string + { + $shortBundleClass = $this->bundleClassResolver->resolveShortBundleClassFromControllerClass($class); + if ($shortBundleClass !== null) { + return '@' . $shortBundleClass; + } + + $bundle = Strings::match($namespace, '#(?[\w]*Bundle)#')['bundle'] ?? ''; + $bundle = Strings::replace($bundle, '#Bundle$#'); + return $bundle !== '' ? '@' . $bundle : ''; } } diff --git a/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/DifferentBundleNameRectorTest.php b/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/DifferentBundleNameRectorTest.php new file mode 100644 index 00000000000..1ca82a7a418 --- /dev/null +++ b/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/DifferentBundleNameRectorTest.php @@ -0,0 +1,39 @@ +getTempPath() . '/DifferentNameBundle.php'; + FileSystem::copy($originalBundleFilePath, $temporaryBundleFilePath, true); + + $this->doTestFileInfo($fileInfo); + + FileSystem::delete($temporaryBundleFilePath); + } + + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/FixtureDifferentBundleName'); + } + + protected function getRectorClass(): string + { + return TemplateAnnotationToThisRenderRector::class; + } +} diff --git a/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/array_and_many_response.php.inc b/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/array_and_many_response.php.inc index 5620e9441d7..0e7ba6367b7 100755 --- a/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/array_and_many_response.php.inc +++ b/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/array_and_many_response.php.inc @@ -51,7 +51,7 @@ final class ClassWithArrayAndManyResponseController extends AbstractController if ($responseOrData instanceof \Symfony\Component\HttpFoundation\Response) { return $responseOrData; } - return $this->render('@App/class_with_array_and_many_response/index.html.twig', $responseOrData); + return $this->render('@App/Class_With_Array_And_Many_Response/index.html.twig', $responseOrData); } /** diff --git a/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/basic.php.inc b/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/basic.php.inc index ba13dd10284..9ef53cc767f 100755 --- a/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/basic.php.inc +++ b/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/basic.php.inc @@ -45,12 +45,12 @@ class ClassWithNamedService15Controller extends AbstractController { public function indexAction(): \Symfony\Component\HttpFoundation\Response { - return $this->render('@App/class_with_named_service15/index.html.twig'); + return $this->render('@App/Class_With_Named_Service15/index.html.twig'); } public function index2Action(): \Symfony\Component\HttpFoundation\Response { - return $this->render('@App/class_with_named_service15/index2.html.twig', ['someKey' => 'someValue']); + return $this->render('@App/Class_With_Named_Service15/index2.html.twig', ['someKey' => 'someValue']); } public function index3Action(): \Symfony\Component\HttpFoundation\Response diff --git a/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/empty_body.php.inc b/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/empty_body.php.inc index 3ce4224f0da..4a2bd3b0ba5 100755 --- a/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/empty_body.php.inc +++ b/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/empty_body.php.inc @@ -28,7 +28,7 @@ class EmptyBodyController extends AbstractController { public function indexAction(): \Symfony\Component\HttpFoundation\Response { - return $this->render('@App/empty_body/index.html.twig'); + return $this->render('@App/Empty_Body/index.html.twig'); } } diff --git a/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/fixture3.php.inc b/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/fixture3.php.inc index 5f9fc41d72f..37993679136 100755 --- a/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/fixture3.php.inc +++ b/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/fixture3.php.inc @@ -49,7 +49,7 @@ class ClassWithNamedService35Controller extends AbstractController return $this->redirectToRoute('rector_is_cool'); } - return $this->render('@App/class_with_named_service35/index.html.twig', array( + return $this->render('@App/Class_With_Named_Service35/index.html.twig', array( 'form' => $form->createView() )); } diff --git a/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/fixture5.php.inc b/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/fixture5.php.inc index 68eda263833..1c037f7dc98 100755 --- a/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/fixture5.php.inc +++ b/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/fixture5.php.inc @@ -29,7 +29,7 @@ class ClassWithNamedService55Controller extends AbstractController { public function index(): \Symfony\Component\HttpFoundation\Response { - return $this->render('class_with_named_service55/index.html.twig'); + return $this->render('Class_With_Named_Service55/index.html.twig'); } } diff --git a/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/if_else_array_response.php.inc b/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/if_else_array_response.php.inc index cbcb836e524..0a3a9c0f5c0 100755 --- a/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/if_else_array_response.php.inc +++ b/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/if_else_array_response.php.inc @@ -44,11 +44,11 @@ class ClassIfElseArrayController extends AbstractController public function indexAction(): \Symfony\Component\HttpFoundation\Response { if (mt_rand(0, 100)) { - return $this->render('@App/class_if_else_array/index.html.twig', [ + return $this->render('@App/Class_If_Else_Array/index.html.twig', [ 'key' => 'value' ]); } elseif (mt_rand(0, 200)) { - return $this->render('@App/class_if_else_array/index.html.twig', [ + return $this->render('@App/Class_If_Else_Array/index.html.twig', [ 'key' => 'value2' ]); } else { diff --git a/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/try_catch_array_response.php.inc b/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/try_catch_array_response.php.inc index af27eb16c2b..7ea22d53959 100755 --- a/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/try_catch_array_response.php.inc +++ b/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/try_catch_array_response.php.inc @@ -40,7 +40,7 @@ class ClassTryCatchArrayResponseController extends AbstractController public function indexAction(): \Symfony\Component\HttpFoundation\Response { try { - return $this->render('@App/class_try_catch_array_response/index.html.twig', [ + return $this->render('@App/Class_Try_Catch_Array_Response/index.html.twig', [ 'key' => 'value' ]); } catch (Throwable $throwable) { diff --git a/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/with_only_vars.php.inc b/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/with_only_vars.php.inc index bb8a461cb89..fe57102a64c 100755 --- a/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/with_only_vars.php.inc +++ b/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/Fixture/with_only_vars.php.inc @@ -28,7 +28,7 @@ class WithOnlyVarsController extends AbstractController { public function index(Post $post): \Symfony\Component\HttpFoundation\Response { - return $this->render('with_only_vars/index.html.twig', ['post' => $post]); + return $this->render('With_Only_Vars/index.html.twig', ['post' => $post]); } } diff --git a/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/FixtureDifferentBundleName/SomeActionBundle/Controller/basic_controller.php.inc b/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/FixtureDifferentBundleName/SomeActionBundle/Controller/basic_controller.php.inc new file mode 100755 index 00000000000..83e563dc985 --- /dev/null +++ b/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/FixtureDifferentBundleName/SomeActionBundle/Controller/basic_controller.php.inc @@ -0,0 +1,36 @@ + +----- +render('@DifferentNameBundle/Basic/some.html.twig'); + } +} + +?> diff --git a/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/FixtureDifferentBundleName/SomeActionBundle/DifferentNameBundle.php b/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/FixtureDifferentBundleName/SomeActionBundle/DifferentNameBundle.php new file mode 100644 index 00000000000..a3eb4f3c64a --- /dev/null +++ b/rules/sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationToThisRenderRector/FixtureDifferentBundleName/SomeActionBundle/DifferentNameBundle.php @@ -0,0 +1,11 @@ +findFirst($nodes, function (Node $node): bool { + if (! $node instanceof ClassLike) { + return false; + } + + // skip anonymous classes + return ! ($node instanceof Class_ && $node->isAnonymous()); + }); + } + /** * @param Node|Node[] $nodes */