One common validation test for multiple attributes

Hi,

I have a AR model that I would like to validate so that only one of three attributes in the model (I know which ones they are) is allowed to be non-empty.

Say for example that I have the following attributes in a model:

card_id

card_holder_id

new_card_id

There is a form and for each of these attributes an input field. When the user submits the form I want to perform validation, and the validation should be successful if and only if exactly ONE of these three fields is filled in. The other two must be empty, otherwise a validation error should be displayed (preferrably one error for all three fields). It does not matter which one of the fields is filled in.

I know about making my own validation functions in the model class, but if I were to do that and set it for each of the fields, the validator would be called once for each field. I am thinking that a validation handling these three fields in one go would be a cleaner solution.

Is there a way to do that? If not, I guess I can do it by using the same custom validation function for each field, and have it use a variable to keep track of the previous fields’ tests (of the three) and on the final one making a decision, setting a warning if appropriate.

Another way might be to create a validator class as a singleton, but I don’t know if/how Yii can instantiate such a class (via a static factory function), can it?

I once tried to create a custom validation rule that does what you described but it makes things complicated and is not worth the effort. Simplest workaround is to do such kind of validation in beforeValidate().

Interesting. May I ask how you did the error feedback? Usually errors are added to a certain field, and in this case we’d like to just output a “common” error.

I guess that in worst case one could just add the error to each of the three fields (when more than one is filled in), but it will look ugly and redundant :confused:

Really, the more I think about this, the more I get a feeling that there shold be some way to do "common" or global validation. Like for the form level or globally in the model.

I just think that there could be a lot of scenarios where one want to check a combination of circumstances and the error isn’t really applicable to one specific field.

I looked at CForm, CFormModel and CActiveRecord but cannot find such a feature.

I picked one of the attributes and added the error there. Not really nice. But in the current CModel implementation errors are always related to 1 specific attribute. As some things rely on this logic it might not be easy to implement model-level-errors to CModel’s implementation without breaking BC.

Okay. Thanks for your help :) I think I’ll pretty much just do what I can for now, the error messages will have to be sub-optimal. Maybe there’s time for some patching to get this functionality in the future.

In your model you can create an additional attribute, like:




public selectionGroup;  // or a more descriptive group name like 'realtyType'



use a common rule to check that selectionGroup must be 1;

Use the beforeValidate event to fill this attribute, like:




selectionGroup=0;

if(isset(selector1)) selectionGroup+=1;

if(isset(selector2)) selectionGroup+=1;

if(isset(selectorX)) selectionGroup+=1;



;

Use the generateAttributeLabels() to set a nice label for the ‘selectionGroup’ attribute.

Use CValidator::createValidator() to retrieve the CValidator for ‘selectionGroup’ and then the addError() method to add a custom error description if more (or less) than 1 item is selected.

That’s a cool idea - i was thinking about something similar. But why not use a custom validator for selectionGroup? In that validator we then can perform any complex check we want, like testing wether one of the required attributes is set.

Like this:


    public function rules()

    {

        return array(

            array('selectionGroup','validateGroup'),


        );

    }


    public function validateGroup($attr,$param)

    {

        if (empty($this->x) && empty($this->y))

            $this->addError($attr,'You need to specify either X or Y');

    }



Where is the error output in this case? Do you need to add some label element in the view, so there’s a place for the error to display?

Also, I presume Onman meant attributeLabels() and not generateAttributeLabels()? I cannot find the latter one.

Oops, typo, indeed I meant attributeLabels().

The error output can still be done using CHtml::errorSummary($model).

You have to create it manually e.g. with CHtml::error. Put it in your view, where you want to display it:


<?php echo CHtml::error($model,'selectionGroup') ?>

I don’t think, this attribute requires a label. If you need one, add it to attributeLabels() and display it with


<?php echo CHtml::activeLabel($model, 'selectionGroup') ?>