Ad Hoc Data Validation: Error Messages are not displayed

I need help in figuring out why my Ad Hoc Validation Error messages are not displayed in my Form.

If I hard code my validation rules in the model, Error messages correctly show in my form as you would expect. That works beautifully.

However, I have 65 models and each one has different validation rules stored in a php array. Instead of hard coding them manually in each model, I’m applying rules dynamically during validation.

For this example, I have an active form with one text input called "data" which must be a integer. This number must be between 900 and 3000. When I add rules in my controller validation method as described in the Guide (Ad Hoc Validation), the validation works and an Error Message is sent back to the form in the JSON Response. But the form does NOT highlight any labels and the error message is not displayed in the form. How do I get the errors to display in the form?

View file with Form




<?php $form = ActiveForm::begin([[

    'id'  => 'flat-form',

    'action' => $saveUrl,

    'enableAjaxValidation' => true,

    'validationUrl'  => Url::to(['/customers/validate-flat-data', 'mapID' => $model->mapID, 'arrayName' => $name]),

    'options' => [

        'class'  => 'form-inline',

        'method' => 'post',

    ],

]);

?>


<?= $form->field($model, 'data')

    ->textInput([

        'maxlength' => true,

        'class' => 'form-control help-block',

        ])

    ->label('Current Value')

?>


<?= Html::submitButton('Update',

    [

        'class'   => 'btn btn-primary',

        'id'  => 'flat-submit-button',

    ]);

?>

<?php ActiveForm::end(); ?>



Controller with validation method:




    public function actionValidateFlatData($mapID, $arrayName)

    {


        $customerID   	= CustomersSearch::getCustomerIDByMapID( $mapID );

        $customerModel = $this->findModel($customerID);

        $ecmFirmware   = $customerModel->ecmFirmware;


        $mapStructure  = myFunctions::getMapStructure( $ecmFirmware );

        $tableName  = $mapStructure[$arrayName]['DBTableName'];

        $min = $mapStructure[$arrayName]['min'];

        $max = $mapStructure[$arrayName]['max'];


        $request = Yii::$app->request;

        if (Yii::$app->request->isAjax) {


            $data = $request->post($tableName)['data'];

            settype($data, "integer");


            // ad hoc validation rule

            $model = new DynamicModel( compact('data') );

            $model->addRule(['data'], 'integer', ['min'=>$min, 'max'=>$max]);


            Yii::$app->response->format = Response::FORMAT_JSON;

            return ActiveForm::validate($model);


        }

    }



Here are examples of the ajax response from the validation method:


{"dynamicmodel-data":["Data must be no less than 900."]}


{"dynamicmodel-data":["Data must be no greater than 3000."]}

Hi James,

Documentation states:

So, AFAIK, you can only check if


$model->hasErrors(),

or maybe check $model->getFirstError(‘attribX’) because model, in this case, is just a DynamicModel and [size=“2”]I assume by your code that you have in your view an ActiveForm using a Customer model and its errors validations, so it would not able to show errors for a DynamicModel.[/size]

Hi slinstj,

Thanks for responding!

I think you’re first point maybe my issue cause I do have a model. Maybe I’m just going about this the wrong way, then. In fact, the rules in my model class generated by gii still validates ( and I don’t want it too) while at the same time my custom validation method returns DynamicModel errors so I’ve kinda created messy technology here.

And Yes,


$model->hasErrors()

and


$model->getFirstError('data')

both work and I can get the error message from either one but I have to do this in the controller / validation method instead of the view. It makes me wonder how ad hoc[size="2"] validation is supposed to work then? Am I not suppose to use it ActiveForm? Will that make the error messages display in the view? A more exhaustive example in the guide would be helpful.[/size]

With all the power of Yii 2’s validators I was initially thinking it was going to be an easy problem to tackle. Now I’m thinking I just have to de-couple from the framework and rely on my own methods for this portion of the site.

So the problem I’m trying to solve is that I have 65 models each with 1 attribute called ‘data’ and to make things worse, this attribute contains JSON encoded numeric data. Just think of a database table with 1 column that gets JSON encoded data stored in it. Sometimes this JSON data is just a scalar value and other times it could be several hundred numbers (with it’s corresponding input fields in a form) that have to be validated and JSON encoded and stored in the database. ActiveForm doesn’t seem right for this situation.

I was hoping I had a solution with the ad hoc dynamicModel but it’s not fully working for me. [size=“2”]Any suggestions on how to go about achieving a solution would be greatly appreciated.[/size]

I cant understand your circunstances completely, but, since you want to use AdHoc Validation (and it seems already fully implemented in your code), you could just try to ‘transfer’ errors from it to the real model, something like this:




$dynModel->validate();

$customer->addErrors($dynModel->getErrors());

// Here, either $customer and $dynModel has the same errors

// You could send $customer to view even although $dynModel has done your validations



Just a question: Why dont you use ‘normal’ validation in your models? The amount of models should not be a reasonable argument to dont use it since each model having its own rules it is ok.

I wonder what your 65 models and their ‘data’ fields are like.

Do they share only the name ‘data’ and the JSON format? I mean, does each of them have a fixed meaning and the corresponding structure of JSON data?

Probably I would go with a standalone validator which handles the JSON format.

http://www.yiiframework.com/doc-2.0/guide-input-validation.html#creating-validators

My app has 90+ MySQL tables and it’s an Electrical Engineering application so I’m dealing with lots of numbers.

[size="2"]Only 65 tables relate to my question though. [/size][size="2"]Each of these tables include 1 "TEXT" column called data which will store JSON encoded data that come from web forms. Think of it as NO SQL data stored in a MySQL database column. [/size]

[size=“2”]I used Gii to generate all 65 models. Here’s a simplified representation of my tables:[/size]




CREATE TABLE `ABC1` (

  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,

  `data` text

);


CREATE TABLE `ABC2` (

  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,

  `data` text

);



Each table of course, corresponds to a web form where users will input numbers. These numbers must be between a min and max value.

[size="2"]Sometimes a form will have only 1 text input box and other times the form can have over 350. In all cases, only numbers can be entered and each must be validated. If a field is empty or a number is out of range the input field must be highlighted and the form prevented from being submitted. [/size]

The model that Gii generated will validate text but I need to validate numbers and the rule is simple:




[['data'], 'integer','min'=> $min, 'max'=> $max]



65 tables is a lot of tables to work with but the real difficultly is that the min and max values for each table will change over time so I want to apply validation rules dynamically in the controller / validation method.

You suggested using standalone validators: I would like to do that in my controller / validation method (ActiveForm)


enableAjaxValidation'   => true,

'validationUrl'          => $validateUrl,

but the problem is I don’t know how that method communicates back to the form. I’ve gotten this far (with ad hoc validation ) BUT Yii doesn’t give access to the AJAX response and doesn’t allow you to specify the html tag or class the Error messages will display in? I’m also wondering if I should stop using ActiveForm in place of my own custom code cause I just don’t know how to communicate back to my form?.

Normal validation works for me only when my form has 1 text input field.

Yii’s normal validation features are awesome, but my situation is too complex and doesn’t fit at all with how Yii wants things set up (see my response to softArk ). For example, my forms can have over 350 text input boxes with no corresponding Model attributes. It’s correct for my application but it’s non-standard.

By the way, I tried your suggestion:


 $customer->addErrors($dynModel->getErrors()); 

but I couldn’t make it work. I am sending $customer to the view but I don’t know how to get it back to the controller / validation method where I could then pass the errors to it? Should I use sessions to pass the $customer (model) from the view back to the controller?

Standalone validation is about create specif validator class for specific situations (MyOwnDataValidator(), and so on).

[/size]

[size=“2”]When you pass $customer to your view, you should use $customer as your $model in your ActiveForm. So, when you submit your form, you get the data (not the model instance) back into controller and you have to intanciate your $customer (again) and load it with the new data ($customer->load($postData)) so that you can validate again, closing the ‘circuit’ (horrible pun!:).[/size]

[size="2"]When ActiveForm receives a model that has errors, it will show those errors automatically (and you have client side validation for core validator automatically). You probably should not make an action only to validate it.[/size]




    public function actionCreateMyNewEletrComponent()


    {

        $model = new MyEletrComponent; [size=2] [/size][size=2]// or find, if updating[/size]


        if ($model->load(Yii::$app->request->post()) && $model->save()) { // or just $model->validate() to dont save

            // here model is valid (and saved)

    	// you can redirect user to a new page or do another thing

        } else {

            return $this->render('a-view-file-for-this-action', [

                'model' => $model, //passes your model *again* to the view where you will be able to use it in your activeForm.

            ]);

        }



I was thinking about something like the following:




namespace app\components;


use yii\validators\Validator;


class DataValidator extends Validator

{

    public $tableName;


    public function validateAttribute($model, $attribute)

    {

        // decode input values from JSON to an associative array

        $inputs = Json::decode($model->$attribute);

        // get the field definitions of the specified table

        $fieldDefs = SomeClass::getFieldDefinition($this->tableName);

        // check each input value

        foreach($fieldsDefs as $fd) {

            if ( ($v = ArrayHelper::getValue($inputs, $fd->name, 0)) === 0) {

                $this->addError($model, $attribute, "{$fd->name} must be set.");

            } else if ($v < $fd->min) {

                $this->addError($model, $attribute, "{$fd->name} must be equal or larger than {$fd->min}.");

            } else if ($v > $fd->max) {

                $this->addError($model, $attribute, "{$fd->name} must be equal or smaller than {$fd->max}.");

            }

        }

    }

}



And in each model:




    public function rules()

    {

        return [

            ...,

            ['data', DataValidator::className(), 'tableName' => 'some_table'],

            ...,

        ];

    }



Though it may not good enough, I hope you get the concept.

Well, but some difficulty may lie in the view part. We will need some trick in displaying the validation error messages.