vendor/symfony/http-kernel/Controller/ControllerResolver.php line 94

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\HttpKernel\Controller;
  11. use Psr\Log\LoggerInterface;
  12. use Symfony\Component\HttpFoundation\Request;
  13. /**
  14.  * This implementation uses the '_controller' request attribute to determine
  15.  * the controller to execute.
  16.  *
  17.  * @author Fabien Potencier <fabien@symfony.com>
  18.  * @author Tobias Schultze <http://tobion.de>
  19.  */
  20. class ControllerResolver implements ControllerResolverInterface
  21. {
  22.     private ?LoggerInterface $logger;
  23.     public function __construct(LoggerInterface $logger null)
  24.     {
  25.         $this->logger $logger;
  26.     }
  27.     /**
  28.      * {@inheritdoc}
  29.      */
  30.     public function getController(Request $request): callable|false
  31.     {
  32.         if (!$controller $request->attributes->get('_controller')) {
  33.             $this->logger?->warning('Unable to look for the controller as the "_controller" parameter is missing.');
  34.             return false;
  35.         }
  36.         if (\is_array($controller)) {
  37.             if (isset($controller[0]) && \is_string($controller[0]) && isset($controller[1])) {
  38.                 try {
  39.                     $controller[0] = $this->instantiateController($controller[0]);
  40.                 } catch (\Error|\LogicException $e) {
  41.                     if (\is_callable($controller)) {
  42.                         return $controller;
  43.                     }
  44.                     throw $e;
  45.                 }
  46.             }
  47.             if (!\is_callable($controller)) {
  48.                 throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: '$request->getPathInfo()).$this->getControllerError($controller));
  49.             }
  50.             return $controller;
  51.         }
  52.         if (\is_object($controller)) {
  53.             if (!\is_callable($controller)) {
  54.                 throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: '$request->getPathInfo()).$this->getControllerError($controller));
  55.             }
  56.             return $controller;
  57.         }
  58.         if (\function_exists($controller)) {
  59.             return $controller;
  60.         }
  61.         try {
  62.             $callable $this->createController($controller);
  63.         } catch (\InvalidArgumentException $e) {
  64.             throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: '$request->getPathInfo()).$e->getMessage(), 0$e);
  65.         }
  66.         if (!\is_callable($callable)) {
  67.             throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: '$request->getPathInfo()).$this->getControllerError($callable));
  68.         }
  69.         return $callable;
  70.     }
  71.     /**
  72.      * Returns a callable for the given controller.
  73.      *
  74.      * @throws \InvalidArgumentException When the controller cannot be created
  75.      */
  76.     protected function createController(string $controller): callable
  77.     {
  78.         if (!str_contains($controller'::')) {
  79.             $controller $this->instantiateController($controller);
  80.             if (!\is_callable($controller)) {
  81.                 throw new \InvalidArgumentException($this->getControllerError($controller));
  82.             }
  83.             return $controller;
  84.         }
  85.         [$class$method] = explode('::'$controller2);
  86.         try {
  87.             $controller = [$this->instantiateController($class), $method];
  88.         } catch (\Error|\LogicException $e) {
  89.             try {
  90.                 if ((new \ReflectionMethod($class$method))->isStatic()) {
  91.                     return $class.'::'.$method;
  92.                 }
  93.             } catch (\ReflectionException) {
  94.                 throw $e;
  95.             }
  96.             throw $e;
  97.         }
  98.         if (!\is_callable($controller)) {
  99.             throw new \InvalidArgumentException($this->getControllerError($controller));
  100.         }
  101.         return $controller;
  102.     }
  103.     /**
  104.      * Returns an instantiated controller.
  105.      */
  106.     protected function instantiateController(string $class): object
  107.     {
  108.         return new $class();
  109.     }
  110.     private function getControllerError(mixed $callable): string
  111.     {
  112.         if (\is_string($callable)) {
  113.             if (str_contains($callable'::')) {
  114.                 $callable explode('::'$callable2);
  115.             } else {
  116.                 return sprintf('Function "%s" does not exist.'$callable);
  117.             }
  118.         }
  119.         if (\is_object($callable)) {
  120.             $availableMethods $this->getClassMethodsWithoutMagicMethods($callable);
  121.             $alternativeMsg $availableMethods sprintf(' or use one of the available methods: "%s"'implode('", "'$availableMethods)) : '';
  122.             return sprintf('Controller class "%s" cannot be called without a method name. You need to implement "__invoke"%s.'get_debug_type($callable), $alternativeMsg);
  123.         }
  124.         if (!\is_array($callable)) {
  125.             return sprintf('Invalid type for controller given, expected string, array or object, got "%s".'get_debug_type($callable));
  126.         }
  127.         if (!isset($callable[0]) || !isset($callable[1]) || !== \count($callable)) {
  128.             return 'Invalid array callable, expected [controller, method].';
  129.         }
  130.         [$controller$method] = $callable;
  131.         if (\is_string($controller) && !class_exists($controller)) {
  132.             return sprintf('Class "%s" does not exist.'$controller);
  133.         }
  134.         $className \is_object($controller) ? get_debug_type($controller) : $controller;
  135.         if (method_exists($controller$method)) {
  136.             return sprintf('Method "%s" on class "%s" should be public and non-abstract.'$method$className);
  137.         }
  138.         $collection $this->getClassMethodsWithoutMagicMethods($controller);
  139.         $alternatives = [];
  140.         foreach ($collection as $item) {
  141.             $lev levenshtein($method$item);
  142.             if ($lev <= \strlen($method) / || str_contains($item$method)) {
  143.                 $alternatives[] = $item;
  144.             }
  145.         }
  146.         asort($alternatives);
  147.         $message sprintf('Expected method "%s" on class "%s"'$method$className);
  148.         if (\count($alternatives) > 0) {
  149.             $message .= sprintf(', did you mean "%s"?'implode('", "'$alternatives));
  150.         } else {
  151.             $message .= sprintf('. Available methods: "%s".'implode('", "'$collection));
  152.         }
  153.         return $message;
  154.     }
  155.     private function getClassMethodsWithoutMagicMethods($classOrObject): array
  156.     {
  157.         $methods get_class_methods($classOrObject);
  158.         return array_filter($methods, function (string $method) {
  159.             return !== strncmp($method'__'2);
  160.         });
  161.     }
  162. }