doctrine/orm · error · RuntimeException

ClassMetadata::setTable() has to be called before defining a

Error message

ClassMetadata::setTable() has to be called before defining a one to one relationship.

What it means

When an owning one-to-one association has MULTIPLE join columns that are not the primary key, Doctrine must record a composite UNIQUE constraint on the owning table (fieldName . '_uniq'). That write needs ClassMetadata::$table to be populated already; the mapping builder throws when the one-to-one is defined before setTable(). Standard mapping drivers always set the table first, so this error is almost exclusive to programmatic metadata construction out of order.

Source

Thrown at src/Mapping/ToOneOwningSideMapping.php:184

            }

            if ($joinColumn->name[0] === '`') {
                $joinColumn->name   = trim($joinColumn->name, '`');
                $joinColumn->quoted = true;
            }

            if ($joinColumn->referencedColumnName[0] === '`') {
                $joinColumn->referencedColumnName = trim($joinColumn->referencedColumnName, '`');
                $joinColumn->quoted               = true;
            }

            $mapping->sourceToTargetKeyColumns[$joinColumn->name] = $joinColumn->referencedColumnName;
            $mapping->joinColumnFieldNames[$joinColumn->name]     = $joinColumn->fieldName ?? $joinColumn->name;
        }

        if ($uniqueConstraintColumns) {
            if (! $table) {
                throw new RuntimeException('ClassMetadata::setTable() has to be called before defining a one to one relationship.');
            }

            $table['uniqueConstraints'][$mapping->fieldName . '_uniq'] = ['columns' => $uniqueConstraintColumns];
        }

        $mapping->targetToSourceKeyColumns = array_flip($mapping->sourceToTargetKeyColumns);

        return $mapping;
    }

    public function offsetSet(mixed $offset, mixed $value): void
    {
        if ($offset === 'joinColumns') {
            $joinColumns = [];
            foreach ($value as $column) {
                $joinColumns[] = JoinColumnMapping::fromMappingArray($column);
            }

View on GitHub (pinned to d9b9ff7301)

Solutions

  1. Call $classMetadata->setTable(...) (set the primary table) before mapping the one-to-one association
  2. Prefer a standard mapping driver (attributes/XML/YAML), which sets the table before associations by construction
  3. If writing a custom driver, mirror the order used by the AttributeDriver

Example fix

// before
$classMetadata->mapOneToOne($mapping); // composite join columns
$classMetadata->setTable('invoice');

// after
$classMetadata->setTable('invoice');
$classMetadata->mapOneToOne($mapping);
Defensive patterns

Strategy: validation

Validate before calling

if ($classMetadata->getTableName() === null) {
    throw new RuntimeException('Set the primary table before mapping one-to-one associations.');
}
$classMetadata->mapOneToOne($mapping);

Prevention

When it happens

Trigger: Calling $classMetadata->mapOneToOne(['joinColumns' => [...multiple...]]) (or mapManyToOne/joinColumn building that produces uniqueConstraintColumns) before $classMetadata->setTable()/setPrimaryTable(); custom mapping drivers or runtime metadata builders that map associations before the table.

Common situations: Hand-built ClassMetadata (dynamic entities, tests, migration tooling); custom mapping driver with wrong call order; code that copies mappings between ClassMetadata instances.

Related errors


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