25

In my form's model, I have a custom validation function for a field defined in this way

class SignupForm extends Model
{
    public function rules()
    {
        return [
            ['birth_date', 'checkDateFormat'],

            // other rules
        ];
    }

    public function checkDateFormat($attribute, $params)
    {
        // no real check at the moment to be sure that the error is triggered
        $this->addError($attribute, Yii::t('user', 'You entered an invalid date format.'));
    }
}

The error message doesn't appear under the field in the form view when I push the submit button, while other rules like the required email and password appear.

I'm working on the Signup native form, so to be sure that it is not a filed problem, I've set the rule

['username', 'checkDateFormat']

and removed all the other rules related to the username field, but the message doesn't appear either for it.

I've tried passing nothing as parameters to checkDateFormat, I've tried to explicitly pass the field's name to addError()

$this->addError('username', '....');

but nothing appears.

Which is the correct way to set a custom validation function?

Muhammad Omer Aslam
  • 22,976
  • 9
  • 42
  • 68
Luigi Caradonna
  • 1,044
  • 2
  • 12
  • 33

13 Answers13

12

Did you read documentation?

According to the above validation steps, an attribute will be validated if and only if it is an active attribute declared in scenarios() and is associated with one or multiple active rules declared in rules().

So your code should looks like:

class SignupForm extends Model
{
    public function rules()
    {
        return [
            ['birth_date', 'checkDateFormat'],

            // other rules
        ];
    }

    public function scenarios()
    {
        $scenarios = [
            'some_scenario' => ['birth_date'],
        ];

        return array_merge(parent::scenarios(), $scenarios);
    }

    public function checkDateFormat($attribute, $params)
    {
        // no real check at the moment to be sure that the error is triggered
        $this->addError($attribute, Yii::t('user', 'You entered an invalid date format.'));
    }
}

And in controller set scenario, example:

$signupForm = new SignupForm(['scenario' => 'some_scenario']);
TomaszKane
  • 805
  • 1
  • 11
  • 26
  • That's not the problem, I've tried to apply the custom validation function to the username field, which is correctly set by Yii itself. The validation doesn't work for that field as well. – Luigi Caradonna Jan 07 '15 at 12:04
  • 1
    OK, but did you set scenarios? Without it your custom validator just isn't trigger. – TomaszKane Jan 13 '15 at 15:09
  • 3
    Are you sure, that scenarios has anything to do with this issue? If your solution works, then I'd rather call it a side-effect. There real problem, according to [this answer](http://stackoverflow.com/a/26859043/1469208), is to set `skipOnEmpty` to `false` for custom validator, because by default it is set to `true` and that's what is causing it to not be executed. Seems, that scenarios doesn't have much in common with OP's problem. – trejder Jun 30 '15 at 07:46
  • 2
    You're not only wrong, you're authoritatively wrong. It's not necessary to declare a scenario. – 111 Nov 05 '17 at 23:01
8

Try forcing the validation on empty field

['birth_date', 'checkDateFormat', 'skipOnEmpty' => false, 'skipOnError' => false],

Also, make sure you don't assign id to your birth_date field in your view.

If you do have id for your birth_date, you need to specify the selectors

<?= $form->field($model, 'birth_date', ['selectors' => ['input' => '#myBirthDate']])->textInput(['id' => 'myBirthDate']) ?>
Sohel Ahmed Mesaniya
  • 3,344
  • 1
  • 23
  • 29
Michael Nguyen
  • 1,691
  • 2
  • 18
  • 33
8

To make custom validations in yii 2 , you can write custom function in model and assign that function in rule. for eg. I have to apply password criteria in password field then I will write like this in model.

public function rules()
{
    return [
        ['new_password','passwordCriteria'],
    ];
}

public function passwordCriteria()
{
    if(!empty($this->new_password)){
        if(strlen($this->new_password)<8){
            $this->addError('new_password','Password must contains eight letters one digit and one character.');
        }
        else{
            if(!preg_match('/[0-9]/',$this->new_password)){
                $this->addError('new_password','Password must contain one digit.');
            }
            if(!preg_match('/[a-zA-Z]/', $this->new_password)){
                $this->addError('new_password','Password must contain one character.');
            }
        }
    }
}
Sudhanshu sharma
  • 1,917
  • 3
  • 20
  • 29
5

You need to trigger $model->validate() somewhere if you are extending from class Model.

josliber
  • 43,891
  • 12
  • 98
  • 133
Josue Mota
  • 51
  • 1
  • 1
4

I stumbled on this when using the CRUD generator. The generated actionCreate() function doesn't include a model validation call so custom validators never get called. Also, the _form doesn't include and error summary.

So add the error summary to the _form.

<?= $form->errorSummary($model); ?>

...and add the validation call - $model->validate() - to the controller action

