Yii2, пользовательская проверка: clientValidateAttribute() работает неправильно

У меня есть форма, созданная виджетом ActiveForm. Пользователь вводит там польский почтовый индекс. В соответствующем контроллере я помещаю введенные данные в БД, например:

$company_profile_data->postal_code = $_POST['CompanyProfiles']['postal_code'];
$company_profile_data->update();

Я решил использовать автономный валидатор для проверки почтового индекса. Правила для этого атрибута в модели:

public function rules() {
    return [
        //...some other rules...
        ['postal_code', 'string', 'length' => [6,6]],
        ['postal_code', PostalValidator::className()], //standalone validator
    ];
}

код класса app/components/validators/PostalValidator:

namespace app\components\validators;

use yii\validators\Validator;
use app\models\CompanyProfiles;
use app\models\Users;

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

    public function validateAttribute($model, $attribute) {

    if (!preg_match('/^[0-9]{2}-[0-9]{3}$/', $model->$attribute))
        $model->addError($attribute, 'Wrong postal code format.');
    }

public function clientValidateAttribute($model, $attribute, $view) { //want js-validation too
    $message = 'Invalid status input.';
    return <<<JS
if (!/^[0-9]{2}-[0-9]{3}$/.test("{$model->$attribute}")) {
    messages.push("$message");
}
JS;
    }
}

Итак, пример правильного кода: 00-202.

Когда я (в роли пользователя) ввожу неверное значение, страница перезагружается и я вижу сообщение Wrong postal code format., хотя я переопределил метод clientValidateAttribute и написал JS-validation, который, как я и предполагал, не даст перезагрузить страницу. Затем я снова нажимаю кнопку отправки: на этот раз страница не перезагружается, и я вижу сообщение Invalid status input. (итак, время второго нажатия запускает JS). Но когда я после этого ввожу правильный код, я все еще вижу сообщение Invalid status input. и ничего не происходит.

Итак, что не так с моим методом clientValidateAttribute()? validateAttribute() отлично работает.

ОБНОВЛЕНИЕ Фрагмент из контроллера

public function actionProfile(){ //can't use massive assignment here, cause info from 2 (not 1) user models is needed
    if (\Yii::$app->user->isGuest) {
        return $this->redirect('/site/index/');
    }
    $is_user_admin = Users::findOne(['is_admin' => 1]);
    if ($is_user_admin->id == \Yii::$app->user->id)
        return $this->redirect('/admin/login/');

    $is_user_blocked = Users::find()->where(['is_blocked' => 1, 'id' => \Yii::$app->user->id])->one();
    if($is_user_blocked)
        return $this->actionLogout();

//3 model instances to retrieve data from users && company_profiles && logo
    $user_data = Users::find()->where(['id'=>\Yii::$app->user->id])->one();
    $user_data->scenario = 'update';

    $company_profile_data = CompanyProfiles::find()->where(['user_id'=>Yii::$app->user->id])->one();
    $logo = LogoData::findOne(['user_id' => \Yii::$app->user->id]);
    $logo_name = $logo->logo_name; //will be NULL, if user have never uploaded logo. In this case placeholder will be used

    $upload_logo = new UploadLogo();
    if (Yii::$app->request->isPost) {

        $upload_logo->imageFile = UploadedFile::getInstance($upload_logo, 'imageFile');

        if ($upload_logo->imageFile) { //1st part ($logo_data->imageFile) - whether user have uploaded logo
            $logo_file_name = md5($user_data->id);
            $is_uploaded = $upload_logo->upload($logo_file_name);
            if ($is_uploaded) { //this cond is needed, cause validation for image fails (?)
                //create record in 'logo_data' tbl, deleting previous
                if ($logo_name) {
                    $logo->delete();
                } else { //if upload logo first time, set val to $logo_name. Otherwise NULL val will pass to 'profile' view, and user wont see his new logo at once
                    $logo_name = $logo_file_name.'.'.$upload_logo->imageFile->extension;
                }
                $logo_data = new LogoData;
                $logo_data->user_id = \Yii::$app->user->id;
                $logo_data->logo_name = $logo_name;
                $logo_data->save();
            }
        }
    }

    if (isset($_POST['CompanyProfiles'])){

        $company_profile_data->firm_data = $_POST['CompanyProfiles']['firm_data'];
        $company_profile_data->company_name = $_POST['CompanyProfiles']['company_name'];
        $company_profile_data->regon = $_POST['CompanyProfiles']['regon'];
        $company_profile_data->pesel = $_POST['CompanyProfiles']['pesel'];
        $company_profile_data->postal_code = $_POST['CompanyProfiles']['postal_code'];
        $company_profile_data->nip = $_POST['CompanyProfiles']['nip'];
        $company_profile_data->country = $_POST['CompanyProfiles']['country'];
        $company_profile_data->city = $_POST['CompanyProfiles']['city'];
        $company_profile_data->address = $_POST['CompanyProfiles']['address'];
        $company_profile_data->telephone_num = $_POST['CompanyProfiles']['telephone_num'];
        $company_profile_data->email = $_POST['CompanyProfiles']['email'];          
        $company_profile_data->update();
    }

    if (isset($_POST['personal-data-button'])) {
        $user_data->username = $_POST['Users']['username'];
        $user_data->password_repeat = $user_data->password = md5($_POST['Users']['password']);
        $user_data->update();
    }
    return $this->render('profile', ['user_data' => $user_data, 'company_profile_data' => $company_profile_data, 'upload_logo' => $upload_logo, 'logo_name' => $logo_name]);
}

person Boolean_Type    schedule 11.09.2015    source источник
comment
Вы используете функцию загрузки? Я полагаю, что загрузка вызывается только после отправки формы.   -  person Onedev_Link    schedule 12.09.2015
comment
Нет, я не использую его. Делаю так (как писал выше):$company_profile_data->postal_code = $_POST['CompanyProfiles']['postal_code']. Итак, я не использую массовое назначение. Видите ли, другие атрибуты проверяются правильно (перед отправкой) без каких-либо load(). Но не в случае автономной проверки.   -  person Boolean_Type    schedule 12.09.2015
comment
проблема в контроллере. Вы можете обновить сообщение с кодом контроллера?   -  person Onedev_Link    schedule 12.09.2015
comment
Конечно. На всякий случай добавил весь actionProfile код, но, думаю, самое интересное находится в IF stmt: if (isset($_POST['CompanyProfiles'])){....   -  person Boolean_Type    schedule 12.09.2015


Ответы (2)


Моя неточность была в методе clientValidateAttribute(). Вместо $model->$attribute во фрагменте кода:

if (!/^[0-9]{2}-[0-9]{3}$/.test("{$model->$attribute}")) {

... Мне пришлось использовать предопределенный JS-var value, чтобы эта переменная изменялась при изменении введенного значения. Итак, мой новый код:

public function clientValidateAttribute($model, $attribute, $view) {
    return <<<JS
if (!/^[0-9]{2}-[0-9]{3}$/.test(value)) {
    messages.push("Wrong postal code format.");
} 
JS;
}
person Boolean_Type    schedule 15.09.2015

Модель не загружает правила и поведения до тех пор, пока не будет вызвана какая-либо функция из модели. При вызове модели $company_profile_data->update(); вызываются функции update и validate.

Попробуйте добавить после $company_profile_data = CompanyProfiles::find() этот код:

$company_profile_data->validate();

Или просто используйте функцию load. Я думаю, это поможет.

person Onedev_Link    schedule 11.09.2015
comment
Безуспешно в обоих случаях. Но, думаю, я решил проблему. Спасибо за участие. :) - person Boolean_Type; 16.09.2015