{"record":{"id":"30165836557dbc33","repo":"doctrine/orm","slug":"the-attribute-s-is-not-repeatable-call-getprop","errorCode":null,"errorMessage":"The attribute \"%s\" is not repeatable. Call getPropertyAttribute() instead.","messagePattern":"The attribute \"(.+?)\" is not repeatable\\. Call getPropertyAttribute\\(\\) instead\\.","errorType":"exception","errorClass":"LogicException","httpStatus":null,"severity":"error","filePath":"src/Mapping/Driver/AttributeReader.php","lineNumber":87,"sourceCode":"            ));\n        }\n\n        return $this->getPropertyAttributes($property)[$attributeName] ?? null;\n    }\n\n    /**\n     * @param class-string<T> $attributeName The name of the annotation.\n     *\n     * @return RepeatableAttributeCollection<T>\n     *\n     * @template T of MappingAttribute\n     */\n    public function getPropertyAttributeCollection(\n        ReflectionProperty $property,\n        string $attributeName,\n    ): RepeatableAttributeCollection {\n        if (! $this->isRepeatable($attributeName)) {\n            throw new LogicException(sprintf(\n                'The attribute \"%s\" is not repeatable. Call getPropertyAttribute() instead.',\n                $attributeName,\n            ));\n        }\n\n        return $this->getPropertyAttributes($property)[$attributeName] ?? new RepeatableAttributeCollection();\n    }\n\n    /**\n     * @param array<ReflectionAttribute> $attributes\n     *\n     * @return class-string-map<T, T|RepeatableAttributeCollection<T>>\n     *\n     * @template T of MappingAttribute\n     */\n    private function convertToAttributeInstances(array $attributes): array\n    {\n        $instances = [];","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/doctrine/orm/blob/d9b9ff73016bf598ae07515f97289ce8074e97a5/src/Mapping/Driver/AttributeReader.php#L69-L105","documentation":"The mirror image of getPropertyAttribute()'s guard: getPropertyAttributeCollection() returns a RepeatableAttributeCollection and is only valid for mapping attribute classes declared with Attribute::IS_REPEATABLE. Passing a non-repeatable attribute class (which can appear at most once per property) throws LogicException pointing back to the singular getPropertyAttribute().","triggerScenarios":"Calling AttributeReader::getPropertyAttributeCollection($property, $attribute) with a non-repeatable mapping attribute such as Column::class, JoinColumn::class, OneToMany::class, Embedded::class.","commonSituations":"Generic attribute-walking code that always calls the collection API for uniformity; refactoring from one getter to the other without checking the attribute's #[Attribute] flags; custom attributes missing the IS_REPEATABLE flag.","solutions":["Call getPropertyAttribute($property, $attribute) instead and handle the null return when the attribute is absent.","If the attribute genuinely needs to be repeatable, declare it as #[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)].","Dispatch on the IS_REPEATABLE flag (via reflection) when writing generic code that handles both kinds."],"exampleFix":"// before\n$col = $reader->getPropertyAttributeCollection($property, Mapping\\Column::class); // LogicException: not repeatable\n\n// after\n$col = $reader->getPropertyAttribute($property, Mapping\\Column::class); // Column|null","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"/** @param class-string $attribute */\nfunction isRepeatableAttribute(string $attribute): bool\n{\n    $flags = (new ReflectionClass($attribute))\n        ->getAttributes(Attribute::class)[0]?->getArguments()[0] ?? 0;\n\n    return ($flags & Attribute::IS_REPEATABLE) !== 0;\n}\n\nif (! isRepeatableAttribute($name)) {\n    $single = $reader->getPropertyAttribute($property, $name);\n}","tryCatchPattern":null,"preventionTips":["Use the singular getter unless the attribute is declared IS_REPEATABLE.","For generic walkers, dispatch on the IS_REPEATABLE flag.","Treat AttributeReader as @internal: wrap it behind your own interface."],"tags":["doctrine-orm","php-attributes","internal-api","api-misuse"],"backgroundTag":"wrong-api-call","analyzedSha":"d9b9ff73016bf598ae07515f97289ce8074e97a5","analyzedAt":"2026-08-21T06:13:15.863Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}