{"record":{"id":"8a38e96a478d140d","repo":"doctrine/orm","slug":"the-attribute-s-is-repeatable-call-getproperty","errorCode":null,"errorMessage":"The attribute \"%s\" is repeatable. Call getPropertyAttributeCollection() instead.","messagePattern":"The attribute \"(.+?)\" is repeatable\\. Call getPropertyAttributeCollection\\(\\) instead\\.","errorType":"exception","errorClass":"LogicException","httpStatus":null,"severity":"error","filePath":"src/Mapping/Driver/AttributeReader.php","lineNumber":66,"sourceCode":"     *\n     * @template T of MappingAttribute\n     */\n    public function getPropertyAttributes(ReflectionProperty $property): array\n    {\n        return $this->convertToAttributeInstances($property->getAttributes());\n    }\n\n    /**\n     * @param class-string<T> $attributeName The name of the annotation.\n     *\n     * @return T|null\n     *\n     * @template T of MappingAttribute\n     */\n    public function getPropertyAttribute(ReflectionProperty $property, string $attributeName)\n    {\n        if ($this->isRepeatable($attributeName)) {\n            throw new LogicException(sprintf(\n                'The attribute \"%s\" is repeatable. Call getPropertyAttributeCollection() instead.',\n                $attributeName,\n            ));\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,","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/doctrine/orm/blob/d9b9ff73016bf598ae07515f97289ce8074e97a5/src/Mapping/Driver/AttributeReader.php#L48-L84","documentation":"AttributeReader is Doctrine ORM's internal wrapper over native PHP attributes. getPropertyAttribute() returns exactly one MappingAttribute instance, so it refuses attribute classes declared with Attribute::IS_REPEATABLE (e.g. Index, UniqueConstraint, AssociationOverride, AttributeOverride), where one property may carry several instances. For those it throws LogicException and directs you to getPropertyAttributeCollection().","triggerScenarios":"Calling AttributeReader::getPropertyAttribute($reflectionProperty, $attribute) where $attribute is a repeatable mapping attribute such as Doctrine\\ORM\\Mapping\\Index, UniqueConstraint, AssociationOverride or AttributeOverride attached to a property.","commonSituations":"Custom tooling or bundles reusing Doctrine's AttributeReader to inspect entity mapping attributes; code ported from the old doctrine/annotations AnnotationReader where a single getAttribute()-style call was generic; PHPStan/PSalm plugins walking mapping attributes.","solutions":["Call getPropertyAttributeCollection($property, $attribute) and iterate the returned RepeatableAttributeCollection (an ArrayObject holding every instance) instead.","For non-repeatable attributes (Column, JoinColumn, OneToMany, ...) keep using getPropertyAttribute().","If your custom attribute was not meant to be repeatable, remove Attribute::IS_REPEATABLE from its #[Attribute] declaration."],"exampleFix":"// before\n$attr = $reader->getPropertyAttribute($property, Mapping\\Index::class); // LogicException: repeatable\n\n// after\n/** @var RepeatableAttributeCollection<Mapping\\Index> $indexes */\n$indexes = $reader->getPropertyAttributeCollection($property, Mapping\\Index::class);\nforeach ($indexes as $index) { /* ... */ }","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\n$attrs = isRepeatableAttribute($name)\n    ? $reader->getPropertyAttributeCollection($property, $name)\n    : (null !== ($a = $reader->getPropertyAttribute($property, $name)) ? [$a] : []);","tryCatchPattern":null,"preventionTips":["Prefer getClassAttributes()/getPropertyAttributes() which return both shapes and let you inspect.","Check the #[Attribute] declaration before choosing the singular vs collection getter.","Remember AttributeReader is @internal — pin exact ORM versions when you use it."],"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"}