Search code examples
phpyii2yii-form

Custom attribute validation in Yii2


I have model form (SomeForm) and custom validation function in there:

use yii\base\Model; 
class SomeForm extends Model
{
      public $age;
      public function custom_validation($attribute, $params){
             if($this->age < 18){
                    $this->addError($attribute, 'Some error Text');
                    return true;
             }
             else{
                     return false;
             }
      }
      public function rules(){
             return [
                 ['age', 'custom_validation']
             ];
      }
}

I use this custom_validation in rules() function but form even submitting whatever value has age attribute.

Here is the form:

age.php

<?php $form = ActiveForm::begin(); ?>
    <?= $form->field($model, 'age')->label("Age") ?>
    <div class="form-group">
        <?= Html::submitButton('Submit', ['class' => 'btn btn-primary']) ?>
    </div>
    <?php ActiveForm::end(); ?>

and the controller:

use yii\web\Controller;

class SomeController extends Controller
{
     //this controller is just for rendering
     public function actionIndex(){
             return $this->render('age');
     }
     public function actionSubmit(){
             $model = new SomeForm();
             if($model->load(Yii::$app->request->post()){
                   //do something here
             }
     }
}

Solution

  • You don't need to return anything just adding the error to the attribute is enough.

    Since version 2.0.11 you can use yii\validators\InlineValidator::addError() for adding errors instead of using $this. That way the error message can be formatted using yii\i18n\I18N::format() right away.

    Use {attribute} and {value} in the error message to refer to an attribute label (no need to get it manually) and attribute value accordingly:

    What I suspect is the problem in your case is that you are missing the $formModel->validate() as in the model given above extends the yii\base\Model and not \yii\db\ActiveRecord and you must be saving some other ActiveRecord model and want to validate this FormModel before saving the ActiveRecord model, you have to call the $formModel->validate() to check if valid input is provided and trigger the model validation after loading the post array to the model.

    And another thing to notice is by default, inline validators will not be applied if their associated attributes receive empty inputs or if they have already failed some validation rules. If you want to make sure a rule is always applied, you may configure the skipOnEmpty and/or skipOnError properties to be false in the rule declarations.

    Your model should look like below you are missing the namespace in your model definition if that is not just intentional or due to sample code. just update you namespace according to the path where it is.

    namespace frontend\models;
    
    use yii\base\Model; 
    class SomeForm extends Model
    {
          public $age;
          const AGE_LIMIT=18;
    
          public function rules(){
                 return [
                     ['age', 'custom_validation','skipOnEmpty' => false, 'skipOnError' => false]
                 ];
          }
    
          public function custom_validation($attribute, $params,$validator){
              if($this->$attribute< self::AGE_LIMIT){
                 $validator->addError($this, $attribute, 'The value "{value}" is not acceptable for {attribute}, should be greater than '.self::AGE_LIMIT.'.');
               }
          }
    }
    

    your controller/action should look like

    public function actionTest()
        {
            //use appropriate namespace
            $formModel = new \frontend\models\SomeForm();
            $model= new \frontend\models\SomeActiveRecordModel();
    
            if ($formModel->load(Yii::$app->request->post()) && $model->load(Yii::$app->request->post())) {
                if ($formModel->validate()) {
                 // your code after validation to save other ActiveRecord model
                 if($model->save()){
                  Yii::$app->session->setFlash('success','Record added succesfully.')
                 }
                }
            }
    
            return $this->render('test', ['model' => $model,'formModel'=>$formModel]);
        }
    

    The Input field age in the view file should use the $formMoedl object

    echo $form->field($formModel, 'age')->textInput();