1

LoginForm:

public function rules()
{
    return [
        // username and password are both required
        [['username', 'password'], 'required'],
        // username should be a number and of 8 digits
        [['username'], 'number', 'message'=>'{attribute} must be a number'],
        [['username'], 'string', 'length' => 8],
        // password is validated by validatePassword()
        ['password', 'validatePassword'],
    ];
}

/**
 * Validates the password.
 * This method serves as the inline validation for password.
 *
 * @param string $attribute the attribute currently being validated
 * @param array $params the additional name-value pairs given in the rule
 */
public function validatePassword($attribute, $params)
{
    if (!$this->hasErrors()) {
        $user = $this->getUser();
        if (!$user || !$user->validatePassword($this->password)) {
            $this->addError($attribute, 'Incorrect username or password.');
        }
    }
}

I have set up 2 rules for the same field as you can see above:

[['username'], 'number', 'message'=>'{attribute} must be a number'],
[['username'], 'string', 'length' => 8],

I would like the form to display different error messages for the following 3 scenarios situations:

  1. The provided value is neither a number, nor 8 characters (digits).
  2. The provided value is a number, but is not of 8 characters (digits).
  3. The provided value is not a number, but is of 8 characters (digits).

My question is 2 fold:

A. Is there a way to combine these rules in any standard, Yii2 way.
B. In my previous question I have tried to set up a custom validator (the obvious way to solve this), but it was very simply ignored. The only way I could make it validate was if I added the username field to a scenario. However, once I added password too, it was again ignored. Any reason's for this that you can think of? EDIT: skipOnError = false changed nothing at all in this behaviour.

So please, when you answer, make sure you test it preferably in yii2/advanced; I barely touched the default set up, so it should be easy to test.

EDIT: for clarity, I would like to only allow numbers that are of 8 characters (digits), so they can potentially have a leading 0, eg. 00000001, or 00000000 for that matter. This is why it has to be a numeric string.

Community
  • 1
  • 1
benomatis
  • 5,536
  • 7
  • 36
  • 59
  • By default Yii2 ignores validators if the value is empty (unlike Yii1). To enforce validation even on empty attributes, set skipOnEmpty = false (related to your B. part - ignored custom validator). – lubosdz Oct 12 '15 at 20:42
  • @lubosdz sorry, but that didn't change anything in the behaviour; I added `skipOnEmpty = false` to username, and whether the field was empty or not, it only kicked in if the only field in the scenario was the `username` (in which case, of course, the `validatePassword` validator didn't kick in)... – benomatis Oct 12 '15 at 20:58
  • How about `skipOnError`, maybe that's the reason validator got skipped? – Beowulfenator Oct 13 '15 at 06:40
  • @Beowulfenator it was suggested in the other question but unfortunately it didn't change a thing... I'll edit my question to include that. – benomatis Oct 13 '15 at 06:45
  • As far as I know, there is no way you can solve this using existing yii2 validators. However, I think the problem with your custom validator is that it doesn't work in frontend. So, if any frontend checks fail, your form never gets submitted, hence your custom validator doesn't get a chance to run. Try turning on ajax validation. – Beowulfenator Oct 13 '15 at 10:44
  • @Beowulfenator I may have misunderstood you, but for info, this is a test in backend's login form... – benomatis Oct 13 '15 at 12:14
  • When I say "frontend" I mean JavaScript. There are two steps to form validation. When you hit "Submit" form is first validated with JavaScript. If all checks are good, then the form gets submitted (the actual HTTP request is generated). Then it is validated the second time, by the server with PHP code. That's where your custom validator is processed, and if there is an error the form is returned with an error highlighted. What I'm trying to say is that your custom validator does not exist in JavaScript, that's why you won't see it working unless all JS checks are good and form is submitted. – Beowulfenator Oct 13 '15 at 17:00
  • B: Your validation might be getting ignored in this case because the attribute you want to validate may not be assigned to your model. An attribute must be a "safe attribute" to be massively assigned to a model. You must declare which attributes are safe for each scenario. Look at the following section under "Safe Attributes", I can't explain it better: http://www.yiiframework.com/doc-2.0/guide-structure-models.html#safe-attributes – dataskills Oct 13 '15 at 17:02
  • @Beowulfenator this may very well make sense if I look at the way I'm experiencing this error. I'll try to set it up via js and report back... – benomatis Oct 13 '15 at 17:03
  • @dataskills could be the case, though why are the standard (non-custom) validators working? or is the "safe attribute" rule only true to custom validators? – benomatis Oct 13 '15 at 17:05
  • If I could see more of the code I think I could answer. I would need to see 1) your scenario declaration 2) your model value assignment (i.e. $model->attributes = $_POST['Modelname']) 3) your validation rules 4) the custom validation function – dataskills Oct 13 '15 at 22:08
  • @dataskills ok, give me some time and I'll update my question with these info... – benomatis Oct 13 '15 at 22:37
  • @Beowulfenator I gave you credit in my answer below. If you want, you may post one yourself and I'll be happy to chose that as the correct one, if it answers all questions of course, pretty sure it would... – benomatis Oct 16 '15 at 20:19

