Страница 1 из 2

Как запретить сохранять поле при save() модели?

Добавлено: 2012.06.18, 11:07
MozgoEd
Привет всем любителям Yii!

Измучен я страданьем этим,
Я до тебя не плохо жил.
Зачем тебя я только встретил,
Скажи пожалуйста, скажи.

Это то я про этот замечательный фреймворк. Теперь по делу.

Пишу социальную сеть на Yii + MongoDB. Использую расширение YiiMongoDbSuite. Возникла проблема.
Дело в том, что в MongoDb нет жесткой структуры документа и если я запихну новое поле - оно сохраняется.
Вопрос в следующем: Как запретить Yii сохранять определенные аттрибуты? Чтобы они просто не сохранялись при вызове $model->save() ?????
Например, при авторизации пользователь вводит кроме логина и пароля еще и капчу. Чтобы код капчи проверялся, он должен быть свойством класса модели, притом публичным свойством, иначе каждый раз введенный код будет не верным. И в Mongo получается, что при сохранении модели в базу ложится и код капчи, а он там нафиг не нужен. Еще такая забавная штука происходит, когда у нас есть поле Пароль и Подтверждение пароля, пароль мы после валидации шифруем и сохраняем в базу, но поле Подтверждение пароля (оно тоже проходит валидации и значит определенно в модели) тоже ложится в базу при сохранении. Получается классная штука, мы термоядерными алгоритмами хешируем пароль, и все это напрасно, так как рядом есть поле подтверждение пароля, которое в откром виде. Как быть? Пробывал колдовать с safe/unsafe, но ничего не выходит, как только объявляю код капчи как unsafe, так сразу пропадает валидация этого поля, т.е. все время пишет, что введенный код не правильный. Гугл не дал никаких результатов. Есть идеи?

Код модели:

Код: Выделить всё

class Item extends EMongoDocument{
    public $name;
    public $soname;
    
    public $verifyCode;
    
    public static function model($className = __CLASS__) {
        return parent::model($className);
    }
    
    public function getCollectionName() {
        return 'item';
    }
    
    public function rules() {
        return array(
            array('name, soname', 'required', 'message'=>'Warning bleat!!!!!!'),
            array('verifyCode', 'captcha', 'message'=>'hueviy cod'),
        );
    }
    
    public function attributeLabels() {
        return array(
            'name'=>'Name',
            'soname'=>'Soname',
        );
    }
    
    public function attributeNames() {
        return array(
            'name',
            'soname',
        );
    }
}
Код действия:

Код: Выделить всё

    public function actionIndex()
    {
        // renders the view file 'protected/views/site/index.php'
        // using the default layout 'protected/views/layouts/main.php'
                $model = new Item;
                if(isset($_POST['Item'])) {
                    $model->attributes = $_POST['Item'];
                    if($model->validate()) {
                        $model->save();
                        Yii::app()->end();
                    }
                }
        $this->render('index', array('model'=>$model));
    }
Код представления:

Код: Выделить всё

<div class="form">
<?php $form=$this->beginWidget('CActiveForm', array(
    'id'=>'UserForm',
    'method'=>'post',
)); ?>
 
    <?php echo $form->errorSummary($model); ?>
 
    <div class="row">
        <?php echo $form->label($model,'name'); ?>
        <?php echo $form->textField($model,'name') ?>
    </div>
 
    <div class="row">
        <?php echo $form->label($model,'soname'); ?>
        <?php echo $form->textField($model,'soname') ?>
    </div>
 
    <div class="row rememberMe">
<?if(extension_loaded('gd') && Yii::app()->user->isGuest):?>
    <?=CHtml::activeLabelEx($model, 'verifyCode')?>
    <?$this->widget('CCaptcha')?>
    <?=CHtml::activeTextField($model, 'verifyCode')?>
<?endif?>
    </div>
 
    <div class="row submit">
        <?php echo CHtml::submitButton('Send'); ?>
    </div>
 
<?php $this->endWidget(); ?>
</div><!-- form -->

Re: Как запретить сохранять поле при save() модели?

Добавлено: 2012.06.18, 11:29
anton44eg
unset() поля в beforeSave()?

Re: Как запретить сохранять поле при save() модели?

Добавлено: 2012.06.18, 12:15
MozgoEd
anton44eg писал(а):unset() поля в beforeSave()?
На безрыбье, и этот вариант рыба. Был.

Код: Выделить всё

    public function rules() {
        return array(
            array('name, soname', 'required', 'message'=>'Warning bleat!!!!!!'),
            array('verifyCode', 'captcha', 'message'=>'hueviy cod'),
        );
    }
    
    public function beforeSave() {
        unset($this->verifyCode);
        return parent::beforeSave();
    }
