doctrine/orm · error · LogicException

The attribute "%s" is not repeatable. Call getPropertyAttrib

Error message

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

What it means

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

Source

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

            ));
        }

        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,
    ): RepeatableAttributeCollection {
        if (! $this->isRepeatable($attributeName)) {
            throw new LogicException(sprintf(
                'The attribute "%s" is not repeatable. Call getPropertyAttribute() instead.',
                $attributeName,
            ));
        }

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

    /**
     * @param array<ReflectionAttribute> $attributes
     *
     * @return class-string-map<T, T|RepeatableAttributeCollection<T>>
     *
     * @template T of MappingAttribute
     */
    private function convertToAttributeInstances(array $attributes): array
    {
        $instances = [];

View on GitHub (pinned to d9b9ff7301)

Solutions

  1. Call getPropertyAttribute($property, $attribute) instead and handle the null return when the attribute is absent.
  2. If the attribute genuinely needs to be repeatable, declare it as #[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)].
  3. Dispatch on the IS_REPEATABLE flag (via reflection) when writing generic code that handles both kinds.

Example fix

// before
$col = $reader->getPropertyAttributeCollection($property, Mapping\Column::class); // LogicException: not repeatable

// after
$col = $reader->getPropertyAttribute($property, Mapping\Column::class); // Column|null
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;
}

if (! isRepeatableAttribute($name)) {
    $single = $reader->getPropertyAttribute($property, $name);
}

Prevention

When it happens

Trigger: Calling AttributeReader::getPropertyAttributeCollection($property, $attribute) with a non-repeatable mapping attribute such as Column::class, JoinColumn::class, OneToMany::class, Embedded::class.

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

Related errors


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