public function actionCreate()
{
    $model = new YourModel();

    if ($model->load(Yii::$app->request->post()) && $model->validate()) {...
Steven McElveen
  • 429
  • 5
  • 6
2

Although it's an old post i thought I should answer.

You should create a Custom Validator Class and to create a validator that supports client-side validation, you should implement the yii\validators\Validator::clientValidateAttribute() method which returns a piece of JavaScript code that performs the validation on the client-side. Within the JavaScript code.

You may use the following predefined variables:

attribute: the name of the attribute being validated.

value: the value being validated.

messages: an array used to hold the validation error messages for the attribute.

deferred: an array which deferred objects can be pushed into (explained in the next subsection).

SO that means you can use messages array to push your messages to the client end on runtime within the javascript code block in this method.

I will create a class that includes dummy checks that could be replaced the way you want them to. and change the namespace according to your yii2 advanced or basic.

Custom Client-side Validator

namespace common\components;
use yii\validators\Validator;

class DateFormatValidator extends Validator{
public function init() {
        parent::init ();
        $this->message = 'You entered an invalid date format.';
    }

    public function validateAttribute( $model , $attribute ) {

        if ( /*SOME CONDITION TO CHECK*/) {
            $model->addError ( $attribute , $this->message );
        }
    }

    public function clientValidateAttribute( $model , $attribute , $view ) {

        $message = json_encode ( $this->message , JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
        return <<<JS
if ($("#DATE-1").val()=="" || $("#DATE-2").val() =="") {
    messages.push($message);
}
JS;
    }
}

and then inside your model SigupForm add the rule

['birth_date', 'common\components\DateFormatValidator'],

Deferred Validation

You can even add ajax calls inside the clientValidateAttribute function and on the base of the result of that ajax call you can push message to the client end but you can use the deferred object provided by yii that is an array of Deferred objects and you push your calls inside that array or explicitly create the Deferred Object and call its resolve() method.

Default Yii's deferred Object

public function clientValidateAttribute($model, $attribute, $view)
{
    return <<<JS
        deferred.push($.get("/check", {value: value}).done(function(data) {
            if ('' !== data) {
                messages.push(data);
            }
        }));
JS;
}

More about Deferred Validation

Muhammad Omer Aslam
  • 22,976
  • 9
  • 42
  • 68
1

You need to render the model from controller. Without initializing the model in view. And in the controller you need to call the validate function

Betson
  • 25
  • 6
1

Are you sure the first parameter of addError shouldn't be like this

$this->addError(**'attribute'**, Yii::t('user', 'You entered an invalid date format.'));
Seth
  • 10,198
  • 10
  • 45
  • 68
1

I had common problem.

In your validation function:

public function checkDateFormat($attribute, $params)
    {
        // no real check at the moment to be sure that the error is triggered
        $this->addError($attribute, Yii::t('user', 'You entered an invalid date format.'));
    }

$params doesn`t get any value at all. It actually always equals to Null. You have to check for your attribute value in function:

public function checkDateFormat($attribute, $params)
{
    if($this->birth_date == False)
    {
        $this->addError($attribute, Yii::t('user', 'You entered an invalid date format.'));
    }
}

that`s how it worked for me.

0

If you don't use scenarios for your model, you must mark your atribute as 'safe':

    ['birth_date','safe'],
    ['birth_date', 'checkDateFormat'],

And, on the other hand, you can use this for date validation:

    ['birth_date','safe'],
    [['birth_date'],'date', 'format'=>'php:Y-m-d'],

You can change format as you want.

hesselek
  • 181
  • 1
  • 5
0

**We should set attributes to the function to work with input value **

public function rules()
{
    return [
        ['social_id','passwordCriteria'],
    ];
}

public function passwordCriteria($attribute, $params)
{
   
    if(!empty($this->$attribute)){
       $input_value = $this->$attribute;
       //all good 
    }else{
       //Error empty value
       $this->addError('social_id','Error - value is empty');
    }
 }
-1

Are you by any chance using client side validation? If you do then you have to write a javascript function that would validate the input. You can see how they do it here:

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

Another solution would be to disable client validation, use ajax validation, that should bring back the error too.

Also make sure that you have not overwritten the template of the input, meaning make sure you still have the {error} in there if you did overwrite it.

Mihai P.
  • 9,307
  • 3
  • 38
  • 49
  • conditional validation does not work in this scenario. – Muhammad Omer Aslam May 19 '17 at 04:40
  • that's my point, that kind of validation would not work right away you have client validation on. You would press the submit button, that validation would not run, the rest of the client validation would do.... exactly what the OP says it sees. – Mihai P. May 22 '17 at 00:52
  • you need to create a standalone validator as yii documentation says, that way you can provide the javascript and model / server side both constraints from one validator class, and it would trigger the same way on front-end with the fields focus out event. but conditional validation is totally different from this scenario. – Muhammad Omer Aslam May 22 '17 at 02:09
-2

Your syntax on rules should be something like this man,

[['birth_date'], 'checkDateFormat']

not this

['birth_date', 'checkDateFormat']

So in your case, it should look like below

...
class SignupForm extends Model
{
    public function rules()
    {
        // Notice the different with your previous code here
        return [
            [['birth_date'], 'checkDateFormat'], 

            // other rules
        ];
    }

    public function checkDateFormat($attribute, $params)
    {
        // no real check at the moment to be sure that the error is triggered
        $this->addError($attribute, Yii::t('user', 'You entered an invalid date format.'));
    }
}
azamuddin
  • 47
  • 2
  • 7
  • 3
    That's not true. You need that array only if you declaring the rule on more than one property. – robsch Mar 13 '15 at 08:13