doctrine/orm · error · BadMethodCallException

Selecting a collection by index is only supported on indexed

Error message

Selecting a collection by index is only supported on indexed collections.

What it means

ManyToManyPersister::get() loads a single element of an EXTRA_LAZY many-to-many collection straight from the database by its key, which requires knowing which target field is the key (indexBy). If the association mapping has no indexBy, the persister cannot translate the key into a WHERE condition and throws BadMethodCallException. PersistentCollection::get()/offsetGet() only reach the persister when the collection is uninitialized and fetch is EXTRA_LAZY.

Source

Thrown at src/Persisters/Collection/ManyToManyPersister.php:89

                $deleteTypes,
            );
        }

        foreach ($collection->getInsertDiff() as $element) {
            $this->conn->executeStatement(
                $insertSql,
                $this->getInsertRowSQLParameters($collection, $element),
                $insertTypes,
            );
        }
    }

    public function get(PersistentCollection $collection, mixed $index): object|null
    {
        $mapping = $this->getMapping($collection);

        if (! $mapping->isIndexed()) {
            throw new BadMethodCallException('Selecting a collection by index is only supported on indexed collections.');
        }

        $persister = $this->uow->getEntityPersister($mapping->targetEntity);
        $mappedKey = $mapping->isOwningSide()
            ? $mapping->inversedBy
            : $mapping->mappedBy;

        assert($mappedKey !== null);

        return $persister->load(
            [$mappedKey => $collection->getOwner(), $mapping->indexBy() => $index],
            null,
            $mapping,
            [],
            LockMode::NONE,
            1,
        );
    }

View on GitHub (pinned to d9b9ff7301)

Solutions

  1. Declare indexBy in the mapping: #[ManyToMany(targetEntity: Tag::class, indexBy: 'id')] (or any unique target field)
  2. Drop EXTRA_LAZY for that association so get() initializes the collection in memory first
  3. Force initialization before access: $collection->initialize(); then $collection->get($key)

Example fix

// before
#[ManyToMany(targetEntity: Tag::class), Fetch(FetchMode::EXTRA_LAZY)]
private Collection $tags;
$tag = $post->getTags()->get(42); // throws

// after
#[ManyToMany(targetEntity: Tag::class, indexBy: 'id'), Fetch(FetchMode::EXTRA_LAZY)]
private Collection $tags;
Defensive patterns

Strategy: validation

Validate before calling

$mapping = $em->getClassMetadata($post::class)->getAssociationMapping('tags');
if (! $mapping->isIndexed()) {
    $post->getTags()->initialize(); // fall back to in-memory keyed access
}
$tag = $post->getTags()->get($key);

Prevention

When it happens

Trigger: $collection->get($key) or $collection[$key] on an uninitialized EXTRA_LAZY many-to-many association without `indexBy` in its #[ManyToMany]/YAML/XML mapping.

Common situations: Switching an association to fetch: EXTRA_LAZY for performance and keeping array-style access; assuming collections are keyed by target primary key without declaring indexBy.

Related errors


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