* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Sensio\Bundle\FrameworkExtraBundle\Templating; use Doctrine\Persistence\Proxy; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\KernelInterface; /** * The TemplateGuesser class handles the guessing of template name based on controller. * * @author Fabien Potencier */ class TemplateGuesser { /** * @var KernelInterface */ private $kernel; /** * @var string[] */ private $controllerPatterns; /** * @param string[] $controllerPatterns Regexps extracting the controller name from its FQN */ public function __construct(KernelInterface $kernel, array $controllerPatterns = []) { $controllerPatterns[] = '/Controller\\\(.+)Controller$/'; $this->kernel = $kernel; $this->controllerPatterns = $controllerPatterns; } /** * Guesses and returns the template name to render based on the controller * and action names. * * @param callable $controller An array storing the controller object and action method * * @return string The template name * * @throws \InvalidArgumentException */ public function guessTemplateName($controller, Request $request) { if (\is_object($controller) && method_exists($controller, '__invoke')) { $controller = [$controller, '__invoke']; } elseif (!\is_array($controller)) { throw new \InvalidArgumentException(sprintf('First argument of "%s" must be an array callable or an object defining the magic method __invoke. "%s" given.', __METHOD__, \gettype($controller))); } $className = $this->getRealClass(\get_class($controller[0])); $matchController = null; foreach ($this->controllerPatterns as $pattern) { if (preg_match($pattern, $className, $tempMatch)) { $matchController = str_replace('\\', '/', strtolower(preg_replace('/([a-z\d])([A-Z])/', '\\1_\\2', $tempMatch[1]))); break; } } if (null === $matchController) { throw new \InvalidArgumentException(sprintf('The "%s" class does not look like a controller class (its FQN must match one of the following regexps: "%s").', \get_class($controller[0]), implode('", "', $this->controllerPatterns))); } if ('__invoke' === $controller[1]) { $matchAction = $matchController; $matchController = null; } else { $matchAction = preg_replace('/Action$/', '', $controller[1]); } $matchAction = strtolower(preg_replace('/([a-z\d])([A-Z])/', '\\1_\\2', $matchAction)); $bundleName = $this->getBundleForClass($className); return sprintf(($bundleName ? '@'.$bundleName.'/' : '').$matchController.($matchController ? '/' : '').$matchAction.'.'.$request->getRequestFormat().'.twig'); } /** * Returns the bundle name in which the given class name is located. * * @param string $class A fully qualified controller class name * * @return string|null $bundle A bundle name */ private function getBundleForClass($class) { $reflectionClass = new \ReflectionClass($class); $bundles = $this->kernel->getBundles(); do { $namespace = $reflectionClass->getNamespaceName(); foreach ($bundles as $bundle) { if ('Symfony\Bundle\FrameworkBundle' === $bundle->getNamespace()) { continue; } if (0 === strpos($namespace, $bundle->getNamespace())) { return preg_replace('/Bundle$/', '', $bundle->getName()); } } $reflectionClass = $reflectionClass->getParentClass(); } while ($reflectionClass); } private static function getRealClass(string $class): string { if (!class_exists(Proxy::class)) { return $class; } if (false === $pos = strrpos($class, '\\'.Proxy::MARKER.'\\')) { return $class; } return substr($class, $pos + Proxy::MARKER_LENGTH + 2); } }