Unique Validator Criteria

Hey,

So I need to check if a user is trying to add a friend who he has already added.

It seems obvious to use the Unique validator, but this causes issues.

My problem is, I need to supply the validator with criteria stating that ‘uid’ must equal the model’s currently set uid attribute.

E.g.

SELECT id FROM friends WHERE friend = value AND uid = theuid

But when I try set the criteria to use $this->uid, it is null.

Although after validation it is not null and is correctly set.

How does the rules() method get called? Because clearly it isn’t allowing me to access attributes.

How else can I do this without writing my own method.




                        array('friend', 'unique', 'className' => 'Friends', 'attributeName' => 'friend', 'criteria' => array(

                                'condition' => 'uid = :uid',

                                'params' => array(':uid' => $this->uid) // $this->uid is null here

                        )),



Hello, I had exactly the same problem right now… but I only had the problem, that my criteria was malformed… and with cheating at your code mine works now :)

I’ll post you parts of my solution:

I guess you haven’t set the uid before validation/saving

and $this must work in rules() since it’s just a normal method of your class

model:


     public function rules()

     {   

         // NOTE: you should only define rules for those attributes that

         // will receive user inputs.

         return array(

             array('ruleid, from, to', 'required'),

 

             // from and to must be unique for each rule

             array('from', 'unique',

                 'criteria' => array(

                     'condition' => 'ruleid= :ruleid',

                     'params' => array(':ruleid' => $this->ruleid))),

             array('to', 'unique',

                 'criteria' => array(

                     'condition' => 'ruleid= :ruleid',

                     'params' => array(':ruleid' => $this->ruleid))),

 

             array('ruleid', 'numerical', 'integerOnly'=>true),

             array('from, to', 'length', 'max'=>255),

         );

     }



controller:




        $scenario = 'add';

        $model = new MyModel($scenario);

        if (isset($_POST['MyModel']) && $_POST['MyModel']['scenario'] == $scenario)

        {

// FOLLOWING IS IMPORTANT - ELSE VALIDATOR CAN'T CHECK IF UNIQUE

            $model->ruleid = $otherModel->id;

            $model->attributes = $_POST['MyModel'];

            if ($model->save())

            {

                // start with a new field if saving was successful

                $scenario = 'add';

                $model = new MyModel($scenario);

            }

        }

        $newField = $model;

    

        $scenario = 'update';

        if (isset($_POST['MyModel']) && $_POST['MyModel']['scenario'] == $scenario)

        {

            $model = MyModel::model()->findbyPk($_POST['MyModel']['id']);

            $model->attributes = $_POST['MyModel'];

            $model->save();

        } 



PS: i would vote for simpler unique-syntax something like:

array(‘field1, field2’, ‘unique’)

also setting unique-rules through gii would also be great :)

Well $this should always be accessible within a local non-static method.

I’m setting $model->uid in the controller before calling $model->validate().

Which is why im completely clueless as to why the uid is not set.

Just like yours really, except I set ->attributes before ->uid to prevent users modifying POST data to re-set them.

But yeah, my controller code is the same as yours pretty much except i validate() then save() rather than just save().

Edit

So i fixed this with help from rawtaz.

Basically when you set attributes using $model->attributes, it caches values in some way which means uid is null.

So if i set both friend and uid directly, one by one, it works correctly.

Here’s an example for those who have this same issue:

Does not work:




$model->attributes = $_POST['Something'];

$model->user_id = 6;

// Currently user_id is cached as null, but is set as 6

$model->validate();

// Validate calls rules() which uses the cached value, null



Works, not secure:




$model->user_id = 6;

$model->attributes = $_POST['Something'];

// Currently user_id is 6, cached as 6 too

$model->validate();

// Validate calls rules() which uses the cached value, 6

// Users can edit their POST data to re-set user_id, this is not secure



Works, lengthy:




$model->user_id = $_POST['user_id'];

$model->friend = $_POST['friend'];

// Both are set directly so are cached and set correctly

$model->validate();

// Works perfectly

// Problem here is if you have many fields, it becomes a lengthy process of setting each one



wow, i just ran into this – that’s pretty bad :(

The reason for this is described here:

http://www.yiiframework.com/forum/index.php?/topic/13085-early-call-of-rules-in-model-lifecycle-prevents-custom-validation-logic

Sorry to bump an old thread, but I had the same issue.

Why not do this?




$_POST['Something']['user_id'] = 6

$model->attributes = $_POST['Something'];

$model->validate();



That way it doesn’t matter if the user tampers with the post data because it’s overwritten, and it caches it fine.