symfony/http-kernel · error · InvalidArgumentException

Message built by getControllerError(): e.g. Function

Error message

Message built by getControllerError(): e.g. Function "%s" does not exist. / Controller class "%s" cannot be called without a method name. You need to implement "__invoke". / Class "%s" does not exist.

What it means

Symfony's ControllerResolver fails to turn a controller string into a callable, so it throws InvalidArgumentException with a message produced by getControllerError(). The message tells you whether the function/class does not exist, or whether the class is not callable without a method name (no __invoke). It is thrown in createController() when the controller string contains no '::' and instantiating it does not yield something callable.

Solutions

  1. Check that the controller string matches an existing class (correct namespace and spelling) and is a real class, not an abstract/interface.
  2. If the controller is a service, ensure the class is autowired/autoconfigured in services.yaml and the class implements __invoke or the route names a method via 'Class::method'.
  3. Run `php bin/console debug:router` and `php bin/console debug:container <class>` to verify the controller resolves.
  4. Add a __invoke() method or an explicit '::method' suffix to the controller definition.

Example fix

// before: routes.yaml
app_foo:
    path: /foo
    controller: App\Controller\FooController
// after
app_foo:
    path: /foo
    controller: App\Controller\FooController::index
Defensive patterns

Strategy: validation

Validate before calling

if (!class_exists($controllerClass) && !str_contains($controllerString, '::')) {
    throw new \InvalidArgumentException(sprintf('Controller "%s" must be "Class::method" or implement __invoke().', $controllerString));
}

Type guard

if (!\is_callable([$controllerClass, $method]) && !\is_callable([new $controllerClass(), '__invoke'])) {
    return null; // not a resolvable controller
}

Try / catch

try {
    $controller = $resolver->getController($request);
} catch (\InvalidArgumentException $e) {
    $logger->error('Unresolvable controller: '.$e->getMessage());
    return new Response('Not Found', 404);
}

Prevention

When it happens

Trigger: Route/service configured with a controller string like 'App\Controller\FooController' where the class does not exist, is not registered as a service (controller.service_arguments), or lacks a default method and does not implement __invoke(); passing a non-callable value to the resolver directly.

Common situations: Typo in controller class name in routes.yaml/attributes; controller class defined but not tagged for the service container (missing services.yaml autoconfigure); class exists but expects constructor arguments not resolvable from the container; framework version upgrade changing how controllers are resolved.

Understand the failure class

Background: "must be a positive integer", "cannot be empty", "invalid argument": how invalid-argument errors work across open-source libraries — this error's family across 33 libraries.

Related errors


AI-assisted analysis of symfony/http-kernel@aa3a39d728 (2026-09-13). Data as JSON: /api/errors/0a91fd13870680dc. Report an issue: GitHub.

Appendix: source

Thrown at Controller/ControllerResolver.php:118

        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;
            }

View on GitHub (pinned to aa3a39d728)