Но ошибка происходит.
Property "Item.verifyCode" is not defined. Не видит он это свойство, грохнули мы его, грохнули.

Re: Как запретить сохранять поле при save() модели?

Добавлено: 2012.06.18, 12:36
anton44eg
или делайте validate(), потом обнуляйте аттрибут, потом save(false)

Re: Как запретить сохранять поле при save() модели?

Добавлено: 2012.06.18, 12:39
MozgoEd
anton44eg писал(а):или делайте validate(), потом обнуляйте аттрибут, потом save(false)
А что? Это тоже вариант, правда костыль конечно, но так как валидация уже прошла, можно и сохранить с save(false). Ушел пробывать

Re: Как запретить сохранять поле при save() модели?

Добавлено: 2012.06.18, 12:46
MozgoEd
MozgoEd писал(а):
anton44eg писал(а):или делайте validate(), потом обнуляйте аттрибут, потом save(false)
А что? Это тоже вариант, правда костыль конечно, но так как валидация уже прошла, можно и сохранить с save(false). Ушел пробывать
Фиг бы там. Yii не сдается. Славный фреймворк, душевный.

Код: Выделить всё

    public function actionIndex() {
                $model = new Item;
                if(isset($_POST['Item'])) {
                    $model->attributes = $_POST['Item'];
                    if($model->validate()) {
                        unset($model->verifyCode);
                        $model->save(false);
                        Yii::app()->end();
                    }
                }
        $this->render('index', array('model'=>$model));
    } 
Один фиг: Property "Item.verifyCode" is not defined. А счастье было так близко :cry:

Re: Как запретить сохранять поле при save() модели?

Добавлено: 2012.06.18, 12:51
MozgoEd
Я конечно понимаю, что вопрос совсем не детский, да и способы решения в голове рисуются (например переопределять метод присваивания, но это же жесть), но хотелось бы услышать мнение официальных лиц, продвигающих фреймворк. Не хочу костыли городить. Как например, связаться с Sam Dark? Кто-нибудь знает?

Re: Как запретить сохранять поле при save() модели?

Добавлено: 2012.06.18, 13:44
samdark
Я не работал с YiiMongoDbSuite, именно по нему подсказать не могу.

Re: Как запретить сохранять поле при save() модели?

Добавлено: 2012.06.18, 13:50
MozgoEd
Sam Dark писал(а):Я не работал с YiiMongoDbSuite, именно по нему подсказать не могу.
На самом деле, тут, скорее всего, дело даже не в YiiMongoDbSuite, а в том, что когда мы строим через Gii модель из бд MySQL, там читаются мета-данные и создаются поля класса модели. А при сохранении такой проблемы не возникает (я проделывал аналогичные операции в MySQL) по той причине,что в таблице нет такого поля, например verifyCode или ConfirmPass. Но структура документов MongoDB, даже при наличии в ней записей, позволяет сохранить несуществующее до этого момента поле.

Re: Как запретить сохранять поле при save() модели?

Добавлено: 2012.06.18, 14:12
slavcodev
Тут как раз скорее всего дело в YiiMongoDbSuite. Потому что yii считает атрибутами то что вернет функция:

Код: Выделить всё

public function attributeNames() {
        return array(
            'name',
            'soname',
        );
    } 

Re: Как запретить сохранять поле при save() модели?

Добавлено: 2012.06.18, 14:14
MozgoEd
mc-bear писал(а):Тут как раз скорее всего дело в YiiMongoDbSuite. Потому что yii считает атрибутами то что вернет функция:

Код: Выделить всё

public function attributeNames() {
        return array(
            'name',
            'soname',
        );
    }
Я думал об этом, дело в том, что даже если вообще не переопределять метод attributeNames(), т.е его вообще тупо нет в коде модели, проблема остается.

Re: Как запретить сохранять поле при save() модели?

Добавлено: 2012.06.18, 14:19
slavcodev
так если его нет то фреймоворк сгенерирует из базы, но если он есть, должен рабоатьт он

Re: Как запретить сохранять поле при save() модели?

Добавлено: 2012.06.18, 14:23
MozgoEd
mc-bear писал(а):так если его нет то фреймоворк сгенерирует из базы, но если он есть, должен рабоатьт он
Сгенерирует из базы? Так поля verifyCode нет в БД. И это Mongo, а не MySQL или Postgresql какой-нибудь.

Re: Как запретить сохранять поле при save() модели?

Добавлено: 2012.06.18, 14:35
slavcodev
Вот поэтому не известно что там делает YiiMongoDbSuite и как он генерирует запрос на сохранение. Нужно смотреть API EMongoDocument::save()

Re: Как запретить сохранять поле при save() модели?

Добавлено: 2012.06.18, 14:36
MozgoEd
mc-bear писал(а):Вот поэтому не известно что там делает YiiMongoDbSuite и как он генерирует запрос на сохранение. Нужно смотреть API EMongoDocument::save()
Ну так вот:

