symfony/routing · error · InvalidArgumentException

Route aliases cannot be used on non-invokable class

Error message

Route aliases cannot be used on non-invokable class "%s".

What it means

AttributeClassLoader rejects #[AsAlias]/alias attributes applied at the class level when the controller class has no __invoke method. Route aliases are only meaningful on an invokable class or on individual methods, because aliases point to a single route.

Solutions

  1. Add an __invoke() method to the class so it is invokable.
  2. Move the aliases argument to a method-level #[Route] attribute instead.
  3. Remove the aliases argument if no alias is needed.

Example fix

// before
#[Route('/x', name: 'x', aliases: ['x_alias'])]
class XController { public function show() {...} }
// after
#[Route('/x', name: 'x', aliases: ['x_alias'])]
class XController { public function __invoke() {...} }
Defensive patterns

Strategy: validation

Validate before calling

if (!$ref->hasMethod('__invoke')) { foreach ($ref->getAttributes(\Symfony\Component\Routing\Attribute\Route::class) as $a) { if (!empty($a->getArguments()['aliases'])) { throw new \LogicException('aliases require __invoke'); } } }

Type guard

function isInvokableController(string $class): bool { return method_exists($class, '__invoke'); }

Try / catch

try { $loader->load($class); } catch (\InvalidArgumentException $e) { if (str_contains($e->getMessage(), 'Route aliases cannot be used')) { /* fix config or rethrow */ throw $e; } throw $e; }

Prevention

When it happens

Trigger: Loading a controller class (e.g. #[Route(...)] with aliases: ['name']) that has no __invoke(); running cache:warmup or the app so the class is loaded through the attribute loader.

Common situations: Refactoring a method-controller to a class controller and copying the aliases argument to the class-level attribute; typos placing aliases on #[Route] at class level.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of symfony/routing@83fa223250 (2026-09-14). Data as JSON: /api/errors/ad12156797c30d35. Report an issue: GitHub.

Appendix: source

Thrown at Loader/AttributeClassLoader.php:99

        }

        $class = new \ReflectionClass($class);
        if ($class->isAbstract()) {
            throw new \InvalidArgumentException(\sprintf('Attributes from class "%s" cannot be read as it is abstract.', $class->getName()));
        }

        $globals = $this->getGlobals($class);
        $collection = new RouteCollection();
        $collection->addResource(new ReflectionClassResource($class));
        if ($globals['env'] && !\in_array($this->env, $globals['env'], true)) {
            return $collection;
        }
        $fqcnAlias = false;

        if (!$class->hasMethod('__invoke')) {
            foreach ($this->getAttributes($class) as $attr) {
                if ($attr->aliases) {
                    throw new InvalidArgumentException(\sprintf('Route aliases cannot be used on non-invokable class "%s".', $class->getName()));
                }
            }
        }

        foreach ($class->getMethods() as $method) {
            $this->defaultRouteIndex = 0;
            $routeNamesBefore = array_keys($collection->all());
            foreach ($this->getAttributes($method) as $attr) {
                $this->addRoute($collection, $attr, $globals, $class, $method);
                if ('__invoke' === $method->name) {
                    $fqcnAlias = true;
                }
            }

            if (1 === $collection->count() - \count($routeNamesBefore)) {
                $newRouteName = current(array_diff(array_keys($collection->all()), $routeNamesBefore));
                if ($newRouteName !== $aliasName = \sprintf('%s::%s', $class->name, $method->name)) {
                    $collection->addAlias($aliasName, $newRouteName);

View on GitHub (pinned to 83fa223250)