{"record":{"id":"ad12156797c30d35","repo":"symfony/routing","slug":"route-aliases-cannot-be-used-on-non-invokable-class-s","errorCode":null,"errorMessage":"Route aliases cannot be used on non-invokable class \"%s\".","messagePattern":"Route aliases cannot be used on non-invokable class \"(.+?)\"\\.","errorType":"validation","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"Loader/AttributeClassLoader.php","lineNumber":99,"sourceCode":"        }\n\n        $class = new \\ReflectionClass($class);\n        if ($class->isAbstract()) {\n            throw new \\InvalidArgumentException(\\sprintf('Attributes from class \"%s\" cannot be read as it is abstract.', $class->getName()));\n        }\n\n        $globals = $this->getGlobals($class);\n        $collection = new RouteCollection();\n        $collection->addResource(new ReflectionClassResource($class));\n        if ($globals['env'] && !\\in_array($this->env, $globals['env'], true)) {\n            return $collection;\n        }\n        $fqcnAlias = false;\n\n        if (!$class->hasMethod('__invoke')) {\n            foreach ($this->getAttributes($class) as $attr) {\n                if ($attr->aliases) {\n                    throw new InvalidArgumentException(\\sprintf('Route aliases cannot be used on non-invokable class \"%s\".', $class->getName()));\n                }\n            }\n        }\n\n        foreach ($class->getMethods() as $method) {\n            $this->defaultRouteIndex = 0;\n            $routeNamesBefore = array_keys($collection->all());\n            foreach ($this->getAttributes($method) as $attr) {\n                $this->addRoute($collection, $attr, $globals, $class, $method);\n                if ('__invoke' === $method->name) {\n                    $fqcnAlias = true;\n                }\n            }\n\n            if (1 === $collection->count() - \\count($routeNamesBefore)) {\n                $newRouteName = current(array_diff(array_keys($collection->all()), $routeNamesBefore));\n                if ($newRouteName !== $aliasName = \\sprintf('%s::%s', $class->name, $method->name)) {\n                    $collection->addAlias($aliasName, $newRouteName);","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/symfony/routing/blob/83fa223250b50f4f018c011e101c330e65ac63cc/Loader/AttributeClassLoader.php#L81-L117","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Add an __invoke() method to the class so it is invokable.","Move the aliases argument to a method-level #[Route] attribute instead.","Remove the aliases argument if no alias is needed."],"exampleFix":"// before\n#[Route('/x', name: 'x', aliases: ['x_alias'])]\nclass XController { public function show() {...} }\n// after\n#[Route('/x', name: 'x', aliases: ['x_alias'])]\nclass XController { public function __invoke() {...} }","handlingStrategy":"validation","validationCode":"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'); } } }","typeGuard":"function isInvokableController(string $class): bool { return method_exists($class, '__invoke'); }","tryCatchPattern":"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; }","preventionTips":["Only place aliases on __invoke controllers or method-level Route attributes.","Run bin/console cache:warmup or debug:router in CI to catch attribute misconfigurations early.","Code-review rule: class-level #[Route] without __invoke must not carry aliases."],"tags":["symfony","routing","attributes"],"backgroundTag":"invalid-config-value","analyzedSha":"83fa223250b50f4f018c011e101c330e65ac63cc","analyzedAt":"2026-09-14T03:19:46.280Z","contentChangedAt":"2026-09-14T03:19:46.280Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}