yiisoft/yii2 · error · InvalidConfigException
Primary key of '{$class}' can not be empty.
Error message
Primary key of '{$class}' can not be empty. What it means
After a JOIN query, ActiveQuery::populate() calls removeDuplicatedModels() to drop rows duplicated by the join, building a uniqueness hash from the model's primary key values. When $class::primaryKey() returns an empty array (the underlying table declares no primary key), the hash key cannot be computed and InvalidConfigException is thrown. The dedup path only runs when the query has joins and indexBy is null, so plain queries on the same model work fine.
Source
Thrown at framework/db/ActiveQuery.php:283
// composite primary key
foreach ($models as $i => $model) {
$key = [];
foreach ($pks as $pk) {
if (!isset($model[$pk])) {
// do not continue if the primary key is not part of the result set
break 2;
}
$key[] = $model[$pk];
}
$key = serialize($key);
if (isset($hash[$key])) {
unset($models[$i]);
} else {
$hash[$key] = true;
}
}
} elseif (empty($pks)) {
throw new InvalidConfigException("Primary key of '{$class}' can not be empty.");
} else {
// single column primary key
$pk = reset($pks);
foreach ($models as $i => $model) {
if (!isset($model[$pk])) {
// do not continue if the primary key is not part of the result set
break;
}
$key = $model[$pk];
if (isset($hash[$key])) {
unset($models[$i]);
} elseif ($key !== null) {
$hash[$key] = true;
}
}
}
return array_values($models);View on GitHub (pinned to 66f00d18a2)
Solutions
- Add a PRIMARY KEY constraint to the underlying table (preferred)
- Override primaryKey() in the AR class to return the column(s) that uniquely identify a row, e.g. public static function primaryKey() { return ['id']; }
- Set ->indexBy('unique_column') on the query to bypass the dedup logic entirely
- Remove the join if it is not needed, or deduplicate at SQL level (SELECT DISTINCT / GROUP BY)
Example fix
// before
class SalesView extends \yii\db\ActiveRecord
{
public static function tableName() { return 'vw_sales'; }
}
$rows = SalesView::find()->joinWith('customer')->all(); // throws
// after
class SalesView extends \yii\db\ActiveRecord
{
public static function tableName() { return 'vw_sales'; }
public static function primaryKey() { return ['sales_id']; }
}
$rows = SalesView::find()->joinWith('customer')->all(); Defensive patterns
Strategy: validation
Validate before calling
if (!empty($this->join) && $query->indexBy === null && empty($modelClass::primaryKey())) {
throw new InvalidConfigException("{$modelClass} has no primary key; JOIN deduplication is impossible.");
}
$rows = $query->all(); Try / catch
try {
$rows = Model::find()->joinWith('rel')->all();
} catch (yii\base\InvalidConfigException $e) {
if (strpos($e->getMessage(), 'Primary key') === 0) {
// fall back to a keyless query without dedup
$rows = Model::find()->indexBy('id')->joinWith('rel')->all();
} else {
throw $e;
}
} Prevention
- Ensure every AR-mapped table or view declares (or overrides) a primary key as a project convention
- Add a bootstrap check: assert !empty(Model::primaryKey()) for models used in joined queries
- Document keyless views as read-only and never join them through AR
When it happens
Trigger: Model::find()->joinWith('relation')->all(), or ->innerJoin(...)->all(), where the model's table or view has no primary key and indexBy is not set; the same via ->leftJoin() with ->with() eager loading.
Common situations: AR classes mapped to MySQL/MariaDB views (which cannot declare primary keys); legacy tables created without a PRIMARY KEY constraint; stale schema cache after the key was added; queries that worked until a join was introduced.
Related errors
- {class} does not have a primary key. You should either defin
- "{}" must have a primary key.
- "{class}" must have a primary key.
- Key "{}" is not a column name and can not be used as a filte
- The table does not exist: {table}
AI-assisted analysis of yiisoft/yii2@66f00d18a2 (2026-08-17).
Data as JSON: /api/errors/a5389f72c53d6808.
Report an issue: GitHub.