3 Answers3

3

The best way to combine rules and display custom error messages for different situations is to create a custom validator. Now if you want that to work on client-side too (it was one of my problems detailed in question B above, thanks to @Beowulfenator for the lead on this), you have to create an actual custom validator class extended from the yii2 native validator class.

Here is an example:

CustomValidator.php

<?php

namespace app\components\validators;

use Yii;
use yii\validators\Validator;

class CustomValidator extends Validator
{
    public function init() {
        parent::init();
    }

    public function validateAttribute($model, $attribute) {
        $model->addError($attribute, $attribute.' message');
    }

    public function clientValidateAttribute($model, $attribute, $view)
    {
return <<<JS
messages.push('$attribute message');
JS;
    }
}

LoginForm.php

<?php
namespace common\models;

use Yii;
use yii\base\Model;
use app\components\validators\CustomValidator;

/**
 * Login form
 */
class LoginForm extends Model
{
    public $username;
    public $password;
    public $custom;

    private $_user;


    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            // username and password are both required
            [['username', 'password'], 'required'],
            // username should be a number and of 8 digits
            [['username'], 'number', 'message'=>'{attribute} must be a number'],
            [['username'], 'string', 'length' => 8],
            // password is validated by validatePassword()
            ['password', 'validatePassword'],
            ['custom', CustomValidator::className()],
        ];
    }

    // ...

login.php

<?php

/* @var $this yii\web\View */
/* @var $form yii\bootstrap\ActiveForm */
/* @var $model \common\models\LoginForm */

use yii\helpers\Html;
use yii\bootstrap\ActiveForm;

$this->title = 'Login';
?>
<div class="site-login text-center">
    <h1><?php echo Yii::$app->name; ?></h1>

    <?php $form = ActiveForm::begin([
        'id' => 'login-form',
        'fieldConfig' => ['template' => "{label}\n{input}"],
        'enableClientValidation' => true,
        'validateOnSubmit' => true,
    ]); ?>

    <?= $form->errorSummary($model, ['header'=>'']) ?>

    <div class="row">
        <div class="col-lg-4 col-lg-offset-4">
            <div class="col-lg-10 col-lg-offset-1">

                    <div style="margin-top:40px">
                        <?= $form->field($model, 'username') ?>
                    </div>

                    <div>
                        <?= $form->field($model, 'password')->passwordInput() ?>
                    </div>

                    <div>
                        <?= $form->field($model, 'custom') ?>
                    </div>

                    <div class="form-group" style="margin-top:40px">
                        <?= Html::submitButton('Login', ['class' => 'btn btn-default', 'name' => 'login-button']) ?>
                    </div>

            </div>
        </div>
    </div>

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

</div>
benomatis
  • 5,536
  • 7
  • 36
  • 59
2

Finally you need this :

  • the value is required
  • the value must be a string of 8 chars
  • the value must contains only digits

So you should simply try :

['username', 'required'],
['username', 'string', 'min' => 8, 'max' => 8],
['username', 'match', 'pattern' => '/^[0-9]{8}$/', 'message'=>'{attribute} must be a number'],
soju
  • 25,111
  • 3
  • 68
  • 70
  • this will only display one error message... my goal is to set up a separate message for each scenario, as indicated in my question... – benomatis Oct 13 '15 at 06:54
  • You have 3 error messages here, but not exactly what you asked (not really needed). – soju Oct 13 '15 at 06:56
  • ok, ignore required, I don't need a different message for that... but if I add a string that is of 3 characters for example, I'll only get the message that the value should be a number, nothing about that it should also be 8 characters... if setting up a custom validator actually worked (and beats me why it doesn't - see question _B_ in my original question), I could take care of this by using `if` statements for the different situations... – benomatis Oct 13 '15 at 06:59
  • I wouldn't say this without testing, but I just did it again for you, and like I said: I put 3 letters into the username field, and the only message I get is `...must contains only digits`, nothing about that it should be a number. – benomatis Oct 13 '15 at 07:05
  • I may have not been very clear: I'd like the error messages to adapt to the situation and give "combined" messages too, ie if more that one criteria is not met, to then display a differrent message from when only one criteria is missing... – benomatis Oct 13 '15 at 09:12
  • Well, help yourself and create your own validator... But I think it is not really needed since the rules described here are the logical ones. – soju Oct 13 '15 at 09:32
0

Yii2 ignores your validation rules may because you duplicated not only attribue but also types. With number validation, i think you should use min/max option to validate number length.

For this case:

'min'=>10000000,'max'=>99999999
benomatis
  • 5,536
  • 7
  • 36
  • 59
Ivoglent Nguyen
  • 504
  • 3
  • 9
  • Sorry, but this is not going to allow numbers like `00000001`. As indicated in my question, it has to be a number AND be of 8 characters (digits). – benomatis Oct 13 '15 at 06:24