doctrine/orm · error · LogicException

The attribute "%s" is repeatable. Call getPropertyAttributeC

Error message

The attribute "%s" is repeatable. Call getPropertyAttributeCollection() instead.

What it means

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().

Source

Thrown at src/Mapping/Driver/AttributeReader.php:66

     *
     * @template T of MappingAttribute
     */
    public function getPropertyAttributes(ReflectionProperty $property): array
    {
        return $this->convertToAttributeInstances($property->getAttributes());
    }

    /**
     * @param class-string<T> $attributeName The name of the annotation.
     *
     * @return T|null
     *
     * @template T of MappingAttribute
     */
    public function getPropertyAttribute(ReflectionProperty $property, string $attributeName)
    {
        if ($this->isRepeatable($attributeName)) {
            throw new LogicException(sprintf(
                'The attribute "%s" is repeatable. Call getPropertyAttributeCollection() instead.',
                $attributeName,
            ));
        }

        return $this->getPropertyAttributes($property)[$attributeName] ?? null;
    }

    /**
     * @param class-string<T> $attributeName The name of the annotation.
     *
     * @return RepeatableAttributeCollection<T>
     *
     * @template T of MappingAttribute
     */
    public function getPropertyAttributeCollection(
        ReflectionProperty $property,
        string $attributeName,

View on GitHub (pinned to d9b9ff7301)

Solutions

  1. Call getPropertyAttributeCollection($property, $attribute) and iterate the returned RepeatableAttributeCollection (an ArrayObject holding every instance) instead.
  2. For non-repeatable attributes (Column, JoinColumn, OneToMany, ...) keep using getPropertyAttribute().
  3. If your custom attribute was not meant to be repeatable, remove Attribute::IS_REPEATABLE from its #[Attribute] declaration.

Example fix

// before
$attr = $reader->getPropertyAttribute($property, Mapping\Index::class); // LogicException: repeatable

// after
/** @var RepeatableAttributeCollection<Mapping\Index> $indexes */
$indexes = $reader->getPropertyAttributeCollection($property, Mapping\Index::class);
foreach ($indexes as $index) { /* ... */ }
Defensive patterns

Strategy: type-guard

Type guard

/** @param class-string $attribute */
function isRepeatableAttribute(string $attribute): bool
{
    $flags = (new ReflectionClass($attribute))
        ->getAttributes(Attribute::class)[0]?->getArguments()[0] ?? 0;

    return ($flags & Attribute::IS_REPEATABLE) !== 0;
}

$attrs = isRepeatableAttribute($name)
    ? $reader->getPropertyAttributeCollection($property, $name)
    : (null !== ($a = $reader->getPropertyAttribute($property, $name)) ? [$a] : []);

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of doctrine/orm@d9b9ff7301 (2026-08-21). Data as JSON: /api/errors/8a38e96a478d140d. Report an issue: GitHub.