doctrine/orm · error · MappingException

Table {tableName} has no primary key. Doctrine does not supp

Error message

Table {tableName} has no primary key. Doctrine does not support reverse engineering from tables that don't have a primary key.

What it means

While reverse engineering a database schema, DatabaseDriver requires every entity table to have a primary key constraint, because Doctrine entities fundamentally need an identifier to manage identity and unit-of-work tracking. When a table's primary key cannot be determined (it literally has none, considering the DBAL version's API), a MappingException is thrown naming the table.

Source

Thrown at src/Mapping/Driver/DatabaseDriver.php:298

        foreach ($this->sm->listTables() as $table) {
            $tableName   = self::getAssetName($table);
            $foreignKeys = $table->getForeignKeys();

            $allForeignKeyColumns = [];

            foreach ($foreignKeys as $foreignKey) {
                $allForeignKeyColumns = array_merge($allForeignKeyColumns, self::getReferencingColumnNames($foreignKey));
            }

            if (method_exists($table, 'getPrimaryKeyConstraint')) {
                $primaryKey = $table->getPrimaryKeyConstraint();
            } else {
                $primaryKey = $table->getPrimaryKey();
            }

            if ($primaryKey === null) {
                throw new MappingException(
                    'Table ' . $tableName . ' has no primary key. Doctrine does not ' .
                    "support reverse engineering from tables that don't have a primary key.",
                );
            }

            if ($primaryKey instanceof PrimaryKeyConstraint) {
                $pkColumns = array_map(static fn (UnqualifiedName $name) => $name->toString(), $primaryKey->getColumnNames());
            } else {
                $pkColumns = self::getIndexedColumns($primaryKey);
            }

            sort($pkColumns);
            sort($allForeignKeyColumns);

            if ($pkColumns === $allForeignKeyColumns && count($foreignKeys) === 2) {
                $this->manyToManyTables[$tableName] = $table;
            } else {
                // lower-casing is necessary because of Oracle Uppercase Tablenames,

View on GitHub (pinned to d9b9ff7301)

Solutions

  1. Add a primary key to the offending table: ALTER TABLE my_table ADD PRIMARY KEY (id); (or add an AUTOINCREMENT id column first).
  2. Exclude the table from reverse engineering via the schema asset filter (FilterSchemaAssetsExpression or schema_assets_filter config) so it never reaches the driver.
  3. If the table is a many-to-many join table, pass it through DatabaseDriver::setTables($entityTables, [$joinTable]) as a manyToManyTable instead of an entity table.

Example fix

-- before
CREATE TABLE report_temp (fetched_at DATETIME, sku VARCHAR(64), qty INT);

-- after
CREATE TABLE report_temp (fetched_at DATETIME, sku VARCHAR(64), qty INT,
  PRIMARY KEY (fetched_at, sku));
Defensive patterns

Strategy: validation

Validate before calling

// Exclude PK-less utility tables before reverse engineering
$schemaManager->getDatabasePlatform()->...;
// DBAL 3: $conn->getConfiguration()->setSchemaAssetsFilter('#^(?!report_temp)#');
// DBAL 4: $conn->getConfiguration()->setSchemaAssetsFilter(static fn ($name) => $name !== 'report_temp');

Try / catch

try { $driver->loadMetadataForClass($class, $metadata); } catch (MappingException $e) { /* log and skip tables that cannot be mapped */ }

Prevention

When it happens

Trigger: Running orm:convert-mapping --from-database, the SchemaTool, or DatabaseDriver::loadMetadataForClass() against a schema where at least one table has no primary key constraint — e.g. a hand-made many-to-many join table, staging/import tables, or legacy reporting tables.

Common situations: Reverse engineering legacy databases (older WordPress/Magento-style schemas) known for PK-less tables; generated tables where the PK was dropped; CI converting a shared/staging schema that contains utility tables.

Related errors


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