{"record":{"id":"8045e8a86d02c9cc","repo":"symfony/routing","slug":"attributes-from-class-s-cannot-be-read-as-it-is-abstract","errorCode":null,"errorMessage":"Attributes from class \"%s\" cannot be read as it is abstract.","messagePattern":"Attributes from class \"(.+?)\" cannot be read as it is abstract\\.","errorType":"exception","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"Loader/AttributeClassLoader.php","lineNumber":85,"sourceCode":"     * Sets the attribute class to read route properties from.\n     */\n    public function setRouteAttributeClass(string $class): void\n    {\n        $this->routeAttributeClass = $class;\n    }\n\n    /**\n     * @throws \\InvalidArgumentException When route can't be parsed\n     */\n    public function load(mixed $class, ?string $type = null): RouteCollection\n    {\n        if (!class_exists($class)) {\n            throw new \\InvalidArgumentException(\\sprintf('Class \"%s\" does not exist.', $class));\n        }\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","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/symfony/routing/blob/83fa223250b50f4f018c011e101c330e65ac63cc/Loader/AttributeClassLoader.php#L67-L103","documentation":"AttributeClassLoader::load() rejects abstract classes because attributes (Route/condition decorators) on an abstract class cannot yield an instantiable controller route set in the normal way; reading them is considered a configuration mistake.","triggerScenarios":"$loader->load(AbstractControllerBase::class) where the class is declared abstract — e.g. pointing the loader at a shared base controller instead of a concrete one.","commonSituations":"Loading a base controller class with common route attributes; auto-discovery mistakenly including abstract classes in the controller scan list.","solutions":["Load a concrete (non-abstract) controller class","Exclude abstract classes from your controller-discovery list (e.g. instanceof / isAbstract filter)","Move route attributes from the abstract base to concrete subclasses, or use a parent-class-aware loader"],"exampleFix":"// before\n$loader->load(AbstractApiController::class); // abstract\n// after\n$loader->load(ProductApiController::class); // concrete subclass","handlingStrategy":"validation","validationCode":"$ref = new \\ReflectionClass($class);\nif ($ref->isAbstract()) {\n    throw new \\InvalidArgumentException(\"Cannot load attributes from abstract class '$class'\");\n}","typeGuard":"function isConcreteClass(string $class): bool {\n    return class_exists($class) && !(new \\ReflectionClass($class))->isAbstract();\n}","tryCatchPattern":"try {\n    $collection = $loader->load($class);\n} catch (\\InvalidArgumentException $e) {\n    // skip abstract classes discovered by the scanner\n}","preventionTips":["Filter abstract classes out of controller auto-discovery lists","Only reference concrete controllers in route configuration","Add a unit test asserting all scanned controller classes are instantiable"],"tags":["php","routing","loader","attributes","abstract-class"],"backgroundTag":"invalid-argument-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"}