Skip to main content
Drupal API
User account menu
  • Log in

Breadcrumb

  1. Drupal Core 11.1.x
  2. ControllerResolver.php

class ControllerResolver

Same name in this branch
  1. 11.1.x core/lib/Drupal/Core/Controller/ControllerResolver.php \Drupal\Core\Controller\ControllerResolver

This implementation uses the '_controller' request attribute to determine the controller to execute.

@author Fabien Potencier <fabien@symfony.com> @author Tobias Schultze <http://tobion.de&gt;

Hierarchy

  • class \Symfony\Component\HttpKernel\Controller\ControllerResolver implements \Symfony\Component\HttpKernel\Controller\ControllerResolverInterface

Expanded class hierarchy of ControllerResolver

File

vendor/symfony/http-kernel/Controller/ControllerResolver.php, line 26

Namespace

Symfony\Component\HttpKernel\Controller
View source
class ControllerResolver implements ControllerResolverInterface {
    private array $allowedControllerTypes = [];
    private array $allowedControllerAttributes = [
        AsController::class => AsController::class,
    ];
    public function __construct(?LoggerInterface $logger = null) {
    }
    
    /**
     * @param array<class-string> $types
     * @param array<class-string> $attributes
     */
    public function allowControllers(array $types = [], array $attributes = []) : void {
        foreach ($types as $type) {
            $this->allowedControllerTypes[$type] = $type;
        }
        foreach ($attributes as $attribute) {
            $this->allowedControllerAttributes[$attribute] = $attribute;
        }
    }
    
    /**
     * @throws BadRequestException when the request has attribute "_check_controller_is_allowed" set to true and the controller is not allowed
     */
    public function getController(Request $request) : callable|false {
        if (!($controller = $request->attributes
            ->get('_controller'))) {
            $this->logger?->warning('Unable to look for the controller as the "_controller" parameter is missing.');
            return false;
        }
        if (\is_array($controller)) {
            if (isset($controller[0]) && \is_string($controller[0]) && isset($controller[1])) {
                try {
                    $controller[0] = $this->instantiateController($controller[0]);
                } catch (\Error|\LogicException $e) {
                    if (\is_callable($controller)) {
                        return $this->checkController($request, $controller);
                    }
                    throw $e;
                }
            }
            if (!\is_callable($controller)) {
                throw new \InvalidArgumentException(\sprintf('The controller for URI "%s" is not callable: ', $request->getPathInfo()) . $this->getControllerError($controller));
            }
            return $this->checkController($request, $controller);
        }
        if (\is_object($controller)) {
            if (!\is_callable($controller)) {
                throw new \InvalidArgumentException(\sprintf('The controller for URI "%s" is not callable: ', $request->getPathInfo()) . $this->getControllerError($controller));
            }
            return $this->checkController($request, $controller);
        }
        if (\function_exists($controller)) {
            return $this->checkController($request, $controller);
        }
        try {
            $callable = $this->createController($controller);
        } catch (\InvalidArgumentException $e) {
            throw new \InvalidArgumentException(\sprintf('The controller for URI "%s" is not callable: ', $request->getPathInfo()) . $e->getMessage(), 0, $e);
        }
        if (!\is_callable($callable)) {
            throw new \InvalidArgumentException(\sprintf('The controller for URI "%s" is not callable: ', $request->getPathInfo()) . $this->getControllerError($callable));
        }
        return $this->checkController($request, $callable);
    }
    
    /**
     * Returns a callable for the given controller.
     *
     * @throws \InvalidArgumentException When the controller cannot be created
     */
    protected function createController(string $controller) : callable {
        if (!str_contains($controller, '::')) {
            $controller = $this->instantiateController($controller);
            if (!\is_callable($controller)) {
                throw new \InvalidArgumentException($this->getControllerError($controller));
            }
            return $controller;
        }
        [
            $class,
            $method,
        ] = explode('::', $controller, 2);
        try {
            $controller = [
                $this->instantiateController($class),
                $method,
            ];
        } catch (\Error|\LogicException $e) {
            try {
                if ((new \ReflectionMethod($class, $method))->isStatic()) {
                    return $class . '::' . $method;
                }
            } catch (\ReflectionException) {
                throw $e;
            }
            throw $e;
        }
        if (!\is_callable($controller)) {
            throw new \InvalidArgumentException($this->getControllerError($controller));
        }
        return $controller;
    }
    
