yiisoft/yii2 · error · InvalidConfigException

"{}" must have a primary key.

Error message

"{}" must have a primary key.

What it means

ActiveRecord::findByCondition() powers findOne()/findAll(). When the condition is neither an associative array nor an ExpressionInterface, it is treated as a primary key value and gets wrapped as [pk => value]. If static::primaryKey() returns an empty array, the wrapping cannot happen and InvalidConfigException is thrown stating the class must have a primary key.

Source

Thrown at framework/db/ActiveRecord.php:187

     * @throws InvalidConfigException if there is no primary key defined.
     * @internal
     */
    protected static function findByCondition($condition)
    {
        $query = static::find();

        if (!ArrayHelper::isAssociative($condition) && !$condition instanceof ExpressionInterface) {
            // query by primary key
            $primaryKey = static::primaryKey();
            if (isset($primaryKey[0])) {
                $pk = $primaryKey[0];
                if (!empty($query->join) || !empty($query->joinWith)) {
                    $pk = static::tableName() . '.' . $pk;
                }
                // if condition is scalar, search for a single primary key, if it is array, search for multiple primary key values
                $condition = [$pk => is_array($condition) ? array_values($condition) : $condition];
            } else {
                throw new InvalidConfigException('"' . get_called_class() . '" must have a primary key.');
            }
        } elseif (is_array($condition)) {
            $aliases = static::filterValidAliases($query);
            $condition = static::filterCondition($condition, $aliases);
        }

        return $query->andWhere($condition);
    }

    /**
     * Returns table aliases which are not the same as the name of the tables.
     *
     * @param Query $query
     * @return array
     * @throws InvalidConfigException
     * @since 2.0.17
     * @internal
     */

View on GitHub (pinned to 66f00d18a2)

Solutions

  1. Define a primary key on the underlying table
  2. Override primaryKey() in the AR class to return identifying column(s)
  3. Query by an associative condition instead of a bare scalar: Model::findOne(['slug' => $slug])
  4. Use find()->andWhere(['column' => $value]) directly, which never assumes a primary key

Example fix

// before
$user = UserView::findOne(5); // throws: no primary key

// after
$user = UserView::find()->where(['email' => $email])->one();
Defensive patterns

Strategy: validation

Validate before calling

// before findOne/findAll with a scalar
if (!is_array($condition) || !ArrayHelper::isAssociative($condition)) {
    if (empty($modelClass::primaryKey())) {
        throw new InvalidArgumentException("{$modelClass} has no primary key; query by column instead.");
    }
}
$model = $modelClass::findOne($condition);

Try / catch

try {
    $model = Model::findOne($id);
} catch (yii\base\InvalidConfigException $e) {
    // model lacks a PK: degrade to a safe column lookup
    $model = Model::find()->where(['external_id' => $id])->one();
}

Prevention

When it happens

Trigger: Calling Model::findOne(1) or Model::findAll([1, 2, 3]) on an AR whose table has no primary key and whose primaryKey() method is not overridden.

Common situations: Database views modeled as AR; tables without a PRIMARY KEY constraint; following tutorial examples that use findOne($id) on a model that was generated for a keyless table; schema cache stale after dropping the key.

Related errors


AI-assisted analysis of yiisoft/yii2@66f00d18a2 (2026-08-17). Data as JSON: /api/errors/632d062c49626893. Report an issue: GitHub.