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
- Call $classMetadata->setTable(...) (set the primary table) before mapping the one-to-one association
- Prefer a standard mapping driver (attributes/XML/YAML), which sets the table before associations by construction
- 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
- Prefer standard mapping drivers over hand-built ClassMetadata
- When building metadata programmatically, set the table first, then associations
- Write a smoke test that builds metadata for every dynamic entity
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
- Attempting to change readonly property %s::$%s.
- This mapping is not indexed. Use %s::isIndexed() to check th
AI-assisted analysis of doctrine/orm@d9b9ff7301 (2026-08-21).
Data as JSON: /api/errors/5ea9995ff2aff1c7.
Report an issue: GitHub.