    /**
     * Returns an instantiated controller.
     */
    protected function instantiateController(string $class) : object {
        return new $class();
    }
    private function getControllerError(mixed $callable) : string {
        if (\is_string($callable)) {
            if (str_contains($callable, '::')) {
                $callable = explode('::', $callable, 2);
            }
            else {
                return \sprintf('Function "%s" does not exist.', $callable);
            }
        }
        if (\is_object($callable)) {
            $availableMethods = $this->getClassMethodsWithoutMagicMethods($callable);
            $alternativeMsg = $availableMethods ? \sprintf(' or use one of the available methods: "%s"', implode('", "', $availableMethods)) : '';
            return \sprintf('Controller class "%s" cannot be called without a method name. You need to implement "__invoke"%s.', get_debug_type($callable), $alternativeMsg);
        }
        if (!\is_array($callable)) {
            return \sprintf('Invalid type for controller given, expected string, array or object, got "%s".', get_debug_type($callable));
        }
        if (!isset($callable[0]) || !isset($callable[1]) || 2 !== \count($callable)) {
            return 'Invalid array callable, expected [controller, method].';
        }
        [
            $controller,
            $method,
        ] = $callable;
        if (\is_string($controller) && !class_exists($controller)) {
            return \sprintf('Class "%s" does not exist.', $controller);
        }
        $className = \is_object($controller) ? get_debug_type($controller) : $controller;
        if (method_exists($controller, $method)) {
            return \sprintf('Method "%s" on class "%s" should be public and non-abstract.', $method, $className);
        }
        $collection = $this->getClassMethodsWithoutMagicMethods($controller);
        $alternatives = [];
        foreach ($collection as $item) {
            $lev = levenshtein($method, $item);
            if ($lev <= \strlen($method) / 3 || str_contains($item, $method)) {
                $alternatives[] = $item;
            }
        }
        asort($alternatives);
        $message = \sprintf('Expected method "%s" on class "%s"', $method, $className);
        if (\count($alternatives) > 0) {
            $message .= \sprintf(', did you mean "%s"?', implode('", "', $alternatives));
        }
        else {
            $message .= \sprintf('. Available methods: "%s".', implode('", "', $collection));
        }
        return $message;
    }
    private function getClassMethodsWithoutMagicMethods($classOrObject) : array {
        $methods = get_class_methods($classOrObject);
        return array_filter($methods, fn(string $method) => 0 !== strncmp($method, '__', 2));
    }
    private function checkController(Request $request, callable $controller) : callable {
        if (!$request->attributes
            ->get('_check_controller_is_allowed', false)) {
            return $controller;
        }
        $r = null;
        if (\is_array($controller)) {
            [
                $class,
                $name,
            ] = $controller;
            $name = (\is_string($class) ? $class : $class::class) . '::' . $name;
        }
        elseif (\is_object($controller) && !$controller instanceof \Closure) {
            $class = $controller;
            $name = $class::class . '::__invoke';
        }
        else {
            $r = new \ReflectionFunction($controller);
            $name = $r->name;
            if ($r->isAnonymous()) {
                $name = $class = \Closure::class;
            }
            elseif ($class = $r->getClosureCalledClass()) {
                $class = $class->name;
                $name = $class . '::' . $name;
            }
        }
        if ($class) {
            foreach ($this->allowedControllerTypes as $type) {
                if (is_a($class, $type, true)) {
                    return $controller;
                }
            }
        }
        $r ??= new \ReflectionClass($class);
        foreach ($r->getAttributes() as $attribute) {
            if (isset($this->allowedControllerAttributes[$attribute->getName()])) {
                return $controller;
            }
        }
        if (str_contains($name, '@anonymous')) {
            $name = preg_replace_callback('/[a-zA-Z_\\x7f-\\xff][\\\\a-zA-Z0-9_\\x7f-\\xff]*+@anonymous\\x00.*?\\.php(?:0x?|:[0-9]++\\$)?[0-9a-fA-F]++/', fn($m) => class_exists($m[0], false) ? ((get_parent_class($m[0]) ?: key(class_implements($m[0]))) ?: 'class') . '@anonymous' : $m[0], $name);
        }
        throw new BadRequestException(\sprintf('Callable "%s()" is not allowed as a controller. Did you miss tagging it with "#[AsController]" or registering its type with "%s::allowControllers()"?', $name, self::class));
    }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title Overrides
ControllerResolver::$allowedControllerAttributes private property
ControllerResolver::$allowedControllerTypes private property
ControllerResolver::allowControllers public function
ControllerResolver::checkController private function
ControllerResolver::createController protected function Returns a callable for the given controller.
ControllerResolver::getClassMethodsWithoutMagicMethods private function
ControllerResolver::getController public function Overrides ControllerResolverInterface::getController
ControllerResolver::getControllerError private function
ControllerResolver::instantiateController protected function Returns an instantiated controller. 1
ControllerResolver::__construct public function 1

API Navigation

  • Drupal Core 11.1.x
  • Topics
  • Classes
  • Functions
  • Constants
  • Globals
  • Files
  • Namespaces
  • Deprecated
  • Services
RSS feed
Powered by Drupal