2021-05-10 00:23:30 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare (strict_types=1);
|
2022-06-06 17:12:56 +00:00
|
|
|
namespace Rector\Symfony\Helper;
|
2021-05-10 00:23:30 +00:00
|
|
|
|
2024-04-01 16:51:34 +00:00
|
|
|
use RectorPrefix202404\Nette\Utils\Strings;
|
2022-06-06 17:12:56 +00:00
|
|
|
use PhpParser\Node\Stmt\ClassMethod;
|
|
|
|
use PHPStan\Analyser\Scope;
|
2022-06-16 11:26:51 +00:00
|
|
|
use PHPStan\Reflection\ClassReflection;
|
2024-01-02 02:40:38 +00:00
|
|
|
use Rector\Exception\ShouldNotHappenException;
|
2022-06-06 17:12:56 +00:00
|
|
|
use Rector\NodeNameResolver\NodeNameResolver;
|
|
|
|
use Rector\NodeTypeResolver\Node\AttributeKey;
|
|
|
|
use Rector\Symfony\BundleClassResolver;
|
2021-05-10 00:23:30 +00:00
|
|
|
/**
|
|
|
|
* @see \Rector\Symfony\Tests\Rector\ClassMethod\TemplateAnnotationToThisRenderRector\TemplateAnnotationToThisRenderRectorTest
|
|
|
|
*/
|
|
|
|
final class TemplateGuesser
|
|
|
|
{
|
2023-06-08 22:00:17 +00:00
|
|
|
/**
|
2023-06-11 23:01:39 +00:00
|
|
|
* @readonly
|
2023-06-08 22:00:17 +00:00
|
|
|
* @var \Rector\Symfony\BundleClassResolver
|
|
|
|
*/
|
|
|
|
private $bundleClassResolver;
|
|
|
|
/**
|
2023-06-11 23:01:39 +00:00
|
|
|
* @readonly
|
2023-06-08 22:00:17 +00:00
|
|
|
* @var \Rector\NodeNameResolver\NodeNameResolver
|
|
|
|
*/
|
|
|
|
private $nodeNameResolver;
|
2021-05-10 00:23:30 +00:00
|
|
|
/**
|
|
|
|
* @var string
|
|
|
|
* @see https://regex101.com/r/yZAUAC/1
|
|
|
|
*/
|
|
|
|
private const BUNDLE_SUFFIX_REGEX = '#Bundle$#';
|
|
|
|
/**
|
|
|
|
* @var string
|
|
|
|
* @see https://regex101.com/r/T6ItFG/1
|
|
|
|
*/
|
|
|
|
private const BUNDLE_NAME_MATCHING_REGEX = '#(?<bundle>[\\w]*Bundle)#';
|
|
|
|
/**
|
|
|
|
* @var string
|
|
|
|
* @see https://regex101.com/r/5dNkCC/2
|
|
|
|
*/
|
|
|
|
private const SMALL_LETTER_BIG_LETTER_REGEX = '#([a-z\\d])([A-Z])#';
|
|
|
|
/**
|
|
|
|
* @var string
|
|
|
|
* @see https://regex101.com/r/YUrmAD/1
|
|
|
|
*/
|
|
|
|
private const CONTROLLER_NAME_MATCH_REGEX = '#Controller\\\\(?<class_name_without_suffix>.+)Controller$#';
|
|
|
|
/**
|
|
|
|
* @var string
|
|
|
|
* @see https://regex101.com/r/nj8Ojf/1
|
|
|
|
*/
|
|
|
|
private const ACTION_MATCH_REGEX = '#Action$#';
|
2022-06-07 08:22:29 +00:00
|
|
|
public function __construct(BundleClassResolver $bundleClassResolver, NodeNameResolver $nodeNameResolver)
|
2021-05-10 00:23:30 +00:00
|
|
|
{
|
|
|
|
$this->bundleClassResolver = $bundleClassResolver;
|
2021-05-15 08:37:15 +00:00
|
|
|
$this->nodeNameResolver = $nodeNameResolver;
|
2021-05-10 00:23:30 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
public function resolveFromClassMethod(ClassMethod $classMethod) : string
|
2021-05-10 00:23:30 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
$scope = $classMethod->getAttribute(AttributeKey::SCOPE);
|
|
|
|
if (!$scope instanceof Scope) {
|
|
|
|
throw new ShouldNotHappenException();
|
2021-05-10 00:23:30 +00:00
|
|
|
}
|
|
|
|
$namespace = $scope->getNamespace();
|
|
|
|
if (!\is_string($namespace)) {
|
2022-06-07 08:22:29 +00:00
|
|
|
throw new ShouldNotHappenException();
|
2021-05-10 00:23:30 +00:00
|
|
|
}
|
2022-06-16 11:26:51 +00:00
|
|
|
$classReflection = $scope->getClassReflection();
|
|
|
|
if (!$classReflection instanceof ClassReflection) {
|
2022-06-07 08:22:29 +00:00
|
|
|
throw new ShouldNotHappenException();
|
2021-05-10 00:23:30 +00:00
|
|
|
}
|
2022-06-16 11:26:51 +00:00
|
|
|
$className = $classReflection->getName();
|
2021-05-10 00:23:30 +00:00
|
|
|
/** @var string $methodName */
|
|
|
|
$methodName = $this->nodeNameResolver->getName($classMethod);
|
2021-11-04 15:30:20 +00:00
|
|
|
return $this->resolve($namespace, $className, $methodName);
|
2021-05-10 00:23:30 +00:00
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Mimics https://github.com/sensiolabs/SensioFrameworkExtraBundle/blob/v5.0.0/Templating/TemplateGuesser.php
|
|
|
|
*/
|
|
|
|
private function resolve(string $namespace, string $class, string $method) : string
|
|
|
|
{
|
|
|
|
$bundle = $this->resolveBundle($class, $namespace);
|
|
|
|
$controller = $this->resolveController($class);
|
2022-06-07 08:22:29 +00:00
|
|
|
$action = Strings::replace($method, self::ACTION_MATCH_REGEX, '');
|
2024-04-09 21:01:48 +00:00
|
|
|
$action = Strings::replace($action, self::SMALL_LETTER_BIG_LETTER_REGEX, '1_\\2');
|
2021-05-10 00:23:30 +00:00
|
|
|
$fullPath = '';
|
|
|
|
if ($bundle !== '') {
|
|
|
|
$fullPath .= $bundle . '/';
|
|
|
|
}
|
|
|
|
if ($controller !== '') {
|
2024-04-09 21:01:48 +00:00
|
|
|
$fullPath .= \strtolower($controller) . '/';
|
2021-05-10 00:23:30 +00:00
|
|
|
}
|
2024-04-09 21:01:48 +00:00
|
|
|
return $fullPath . \strtolower($action) . '.html.twig';
|
2021-05-10 00:23:30 +00:00
|
|
|
}
|
|
|
|
private function resolveBundle(string $class, string $namespace) : string
|
|
|
|
{
|
|
|
|
$shortBundleClass = $this->bundleClassResolver->resolveShortBundleClassFromControllerClass($class);
|
|
|
|
if ($shortBundleClass !== null) {
|
|
|
|
return '@' . $shortBundleClass;
|
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
$bundle = Strings::match($namespace, self::BUNDLE_NAME_MATCHING_REGEX)['bundle'] ?? '';
|
|
|
|
$bundle = Strings::replace($bundle, self::BUNDLE_SUFFIX_REGEX, '');
|
2021-05-10 00:23:30 +00:00
|
|
|
return $bundle !== '' ? '@' . $bundle : '';
|
|
|
|
}
|
|
|
|
private function resolveController(string $class) : string
|
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
$match = Strings::match($class, self::CONTROLLER_NAME_MATCH_REGEX);
|
2021-11-29 06:47:55 +00:00
|
|
|
if ($match === null) {
|
2021-05-10 00:23:30 +00:00
|
|
|
return '';
|
|
|
|
}
|
2022-06-07 09:46:15 +00:00
|
|
|
$controller = Strings::replace($match['class_name_without_suffix'], self::SMALL_LETTER_BIG_LETTER_REGEX, '1_\\2');
|
2021-05-10 00:23:30 +00:00
|
|
|
return \str_replace('\\', '/', $controller);
|
|
|
|
}
|
|
|
|
}
|