yiisoft/yii2 · error · InvalidConfigException

Invalid validation rule: a rule must specify both attribute

Error message

Invalid validation rule: a rule must specify both attribute names and validator type.

What it means

yii\base\Model::createValidators() walks the array returned by rules() and accepts only two entry shapes: a Validator instance, or an array whose positional offsets 0 and 1 are both set (attribute list, validator type). Any other entry — a one-element array, an array built with associative keys instead of positional ones, or a scalar — throws InvalidConfigException. It runs lazily on first validator access, usually the first validate() or isAttributeRequired() call.

Source

Thrown at framework/base/Model.php:488

    }

    /**
     * Creates validator objects based on the validation rules specified in [[rules()]].
     * Unlike [[getValidators()]], each time this method is called, a new list of validators will be returned.
     * @return ArrayObject<int, Validator> validators
     * @throws InvalidConfigException if any validation rule configuration is invalid
     */
    public function createValidators()
    {
        $validators = new ArrayObject();
        foreach ($this->rules() as $rule) {
            if ($rule instanceof Validator) {
                $validators->append($rule);
            } elseif (is_array($rule) && isset($rule[0], $rule[1])) { // attributes, validator type
                $validator = Validator::createValidator($rule[1], $this, (array) $rule[0], array_slice($rule, 2));
                $validators->append($validator);
            } else {
                throw new InvalidConfigException('Invalid validation rule: a rule must specify both attribute names and validator type.');
            }
        }

        return $validators;
    }

    /**
     * Returns a value indicating whether the attribute is required.
     * This is determined by checking if the attribute is associated with a
     * [[\yii\validators\RequiredValidator|required]] validation rule in the
     * current [[scenario]].
     *
     * Note that when the validator has a conditional validation applied using
     * [[\yii\validators\RequiredValidator::$when|$when]] this method will return
     * `false` regardless of the `when` condition because it may be called be
     * before the model is loaded with data.
     *
     * @param string $attribute attribute name

View on GitHub (pinned to 66f00d18a2)

Solutions

  1. Open rules() of the model in the stack trace's validate()/createValidators() frame and find the entry with fewer than two positional elements
  2. Rewrite every rule in the positional shape [attributesArray, 'validatorType', ...options]
  3. If the intent was 'make the attribute massive-assignable', use [['attr'], 'safe'] rather than dropping the validator type
  4. Add a unit test that calls createValidators() on every model so malformed rules fail in CI instead of production

Example fix

// before
public function rules()
{
    return [
        [['email'], 'required'],
        ['email'], // stray one-element rule: no validator type
    ];
}

// after
public function rules()
{
    return [
        [['email'], 'required'],
        [['email'], 'email'],
    ];
}
Defensive patterns

Strategy: validation

Validate before calling

foreach ((new $modelClass)->rules() as $rule) {
    if (!isValidRuleShape($rule)) {
        throw new \InvalidConfigException("Malformed validation rule in $modelClass");
    }
}

Type guard

function isValidRuleShape($rule): bool
{
    return $rule instanceof \yii\validators\Validator
        || (is_array($rule) && isset($rule[0], $rule[1]));
}

Try / catch

try {
    $model->validate();
} catch (\yii\base\InvalidConfigException $e) {
    // rules() contains a malformed entry — surface the model class in CI, never retry silently
}

Prevention

When it happens

Trigger: A rules() entry like [['email']] (validator type missing) or ['required'] (attributes missing); a rule written with named keys ['attributes' => ['email'], 'validator' => 'required'] so isset($rule[0], $rule[1]) fails; a stray element left by misnested brackets, e.g. return [ [['email'], 'trim'], ['email'] ]; a plain string rule like 'required' appearing as its own element.

Common situations: Hand-editing a large rules() array and misplacing a closing bracket; merging rule arrays where one branch returns strings or partially built rules; copy-pasted rules from docs that use named keys; refactoring a rule into a variable and dropping the second element.

Related errors


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