Код: Выделить всё

    public function save($runValidation=true,$attributes=null)
    {
        if(!$runValidation || $this->validate($attributes))
            return $this->getIsNewRecord() ? $this->insert($attributes) : $this->update($attributes);
        else
            return false;
    }

Re: Как запретить сохранять поле при save() модели?

Добавлено: 2012.06.18, 14:39
slavcodev
Кстати метод save имеет второй параметр $attributes, может хакнуть удасться

Код: Выделить всё

class Item extends EMongoDocument {
  public function save($runValidators = true, $attributes = null) {
     if($attributes == null) {
         $attributes = $this->attributeNames()
         unset($attributes['verifyCode']);
     }
     parent::save($runValidators, $attributes);
  }
}

Re: Как запретить сохранять поле при save() модели?

Добавлено: 2012.06.18, 14:42
slavcodev
Еще как вариант создать одельный модель extends CFormModel, который будет проверять verifyCode и сохранять уже модель Item
ИМХО более правильный вариант, чем путать в одной моделе и запись БД и элементы формы

Re: Как запретить сохранять поле при save() модели?

Добавлено: 2012.06.18, 14:45
MozgoEd
mc-bear писал(а):Кстати метод save имеет второй параметр $attributes, может хакнуть удасться

Код: Выделить всё

class Item extends EMongoDocument {
  public function save($runValidators = true, $attributes = null) {
     if($attributes == null) {
         $attributes = $this->attributeNames()
         unset($attributes['verifyCode']);
     }
     parent::save($runValidators, $attributes);
  }
} 
Вы не поверите. Сработало. Поле verifyCode в базу не записалось.

Re: Как запретить сохранять поле при save() модели?

Добавлено: 2012.06.18, 14:47
MozgoEd
mc-bear писал(а):Еще как вариант создать одельный модель extends CFormModel, который будет проверять verifyCode и сохранять уже модель Item
ИМХО более правильный вариант, чем путать в одной моделе и запись БД и элементы формы
А как такое можно провернуть. Ведь у CFormModel будут свои поля, тогда придется создавать от CFormModel такую же модель как и Item (в данном случае), только без таких полей в модели, как verifyCode и ConfirmPass. Получается не очень хорошо, т.к. на каждую такую модель для Mongo придется городить две модели, одну от CFormModel, другую от EMongoDocument. Много классов, очень много.

Re: Как запретить сохранять поле при save() модели?

Добавлено: 2012.06.18, 17:01
MozgoEd
Запилил решение свой проблемы. Выкладываю здесь, чтобы хотя бы другие любители Yii не сошли с ума, как сегодня сошел я.

Шаги к решению:

1) Создаем модифицированную модель, которая наследуется от EMongoDocument (метод getCollectionName - абстрактный и должен быть реализован)

Код: Выделить всё

class EModMongoDocument extends EMongoDocument {
    
    public function save($runValidators = true, $attributes = null) {
        if($attributes === null) {
            $attributes = $this->attributeNames();
        } 
        return parent::save($runValidators, $attributes);
    }

    public function getCollectionName() {

    }
} 
2. И все модели наследуем теперь уже от нашей EModMongoDocument (обратите внимание, что в нашей модели ОБЯЗАТЕЛЬНО должен быть определен метод attributeNames, иначе ничего не получится. Очень важно, не один час убил.)

Код: Выделить всё

<?php

class Item extends EModMongoDocument {
    public $name;
    public $soname;

    public $verifyCode;

    public static function model($className = __CLASS__) {
        return parent::model($className);
    }

    public function getCollectionName() {
        return 'item';
    }

    public function rules() {
        return array(
            array('name, soname', 'required', 'message'=>'Warning bleat!!!!!!'),
            array('verifyCode', 'captcha', 'message'=>'hueviy cod'),
        );
    }

    public function attributeLabels() {
        return array(
            'name'=>'Name',
            'soname'=>'Soname',
        );
    }

    public function attributeNames() {
      return array(
          'name',
          'soname',
      );
    }

}

?>
3. Теперь делаем вызов из контроллера (обратите внимание, что не важно как вы будете вызывать сохранение, так $model->save() или так $model->save(false), код будет работать одинаково).

Код: Выделить всё

    public function actionIndex() {
            $model = new Item;
            if(isset($_POST['Item'])) {
                $model->attributes = $_POST['Item'];
                if($model->validate()) {
                    $model->save();
                    Yii::app()->end();
                }
            }
            $this->render('index', array('model'=>$model));
    } 
Результат: поле с кодом капчи (verifyCode) проверяется, но в БД не сохраняется. Админ!
Всем спасибо! Удачи! I love this game...