flagging required attributes in view

When using a model (active record) where the CModel::rules() method has been overridden, and the "required" validator has been applied to any number of attributes, is there a way to easily determine which attributes have been "flagged" as required in the view so that the user is aware of the required field?

For example, I could have an AR class (MyUser) that overrides the rules method like so:



class MyUser extends ActiveRecord


    ....


    public function rules()


    {


        return array(


            array('first_name','length','max'=>32),


            array('last_name','length','max'=>32),


            array('favorite_color','length','min'=>3),


            array('first_name,last_name','required'),


        );


    }


    ....


}


Now, in my view I want to flag (using CSS or by placing a "*" next to the label) the fields that were set as required (first_name,last_name) in the AR class, without having to hard code this in to the view. For example, I'm looking for something like this:



<div class="yiiForm">


<?php echo CHtml::form(); ?>


<?php echo CHtml::errorSummary($myuser); ?>


<div class="simple">


<?php echo CHtml::activeLabel($myuser,'first_name') . ($myuser->isRequired('first_name')?'*':''); ?>


<?php echo CHtml::activeTextField($myuser,'first_name',array('size'=>32,'maxlength'=>32)); ?>


</div>


<div class="simple">


<?php echo CHtml::activeLabel($myuser,'last_name') . ($myuser->isRequired('last_name')?'*':''); ?>


<?php echo CHtml::activeTextField($myuser,'last_name',array('size'=>30,'maxlength'=>32)); ?>


</div>


<div class="simple">


<?php echo CHtml::activeLabel($myuser,'favorite_color') . ($myuser->isRequired('favorite_color')?'*':''); ?>


<?php echo CHtml::activeTextField($myuser,'favorite_color',array('size'=>30,'minlength'=>3)); ?>


</div>


</form>


</div>


Obviously I could write my own function in the MyUser class to parse the array returned from rules(), find the attributes that are required, and then return a boolean if the passed in attribute name was in the list. However, it seems that there is already something in the framework that is aware of which attributes are required for validation purposes…I just don't know how to access that intelligence. =)

I apologize in advance if this is a topic covered somewhere – I did try some searching in the docs and forum first. Thanks for the help.

This topic has been discussed before. Could you please create a ticket for this? I feel this feature is used quite often.

@qiang

Don't forget to let it take use of scenarios.

I am starting to wonder whether you should be able to define the scenario in the following way:

$model->scenario = 'scenario';

This way you don't have to keep typing the scenario in all these places.

Of course the old way (entering it as an argument) should still work too I believe.

@luoshiben

This might work for now in your model

<?php


public function isRequired($attribute, $scenario='') {


	foreach ($this->validators as $validator) {


		if ((get_class($validator) != 'CRequiredValidator') || !$validator->applyTo($scenario)) continue;


		if (in_array($attribute, $validator->attributes))


			return true;


	}


	return false;


}


@Jonah

Thanks for the code, Jonah. I started writing my own method, but yours is definitely more elegant.

@Qiang

An issue has been submitted. Thanks!

Jonah: thank you for the snippet. I am hesitant to add the scenario property because I want to limit the number of properties in CActiveRecord. Any existence of property in CActiveRecord may cause trouble to subclasses if they also want to have a column with the same name.

maybe instead use a method such as getScenario() ?

The method could save the scenario in var $_scenario

less chance of clashing with the database that way

just a thought

Here's what I am thinking of (mainly from performance point of view since determining whether an attribute is required or not is slow):

  1. Add CHtml::activeLabelEx(). This differs from activeLabel() in that it can detect whether the attribute is required or not and render accordingly.

  2. activeLabelEx will render the label with default CSS class 'required' and append the label with <span class="required">*</span>. Both can be changed by setting static members CHtml::$requiredCss and CHtml::$requiredMark.

  3. Add a static member CHtml::$scenario.

Comments are welcome!

For the purposes that I outlined in my original post, this solution will address the need perfectly, while still keeping performance in mind. Purely by way of further discussion, would it also be useful to have a method that can easily return the existence (or application of) any validator class to a specific attribute? That way, if it is necessary to know programatically that a certain attribute is required (or has any other validation rule applied to it) it can be determined without undue difficulty. For example, a sample method description would look something like:



public boolean hasValidator(string $attribute, CValidator $validator)


//returns true if $attribute has the validator of type $validator applied to it


and/or even:



public array getValidatorAttributes(CValidator $validator)


//returns array of attribute names that have the specified validator applied to them


Again, I'm just bringing this up for discussion, although its probably not a huge need. I am more than happy with the solution proposed to mark attributes as required in the view. As always, thanks!

qiang, attaining if a given attribute is required or not is slow is because it has to loop through all the validator class instances, correct?

What if the validation rules were also indexed by scenario, field?

Like so:

array(

'scenario1' => array(

'attrrbute1'=>array(validatorAPointer, ValidatorBPointer)

)

)

or just index the required attributes:

array(

'scenario1' => array('attrrbute1', 'attribute2')

)

It would only be indexed once in CModel::createValidators()

This way it would be fast to retrieve if a attribute is required or not, or if an attribute has any given validation rule.

Actually it's not slow anymore with current implementation.

Yes, there could many ways to specify those rules, but the main idea is to keep the rule syntax as simple as possible while still keeping the performance in mind.

So unless extremely needed (like in 'components' in app config), I am trying to keep those nested arrays as shallow as possible.

I was not clear.  I don't mean to say application users should be able to define rules like that.  I am saying that in CModel::createValidators(), the rules should not only be compiled into CActiveRecordMetaData::_validators, but maybe also compiled into CActiveRecordMetaData::_validatorsAlternativeIndex (for example).

In the alternative index, it would be very fast to retrieve what attributes have what validation rules (eg required)

It would be as simple as something like this:

<?php





public function isRequired($attribute, $scenario) {


	return in_array($attribute, $this->_validatorsAlternativeIndex[$scenario]);


}





//or: (depending on how the index is structured)





public function hasRule($attribute, $scenario, $rule) {


	return in_array($rule, $this->_validatorsAlternativeIndex[$scenario][$attribute]);


}


Sorry I misunderstood you.

Your approach actually has the same complexity as the current implementation, perhaps a bit faster. ;)