yiisoft/yii2 · error · yii\base\InvalidArgumentException

Relation names are case sensitive. {class} has a relation na

Error message

Relation names are case sensitive. {class} has a relation named "{realName}" instead of "{name}".

What it means

Lazy relation access goes through ActiveRelationTrait::findFor($name, $model): it looks up the getter method with method_exists() (which is case-insensitive in PHP), then compares lcfirst of the real method name after 'get' against the property name actually used. Because the comparison is strict, $model->Items still finds getItems() but then throws InvalidArgumentException explaining relation names are case sensitive.

Source

Thrown at framework/db/ActiveRelationTrait.php:184

        $this->inverseOf = $relationName;
        return $this;
    }

    /**
     * Finds the related records for the specified primary record.
     * This method is invoked when a relation of an ActiveRecord is being accessed lazily.
     * @param string $name the relation name
     * @param ActiveRecordInterface|BaseActiveRecord $model the primary model
     * @return mixed the related record(s)
     * @throws InvalidArgumentException if the relation is invalid
     */
    public function findFor($name, $model)
    {
        if (method_exists($model, 'get' . $name)) {
            $method = new \ReflectionMethod($model, 'get' . $name);
            $realName = lcfirst(substr($method->getName(), 3));
            if ($realName !== $name) {
                throw new InvalidArgumentException('Relation names are case sensitive. ' . get_class($model) . " has a relation named \"$realName\" instead of \"$name\".");
            }
        }

        return $this->multiple ? $this->all() : $this->one();
    }

    /**
     * If applicable, populate the query's primary model into the related records' inverse relationship.
     * @param array $result the array of related records as generated by [[populate()]]
     * @since 2.0.9
     */
    private function addInverseRelations(&$result)
    {
        if ($this->inverseOf === null) {
            return;
        }

        foreach ($result as $i => $relatedModel) {

View on GitHub (pinned to 66f00d18a2)

Solutions

  1. Use the exact case matching the getter minus 'get': getItems() is accessed as ->items
  2. Rename the getter to the casing you want and update all references
  3. For dynamic access, normalize the name first: $model->{lcfirst($name)}
  4. Grep for the wrongly-cased property in templates and API mappers after renaming relations

Example fix

// before
$items = $order->Items; // getter is getItems() -> throws

// after
$items = $order->items;
Defensive patterns

Strategy: type-guard

Type guard

function hasExactCaseRelation(\yii\db\BaseActiveRecord $model, string $name): bool
{
    if (!method_exists($model, 'get' . $name)) {
        return false;
    }
    $method = new ReflectionMethod($model, 'get' . $name);
    return lcfirst(substr($method->getName(), 3)) === $name;
}

Try / catch

try {
    $items = $order->items;
} catch (yii\base\InvalidArgumentException $e) {
    if (strpos($e->getMessage(), 'Relation names are case sensitive') === 0) {
        $items = $order->{lcfirst($relationName)}; // corrected casing
    } else {
        throw $e;
    }
}

Prevention

When it happens

Trigger: Accessing $order->Items when the getter is getItems(); $model->Author vs getAuthor(); dynamic access $model->{$name} where $name's casing came from user input or generated code.

Common situations: IDE autocompletion guessing wrong casing; refactors that rename getters without grepping all usages; template or API layer building property names dynamically; copy-pasting property access between codebases with different conventions.

Related errors


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